TIL 43 - 블록체인과 백엔드 활용(데몬 서버 제작)

프동프동·2023년 2월 11일
1

TIL

목록 보기
43/46
post-thumbnail

RemixIDE를 활용하여 스마트 컨트랙트를 배포할 수 있다.

스마트 컨트랙트를 컴파일하게 되면 ABI code와 Byte Code가 생성됩니다.

ABI(Application Binary Interface)

스마트 컨트랙트 코드에 대한 설명이 담긴 JSON 형식의 인터페이스

스마트 컨트랙트 코드에 있는 함수에 대해 정의하고, 컨트랙트에 있는 함수에 어떤 인자를 넣어야하는지, 어떤 데이터가 반환되는지 등을 가지고 있으며, 노드가 컨트랙트를 실행하기 위해 어떤 작업을 수행해야하는지 알려준다.

ABI를 생성하는 방법은 2가지

  • RemixIDE
  • Solc를 활용

RemixIDE에서 스마트 컨트랙트 컴파일 및 ABI를 추출할 수 있다.

RemixIDE ABI 복사

Solc를 활용해 스마트 컨트랙트 컴파일 및 ABI를 추출할 수 있다.

  1. 프로젝트 폴더 경로에서

    go mod init
    
  2. contracts/XXXX.sol 파일 생성 후 solidity 작성

  3. solc 설치 및 확인

    brew update
    brew tap ethereum/ethereum
    brew install solidity
    • 확인
      solc --version
  4. Solidity 파일에서 ABI 추출

    solc --abi --bin ./contracts/CozToken.sol -o build
    # 자신의 파일 이름에 맞게 수정하세요
    • build 폴더 하위에 abi, bin 파일이 생성된다.

WEMIX3.0 Explorer를 활용할 수 있다.

WEMIX3.0 Explorer

Blockchain (Block) Explorer

확인할 수 있는 정보는 다음과 같다.

  • 블록 정보
  • 트랜잭션 정보
  • 지갑 정보 조회
  • 지갑이 보유한 토큰의 양
  • 지갑의 거래 내역
  • 블록의 높이
  • TPS

그 이상의 기능도 지원한다. 스마트컨트랙트와 연결된 기능들

  • Smart Contract 정보 조회
  • Smart Contract 소스 코드 조회
  • Smart Contract 함수 실행
  • 거버넌스 조회 및 활동 등

Go를 활용해 블록체인 네트워크에 트랜잭션을 생성할 수 있다.

ABI 파일은 명령어로 Go 파일로 변환할 수 있다. 이렇게 만든 ABI Go 파일을 이용하여, Go 프로젝트 백엔드에서 스마트 컨트랙트와 통신할 수 있다.

  1. abigen 설치

    $ cd $GOPATH
    $ cd ./pkg/mod/github.com/ethereum/go-ethereum@v1.10.26 
    # 자신이 install한 go-ethereum 버전에 맞게 경로 이동
    
    $ sudo make
    $ sudo make devtools
    • 확인
      $ abigen --version
      abigen version 1.10.25-stable
  2. ABI - Go 파일로 변환

    $ abigen --bin=build/CozToken.bin --abi=build/CozToken.abi --pkg=contracts --out=./contracts/CozToken.go
    # 자신의 파일 이름에 맞게 수정하세요
  3. Go 파일로 컨트랙트 배포

    ethereum-development-with-go-book/contract_deploy.go at master · miguelmota/ethereum-development-with-go-book

Go를 활용해 배포한 컨트랙트의 트랜잭션을 생성할 수 있다.

백엔드(서버)에서 트랜잭션을 발생시키는 경우 서명은 직접 Private Key를 이용해서 서명한다.

Go-Ethereum

go-ethereum 패키지를 이용하면 블록체인 네트워크와 쉽게 연동이 가능합니다.

import (
	"github.com/ethereum/go-ethereum/ethclient"
)
client, err := ethclient.Dial(cf.Network.URL)
if err != nil {
	log.Fatal(err)
}

