feat(Wallet): handle erc721 transfers

This commit is contained in:
Dario Gabriel Lipicar 2023-02-28 00:09:40 -03:00 committed by dlipicar
parent b660672a60
commit 67bcf10466
2 changed files with 80 additions and 15 deletions

View File

@ -21,10 +21,15 @@ type Type string
type MultiTransactionIDType int64 type MultiTransactionIDType int64
const ( const (
ethTransfer Type = "eth" ethTransfer Type = "eth"
erc20Transfer Type = "erc20" erc20Transfer Type = "erc20"
erc721Transfer Type = "erc721"
unknownTokenTransfer Type = "unknown"
erc20TransferEventSignature = "Transfer(address,address,uint256)" erc20_721TransferEventSignature = "Transfer(address,address,uint256)"
erc20TransferEventIndexedParameters = 3 // signature, from, to
erc721TransferEventIndexedParameters = 4 // signature, from, to, tokenId
NoMultiTransactionID = MultiTransactionIDType(0) NoMultiTransactionID = MultiTransactionIDType(0)
) )
@ -208,7 +213,7 @@ func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Bloc
// NewERC20TransfersDownloader returns new instance. // NewERC20TransfersDownloader returns new instance.
func NewERC20TransfersDownloader(client *chain.ClientWithFallback, accounts []common.Address, signer types.Signer) *ERC20TransfersDownloader { func NewERC20TransfersDownloader(client *chain.ClientWithFallback, accounts []common.Address, signer types.Signer) *ERC20TransfersDownloader {
signature := crypto.Keccak256Hash([]byte(erc20TransferEventSignature)) signature := crypto.Keccak256Hash([]byte(erc20_721TransferEventSignature))
return &ERC20TransfersDownloader{ return &ERC20TransfersDownloader{
client: client, client: client,
accounts: accounts, accounts: accounts,
@ -217,7 +222,11 @@ func NewERC20TransfersDownloader(client *chain.ClientWithFallback, accounts []co
} }
} }
// ERC20TransfersDownloader is a downloader for erc20 tokens transfers. // ERC20TransfersDownloader is a downloader for erc20 and erc721 tokens transfers.
// Since both transaction types share the same signature, both will be assigned
// type erc20Transfer. Until the downloader gets refactored and a migration of the
// database gets implemented, differentiation between erc20 and erc721 will handled
// in the controller.
type ERC20TransfersDownloader struct { type ERC20TransfersDownloader struct {
client *chain.ClientWithFallback client *chain.ClientWithFallback
accounts []common.Address accounts []common.Address
@ -478,7 +487,7 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro
} }
func IsTokenTransfer(logs []*types.Log) bool { func IsTokenTransfer(logs []*types.Log) bool {
signature := crypto.Keccak256Hash([]byte(erc20TransferEventSignature)) signature := crypto.Keccak256Hash([]byte(erc20_721TransferEventSignature))
for _, l := range logs { for _, l := range logs {
if len(l.Topics) > 0 && l.Topics[0] == signature { if len(l.Topics) > 0 && l.Topics[0] == signature {
return true return true
@ -488,7 +497,7 @@ func IsTokenTransfer(logs []*types.Log) bool {
} }
func getTokenLog(logs []*types.Log) *types.Log { func getTokenLog(logs []*types.Log) *types.Log {
signature := crypto.Keccak256Hash([]byte(erc20TransferEventSignature)) signature := crypto.Keccak256Hash([]byte(erc20_721TransferEventSignature))
for _, l := range logs { for _, l := range logs {
if len(l.Topics) > 0 && l.Topics[0] == signature { if len(l.Topics) > 0 && l.Topics[0] == signature {
return l return l

View File

@ -29,7 +29,8 @@ type View struct {
TxStatus hexutil.Uint64 `json:"txStatus"` TxStatus hexutil.Uint64 `json:"txStatus"`
Input hexutil.Bytes `json:"input"` Input hexutil.Bytes `json:"input"`
TxHash common.Hash `json:"txHash"` TxHash common.Hash `json:"txHash"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"` // Only used for Type ethTransfer and erc20Transfer
TokenID *hexutil.Big `json:"tokenId"` // Only used for Type erc721Transfer
From common.Address `json:"from"` From common.Address `json:"from"`
To common.Address `json:"to"` To common.Address `json:"to"`
Contract common.Address `json:"contract"` Contract common.Address `json:"contract"`
@ -49,7 +50,7 @@ func castToTransferViews(transfers []Transfer) []View {
func CastToTransferView(t Transfer) View { func CastToTransferView(t Transfer) View {
view := View{} view := View{}
view.ID = t.ID view.ID = t.ID
view.Type = t.Type view.Type = getFixedTransferType(t)
view.Address = t.Address view.Address = t.Address
view.BlockNumber = (*hexutil.Big)(t.BlockNumber) view.BlockNumber = (*hexutil.Big)(t.BlockNumber)
view.BlockHash = t.BlockHash view.BlockHash = t.BlockHash
@ -74,25 +75,54 @@ func CastToTransferView(t Transfer) View {
view.Input = hexutil.Bytes(t.Transaction.Data()) view.Input = hexutil.Bytes(t.Transaction.Data())
view.TxHash = t.Transaction.Hash() view.TxHash = t.Transaction.Hash()
view.NetworkID = t.NetworkID view.NetworkID = t.NetworkID
switch t.Type {
value := new(hexutil.Big)
tokenId := new(hexutil.Big)
switch view.Type {
case ethTransfer: case ethTransfer:
view.From = t.From view.From = t.From
if t.Transaction.To() != nil { if t.Transaction.To() != nil {
view.To = *t.Transaction.To() view.To = *t.Transaction.To()
} }
view.Value = (*hexutil.Big)(t.Transaction.Value()) value = (*hexutil.Big)(t.Transaction.Value())
view.Contract = t.Receipt.ContractAddress view.Contract = t.Receipt.ContractAddress
case erc20Transfer: case erc20Transfer:
view.Contract = t.Log.Address view.Contract = t.Log.Address
from, to, amount := parseLog(t.Log) from, to, valueInt := parseErc20Log(t.Log)
view.From, view.To, view.Value = from, to, (*hexutil.Big)(amount) view.From, view.To, value = from, to, (*hexutil.Big)(valueInt)
case erc721Transfer:
view.Contract = t.Log.Address
from, to, tokenIdInt := parseErc721Log(t.Log)
view.From, view.To, tokenId = from, to, (*hexutil.Big)(tokenIdInt)
} }
view.MultiTransactionID = int64(t.MultiTransactionID) view.MultiTransactionID = int64(t.MultiTransactionID)
view.Value = value
view.TokenID = tokenId
return view return view
} }
func parseLog(ethlog *types.Log) (from, to common.Address, amount *big.Int) { func getFixedTransferType(tx Transfer) Type {
// erc721 transfers share signature with erc20 ones, so they are both (cached and new)
// categorized as erc20 by the Downloader. We fix this on the fly for the moment, until
// the Downloader gets refactored.
if tx.Type == erc20Transfer {
switch len(tx.Log.Topics) {
case erc20TransferEventIndexedParameters:
// do nothing
case erc721TransferEventIndexedParameters:
return erc721Transfer
default:
return unknownTokenTransfer
}
}
return tx.Type
}
func parseErc20Log(ethlog *types.Log) (from, to common.Address, amount *big.Int) {
amount = new(big.Int)
if len(ethlog.Topics) < 3 { if len(ethlog.Topics) < 3 {
log.Warn("not enough topics for erc20 transfer", "topics", ethlog.Topics) log.Warn("not enough topics for erc20 transfer", "topics", ethlog.Topics)
return return
@ -111,6 +141,32 @@ func parseLog(ethlog *types.Log) (from, to common.Address, amount *big.Int) {
log.Warn("data is not padded to 32 byts big int", "data", ethlog.Data) log.Warn("data is not padded to 32 byts big int", "data", ethlog.Data)
return return
} }
amount = new(big.Int).SetBytes(ethlog.Data) amount.SetBytes(ethlog.Data)
return
}
func parseErc721Log(ethlog *types.Log) (from, to common.Address, tokenId *big.Int) {
tokenId = new(big.Int)
if len(ethlog.Topics) < 4 {
log.Warn("not enough topics for erc721 transfer", "topics", ethlog.Topics)
return
}
if len(ethlog.Topics[1]) != 32 {
log.Warn("second topic is not padded to 32 byte address", "topic", ethlog.Topics[1])
return
}
if len(ethlog.Topics[2]) != 32 {
log.Warn("third topic is not padded to 32 byte address", "topic", ethlog.Topics[2])
return
}
if len(ethlog.Topics[3]) != 32 {
log.Warn("fourth topic is not 32 byte tokenId", "topic", ethlog.Topics[3])
return
}
copy(from[:], ethlog.Topics[1][12:])
copy(to[:], ethlog.Topics[2][12:])
tokenId.SetBytes(ethlog.Topics[3][:])
return return
} }