|
| 1 | +# Plan: Auto-generate `allCoinMetas` entries from SDK for EVM recovery coins |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +Currently, every time a new EVM chain is added to the `@bitgo/statics` SDK with `EVM_UNSIGNED_SWEEP_RECOVERY` or `EVM_NON_BITGO_RECOVERY` features, a developer must also manually add a corresponding entry in `allCoinMetas` inside `src/helpers/config.ts`. Without that entry, `assertMetadata()` returns false and the coin is silently excluded from recovery flows. |
| 6 | + |
| 7 | +The goal is to auto-generate a sensible default `CoinMetadata` from the SDK coin object for any EVM recovery coin not already explicitly defined in `allCoinMetas`. After this change, an SDK bump alone is sufficient to surface new EVM recovery coins — no changes to this repo are needed. Manual entries in `allCoinMetas` remain supported as overrides. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Critical File |
| 12 | + |
| 13 | +- `src/helpers/config.ts` — only file to modify |
| 14 | + |
| 15 | +## Key insight: deriving `ApiKeyProvider` from the SDK |
| 16 | + |
| 17 | +Two-step lookup for `ApiKeyProvider`: |
| 18 | + |
| 19 | +**Step 1** — `Environments[env].evm?.[coinName]?.baseUrl` from `@bitgo/sdk-core` |
| 20 | +Covers all coins using `SHARED_EVM_SDK` (the standard path for new EVM chains). Example: |
| 21 | +- `inketh` (prod): `https://explorer.inkonchain.com/api` → `explorer.inkonchain.com` |
| 22 | +- `tinketh` (test): `https://explorer-sepolia.inkonchain.com/api` → `explorer-sepolia.inkonchain.com` ✓ |
| 23 | + |
| 24 | +**Step 2** (fallback) — `coin.network.explorerUrl` from `@bitgo/statics` (already imported) |
| 25 | +31 testnet coins (`tunieth`, `thoodeth`, `thppeth`, etc.) have `EVM_NON_BITGO_RECOVERY` but are |
| 26 | +NOT in the testnet `evm` map — they all have `explorerUrl` set. This fallback covers them. |
| 27 | +Example: `thoodeth` → `https://explorer.testnet.chain.robinhood.com/tx/` → `explorer.testnet.chain.robinhood.com` |
| 28 | + |
| 29 | +`@bitgo/sdk-core` is a safe import in `src/` — it's already a transitive dependency via |
| 30 | +`@bitgo/sdk-coin-ada` (imported in `src/utils/types.ts`). |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## Implementation |
| 35 | + |
| 36 | +### Step 1 — Add `generateEvmCoinMeta` helper (after the `allCoinMetas` closing brace, before `assertMetadata`) |
| 37 | + |
| 38 | +Add import at the top of config.ts: |
| 39 | +```typescript |
| 40 | +import { Environments } from '@bitgo/sdk-core'; |
| 41 | +``` |
| 42 | + |
| 43 | +```typescript |
| 44 | +function generateEvmCoinMeta(coin: { |
| 45 | + name: string; |
| 46 | + fullName: string; |
| 47 | + network: { type: NetworkType; explorerUrl?: string }; |
| 48 | +}): CoinMetadata { |
| 49 | + const isTestnet = coin.network.type === NetworkType.TESTNET; |
| 50 | + // Testnet coins typically start with 't'; use the mainnet name for the icon |
| 51 | + const iconName = isTestnet && coin.name.startsWith('t') ? coin.name.slice(1) : coin.name; |
| 52 | + // Step 1: check Environments evm map (SHARED_EVM_SDK coins) |
| 53 | + const env = isTestnet ? 'test' : 'prod'; |
| 54 | + const evmBaseUrl = Environments[env].evm?.[coin.name]?.baseUrl; |
| 55 | + // Step 2: fall back to statics explorerUrl (covers testnet coins missing from evm map) |
| 56 | + const fallbackUrl = coin.network.explorerUrl; |
| 57 | + const rawUrl = evmBaseUrl ?? fallbackUrl; |
| 58 | + const apiKeyProvider = rawUrl ? new URL(rawUrl).hostname : undefined; |
| 59 | + return { |
| 60 | + Title: coin.name.toUpperCase(), |
| 61 | + Description: coin.fullName, |
| 62 | + value: coin.name, |
| 63 | + Icon: iconName, |
| 64 | + ...(apiKeyProvider && { ApiKeyProvider: apiKeyProvider }), |
| 65 | + defaultGasLimit: '500,000', |
| 66 | + defaultGasLimitNum: 500000, |
| 67 | + defaultMaxFeePerGas: 20, |
| 68 | + defaultMaxPriorityFeePerGas: 10, |
| 69 | + }; |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +### Step 2 — Modify the `coins.forEach` loop (lines 1567-1588) |
| 74 | + |
| 75 | +Replace the existing loop with a version that auto-generates metadata instead of gating on `assertMetadata`: |
| 76 | + |
| 77 | +```typescript |
| 78 | +coins.forEach(coin => { |
| 79 | + if (coin.isToken) return; |
| 80 | + |
| 81 | + const name = coin.name; |
| 82 | + const isTestnet = coin.network.type === NetworkType.TESTNET; |
| 83 | + const hasUnsignedSweep = coin.features.includes(CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY); |
| 84 | + const hasNonBitgo = coin.features.includes(CoinFeature.EVM_NON_BITGO_RECOVERY); |
| 85 | + |
| 86 | + if (!hasUnsignedSweep && !hasNonBitgo) return; |
| 87 | + |
| 88 | + // Auto-generate metadata for any coin not already explicitly defined |
| 89 | + if (!Object.prototype.hasOwnProperty.call(allCoinMetas, name)) { |
| 90 | + allCoinMetas[name] = generateEvmCoinMeta(coin); |
| 91 | + } |
| 92 | + |
| 93 | + if (hasUnsignedSweep) { |
| 94 | + if (isTestnet) testEvmUnsignedSweepCoins.push(name); |
| 95 | + else prodEvmUnsignedSweepCoins.push(name); |
| 96 | + } |
| 97 | + |
| 98 | + if (hasNonBitgo) { |
| 99 | + if (isTestnet) testEvmNonBitgoRecoveryCoins.push(name); |
| 100 | + else prodEvmNonBitgoRecoveryCoins.push(name); |
| 101 | + } |
| 102 | +}); |
| 103 | +``` |
| 104 | + |
| 105 | +### Step 3 — Remove `assertMetadata` function (now unused) |
| 106 | + |
| 107 | +Delete lines 1552–1558. The auto-generation in step 2 replaces its gating role. |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## Default values rationale |
| 112 | + |
| 113 | +| Field | Default | Reason | |
| 114 | +|---|---|---| |
| 115 | +| `Icon` | strip `t` prefix for testnets | Matches existing pattern (`tinketh` → `inketh`) | |
| 116 | +| `ApiKeyProvider` | `Environments[env].evm?.[coin].baseUrl` then `coin.network.explorerUrl` | evm map covers SHARED_EVM_SDK coins; explorerUrl fallback covers 31 testnet coins missing from evm map; both are undefined only for `phrs` (which has a TODO in the SDK) | |
| 117 | +| `defaultGasLimit` | `'500,000'` / `500000` | Conservative safe default | |
| 118 | +| `defaultMaxFeePerGas` | `20` | Matches most explicit entries | |
| 119 | +| `defaultMaxPriorityFeePerGas` | `10` | Matches most explicit entries | |
| 120 | + |
| 121 | +Explicit entries in `allCoinMetas` are checked first via `hasOwnProperty`, so any custom override (different gas limits, custom `ApiKeyProvider`, `isTssSupported`, etc.) is always respected. |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## Verification |
| 126 | + |
| 127 | +### Pre-verification: temporarily add `tempo` to `node_modules/@bitgo/sdk-core` evm map |
| 128 | + |
| 129 | +`tempo` already has an explicit `allCoinMetas` entry (so it won't be auto-generated normally). |
| 130 | +To test the auto-generation path end-to-end, temporarily: |
| 131 | +1. Add `tempo: { baseUrl: 'https://dummy.alchemy.com/api' }` to the `evm` prod block in |
| 132 | + `node_modules/@bitgo/sdk-core/dist/src/bitgo/environments.js` |
| 133 | +2. Temporarily **delete** the `tempo` entry from `allCoinMetas` in `config.ts` |
| 134 | +3. Run verification script (below) — confirm `tempo` gets auto-generated with the correct `ApiKeyProvider: 'dummy.alchemy.com'` |
| 135 | +4. Restore both changes |
| 136 | + |
| 137 | +### After making changes, run inline Node.js snippets to verify: |
| 138 | + |
| 139 | +**1. TypeScript type-check** |
| 140 | +```bash |
| 141 | +npx tsc --noEmit |
| 142 | +``` |
| 143 | + |
| 144 | +**2. UI smoke-test** |
| 145 | +- Start dev server, switch to testnet, open Unsigned Sweep and Non-BitGo Recovery |
| 146 | +- Confirm existing EVM coins appear with correct gas defaults |
| 147 | +- Confirm new auto-generated coins (e.g. `tunieth`, `thoodeth`) now appear in coin dropdown |
0 commit comments