HTTP(HTTPS) server endpoint

WS(WSS) server endpoint

  • wss://ws.test.wemix.com

Use Public API Server(RPC)

Go x Blockchain Network (Coin Transfer)

import (
	"context"
	"crypto/ecdsa"
	"encoding/hex"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rlp"
)

func TransferWemix() {
		// 블록체인 네트워크와 연결할 클라이언트를 생성하기 위한 rpc url 연결
    client, err := ethclient.Dial("https://api.test.wemix.com")
    if err != nil {
        fmt.Println("client error")
    }

    // metamask에서 뽑아낸 privatekey를 변환
    privateKey, err := crypto.HexToECDSA("상기 metamask에서 추출한 자신의 privatekey")
    if err != nil {
        fmt.Println(err)
    }
		
		// privatekey로부터 publickey를 거쳐 자신의 address 변환
    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        fmt.Println("fail convert, publickey")
    }
		// 보낼 address 설정
    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

		// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        fmt.Println(err)
    }
		
		// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
    value := big.NewInt(700000000000000000) 
    gasLimit := uint64(21000)               
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        fmt.Println(err)
    }
		
		// 전송받을 상대방 address 설정
    toAddress := common.HexToAddress("페어의 주소 또는 타 공개 address")
		// 트랜잭션 생성
    var data []byte
    tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
    chainID, err := client.NetworkID(context.Background())
    if err != nil {
        fmt.Println(err)
    }
		
		// 트랜잭션 서명
    signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    if err != nil {
        fmt.Println(err)
    }
		
		// RLP 인코딩 전 트랜잭션 묶음. 현재는 1개의 트랜잭션
    ts := types.Transactions{signedTx}
		// RLP 인코딩
    rawTxBytes, _ := rlp.EncodeToBytes(ts[0])
    rawTxHex := hex.EncodeToString(rawTxBytes)
    rTxBytes, err := hex.DecodeString(rawTxHex)
    if err != nil {
        fmt.Println(err.Error())
    }

		// RLP 디코딩
    rlp.DecodeBytes(rTxBytes, &tx)
		// 트랜잭션 전송
    err = client.SendTransaction(context.Background(), tx)
    if err != nil {
        fmt.Println(err)
    }
    //출력된 tx.hash를 익스플로러에 조회 가능
		//예) 0x4788935cfa4a0f23807ba7d7b17a6304cc52795616889fdb9ebdb4498adf4a35
    fmt.Printf("tx sent: %s\n", tx.Hash().Hex())
}

Go x Smart Contract (Token Transfer)

Full Source Code

import (
	"context"
	"crypto/ecdsa"
	"encoding/hex"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math/big"
	"net/http"
	"net/url"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rlp"
	"golang.org/x/crypto/sha3"

	"wbe/oos/contracts" // 자신의 경로에 맞게 수정
)

