Skip to content

Commit

Permalink
feat: support erc-721 nft transfer.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhangguiguang committed Jan 24, 2024
1 parent 6d777e1 commit ec98ca3
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 0 deletions.
45 changes: 45 additions & 0 deletions core/eth/chain_nft.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package eth

import (
"errors"
"strings"

"github.com/coming-chat/wallet-SDK/core/base"
)

func (c *Chain) TransferNFT(sender, receiver string, nft *base.NFT) (*Transaction, error) {
return c.TransferNFTParams(sender, receiver, nft.Id, nft.ContractAddress, nft.Standard)
}

// TransferNFTParams
// - param nftStandard: only support erc-721 now, else throw error unsupported nft type.
func (c *Chain) TransferNFTParams(sender, receiver, nftId, nftContractAddress, nftStandard string) (txn *Transaction, err error) {
defer base.CatchPanicAndMapToBasicError(&err)

if strings.ToLower(nftStandard) != "erc-721" {
return nil, errors.New("unsupported nft type")
}
data, err := EncodeErc721TransferFrom(sender, receiver, nftId)
if err != nil {
return nil, err
}
gasPrice, err := c.SuggestGasPrice()
if err != nil {
return nil, err
}

msg := NewCallMsg()
msg.SetFrom(sender)
msg.SetTo(nftContractAddress)
msg.SetValue("0")
msg.SetGasPrice(gasPrice.Value)
msg.SetData(data)

gasLimit, err := c.EstimateGasLimit(msg)
if err != nil {
return nil, err
}
msg.SetGasLimit(gasLimit.Value)

return msg.TransferToTransaction(), nil
}
35 changes: 35 additions & 0 deletions core/eth/chain_nft_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package eth

import (
"testing"

"github.com/coming-chat/wallet-SDK/core/testcase"
"github.com/stretchr/testify/require"
)

func TestChain_TransferNFT_Erc721(t *testing.T) {
mn := testcase.M1
sender, err := NewAccountWithMnemonic(mn)
require.Nil(t, err)

receiver := sender.Address()
nftId := "0"
nftContract := "0x199Dcb0132a66b05723882259832e240fF735810"
nftStandard := "erc-721"

chain := NewChainWithRpc("https://canary-testnet.bevm.io/")

txn, err := chain.TransferNFTParams(sender.Address(), receiver,
nftId, nftContract, nftStandard)
require.Nil(t, err)

signedTx, err := chain.BuildTransferTxWithAccount(sender, txn)
require.Nil(t, err)

run := false
if run {
txHash, err := chain.SendRawTransaction(signedTx.Value)
require.Nil(t, err)
t.Log(txHash)
}
}
2 changes: 2 additions & 0 deletions core/eth/ethchain_erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
const (
// 合约 ABI json文件,查询ERC20 相关代币信息需要使用 ABI 文件
Erc20AbiStr = `[{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"pauser","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimal","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"new_operator","type":"address"},{"internalType":"address","name":"new_pauser","type":"address"}],"name":"changeUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
// erc721 的 ABI 文件, 只支持 transferFrom 方法
Erc721Abi_TransferOnly = `[{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
)

// Deprecated: SdkBatchTokenBalance is deprecated. Please Use Chain.BatchFetchErc20TokenBalance() instead.
Expand Down
22 changes: 22 additions & 0 deletions core/eth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,28 @@ func NewTransaction(nonce, gasPrice, gasLimit, to, value, data string) *Transact
return &Transaction{nonce, gasPrice, gasLimit, to, value, data, ""}
}

func NewTransactionNftTransfer(sender, receiver, gasPrice, gasLimit string, nft *base.NFT) *Transaction {
return NewTransactionNftTransferParams(sender, receiver, gasPrice, gasLimit, nft.Id, nft.ContractAddress, nft.Standard)
}

func NewTransactionNftTransferParams(sender, receiver, gasPrice, gasLimit, nftId, nftContractAddress, nftStandard string) *Transaction {
if strings.ToLower(nftStandard) != "erc-721" {
return nil
}
data, err := EncodeErc721TransferFrom(sender, receiver, nftId)
if err != nil {
return nil
}
return &Transaction{
Nonce: "",
GasPrice: gasPrice,
GasLimit: gasLimit,
To: nftContractAddress,
Value: "0",
Data: common.Bytes2Hex(data),
}
}

func NewTransactionFromHex(hexData string) (*Transaction, error) {
rawBytes, err := hex.DecodeString(hexData)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions core/eth/types_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
package eth

import (
"encoding/hex"
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

func TestUtils_EncodeErc721TransferFrom(t *testing.T) {
sender := "0x151e446ca01b57e495a31d53bc622ac33bd7a0be"
receiver := "0x2c32bd5f7d3eab4bc9d968c90c82debb1bdcced9"
nftId := "1"

// Reference: https://scan-canary-testnet.bevm.io/tx/0x2c763fc26b1021340edc5614c7411a8f4d2220d22fecd805557fef4536268ef8
data, err := EncodeErc721TransferFrom(sender, receiver, nftId)
require.Nil(t, err)
dataHex := hex.EncodeToString(data)
require.Equal(t, dataHex,
"23b872dd000000000000000000000000151e446ca01b57e495a31d53bc622ac33bd7a0be0000000000000000000000002c32bd5f7d3eab4bc9d968c90c82debb1bdcced90000000000000000000000000000000000000000000000000000000000000001")
}

func TestNewTransactionFromHex(t *testing.T) {
type args struct {
hexData string
Expand Down
15 changes: 15 additions & 0 deletions core/eth/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"strings"

"github.com/coming-chat/wallet-SDK/core/base"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -57,6 +58,20 @@ func EncodeErc20Approve(spender string, amount *big.Int) ([]byte, error) {
return EncodeContractData(Erc20AbiStr, ERC20_METHOD_APPROVE, common.HexToAddress(spender), amount)
}

func EncodeErc721TransferFrom(sender, receiver, nftId string) ([]byte, error) {
if !common.IsHexAddress(sender) || !common.IsHexAddress(receiver) {
return nil, base.ErrInvalidAddress
}
amtInt, ok := big.NewInt(0).SetString(nftId, 10)
if !ok {
return nil, base.ErrInvalidAmount
}
return EncodeContractData(Erc721Abi_TransferOnly, "transferFrom",
common.HexToAddress(sender),
common.HexToAddress(receiver),
amtInt)
}

func EncodeContractData(abiString, method string, params ...interface{}) ([]byte, error) {
parsedAbi, err := abi.JSON(strings.NewReader(abiString))
if err != nil {
Expand Down

0 comments on commit ec98ca3

Please sign in to comment.