diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..57147c6 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +MORALIS_API_KEY= diff --git a/docs/00_protocol.md b/docs/00_protocol.md index 2c13ef4..e18bfbb 100644 --- a/docs/00_protocol.md +++ b/docs/00_protocol.md @@ -41,6 +41,11 @@ type Protocol interface { // GetContractAddress returns the contract address for a specific chain. GetContractAddress(chainID *big.Int) common.Address + // Can this protocol be withdrawn from or be the source state of an intent? + IsSource() bool + + // Can you supply to this protocol? + IsDestination() bool } // ProtocolConfig contains configuration data for initializing a protocol. diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..5823b6c --- /dev/null +++ b/generate.go @@ -0,0 +1,3 @@ +package protocolregistry + +//go:generate go run tools/tokens.go diff --git a/go.mod b/go.mod index 71146f9..82744fa 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/ethereum/go-ethereum v1.11.5 + github.com/joho/godotenv v1.5.1 github.com/rocket-pool/rocketpool-go v1.8.2 github.com/stretchr/testify v1.9.0 ) @@ -35,4 +36,3 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - diff --git a/go.sum b/go.sum index 75d0832..3182e84 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= diff --git a/pkg/aave.go b/pkg/aave.go index 0454da8..2657c56 100644 --- a/pkg/aave.go +++ b/pkg/aave.go @@ -184,7 +184,7 @@ func NewAaveOperation( case AaveProtocolDeploymentSpark: contract = SparkLendContractAddress case AaveProtocolDeploymentPolygon: - contract = polygonAaveDataProviderContract + contract = AavePolygonV3ContractAddress } var version string = "3" @@ -475,3 +475,6 @@ func (l *AaveOperation) GetName() string { // GetVersion returns the version of the protocol func (l *AaveOperation) GetVersion() string { return l.version } + +func (l *AaveOperation) IsSource() bool { return true } +func (l *AaveOperation) IsDestination() bool { return true } diff --git a/pkg/ankr.go b/pkg/ankr.go index caafa9f..b27d8e3 100644 --- a/pkg/ankr.go +++ b/pkg/ankr.go @@ -202,3 +202,6 @@ func (l *AnkrOperation) GetName() string { return Ankr } // GetVersion returns the version of the protocol func (l *AnkrOperation) GetVersion() string { return l.version } + +func (l *AnkrOperation) IsSource() bool { return true } +func (l *AnkrOperation) IsDestination() bool { return true } diff --git a/pkg/binance.go b/pkg/binance.go index 0a50a6b..cb74433 100644 --- a/pkg/binance.go +++ b/pkg/binance.go @@ -206,3 +206,6 @@ func (l *BinanceWrappedEthOperation) GetName() string { return BinanceStakedETH // GetVersion returns the version of the protocol func (l *BinanceWrappedEthOperation) GetVersion() string { return l.version } + +func (l *BinanceWrappedEthOperation) IsSource() bool { return true } +func (l *BinanceWrappedEthOperation) IsDestination() bool { return true } diff --git a/pkg/compound.go b/pkg/compound.go index c3899c7..8f884a5 100644 --- a/pkg/compound.go +++ b/pkg/compound.go @@ -416,7 +416,24 @@ func (l *CompoundOperation) GetType() ProtocolType { return TypeLoan } func (l *CompoundOperation) GetContractAddress(chainID *big.Int) common.Address { return l.contract } // Name returns the human readable name for the protocol -func (l *CompoundOperation) GetName() string { return Compound } +func (l *CompoundOperation) GetName() string { + + switch strings.ToLower(l.contract.Hex()) { + case strings.ToLower(CompoundV3ETHPool): + return "Compound ETH pool" + case strings.ToLower(CompoundV3PolygonUSDCPool), + strings.ToLower(CompoundV3USDCPool): + return "Compound USDC Pool" + case strings.ToLower(CompoundV3PolygonUSDTPool): + return "Compound USDT pool" + + default: + return Compound + } +} // GetVersion returns the version of the protocol func (l *CompoundOperation) GetVersion() string { return l.version } + +func (l *CompoundOperation) IsSource() bool { return true } +func (l *CompoundOperation) IsDestination() bool { return true } diff --git a/pkg/constants.go b/pkg/constants.go index e5fd765..3f8fcb7 100644 --- a/pkg/constants.go +++ b/pkg/constants.go @@ -56,19 +56,23 @@ type Protocol interface { GetName() string GetVersion() string GetContractAddress(chainID *big.Int) common.Address + IsSource() bool + IsDestination() bool } const ( - AaveV3 ProtocolName = "aave_v3" - SparkLend ProtocolName = "spark_lend" - Lido ProtocolName = "lido" - RocketPool ProtocolName = "rocket_pool" - Ankr ProtocolName = "ankr" - Renzo ProtocolName = "renzo" - Compound ProtocolName = "compound" - ListaDao ProtocolName = "lista_dao" - AvalonFinance ProtocolName = "avalon_finance" - BinanceStakedETH ProtocolName = "binance_staked_eth" + AaveV3 ProtocolName = "aave_v3" + SparkLend ProtocolName = "spark_lend" + Lido ProtocolName = "lido" + RocketPool ProtocolName = "rocket_pool" + Ankr ProtocolName = "ankr" + Renzo ProtocolName = "renzo" + Compound ProtocolName = "compound" + ListaDao ProtocolName = "lista_dao" + AvalonFinance ProtocolName = "avalon_finance" + BinanceStakedETH ProtocolName = "binance_staked_eth" + VenusProtocolIsolatedPool ProtocolName = "venus_isolated_pool" + VenusProtocolCorePool ProtocolName = "venus_core_pool" ) var ( diff --git a/pkg/lido.go b/pkg/lido.go index 0fd1187..5c0da01 100644 --- a/pkg/lido.go +++ b/pkg/lido.go @@ -192,3 +192,6 @@ func (l *LidoOperation) GetName() string { return Lido } // GetVersion returns the version of the protocol func (l *LidoOperation) GetVersion() string { return l.version } + +func (l *LidoOperation) IsSource() bool { return false } +func (l *LidoOperation) IsDestination() bool { return true } diff --git a/pkg/listadao_stake.go b/pkg/listadao_stake.go index 121c396..6f52559 100644 --- a/pkg/listadao_stake.go +++ b/pkg/listadao_stake.go @@ -203,3 +203,6 @@ func (l *ListaStakingOperation) GetName() string { return ListaDao } // GetVersion returns the version of the protocol func (l *ListaStakingOperation) GetVersion() string { return "1" } + +func (l *ListaStakingOperation) IsSource() bool { return false } +func (l *ListaStakingOperation) IsDestination() bool { return true } diff --git a/pkg/registry.go b/pkg/registry.go index 35dcff7..94b504d 100644 --- a/pkg/registry.go +++ b/pkg/registry.go @@ -336,5 +336,5 @@ func (r *ProtocolRegistryImpl) setupBnbProtocols(client *ethclient.Client) error return err } - return nil + return registerVenusPools(r, client, BscChainID.Int64()) } diff --git a/pkg/rocket_pool.go b/pkg/rocket_pool.go index ad07412..0e6be0d 100644 --- a/pkg/rocket_pool.go +++ b/pkg/rocket_pool.go @@ -290,3 +290,6 @@ func (l *RocketpoolOperation) GetName() string { return RocketPool } // GetVersion returns the version of the protocol func (l *RocketpoolOperation) GetVersion() string { return l.version } + +func (l *RocketpoolOperation) IsSource() bool { return true } +func (l *RocketpoolOperation) IsDestination() bool { return true } diff --git a/pkg/validate.go b/pkg/validate.go index be423f8..85bfbfb 100644 --- a/pkg/validate.go +++ b/pkg/validate.go @@ -8,6 +8,10 @@ import ( const zeroAddress = "0x0000000000000000000000000000000000000000" +func IsZeroAddress(addr common.Address) bool { + return common.HexToAddress(zeroAddress).Hex() == addr.Hex() +} + // nativeDenomAddress native denom token address. const nativeDenomAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" diff --git a/pkg/venus.go b/pkg/venus.go new file mode 100644 index 0000000..f7c323f --- /dev/null +++ b/pkg/venus.go @@ -0,0 +1,667 @@ +package pkg + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +// venusABIs contains all abis used by pool registry, vtokens and others +// this allows us only compile and init once +const venusABIs = ` +[ +{ + "inputs": [], + "name": "getAllPools", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockPosted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestampPosted", + "type": "uint256" + } + ], + "internalType": "struct PoolRegistryInterface.VenusPool[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" +}, + { + "inputs": [], + "name": "getAllMarkets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "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": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mintBehalf", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "wrapAndSupply", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "underlying", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "badDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemBehalf", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemAndUnwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + +{ + "name": "actionPaused", + "type": "function", + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "enum Action", + "name": "action", + "type": "uint8" + } + ], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redeemUnderlyingBehalf", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redeemUnderlying", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +} +] +` + +var venusBNBContractAddress ContractAddress = common.HexToAddress("0x9F7b01A536aFA00EF10310A162877fd792cD0666") + +// VenusOperation implements the Protocol interface for Lido +type VenusOperation struct { + parsedABI abi.ABI + contract common.Address + chainID *big.Int + version string + + poolMarkets map[string]struct { + address string + // VWBNB can supply BNB or native assets. In this instance, we want to use the NativeGateway instead + isNative bool + } + + assetToVTokenMap map[string]string + + client *ethclient.Client +} + +// dynamically registers all supported pools +func registerVenusPools(registry ProtocolRegistry, client *ethclient.Client, chainID int64) error { + parsedABI, err := abi.JSON(strings.NewReader(venusABIs)) + if err != nil { + return err + } + + data, err := parsedABI.Pack("getAllPools") + if err != nil { + return fmt.Errorf("failed to pack contract call: %w", err) + } + + callMsg := ethereum.CallMsg{ + To: &venusBNBContractAddress, + Data: data, + } + + result, err := client.CallContract(context.Background(), callMsg, nil) + if err != nil { + return err + } + + var pools []struct { + Name string + Creator common.Address + Comptroller common.Address + BlockPosted *big.Int + TimestampPosted *big.Int + } + err = parsedABI.UnpackIntoInterface(&pools, "getAllPools", result) + if err != nil { + return err + } + + for _, poolAddr := range pools { + c, err := NewVenusOperation(client, big.NewInt(chainID), poolAddr.Comptroller) + if err != nil { + return err + } + + if err := registry.RegisterProtocol(big.NewInt(chainID), poolAddr.Comptroller, c); err != nil { + return err + } + } + + return nil +} + +func NewVenusOperation(client *ethclient.Client, + chainID *big.Int, + marketPool common.Address) (*VenusOperation, error) { + if !IsBnb(chainID) { + return nil, fmt.Errorf("unsupported chain ID (%d)", chainID.Int64()) + } + + if client == nil { + return nil, errors.New("ethclient cannot be nil") + } + + parsedABI, err := abi.JSON(strings.NewReader(venusABIs)) + if err != nil { + return nil, err + } + + v := &VenusOperation{ + parsedABI: parsedABI, + contract: marketPool, + chainID: chainID, + version: "4", + client: client, + poolMarkets: map[string]struct { + address string + isNative bool + }{}, + assetToVTokenMap: make(map[string]string), + } + + if err := v.getSupportedAssets(); err != nil { + return nil, err + } + + return v, nil +} + +func (v *VenusOperation) getSupportedAssets() error { + + getAllMarketsCalldata, err := v.parsedABI.Pack("getAllMarkets") + if err != nil { + return err + } + + msg := ethereum.CallMsg{ + To: &v.contract, + Data: getAllMarketsCalldata, + } + + result, err := v.client.CallContract(context.Background(), msg, nil) + if err != nil { + return err + } + + var marketInfo []common.Address + + err = v.parsedABI.UnpackIntoInterface(&marketInfo, "getAllMarkets", result) + if err != nil { + return fmt.Errorf("failed to unpack output: %v", err) + } + + // get underlying token to add to map + for _, addr := range marketInfo { + + getSymbol, err := v.parsedABI.Pack("symbol") + if err != nil { + return err + } + + msg := ethereum.CallMsg{ + To: &addr, + Data: getSymbol, + } + + result, err := v.client.CallContract(context.Background(), msg, nil) + if err != nil { + return err + } + + var symbol string + + err = v.parsedABI.UnpackIntoInterface(&symbol, "symbol", result) + if err != nil { + return fmt.Errorf("failed to unpack symbol output: %v", err) + } + + getUnderlying, err := v.parsedABI.Pack("underlying") + if err != nil { + return err + } + + msg = ethereum.CallMsg{ + To: &addr, + Data: getUnderlying, + } + + result, err = v.client.CallContract(context.Background(), msg, nil) + if err != nil { + return err + } + + var underlyingAddr common.Address + + err = v.parsedABI.UnpackIntoInterface(&underlyingAddr, "underlying", result) + if err != nil { + return fmt.Errorf("failed to unpack underlying output: %v", err) + } + + v.poolMarkets[addr.Hex()] = struct { + address string + isNative bool + }{ + address: underlyingAddr.Hex(), + isNative: symbol == "vWBNB_LiquidStakedBNB", + } + + v.assetToVTokenMap[underlyingAddr.Hex()] = addr.Hex() + if v.poolMarkets[addr.Hex()].isNative { + v.assetToVTokenMap[nativeDenomAddress] = addr.Hex() + } + } + + return nil +} + +// GenerateCalldata creates the necessary blockchain transaction data to +// supply or withdraw your asset +func (l *VenusOperation) GenerateCalldata(ctx context.Context, chainID *big.Int, + action ContractAction, params TransactionParams) (string, error) { + if !IsBnb(chainID) { + return "", ErrChainUnsupported + } + + var calldata []byte + var err error + + switch action { + case LoanSupply: + + calldata, err = l.parsedABI.Pack("mint", params.Amount) + if err != nil { + return "", err + } + + if !IsZeroAddress(params.Recipient) { + calldata, err = l.parsedABI.Pack("mintBehalf", params.Recipient, params.Amount) + if err != nil { + return "", err + } + } + + if IsNativeToken(params.Asset) { + calldata, err = l.parsedABI.Pack("wrapAndSupply", params.Sender) + if err != nil { + return "", err + } + } + + case LoanWithdraw: + + calldata, err = l.parsedABI.Pack("redeemUnderlying", params.Amount) + if err != nil { + return "", err + } + + if !IsZeroAddress(params.Recipient) { + calldata, err = l.parsedABI.Pack("redeemUnderlyingBehalf", params.Recipient, params.Amount) + if err != nil { + return "", err + } + } + + if IsNativeToken(params.Asset) { + calldata, err = l.parsedABI.Pack("redeemAndUnwrap", params.Amount) + if err != nil { + return "", err + } + } + + default: + return "", errors.New("action not supported") + } + + return HexPrefix + hex.EncodeToString(calldata), nil +} + +// Validate checks if the provided parameters are valid for the specified action +func (l *VenusOperation) Validate(ctx context.Context, + chainID *big.Int, action ContractAction, params TransactionParams) error { + + if !IsBnb(chainID) { + return ErrChainUnsupported + } + + if !l.IsSupportedAsset(ctx, l.chainID, params.Asset) { + return fmt.Errorf("asset not supported %s", params.Asset) + } + + if action != LoanSupply && action != LoanWithdraw { + return errors.New("unsupported action") + } + + if params.Amount.Cmp(big.NewInt(0)) <= 0 { + return errors.New("amount must be greater than zero") + } + + if action == LoanSupply { + return nil + } + + _, balance, err := l.GetBalance(ctx, l.chainID, params.Sender, params.Asset) + if err != nil { + return err + } + + if balance.Cmp(params.Amount) == -1 { + return errors.New("balance not enough") + } + + return nil +} + +// GetBalance retrieves the balance for a specified account and asset +func (l *VenusOperation) GetBalance(ctx context.Context, + chainID *big.Int, account, asset common.Address) (common.Address, *big.Int, error) { + + var address common.Address + if !IsBnb(chainID) { + return address, nil, ErrChainUnsupported + } + + contract, ok := l.assetToVTokenMap[asset.Hex()] + if !ok { + return common.Address{}, big.NewInt(0), errors.New("asset not supported") + } + + callData, err := l.parsedABI.Pack("balanceOf", account) + if err != nil { + return address, nil, err + } + + c := common.HexToAddress(contract) + + result, err := l.client.CallContract(ctx, ethereum.CallMsg{ + To: &c, + Data: callData, + }, nil) + if err != nil { + return address, nil, err + } + + balance := new(big.Int) + err = l.parsedABI.UnpackIntoInterface(&balance, "balanceOf", result) + return c, balance, err +} + +// GetSupportedAssets returns a list of assets supported by the protocol on the specified chain +func (l *VenusOperation) GetSupportedAssets(ctx context.Context, chainID *big.Int) ([]common.Address, error) { + supportedAssets := make([]common.Address, 0) + + // only add native token once. Ideally this might not be needed as vBNB pool is the only one + // right now and most realistic one ever but doesn't hurt to be defensive here + var nativeTokenAdded bool + + for _, v := range l.poolMarkets { + supportedAssets = append(supportedAssets, common.HexToAddress(v.address)) + + if v.isNative && !nativeTokenAdded { + supportedAssets = append(supportedAssets, common.HexToAddress(nativeDenomAddress)) + nativeTokenAdded = true + } + } + + return supportedAssets, nil +} + +// IsSupportedAsset checks if the specified asset is supported on the given chain +func (l *VenusOperation) IsSupportedAsset(ctx context.Context, chainID *big.Int, asset common.Address) bool { + if !IsBnb(chainID) { + return false + } + + assets, err := l.GetSupportedAssets(ctx, chainID) + if err != nil { + return false + } + + for _, a := range assets { + if asset.Hex() == a.Hex() { + return true + } + } + + return false +} + +// GetProtocolConfig returns the protocol config for a specific chain +func (l *VenusOperation) GetProtocolConfig(chainID *big.Int) ProtocolConfig { + return ProtocolConfig{ + ChainID: l.chainID, + Contract: l.contract, + ABI: l.parsedABI, + Type: TypeLoan, + } +} + +// GetABI returns the ABI of the protocol's contract +func (l *VenusOperation) GetABI(chainID *big.Int) abi.ABI { return l.parsedABI } + +// GetType returns the protocol type +func (l *VenusOperation) GetType() ProtocolType { return TypeLoan } + +// GetContractAddress returns the contract address for a specific chain +func (l *VenusOperation) GetContractAddress(chainID *big.Int) common.Address { return l.contract } + +// Name returns the human readable name for the protocol +func (l *VenusOperation) GetName() string { return VenusProtocolIsolatedPool } + +// GetVersion returns the version of the protocol +func (l *VenusOperation) GetVersion() string { return l.version } + +func (l *VenusOperation) IsSource() bool { return false } +func (l *VenusOperation) IsDestination() bool { return true } diff --git a/pkg/venus_test.go b/pkg/venus_test.go new file mode 100644 index 0000000..9bfa360 --- /dev/null +++ b/pkg/venus_test.go @@ -0,0 +1,239 @@ +//go:build integration +// +build integration + +package pkg + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVenusOperation_New(t *testing.T) { + t.Run("unsupported chain", func(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainETH), + big.NewInt(1), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.Error(t, err) + require.Nil(t, venus) + }) + + t.Run("venus correctly setup", func(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + require.NotNil(t, venus) + }) +} + +func TestVenusOperation_Validate(t *testing.T) { + t.Run("unsupported chain", func(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + err = venus.Validate(context.Background(), big.NewInt(1), LoanSupply, TransactionParams{ + Amount: big.NewInt(0), + Asset: common.HexToAddress(nativeDenomAddress), + }) + require.Error(t, err) + }) + + t.Run("unsupported action", func(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + err = venus.Validate(context.Background(), big.NewInt(56), NativeStake, TransactionParams{ + Amount: big.NewInt(1), + Asset: common.HexToAddress(nativeDenomAddress), + }) + require.Error(t, err) + }) + + t.Run("unsupported asset", func(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + err = venus.Validate(context.Background(), big.NewInt(56), LoanSupply, TransactionParams{ + Amount: big.NewInt(1), + Asset: common.HexToAddress("0x1234567890123456789012345678901234567890"), + }) + require.Error(t, err) + }) +} + +func TestVenusOperation_GetBalance(t *testing.T) { + client := getTestClient(t, ChainBSC) + + venus, err := NewVenusOperation(client, + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + t.Run("unsupported chain", func(t *testing.T) { + _, _, err := venus.GetBalance(context.Background(), big.NewInt(1), emptyTestWallet, common.HexToAddress("")) + require.Error(t, err) + }) + + t.Run("get balance for address", func(t *testing.T) { + token, balance, err := venus.GetBalance(context.Background(), big.NewInt(56), emptyTestWallet, common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")) + require.NoError(t, err) + require.NotNil(t, balance) + require.NotEmpty(t, token) + }) +} + +func TestVenusOperation_GenerateCalldata(t *testing.T) { + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + t.Run("unsupported chain", func(t *testing.T) { + _, err := venus.GenerateCalldata(context.Background(), big.NewInt(1), LoanSupply, TransactionParams{}) + require.Error(t, err) + }) + + t.Run("unsupported action", func(t *testing.T) { + _, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), NativeStake, TransactionParams{}) + require.Error(t, err) + }) + + t.Run("generate supply calldata with WBNB", func(t *testing.T) { + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanSupply, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) + + t.Run("generate supply calldata with native BNB", func(t *testing.T) { + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress(nativeDenomAddress), + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanSupply, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) + + t.Run("generate supply calldata with recipient", func(t *testing.T) { + recipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), + Recipient: recipient, + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanSupply, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) + + t.Run("generate withdraw calldata with WBNB", func(t *testing.T) { + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanWithdraw, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) + + t.Run("generate withdraw calldata with native BNB", func(t *testing.T) { + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress(nativeDenomAddress), + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanWithdraw, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) + + t.Run("generate withdraw calldata with recipient", func(t *testing.T) { + recipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + params := TransactionParams{ + Amount: big.NewInt(100000000000000000), // 0.1 BNB + Asset: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), + Recipient: recipient, + } + calldata, err := venus.GenerateCalldata(context.Background(), big.NewInt(56), LoanWithdraw, params) + require.NoError(t, err) + require.NotEmpty(t, calldata) + }) +} + +func TestVenus(t *testing.T) { + + t.Run("Liquid staking pool", func(t *testing.T) { + + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), + big.NewInt(56), + common.HexToAddress("0xd933909A4a2b7A4638903028f44D1d38ce27c352")) + require.NoError(t, err) + + tt := []struct { + asset string + name string + unsupported bool + }{ + {asset: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", name: "WBNB is supported"}, + {asset: nativeDenomAddress, name: "BNB is supported"}, + {asset: "0x55d398326f99059fF775485246999027B3197955", name: "USDT is not supported", unsupported: true}, + } + + _, err = venus.GetSupportedAssets(context.Background(), big.NewInt(56)) + require.NoError(t, err) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + if tc.unsupported { + require.False(t, venus.IsSupportedAsset(context.Background(), big.NewInt(56), common.HexToAddress(tc.asset))) + return + } + + require.True(t, venus.IsSupportedAsset(context.Background(), big.NewInt(56), common.HexToAddress(tc.asset))) + }) + } + }) + + t.Run("Stablecoins pool", func(t *testing.T) { + + venus, err := NewVenusOperation(getTestClient(t, ChainBSC), big.NewInt(56), common.HexToAddress("0x94c1495cD4c557f1560Cbd68EAB0d197e6291571")) + require.NoError(t, err) + + tt := []struct { + asset string + name string + unsupported bool + }{ + {asset: "0x55d398326f99059fF775485246999027B3197955", name: "USDT is supported"}, + {asset: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", name: "WBNB is not supported", unsupported: true}, + {asset: nativeDenomAddress, name: "BNB is not supported", unsupported: true}, + } + + _, err = venus.GetSupportedAssets(context.Background(), big.NewInt(56)) + require.NoError(t, err) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + if tc.unsupported { + require.False(t, venus.IsSupportedAsset(context.Background(), big.NewInt(56), common.HexToAddress(tc.asset))) + return + } + + require.True(t, venus.IsSupportedAsset(context.Background(), big.NewInt(56), common.HexToAddress(tc.asset))) + }) + } + }) +} diff --git a/tokens/1.json b/tokens/1.json index 50cae71..366fc64 100644 --- a/tokens/1.json +++ b/tokens/1.json @@ -1,207 +1,244 @@ { "tokens": [ { - "token_address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "name": "Ethereum", - "symbol": "ETH", + "token_address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + "name": "Aave Token", + "symbol": "AAVE", "decimals": 18 }, { - "token_address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", - "name": "Maker", - "symbol": "MKR", + "token_address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "name": "Coinbase Wrapped BTC", + "symbol": "cbBTC", + "decimals": 8 + }, + { + "token_address": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "name": "Coinbase Wrapped Staked ETH", + "symbol": "cbETH", "decimals": 18 }, { - "token_address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "name": "Wrapped liquid staked Ether 2.0", - "symbol": "wstETH", + "token_address": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "name": "Compound", + "symbol": "COMP", "decimals": 18 }, { - "token_address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "name": "Wrapped BTC", - "symbol": "WBTC", - "decimals": 8 + "token_address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "name": "Dai Stablecoin", + "symbol": "DAI", + "decimals": 18 }, { - "token_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "name": "USD Coin", - "symbol": "USDC", - "decimals": 6 + "token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "name": "Ethereum", + "symbol": "ETH", + "decimals": 18 }, { - "token_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "name": "Tether USD", - "symbol": "USDT", - "decimals": 6 + "token_address": "0x514910771AF9Ca656af840dff83E8264EcF986CA", + "name": "ChainLink Token", + "symbol": "LINK", + "decimals": 18 }, { - "token_address": "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", - "name": "Wrapped eETH", - "symbol": "weETH", + "token_address": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + "name": "Maker", + "symbol": "MKR", "decimals": 18 }, { - "token_address": "0x514910771af9ca656af840dff83e8264ecf986ca", - "name": "ChainLink Token", - "symbol": "LINK", + "token_address": "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38", + "name": "Staked ETH", + "symbol": "osETH", "decimals": 18 }, { - "token_address": "0xae78736cd615f374d3085123a210448e74fc6393", + "token_address": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", + "name": "ezETH", + "symbol": "Renzo Restaked ETH", + "decimals": 18 + }, + { + "token_address": "0xae78736Cd615f374D3085123A210448E74Fc6393", "name": "Rocket Pool ETH", "symbol": "rETH", "decimals": 18 }, { - "token_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "name": "Dai Stablecoin", - "symbol": "DAI", + "token_address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", + "name": "rsETH", + "symbol": "rsETH", "decimals": 18 }, { - "token_address": "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", - "name": "Aave Token", - "symbol": "AAVE", + "token_address": "0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0", + "name": "rswETH", + "symbol": "rswETH", "decimals": 18 }, { - "token_address": "0x83f20f44975d03b1b09e64809b757c47f942beea", + "token_address": "0x83F20F44975D03b1b09e64809B757c47f942BEeA", "name": "Savings Dai", "symbol": "sDAI", "decimals": 18 }, { - "token_address": "0xbe9895146f7af43049ca1c1ae358b0541ea49704", - "name": "Coinbase Wrapped Staked ETH", - "symbol": "cbETH", + "token_address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "name": "tBTC v2", + "symbol": "tBTC", "decimals": 18 }, { - "token_address": "0xf1c9acdc66974dfb6decb12aa385b9cd01190e38", - "name": "Staked ETH", - "symbol": "osETH", + "token_address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + "name": "Uniswap", + "symbol": "UNI", "decimals": 18 }, { - "token_address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "name": "Wrapped Ether", - "symbol": "WETH", + "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "name": "USD Coin", + "symbol": "USDC", + "decimals": 6 + }, + { + "token_address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "name": "Tether USD", + "symbol": "USDT", + "decimals": 6 + }, + { + "token_address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "name": "Wrapped BTC", + "symbol": "WBTC", + "decimals": 8 + }, + { + "token_address": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", + "name": "EtherFi wrapped ETH", + "symbol": "weETH", "decimals": 18 }, { - "token_address": "0xc00e94cb662c3520282e6f5717214004a7f26888", - "name": "Compound", - "symbol": "COMP", + "token_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "name": "Wrapped Ether", + "symbol": "WETH", "decimals": 18 }, { - "token_address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - "name": "Uniswap", - "symbol": "UNI", + "token_address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "name": "Wrapped liquid staked Ether 2.0", + "symbol": "wstETH", "decimals": 18 } ], "protocols": [ { - "address": "0x84db6ee82b7cf3b47e8f19270abde5718b936670", - "type": "staking", - "name": "Ankr", + "address": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", + "name": "aave_v3", "source": true, "destination": true, "tokens": [ - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" - ] - }, - { - "address": "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", - "name": "Lido", - "type": "staking", - "source": false, + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x514910771AF9Ca656af840dff83E8264EcF986CA", + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "0x83F20F44975D03b1b09e64809B757c47f942BEeA", + "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", + "0xae78736Cd615f374D3085123A210448E74Fc6393", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38" + ], + "type": "Loan" + }, + { + "address": "0x84db6eE82b7Cf3b47E8F19270abdE5718B936670", + "name": "ankr", + "source": true, "destination": true, "tokens": [ - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" - ] + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ], + "type": "Stake" }, { - "address": "0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46", - "name": "Rocketpool", - "type": "staking", + "address": "0xA17581A9E3356d9A858b789D68B4d866e593aE94", + "name": "Compound ETH pool", "source": true, "destination": true, "tokens": [ - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "0xae78736cd615f374d3085123a210448e74fc6393" - ] - }, - { - "address": "0xC13e21B648A5Ee794902342038FF3aDAB66BE987", - "name": "SparkLend", - "type": "lending", + "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", + "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", + "0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0", + "0xae78736Cd615f374D3085123A210448E74Fc6393", + "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", + "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38" + ], + "type": "Loan" + }, + { + "address": "0xc3d688B66703497DAA19211EEdff47f25384cdc3", + "name": "Compound USDC Pool", "source": true, "destination": true, "tokens": [ - "0x83f20f44975d03b1b09e64809b757c47f942beea", - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "0xae78736cd615f374d3085123a210448e74fc6393", - "0xdac17f958d2ee523a2206206994597c13d831ec7" - ] - }, - { - "address": "0xa17581a9e3356d9a858b789d68b4d866e593ae94", - "name": "Compound ETH Pool", - "type": "lending", + "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x514910771AF9Ca656af840dff83E8264EcF986CA", + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf" + ], + "type": "Loan" + }, + { + "address": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "name": "lido", "source": true, "destination": true, "tokens": [ - "0xbe9895146f7af43049ca1c1ae358b0541ea49704", - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "0xae78736cd615f374d3085123a210448e74fc6393", - "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", - "0xf1c9acdc66974dfb6decb12aa385b9cd01190e38", - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" - ] + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ], + "type": "Stake" }, { - "address": "0xc3d688b66703497daa19211eedff47f25384cdc3", - "name": "Compound USDC Pool", - "type": "lending", + "address": "0xDD3f50F8A6CafbE9b31a427582963f465E745AF8", + "name": "rocket_pool", "source": true, "destination": true, "tokens": [ - "0xc00e94cb662c3520282e6f5717214004a7f26888", - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - "0x514910771af9ca656af840dff83e8264ecf986ca", - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" - ] - }, - { - "address": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2", - "name": "AaveV3", - "type": "lending", + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ], + "type": "Stake" + }, + { + "address": "0xC13e21B648A5Ee794902342038FF3aDAB66BE987", + "name": "spark_lend", "source": true, "destination": true, "tokens": [ - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "0xdac17f958d2ee523a2206206994597c13d831ec7", - "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", - "0x514910771af9ca656af840dff83e8264ecf986ca", - "0xae78736cd615f374d3085123a210448e74fc6393", - "0x6b175474e89094c44da98b954eedeac495271d0f", - "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", - "0x83f20f44975d03b1b09e64809b757c47f942beea", - "0xbe9895146f7af43049ca1c1ae358b0541ea49704", - "0xf1c9acdc66974dfb6decb12aa385b9cd01190e38", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - ] + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "0x83F20F44975D03b1b09e64809B757c47f942BEeA", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xae78736Cd615f374D3085123A210448E74Fc6393", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ], + "type": "Loan" } ] -} +} \ No newline at end of file diff --git a/tokens/137.json b/tokens/137.json index 7e07f2d..5d86bce 100644 --- a/tokens/137.json +++ b/tokens/137.json @@ -1,98 +1,139 @@ { "tokens": [ { - "token_address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "name": "Polygon ( MATIC )", - "symbol": "POL", + "token_address": "0xD6DF932A45C0f255f85145f286eA0b292B21C90B", + "name": "Aave (PoS)", + "symbol": "AAVE", "decimals": 18 }, { - "token_address": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", - "name": "Wrapped Matic", - "symbol": "WMATIC", + "token_address": "0x172370d5Cd63279eFa6d502DAB29171933a610AF", + "name": "CRV (PoS)", + "symbol": "CRV", "decimals": 18 }, { "token_address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", - "name": "Dai Token", + "name": "(PoS) Dai Stablecoin", "symbol": "DAI", "decimals": 18 }, + { + "token_address": "0x385Eeac5cB85A38A9a07A70c73e0a3271CfB54A7", + "name": "Aavegotchi GHST Token (PoS)", + "symbol": "GHST", + "decimals": 18 + }, { "token_address": "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39", - "name": "Chainlink token", + "name": "ChainLink Token", "symbol": "LINK", "decimals": 18 }, + { + "token_address": "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6", + "name": "Liquid Staking Matic (PoS)", + "symbol": "MaticX", + "decimals": 18 + }, + { + "token_address": "0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4", + "name": "Staked MATIC (PoS)", + "symbol": "stMATIC", + "decimals": 18 + }, + { + "token_address": "0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a", + "name": "SushiToken (PoS)", + "symbol": "SUSHI", + "decimals": 18 + }, { "token_address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", - "name": "USDC.E", - "symbol": "USDC.e", + "name": "USD Coin (PoS)", + "symbol": "USDC", "decimals": 6 }, { "token_address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", - "name": "USDC", + "name": "USD Coin", "symbol": "USDC", "decimals": 6 }, - { - "token_address": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", - "name": "Wrapped Bitcoin", - "symbol": "WBTC", - "decimals": 8 - }, - { - "token_address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", - "name": "Wrapped Ethereum", - "symbol": "WETH", - "decimals": 18 - }, { "token_address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", - "name": "Tether USD", + "name": "(PoS) Tether USD", "symbol": "USDT", "decimals": 6 }, { - "token_address": "0xD6DF932A45C0f255f85145f286eA0b292B21C90B", - "name": "Aave", - "symbol": "AAVE", - "decimals": 18 + "token_address": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + "name": "(PoS) Wrapped BTC", + "symbol": "WBTC", + "decimals": 8 }, { - "token_address": "0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a", - "name": "Sushi token", - "symbol": "SUSHI", + "token_address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + "name": "Wrapped Ether", + "symbol": "WETH", "decimals": 18 }, { - "token_address": "0x03b54A6e9a984069379fae1a4fC4dBAE93B3bCCD", - "name": "Wrapped liquid staked Ether 2.0", - "symbol": "WsETH", + "token_address": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + "name": "Wrapped Matic", + "symbol": "WMATIC", "decimals": 18 } ], "protocols": [ { "address": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", - "name": "AaveV3", - "type": "lending", + "name": "aave_v3", "source": true, "destination": true, "tokens": [ - "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", - "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39", - "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + "0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a", + "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + "0x172370d5Cd63279eFa6d502DAB29171933a610AF", "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + "0x385Eeac5cB85A38A9a07A70c73e0a3271CfB54A7", + "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39", "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", - "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", "0xD6DF932A45C0f255f85145f286eA0b292B21C90B", + "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" + ], + "type": "Loan" + }, + { + "address": "0xF25212E676D1F7F89Cd72fFEe66158f541246445", + "name": "Compound USDC Pool", + "source": true, + "destination": true, + "tokens": [ "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", - "0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a", - "0x03b54A6e9a984069379fae1a4fC4dBAE93B3bCCD", - "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - ] + "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + "0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4", + "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6" + ], + "type": "Loan" + }, + { + "address": "0xaeB318360f27748Acb200CE616E389A6C9409a07", + "name": "Compound USDT pool", + "source": true, + "destination": true, + "tokens": [ + "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + "0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4", + "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6" + ], + "type": "Loan" } ] -} +} \ No newline at end of file diff --git a/tokens/56.json b/tokens/56.json index 0fd9903..56e2a69 100644 --- a/tokens/56.json +++ b/tokens/56.json @@ -1,27 +1,123 @@ { "tokens": [ { - "token_address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "token_address": "0x12f31B73D812C6Bb0d735a218c086d44D5fe5f89", + "name": "agEUR", + "symbol": "agEUR", + "decimals": 18 + }, + { + "token_address": "0x8F0528cE5eF7B51152A59745bEfDD91D97091d2F", + "name": "AlpacaToken", + "symbol": "ALPACA", + "decimals": 18 + }, + { + "token_address": "0xf307910A4c7bbc79691fD374889b36d8531B08e3", + "name": "Ankr", + "symbol": "ANKR", + "decimals": 18 + }, + { + "token_address": "0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827", + "name": "Ankr Staked BNB", + "symbol": "ankrBNB", + "decimals": 18 + }, + { + "token_address": "0xc748673057861a797275CD8A068AbB95A902e8de", + "name": "Baby Doge Coin", + "symbol": "BabyDoge", + "decimals": 9 + }, + { + "token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "name": "Binance Coin", "symbol": "BNB", "decimals": 18 }, { - "token_address": "0x2170ed0880ac9a755fd29b2688956bd959f933f8", - "name": "Ethereum Token", - "symbol": "ETH", + "token_address": "0x1bdd3Cf7F79cfB8EdbB955f20ad99211551BA275", + "name": "Liquid Staking BNB", + "symbol": "BNBx", "decimals": 18 }, { - "token_address": "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", + "token_address": "0x965F527D9159dCe6288a2219DB51fc6Eef120dD1", + "name": "Biswap", + "symbol": "BSW", + "decimals": 18 + }, + { + "token_address": "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "name": "BTCB Token", "symbol": "BTCB", "decimals": 18 }, { - "token_address": "0x55d398326f99059fF775485246999027B3197955", - "name": "Tether USD", - "symbol": "USDT", + "token_address": "0x352Cb5E19b12FC216548a2677bD0fce83BaE434B", + "name": "BitTorrent", + "symbol": "BTT", + "decimals": 18 + }, + { + "token_address": "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", + "name": "Ethereum Token", + "symbol": "ETH", + "decimals": 18 + }, + { + "token_address": "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409", + "name": "First Digital USD", + "symbol": "FDUSD", + "decimals": 18 + }, + { + "token_address": "0xfb5B838b6cfEEdC2873aB27866079AC55363D37E", + "name": "FLOKI", + "symbol": "FLOKI", + "decimals": 9 + }, + { + "token_address": "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5", + "name": "Hay Destablecoin", + "symbol": "HAY", + "decimals": 18 + }, + { + "token_address": "0xCa6d678e74f553f0E59cccC03ae644a3c2c5EE7d", + "name": "PLANET", + "symbol": "PLANET", + "decimals": 18 + }, + { + "token_address": "0x12BB890508c125661E03b09EC06E404bc9289040", + "name": "Radio Caca V2", + "symbol": "RACA", + "decimals": 18 + }, + { + "token_address": "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B", + "name": "Synclub Staked BNB", + "symbol": "SnBNB", + "decimals": 18 + }, + { + "token_address": "0xc2E9d07F66A89c44062459A47a0D2Dc038E4fb16", + "name": "Staked BNB", + "symbol": "stkBNB", + "decimals": 18 + }, + { + "token_address": "0xCE7de646e7208a4Ef112cb6ed5038FA6cC6b12e3", + "name": "TRON", + "symbol": "TRX", + "decimals": 6 + }, + { + "token_address": "0x4B0F1812e5Df2A09796481Ff14017e6005508003", + "name": "Trust Wallet", + "symbol": "TWT", "decimals": 18 }, { @@ -31,76 +127,179 @@ "decimals": 18 }, { - "token_address": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", - "name": "Wrapped BNB", - "symbol": "WBNB", + "token_address": "0x55d398326f99059fF775485246999027B3197955", + "name": "Tether USD", + "symbol": "USDT", "decimals": 18 }, { - "token_address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", - "name": "Dai Token", - "symbol": "DAI", + "token_address": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + "name": "Wrapped BNB", + "symbol": "WBNB", "decimals": 18 }, { - "token_address": "0x5f0da599bb2cccfcf6fdfd7d81743b6020864350", - "name": "Maker", - "symbol": "MKR", + "token_address": "0xaeF0d72a118ce24feE3cD1d43d383897D05B4e99", + "name": "WINk", + "symbol": "WIN", "decimals": 18 }, { - "token_address": "0x52ce071bd9b1c4b00a0b92d298c512478cad67e8", - "name": "Compound Coin", - "symbol": "COMP", + "token_address": "0x26c5e01524d2E6280A48F2c50fF6De7e52E9611C", + "name": "Wrapped liquid staked Ether 2.0", + "symbol": "wstETH", "decimals": 18 } ], "protocols": [ { "address": "0x6807dc923806fE8Fd134338EABCA509979a7e0cB", - "name": "AaveV3", - "type": "lending", + "name": "aave_v3", "source": true, "destination": true, "tokens": [ - "0x2170ed0880ac9a755fd29b2688956bd959f933f8", - "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", + "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", "0x55d398326f99059fF775485246999027B3197955", + "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", - "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" - ] + "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409" + ], + "type": "Loan" }, { "address": "0xf9278C7c4AEfAC4dDfd0D496f7a1C39cA6BCA6d4", - "name": "AvalonFinance", - "type": "lending", + "name": "avalon_finance", "source": true, "destination": true, "tokens": [ + "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", + "0x55d398326f99059fF775485246999027B3197955", + "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", - "0x2170ed0880ac9a755fd29b2688956bd959f933f8", "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" - ] + ], + "type": "Loan" + }, + { + "address": "0xa2E3356610840701BDf5611a53974510Ae27E2e1", + "name": "binance_staked_eth", + "source": true, + "destination": true, + "tokens": [ + "0x2170Ed0880ac9A755fd29B2688956BD959F933F8" + ], + "type": "Stake" }, { "address": "0x1adB950d8bB3dA4bE104211D5AB038628e477fE6", - "name": "ListaDAO", - "type": "staking", - "source": false, + "name": "lista_dao", + "source": true, "destination": true, "tokens": [ - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" - ] + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ], + "type": "Stake" }, { - "address": "0xa2E3356610840701BDf5611a53974510Ae27E2e1", - "name": "Binance Ethereum Staking", - "type": "staking", - "source": false, + "address": "0x3344417c9360b963ca93A4e8305361AEde340Ab9", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x4B0F1812e5Df2A09796481Ff14017e6005508003", + "0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827", + "0x55d398326f99059fF775485246999027B3197955", + "0x8F0528cE5eF7B51152A59745bEfDD91D97091d2F", + "0x965F527D9159dCe6288a2219DB51fc6Eef120dD1", + "0xCa6d678e74f553f0E59cccC03ae644a3c2c5EE7d", + "0xf307910A4c7bbc79691fD374889b36d8531B08e3" + ], + "type": "Loan" + }, + { + "address": "0x9DF11376Cf28867E2B0741348044780FbB7cb1d6", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c" + ], + "type": "Loan" + }, + { + "address": "0x94c1495cD4c557f1560Cbd68EAB0d197e6291571", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5", + "0x12f31B73D812C6Bb0d735a218c086d44D5fe5f89", + "0x55d398326f99059fF775485246999027B3197955" + ], + "type": "Loan" + }, + { + "address": "0x1b43ea8622e76627B81665B1eCeBB4867566B963", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x12BB890508c125661E03b09EC06E404bc9289040", + "0x55d398326f99059fF775485246999027B3197955", + "0xfb5B838b6cfEEdC2873aB27866079AC55363D37E" + ], + "type": "Loan" + }, + { + "address": "0xd933909A4a2b7A4638903028f44D1d38ce27c352", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x1bdd3Cf7F79cfB8EdbB955f20ad99211551BA275", + "0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827", + "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B", + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + "0xc2E9d07F66A89c44062459A47a0D2Dc038E4fb16" + ], + "type": "Loan" + }, + { + "address": "0x23b4404E4E5eC5FF5a6FFb70B7d14E3FabF237B0", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x352Cb5E19b12FC216548a2677bD0fce83BaE434B", + "0x55d398326f99059fF775485246999027B3197955", + "0xCE7de646e7208a4Ef112cb6ed5038FA6cC6b12e3", + "0xaeF0d72a118ce24feE3cD1d43d383897D05B4e99" + ], + "type": "Loan" + }, + { + "address": "0x33B6fa34cd23e5aeeD1B112d5988B026b8A5567d", + "name": "venus_isolated_pool", + "source": true, + "destination": true, + "tokens": [ + "0x55d398326f99059fF775485246999027B3197955", + "0xc748673057861a797275CD8A068AbB95A902e8de" + ], + "type": "Loan" + }, + { + "address": "0xBE609449Eb4D76AD8545f957bBE04b596E8fC529", + "name": "venus_isolated_pool", + "source": true, "destination": true, "tokens": [ - "0x2170ed0880ac9a755fd29b2688956bd959f933f8" - ] + "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", + "0x26c5e01524d2E6280A48F2c50fF6De7e52E9611C" + ], + "type": "Loan" } ] -} +} \ No newline at end of file diff --git a/tokens/token_impl.go b/tokens/token_impl.go index 635c42b..f5d660e 100644 --- a/tokens/token_impl.go +++ b/tokens/token_impl.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/blndgs/protocol_registry/pkg" + "github.com/ethereum/go-ethereum/common" ) //go:embed *.json @@ -80,8 +81,9 @@ func (r *JSONTokenRegistry) GetTokenByAddress(chainID *big.Int, address string) return nil, fmt.Errorf("no data available for chain ID %d", chainID) } + addr := common.HexToAddress(address).Hex() for _, token := range data.Tokens { - if token.TokenAddress == address { + if token.TokenAddress == addr { return &token, nil } } @@ -98,8 +100,10 @@ func (r *JSONTokenRegistry) GetProtocolByAddress(chainID *big.Int, address strin return nil, fmt.Errorf("no data available for chain ID %d", chainID) } + addr := common.HexToAddress(address).Hex() + for _, protocol := range data.Protocols { - if protocol.Address == address { + if protocol.Address == addr { return &protocol, nil } } diff --git a/tokens/token_impl_test.go b/tokens/token_impl_test.go index a641e6f..0e6f8ad 100644 --- a/tokens/token_impl_test.go +++ b/tokens/token_impl_test.go @@ -71,9 +71,9 @@ func TestGetTokens(t *testing.T) { want int wantErr bool }{ - {"Ethereum chain", pkg.EthChainID, 17, false}, - {"BSC chain", pkg.BscChainID, 9, false}, - {"Polyhon chain", pkg.PolygonChainID, 12, false}, + {"Ethereum chain", pkg.EthChainID, 22, false}, + {"BSC chain", pkg.BscChainID, 25, false}, + {"Polygon chain", pkg.PolygonChainID, 14, false}, {"Unknown chain", big.NewInt(999), 0, true}, } @@ -101,8 +101,8 @@ func TestGetProtocols(t *testing.T) { wantErr bool }{ {"Ethereum chain", pkg.EthChainID, 7, false}, - {"BSC chain", pkg.BscChainID, 4, false}, - {"Polygon chain", pkg.PolygonChainID, 1, false}, + {"BSC chain", pkg.BscChainID, 12, false}, + {"Polygon chain", pkg.PolygonChainID, 3, false}, {"Unknown chain", big.NewInt(999), 0, true}, } @@ -161,9 +161,9 @@ func TestGetProtocolByAddress(t *testing.T) { want string wantErr bool }{ - {"Ethereum AaveV3", pkg.EthChainID, "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2", "AaveV3", false}, - {"BSC AaveV3", pkg.BscChainID, "0x6807dc923806fE8Fd134338EABCA509979a7e0cB", "AaveV3", false}, - {"Polygon AaveV3", pkg.PolygonChainID, "0x794a61358D6845594F94dc1DB02A252b5b4814aD", "AaveV3", false}, + {"Ethereum AaveV3", pkg.EthChainID, "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2", "aave_v3", false}, + {"BSC AaveV3", pkg.BscChainID, "0x6807dc923806fE8Fd134338EABCA509979a7e0cB", "aave_v3", false}, + {"Polygon AaveV3", pkg.PolygonChainID, "0x794a61358D6845594F94dc1DB02A252b5b4814aD", "aave_v3", false}, {"Unknown protocol", pkg.EthChainID, "0x1234567890123456789012345678901234567890", "", true}, {"Unknown chain", big.NewInt(999), "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2", "", true}, } diff --git a/tools/tokens.go b/tools/tokens.go new file mode 100644 index 0000000..e0d4c04 --- /dev/null +++ b/tools/tokens.go @@ -0,0 +1,305 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "math/big" + "net/http" + "os" + "sort" + "strings" + + "github.com/blndgs/protocol_registry/pkg" + "github.com/blndgs/protocol_registry/tokens" + "github.com/joho/godotenv" +) + +const ( + nativeTokenAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" +) + +type MoralisResponse struct { + TokenName string `json:"tokenName"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` +} + +func getTokenMetadata(ctx context.Context, tokenAddr string, chainID *big.Int) (string, string, int, error) { + + var moralisAPIKey = os.Getenv("MORALIS_API_KEY") + if moralisAPIKey == "" { + panic("provide moralis api key in .env") + } + + var chainStr string + switch chainID.Int64() { + case 1: + chainStr = "eth" + case 56: + chainStr = "bsc" + case 137: + chainStr = "polygon" + default: + return "", "", 0, fmt.Errorf("unsupported chain ID: %d", chainID) + } + + url := fmt.Sprintf("https://deep-index.moralis.io/api/v2.2/erc20/%s/price?chain=%s&include=percent_change", tokenAddr, chainStr) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", "", 0, fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Add("Accept", "application/json") + req.Header.Add("X-API-Key", moralisAPIKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", "", 0, fmt.Errorf("failed to make request: %v", err) + } + defer resp.Body.Close() + + // Check if response status is greater than 201 + if resp.StatusCode > 201 { + return "", "", 0, fmt.Errorf("invalid token or API error: status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", 0, fmt.Errorf("failed to read response body: %v", err) + } + + var result map[string]interface{} + err = json.Unmarshal(body, &result) + if err != nil { + return "", "", 0, fmt.Errorf("failed to parse response: %v", err) + } + + name, ok := result["tokenName"].(string) + if !ok { + return "", "", 0, fmt.Errorf("token name not found") + } + + symbol, ok := result["tokenSymbol"].(string) + if !ok { + return "", "", 0, fmt.Errorf("token symbol not found") + } + + decimalsStr, ok := result["tokenDecimals"].(string) + if !ok { + return "", "", 0, fmt.Errorf("token decimals not found") + } + + var decimals int + _, err = fmt.Sscanf(decimalsStr, "%d", &decimals) + if err != nil { + return "", "", 0, fmt.Errorf("failed to parse decimals: %v", err) + } + + return name, symbol, decimals, nil +} + +// sortProtocolData sorts both tokens and protocols arrays in the protocol data +func sortProtocolData(data *tokens.Data) { + // Sort tokens by symbol + sort.Slice(data.Tokens, func(i, j int) bool { + return strings.ToLower(data.Tokens[i].Symbol) < strings.ToLower(data.Tokens[j].Symbol) + }) + + // Sort protocols by name + sort.Slice(data.Protocols, func(i, j int) bool { + return strings.ToLower(data.Protocols[i].Name) < strings.ToLower(data.Protocols[j].Name) + }) + + // Deduplicate and sort token arrays within each protocol + for i := range data.Protocols { + // Use a map to deduplicate tokens + tokenMap := make(map[string]bool) + var uniqueTokens []string + + for _, token := range data.Protocols[i].Tokens { + if !tokenMap[token] { + tokenMap[token] = true + uniqueTokens = append(uniqueTokens, token) + } + } + + // Sort the unique tokens + sort.Strings(uniqueTokens) + + // Replace the original tokens array with the sorted, unique tokens + data.Protocols[i].Tokens = uniqueTokens + } +} + +func main() { + + if err := godotenv.Load(); err != nil { + fmt.Println("add an .env file with MORALIS_API_KEY=xyz") + panic(err) + } + + chains := []struct { + Name string + ChainID *big.Int + MoralisChain string + NativeSymbol string + NativeName string + }{ + { + Name: "Ethereum", + ChainID: big.NewInt(1), + MoralisChain: "eth", + NativeSymbol: "ETH", + NativeName: "Ethereum", + }, + { + Name: "BNB Chain", + ChainID: big.NewInt(56), + MoralisChain: "bsc", + NativeSymbol: "BNB", + NativeName: "Binance Coin", + }, + { + Name: "Polygon", + ChainID: big.NewInt(137), + MoralisChain: "polygon", + NativeSymbol: "MATIC", + NativeName: "Polygon", + }, + } + + registry, err := pkg.NewProtocolRegistry([]pkg.ChainConfig{ + { + ChainID: big.NewInt(1), + RPCURL: "https://rpc.ankr.com/eth", + }, + { + ChainID: big.NewInt(56), + RPCURL: "https://rpc.ankr.com/bsc", + }, + { + ChainID: big.NewInt(137), + RPCURL: "https://rpc.ankr.com/polygon", + }, + }) + + if err != nil { + panic(err.Error()) + } + + ctx := context.Background() + + for _, chain := range chains { + fmt.Println("updating file for", chain.Name) + + var protocolData tokens.Data + uniqueTokens := make(map[string]bool) + validTokens := make(map[string]bool) + + protocols := registry.ListProtocols(chain.ChainID) + + // First, collect all unique tokens and validate them + for _, p := range protocols { + supportedAssets, err := p.GetSupportedAssets(ctx, chain.ChainID) + if err != nil { + panic(fmt.Sprintf("error getting supported assets for protocol %s on chain %s: %v", p.GetName(), chain.Name, err)) + } + + for _, addr := range supportedAssets { + addrStr := addr.Hex() + if !uniqueTokens[addrStr] { + uniqueTokens[addrStr] = true + + // Skip validation for native token + if strings.EqualFold(addrStr, nativeTokenAddress) { + validTokens[addrStr] = true + continue + } + + // Validate token using Moralis + _, _, _, err := getTokenMetadata(ctx, addrStr, chain.ChainID) + if err == nil { + validTokens[addrStr] = true + } else { + fmt.Printf("Skipping invalid token %s on chain %s: %v\n", addrStr, chain.Name, err) + } + } + } + } + + // Now add protocols, but only include valid tokens + for _, p := range protocols { + supportedAssets, _ := p.GetSupportedAssets(ctx, chain.ChainID) + var validTokenAddrs []string + + for _, addr := range supportedAssets { + addrStr := addr.Hex() + if validTokens[addrStr] { + validTokenAddrs = append(validTokenAddrs, addrStr) + } + } + + // Only add protocol if it has valid tokens + if len(validTokenAddrs) > 0 { + protocolData.Protocols = append(protocolData.Protocols, tokens.Protocol{ + Address: p.GetContractAddress(chain.ChainID).Hex(), + Name: p.GetName(), + Type: string(p.GetType()), + Source: true, + Destination: true, + Tokens: validTokenAddrs, + }) + } + } + + // Add valid tokens to the tokens array + for addr := range validTokens { + var token tokens.Token + + if strings.EqualFold(addr, nativeTokenAddress) { + // Handle native token + token = tokens.Token{ + TokenAddress: addr, + Name: chain.NativeName, + Symbol: chain.NativeSymbol, + Decimals: 18, // Native tokens always have 18 decimals + } + } else { + // Handle ERC20 token + name, symbol, decimals, err := getTokenMetadata(ctx, addr, chain.ChainID) + if err != nil { + // This shouldn't happen as we already validated the token + continue + } + + token = tokens.Token{ + TokenAddress: addr, + Name: name, + Symbol: symbol, + Decimals: decimals, + } + } + + protocolData.Tokens = append(protocolData.Tokens, token) + } + + // Sort the protocol data + sortProtocolData(&protocolData) + + // Write the data to the corresponding chain file + fileName := fmt.Sprintf("tokens/%d.json", chain.ChainID) + data, err := json.MarshalIndent(protocolData, "", " ") + if err != nil { + panic(fmt.Sprintf("error marshaling data for %s: %v", chain.Name, err)) + } + + err = os.WriteFile(fileName, data, 0644) + if err != nil { + panic(fmt.Sprintf("error writing file for %s: %v", chain.Name, err)) + } + } +}