func TransferCtxCoz() {
	// 블록체인 네트워크와 연결할 클라이언트를 생성하기 위한 rpc url 연결
	client, err := ethclient.Dial("https://api.test.wemix.com")
	if err != nil {
		fmt.Println("client error")
	}

	// 본인이 배포한 토큰 컨트랙트 어드레스
	tokenAddress := common.HexToAddress("0xe3236FEe84ffbcFA7955241CF0Bd0836169e075f")
	instance, err := contracts.NewContracts(tokenAddress, client)
	if err != nil {
		fmt.Println(err)
	}

	// 오너 어드레스
	address := common.HexToAddress("0x7C910BDA16C4774082DaAF7Ed88d94Ca7c45FcaF")
	bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
	if err != nil {
		fmt.Println(err)
	}

	// name 출력
	name, err := instance.Name(&bind.CallOpts{})
	if err != nil {
		fmt.Println(err)
	}

	// symbol 출력
	symbol, err := instance.Symbol(&bind.CallOpts{})
	if err != nil {
		fmt.Println(err)
	}

	// 사용되는 decimals 출력
	decimals, err := instance.Decimals(&bind.CallOpts{})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("balance: %s\n", bal)       // "balance: 999999999300000000000000000"
	fmt.Printf("name: %s\n", name)         // "name: Coz Token"
	fmt.Printf("symbol: %s\n", symbol)     // "symbol: Coz"
	fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"

	privateKey, err := crypto.HexToECDSA("상기 metamask에서 추출한 privatekey")
	if err != nil {
		fmt.Println(err)
	}

	// privatekey로부터 publickey를 거쳐 자신의 address 변환
	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		fmt.Println("fail convert, publickey")
	}
	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

	// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		fmt.Println(err)
	}

	// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
	value := big.NewInt(700000000000000000) 
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		fmt.Println(err)
	}

	// 보낼 주소
	toAddress := common.HexToAddress("0x5D86dE4B82091dBF1fd2c706d36ebC98E3d4d5Cd")
	
	// 컨트랙트 전송시 사용할 함수명
	transferFnSignature := []byte("transfer(address,uint256)")
	hash := sha3.NewLegacyKeccak256()
	hash.Write(transferFnSignature)
	methodID := hash.Sum(nil)[:4]
	fmt.Println(hexutil.Encode(methodID)) 

	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
	fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d

	paddedAmount := common.LeftPadBytes(value.Bytes(), 32)
	fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
	zvalue := big.NewInt(0)

	//컨트랙트 전송 정보 입력
	var pdata []byte
	pdata = append(pdata, methodID...)
	pdata = append(pdata, paddedAddress...)
	pdata = append(pdata, paddedAmount...)

	gasLimit := uint64(200000)
	fmt.Println(gasLimit)

	// 트랜잭션 생성
	tx := types.NewTransaction(nonce, tokenAddress, zvalue, gasLimit, gasPrice, pdata)
	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		fmt.Println(err)
	}

	// 트랜잭션 서명
	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		fmt.Println(err)
	}

	// 트랜잭션 전송
	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		fmt.Println(err)
	}

	//tx.hash를 이용해 전송결과를 확인 
	//예)0x016430c748dad98865afb61038537f3ab8f504b56910769d328e7d857be7886a
	fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
}

위의 코드에서는 스마트 컨트랙트의 어떤 함수를 실행할 지 transferFnSignature 변수에 지정하여 트랜잭션을 생성했습니다. 여기서는 transfer 함수로 지정했기 때문에 토큰 전송 기능이 작동한 것입니다. 이를 응용하여 transfer 함수 외에 다양한 스마트 컨트랙트 함수를 사용할 수 있습니다.

(Mission) Gin Framework 서버에서 트랜잭션을 생성할 수 있다.

Mission - Gin Framework에 적용하기

여러분은 이제 Go에서 트랜잭션을 블록체인 네트워크에 직접 생성할 수도 있고, 스마트 컨트랙트를 실행하여 생성할 수도 있게 되었습니다. 하지만 이는 말 그대로 Go를 이용하여 트랜잭션을 생성한 것일 뿐입니다.

즉, 서버라고 부르기는 어렵습니다.

여러분들이 앞의 백엔드 과정에서 공부한 Gin Framework 백엔드(서버)에, 방금 학습한 트랜잭션 생성을 적용해보세요! 이제 여러분은 블록체인 네트워크와 연동된 백엔드 서버를 개발할 수 있습니다!

구현하기(시간 관계 상 코드의 중복은 처리하지 않았습니다)

