github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/rpc/get_token.go (about) 1 package rpc 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 10 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/decode" 11 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc/query" 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 13 ) 14 15 // erc721SupportsInterfaceData is the data needed to call the ERC-721 supportsInterface function 16 // 0x01ffc9a7: supportsInterface -- eips.ethereum.org/EIPS/eip-165 17 // 0x80ac58cd: ERC-721 interface ID -- eips.ethereum.org/EIPS/eip-721 18 const erc721SupportsInterfaceData = "0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000" 19 20 type tokenStateSelector = string 21 22 // TODO: If we used encoding we could use the function signature instead of the selector. 23 24 const tokenStateTotalSupply tokenStateSelector = "0x18160ddd" 25 const tokenStateDecimals tokenStateSelector = "0x313ce567" 26 const tokenStateSymbol tokenStateSelector = "0x95d89b41" 27 const tokenStateName tokenStateSelector = "0x06fdde03" 28 const tokenStateBalanceOf tokenStateSelector = "0x70a08231" 29 30 // GetTokenState returns token state for given block. `hexBlockNo` can be "latest" or "" for the latest 31 // block or decimal number or hex number with 0x prefix. (search: FromRpc) 32 func (conn *Connection) GetTokenState(tokenAddress base.Address, hexBlockNo string) (token *types.Token, err error) { 33 if hexBlockNo != "" && hexBlockNo != "latest" && !strings.HasPrefix(hexBlockNo, "0x") { 34 hexBlockNo = fmt.Sprintf("0x%x", base.MustParseUint64(hexBlockNo)) 35 } 36 payloads := []query.BatchPayload{ 37 { 38 Key: "name", 39 Payload: &query.Payload{ 40 Method: "eth_call", 41 Params: query.Params{ 42 map[string]any{ 43 "to": tokenAddress, 44 "data": tokenStateName, 45 }, 46 hexBlockNo, 47 }, 48 }, 49 }, 50 { 51 Key: "symbol", 52 Payload: &query.Payload{ 53 Method: "eth_call", 54 Params: query.Params{ 55 map[string]any{ 56 "to": tokenAddress, 57 "data": tokenStateSymbol, 58 }, 59 hexBlockNo, 60 }, 61 }, 62 }, 63 { 64 Key: "decimals", 65 Payload: &query.Payload{ 66 Method: "eth_call", 67 Params: query.Params{ 68 map[string]any{ 69 "to": tokenAddress, 70 "data": tokenStateDecimals, 71 }, 72 hexBlockNo, 73 }, 74 }, 75 }, 76 { 77 Key: "totalSupply", 78 Payload: &query.Payload{ 79 Method: "eth_call", 80 Params: query.Params{ 81 map[string]any{ 82 "to": tokenAddress, 83 "data": tokenStateTotalSupply, 84 }, 85 hexBlockNo, 86 }, 87 }, 88 }, 89 // Supports interface: ERC 721 90 { 91 Key: "erc721", 92 Payload: &query.Payload{ 93 Method: "eth_call", 94 Params: query.Params{ 95 map[string]any{ 96 "to": tokenAddress, 97 "data": erc721SupportsInterfaceData, 98 }, 99 hexBlockNo, 100 }, 101 }, 102 }, 103 } 104 105 results, err := query.QueryBatch[string](conn.Chain, payloads) 106 if err != nil { 107 return 108 } 109 110 name, _ := decode.ArticulateStringOrBytes(*results["name"]) 111 symbol, _ := decode.ArticulateStringOrBytes(*results["symbol"]) 112 113 var decimals uint64 = 0 114 strDecimals := *results["decimals"] 115 parsedDecimals, parseErr := strconv.ParseUint(strDecimals, 0, 8) 116 if parseErr == nil { 117 decimals = uint64(parsedDecimals) 118 } 119 120 totalSupply := base.HexToWei(*results["totalSupply"]) 121 122 // TODO: Maybe reconcsider this 123 // TODO: According to ERC-20, name, symbol and decimals are optional, but such a token 124 // TODO: would be of no use to us 125 if name == "" && symbol == "" && decimals == 0 { 126 return nil, errors.New(tokenAddress.Hex() + " address is not token") 127 } 128 129 tokenType := types.TokenErc20 130 erc721, erc721Err := decode.ArticulateBool(*results["erc721"]) 131 if erc721Err == nil && erc721 { 132 tokenType = types.TokenErc721 133 } 134 135 token = &types.Token{ 136 Address: tokenAddress, 137 Decimals: decimals, 138 Name: name, 139 Symbol: symbol, 140 TotalSupply: *totalSupply, 141 TokenType: tokenType, 142 } 143 144 return 145 } 146 147 // GetBalanceAtToken returns token balance for given block. `hexBlockNo` can be "latest" or "" for the latest block or 148 // decimal number or hex number with 0x prefix. 149 func (conn *Connection) GetBalanceAtToken(token, holder base.Address, hexBlockNo string) (*base.Wei, error) { 150 if hexBlockNo != "" && hexBlockNo != "latest" && !strings.HasPrefix(hexBlockNo, "0x") { 151 hexBlockNo = fmt.Sprintf("0x%x", base.MustParseUint64(hexBlockNo)) 152 } 153 154 payloads := []query.BatchPayload{{ 155 Key: "balance", 156 Payload: &query.Payload{ 157 Method: "eth_call", 158 Params: query.Params{ 159 map[string]any{ 160 "to": token.Hex(), 161 "data": tokenStateBalanceOf + holder.Pad32(), 162 }, 163 hexBlockNo, 164 }, 165 }, 166 }} 167 168 output, err := query.QueryBatch[string](conn.Chain, payloads) 169 if err != nil { 170 return nil, err 171 } 172 173 if output["balance"] == nil { 174 return base.NewWei(0), nil 175 } 176 177 return base.HexToWei(*output["balance"]), nil 178 }