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  }