coin := e.Group("/coin", liteAuth())
	{
		coin.POST("/transfer", r.ct.TransferCoin)
		coin.POST("/transferfrom", r.ct.TransferFromCoin)
	}
	token := e.Group("/token", liteAuth())
	{
		token.GET("/:tokenname", r.ct.GetToken)
		token.GET("/balance/:address", r.ct.GetBalance)
		token.POST("/transfer", r.ct.TransferToken)
		token.POST("/transferfrom", r.ct.TransferFromToken)
	}
  1. GET 이용 - 토큰 이름으로 토큰 심볼 조회

    parameter - token name
    return - symbol name

    func (p *Controller) GetToken(c *gin.Context) {
    	recvTokenName := c.Param("tokenname")
    	fmt.Println(recvTokenName)
    
    	// 토큰 이름으로 해당 토큰 컨트랙트를 찾는다?
    	// 찾은 컨트랙트에 심볼을 가져온다?
    
    	// 본인이 배포한 토큰 컨트랙트 어드레스
    	tokenAddress := common.HexToAddress("0xb105B7b288429209e9Fdf6e5D26E7aC4bcba6552")
    	instance, err := contracts.NewContracts(tokenAddress, p.client)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// symbol 출력
    	symbol, err := instance.Symbol(&bind.CallOpts{})
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("symbol: %s\n", symbol) // "symbol: Coz"
    
    	c.JSON(http.StatusOK, gin.H{
    		"Error": "Request Error",
    		"path":  c.FullPath(),
    		"data":  symbol,
    	})
    }
  2. GET 이용 - 특정 주소가 소유한 토큰의 양 조회

    parameter - address
    return - token balance

    func (p *Controller) GetBalance(c *gin.Context) {
    	recvAddress := c.Param("address")
    	fmt.Println(recvAddress)
    	//토큰 컨트랙트 어드레스
    	tokenAddress := common.HexToAddress(recvAddress)
    	instance, err := contracts.NewContracts(tokenAddress, p.client)
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 오너 어드레스
    	address := common.HexToAddress("0xDc45fE9fF7aF3522bB2B88a602670Ab4bE2C6f91")
    	bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	fmt.Printf("balance: %s\n", bal) // "balance: 999999999300000000000000000"
    }
  3. Post 이용 - 특정 주소에 지정한 양의 위믹스 코인을 전송

    parameter - address, value
    return - 위믹스 코인 전송 결과

    func (p *Controller) TransferFromCoin(c *gin.Context) {
    	recvTransferFrom := model.TransferFrom{}
    	var err error
    	if err = c.ShouldBindJSON(&recvTransferFrom); err != nil {
    		p.RespError(c, nil, http.StatusBadRequest, "fail, This is incorrect JSON", err)
    		return
    	}
    
    	privateKey, err := crypto.HexToECDSA(recvTransferFrom.PrivateKey)
    	if err != nil {
    		log.Fatal(err)
    	}
    	publicKey := privateKey.Public()
    	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    	if !ok {
    		fmt.Errorf("fail convert, publickey : %v", ok)
    	}
    	// 보낼 address 설정
    	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    
    	// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
    	nonce, err := p.client.PendingNonceAt(context.Background(), fromAddress)
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
    	value := recvTransferFrom.Value
    	gasLimit := uint64(21000)
    	gasPrice, err := p.client.SuggestGasPrice(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 전송받을 상대방 address 설정
    	toAddress := common.HexToAddress(recvTransferFrom.Address)
    	// 트랜잭션 생성
    	var data []byte
    	tx := types.NewTransaction(nonce, toAddress, &value, gasLimit, gasPrice, data)
    	chainID, err := p.client.NetworkID(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 트랜잭션 서명
    	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// RLP 인코딩 전 트랜잭션 묶음. 현재는 1개의 트랜잭션
    	ts := types.Transactions{signedTx}
    	// RLP 인코딩
    	rawTxBytes, _ := rlp.EncodeToBytes(ts[0])
    	rawTxHex := hex.EncodeToString(rawTxBytes)
    	rTxBytes, err := hex.DecodeString(rawTxHex)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    
    	// RLP 디코딩
    	rlp.DecodeBytes(rTxBytes, &tx)
    	// 트랜잭션 전송
    	err = p.client.SendTransaction(context.Background(), tx)
    	if err != nil {
    		fmt.Println(err)
    	}
    	c.JSON(http.StatusOK, gin.H{
    		"status": "OK",
    		"path":   c.FullPath(),
    		"tx":     signedTx.Hash().Hex(),
    	})
    }
  4. Post 이용 - 다른 개인 키로, 특정 주소에 지정한 양의 위믹스 코인을 전송

    parameter - private key, address, value
    return - 위믹스 코인 전송 결과

    func (p *Controller) TransferFromCoin(c *gin.Context) {
    	recvTransferFrom := model.TransferFrom{}
    	var err error
    	if err = c.ShouldBindJSON(&recvTransferFrom); err != nil {
    		p.RespError(c, nil, http.StatusBadRequest, "fail, This is incorrect JSON", err)
    		return
    	}
    
    	privateKey, err := crypto.HexToECDSA(recvTransferFrom.PrivateKey)
    	if err != nil {
    		log.Fatal(err)
    	}
    	publicKey := privateKey.Public()
    	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    	if !ok {
    		fmt.Errorf("fail convert, publickey : %v", ok)
    	}
    	// 보낼 address 설정
    	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    
    	// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
    	nonce, err := p.client.PendingNonceAt(context.Background(), fromAddress)
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
    	value := recvTransferFrom.Value
    	gasLimit := uint64(21000)
    	gasPrice, err := p.client.SuggestGasPrice(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 전송받을 상대방 address 설정
    	toAddress := common.HexToAddress(recvTransferFrom.Address)
    	// 트랜잭션 생성
    	var data []byte
    	tx := types.NewTransaction(nonce, toAddress, &value, gasLimit, gasPrice, data)
    	chainID, err := p.client.NetworkID(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 트랜잭션 서명
    	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// RLP 인코딩 전 트랜잭션 묶음. 현재는 1개의 트랜잭션
    	ts := types.Transactions{signedTx}
    	// RLP 인코딩
    	rawTxBytes, _ := rlp.EncodeToBytes(ts[0])
    	rawTxHex := hex.EncodeToString(rawTxBytes)
    	rTxBytes, err := hex.DecodeString(rawTxHex)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    
    	// RLP 디코딩
    	rlp.DecodeBytes(rTxBytes, &tx)
    	// 트랜잭션 전송
    	err = p.client.SendTransaction(context.Background(), tx)
    	if err != nil {
    		fmt.Println(err)
    	}
    	c.JSON(http.StatusOK, gin.H{
    		"status": "OK",
    		"path":   c.FullPath(),
    		"tx":     signedTx.Hash().Hex(),
    	})
    }
  5. Post 이용 - 특정 주소에 지정한 양의 토큰을 전송

    parameter - address, value
    return - 발행한 토큰 전송 결과

    func (p *Controller) TransferToken(c *gin.Context) {
    	recvTransfer := model.Transfer{}
    	if err := c.ShouldBindJSON(&recvTransfer); err != nil {
    		p.RespError(c, nil, http.StatusBadRequest, "fail, This is incorrect JSON", err)
    		return
    	}
    	tokenAddress := common.HexToAddress("0xb105B7b288429209e9Fdf6e5D26E7aC4bcba6552")
    
    	privateKey, err := crypto.HexToECDSA("개인키")
    	if err != nil {
    		fmt.Println(err)
    	}
    	// privatekey로부터 publickey를 거쳐 자신의 address 변환
    	publicKey := privateKey.Public()
    	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    	if !ok {
    		fmt.Println("fail convert, publickey")
    	}
    	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    
    	// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
    	nonce, err := p.client.PendingNonceAt(context.Background(), fromAddress)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
    	value := recvTransfer.Value
    	gasPrice, err := p.client.SuggestGasPrice(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 보낼 주소
    	toAddress := common.HexToAddress(recvTransfer.Address)
    
    	// 컨트랙트 전송시 사용할 함수명
    	transferFnSignature := []byte("transfer(address,uint256)")
    	hash := sha3.NewLegacyKeccak256()
    	hash.Write(transferFnSignature)
    	methodID := hash.Sum(nil)[:4]
    	fmt.Println(hexutil.Encode(methodID))
    
    	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
    	fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
    
    	paddedAmount := common.LeftPadBytes(value.Bytes(), 32)
    	fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
    	zvalue := big.NewInt(0)
    
    	//컨트랙트 전송 정보 입력
    	var pdata []byte
    	pdata = append(pdata, methodID...)
    	pdata = append(pdata, paddedAddress...)
    	pdata = append(pdata, paddedAmount...)
    
    	gasLimit := uint64(200000)
    	fmt.Println(gasLimit)
    
    	// 트랜잭션 생성
    	tx := types.NewTransaction(nonce, tokenAddress, zvalue, gasLimit, gasPrice, pdata)
    	chainID, err := p.client.NetworkID(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 트랜잭션 서명
    	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 트랜잭션 전송
    	err = p.client.SendTransaction(context.Background(), signedTx)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
    
    	c.JSON(http.StatusOK, gin.H{
    		"status": "OK",
    		"path":   c.FullPath(),
    		"tx":     signedTx.Hash().Hex(),
    	})
    }
  6. Post 이용 - 다른 개인 키로, 특정 주소에 지정한 양의 토큰을 전송

    parameter - **private key, address, value
    return -** 발행한 토큰 전송 결과

    func (p *Controller) TransferFromToken(c *gin.Context) {
    	recvTransferFrom := model.TransferFrom{}
    	var err error
    	if err = c.ShouldBindJSON(&recvTransferFrom); err != nil {
    		p.RespError(c, nil, http.StatusBadRequest, "fail, This is incorrect JSON", err)
    		return
    	}
    	tokenAddress := common.HexToAddress("0xb105B7b288429209e9Fdf6e5D26E7aC4bcba6552")
    
    	privateKey, err := crypto.HexToECDSA(recvTransferFrom.PrivateKey)
    	if err != nil {
    		fmt.Println(err)
    	}
    	// privatekey로부터 publickey를 거쳐 자신의 address 변환
    	publicKey := privateKey.Public()
    	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    	if !ok {
    		fmt.Println("fail convert, publickey")
    	}
    	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    
    	// 현재 계정의 nonce를 가져옴. 다음 트랜잭션에서 사용할 nonce
    	nonce, err := p.client.PendingNonceAt(context.Background(), fromAddress)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 전송할 양, gasLimit, gasPrice 설정. 추천되는 gasPrice를 가져옴
    	value := recvTransferFrom.Value
    	gasPrice, err := p.client.SuggestGasPrice(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 보낼 주소
    	toAddress := common.HexToAddress(recvTransferFrom.Address)
    
    	// 컨트랙트 전송시 사용할 함수명
    	transferFnSignature := []byte("transfer(address,uint256)")
    	hash := sha3.NewLegacyKeccak256()
    	hash.Write(transferFnSignature)
    	methodID := hash.Sum(nil)[:4]
    	fmt.Println(hexutil.Encode(methodID))
    
    	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
    	fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
    
    	paddedAmount := common.LeftPadBytes(value.Bytes(), 32)
    	fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
    	zvalue := big.NewInt(0)
    
    	//컨트랙트 전송 정보 입력
    	var pdata []byte
    	pdata = append(pdata, methodID...)
    	pdata = append(pdata, paddedAddress...)
    	pdata = append(pdata, paddedAmount...)
    
    	gasLimit := uint64(200000)
    	fmt.Println(gasLimit)
    
    	// 트랜잭션 생성
    	tx := types.NewTransaction(nonce, tokenAddress, zvalue, gasLimit, gasPrice, pdata)
    	chainID, err := p.client.NetworkID(context.Background())
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 트랜잭션 서명
    	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	// 트랜잭션 전송
    	err = p.client.SendTransaction(context.Background(), signedTx)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
    
    	c.JSON(http.StatusOK, gin.H{
    		"status": "OK",
    		"path":   c.FullPath(),
    		"tx":     signedTx.Hash().Hex(),
    	})
    }
profile
좋은 개발자가 되고싶은

0개의 댓글