From 9420fa51ece0739f386c93b7c0e305f72063a8b7 Mon Sep 17 00:00:00 2001 From: lupitmoxie Date: Sat, 1 Aug 2026 12:27:04 +0700 Subject: [PATCH] chore: add TODO and stubs for Robinhood token-by-contract support --- ROBINHOOD_TOKEN_SUPPORT.md | 19 +++++++++++++++++++ src/chains/robinhood.ts | 6 ++++++ src/tokens/resolveByContract.ts | 24 ++++++++++++++++++++++++ tests/resolveByContract.test.ts | 11 +++++++++++ 4 files changed, 60 insertions(+) create mode 100644 ROBINHOOD_TOKEN_SUPPORT.md create mode 100644 src/chains/robinhood.ts create mode 100644 src/tokens/resolveByContract.ts create mode 100644 tests/resolveByContract.test.ts diff --git a/ROBINHOOD_TOKEN_SUPPORT.md b/ROBINHOOD_TOKEN_SUPPORT.md new file mode 100644 index 0000000..ef772ea --- /dev/null +++ b/ROBINHOOD_TOKEN_SUPPORT.md @@ -0,0 +1,19 @@ +# ROBINHOOD_TOKEN_SUPPORT + +Goal: support any token on the Robinhood chain by contract address. + +Tasks: +1. Add Robinhood chain config (chainId, RPC endpoint, block explorer URL). +2. Implement token resolver: + - Given rpcUrl and contract address, call standard ERC-20 metadata: name(), symbol(), decimals(). + - Return { name, symbol, decimals, address, logo? } and a status for non-ERC20 tokens. +3. Validate and checksum addresses; handle invalid contracts gracefully. +4. Add caching, rate-limiting and retries for RPC calls. +5. Integrate resolver into token lookup flows (backend and UI). +6. Add unit tests and an integration test against a public Robinhood RPC or testnet. +7. Docs: usage, environment variables for RPC, and security notes. + +Notes: +- Use ethers.js (recommended) or web3.js. +- Fall back to tokenlist lookup or a metadata service if metadata calls fail. +- Add feature flag / UI input for manual contract address when chain = Robinhood. diff --git a/src/chains/robinhood.ts b/src/chains/robinhood.ts new file mode 100644 index 0000000..7c5fb3c --- /dev/null +++ b/src/chains/robinhood.ts @@ -0,0 +1,6 @@ +export const robinhoodChain = { + name: 'Robinhood', + chainId: 0x0, // TODO: replace with actual chainId (decimal or hex) + rpc: process.env.ROBINHOOD_RPC || 'https://rpc.robinhood.example', // TODO: real RPC + explorer: 'https://explorer.robinhood.example', // TODO +}; diff --git a/src/tokens/resolveByContract.ts b/src/tokens/resolveByContract.ts new file mode 100644 index 0000000..1eb76f5 --- /dev/null +++ b/src/tokens/resolveByContract.ts @@ -0,0 +1,24 @@ +import { ethers } from 'ethers'; + +const ERC20_ABI = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', +]; + +export async function resolveTokenByContract(rpcUrl: string, address: string) { + const provider = new ethers.providers.JsonRpcProvider(rpcUrl); + const contract = new ethers.Contract(address, ERC20_ABI, provider); + + try { + const [name, symbol, decimals] = await Promise.all([ + contract.name(), + contract.symbol(), + contract.decimals(), + ]); + return { address, name, symbol, decimals: Number(decimals) }; + } catch (err) { + // Non-ERC20 or call failure — return a structured error/status + return { address, error: 'metadata_unavailable', details: String(err) }; + } +} diff --git a/tests/resolveByContract.test.ts b/tests/resolveByContract.test.ts new file mode 100644 index 0000000..578b95c --- /dev/null +++ b/tests/resolveByContract.test.ts @@ -0,0 +1,11 @@ +import { resolveTokenByContract } from '../src/tokens/resolveByContract'; + +describe('resolveTokenByContract', () => { + it('returns token metadata for a valid ERC20 on Robinhood', async () => { + // TODO: use a real test RPC or a mocked provider + const rpc = process.env.ROBINHOOD_RPC || 'https://rpc.robinhood.example'; + const result = await resolveTokenByContract(rpc, '0x0000000000000000000000000000000000000000'); + // replace assertions once a real test token is selected + expect(result).toHaveProperty('address'); + }); +});