Skip to content

Commit a6d8621

Browse files
feat: remove dependency for code changes for evm like coins
1 parent 31351e9 commit a6d8621

9 files changed

Lines changed: 275 additions & 23 deletions

File tree

docs/auto-generate-allCoinMetas.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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

electron/main/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import { registerAll as EVMCoinRegisterAll } from '@bitgo/sdk-coin-evm';
8585
import { CoinFeature, coins } from '@bitgo/statics';
8686
import { Xtz, Txtz } from '@bitgo/sdk-coin-xtz';
8787
import { Ton, Tton, JettonToken } from '@bitgo/sdk-coin-ton';
88+
import { Tempo, Tip20Token } from '@bitgo/sdk-coin-tempo';
8889

8990
const bip32 = BIP32Factory(ecc);
9091

@@ -223,6 +224,8 @@ sdk.register('xtz', Xtz.createInstance);
223224
sdk.register('txtz', Txtz.createInstance);
224225
sdk.register('ton', Ton.createInstance);
225226
sdk.register('tton', Tton.createInstance);
227+
sdk.register('tempo', Tempo.createInstance);
228+
sdk.register('ttempo', Tempo.createInstance);
226229
EVMCoinRegisterAll(sdk);
227230

228231
Erc20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
@@ -264,6 +267,9 @@ VetToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
264267
JettonToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
265268
sdk.register(name, coinConstructor);
266269
});
270+
Tip20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
271+
sdk.register(name, coinConstructor);
272+
});
267273

268274
function handleSdkError(e: unknown): string {
269275
if (typeof e === 'string' && e !== null) {

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"@bitgo/sdk-coin-zec": "2.8.7",
7777
"@bitgo/sdk-coin-zeta": "3.7.7",
7878
"@bitgo/sdk-opensslbytes": "2.1.0",
79+
"@bitgo/sdk-coin-tempo": "^1.12.4",
7980
"@bitgo/statics": "58.39.0",
8081
"@bitgo/utxo-lib": "11.22.1",
8182
"@ethereumjs/common": "2.6.5",

scripts/debug-recover.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import EthereumCommon from '@ethereumjs/common';
2+
import { BitGoAPI } from '@bitgo/sdk-api';
3+
import { AbstractEthLikeNewCoins } from '@bitgo/sdk-coin-eth';
4+
import { Tempo } from '@bitgo/sdk-coin-tempo';
5+
import { loadWebAssembly } from '@bitgo/sdk-opensslbytes';
6+
7+
const coin = 'ttempo';
8+
const parameters = JSON.parse('{"apiKey":"sdas","backupKey":"{\\"iv\\":\\"Avq5oqmrh7IAuS8oDX2JHA==\\",\\"v\\":1,\\"iter\\":10000,\\"ks\\":256,\\"ts\\":64,\\"mode\\"\\n:\\"ccm\\",\\"adata\\":\\"\\",\\"cipher\\":\\"aes\\",\\"salt\\":\\"InEpO9AFH/o=\\",\\"ct\\":\\"0sWQ5FVrRv3d56\\njJO3XPfgCyVccp4wcmRYL+Cy5DYHbfCxe6HM03ubobTdWuJ8RGRRINYXO+f96XoeS2Xm3mJhgUF\\nWhzQkTvQwlYIPSwCLRQeGT3sOdAxGFncNFjvcybOsZ9cOLMi9PKpak0f6BWQGDUM4L1V5ufObJ9\\nk8HzOfdsug33YsruvfZaOKgM2e6CCLxrdjUMw8mvQgExEWGuOCif8AQvnYsmEZ4swYbDrfaN53V\\nyNJtWwkYXkfgOF2vubrqo3O5LeqlqZ9Kulwo9X2HZQ9iejC8a38wzE+m6krTZDJI1sgrX1MBSZ4\\nPtkji/eIDOTjzC+A04bEhYHJi4gTpWIqweBzrE43wIXQ2QEaHMpLnYyhayyJD/WbQ6En+rW21yv\\nO2JDRIy+tnBM5uxe1do9Mzjr5ocmcsAgq8vTTS2bzrOZ+/ax9XUBeVXxkmSNYypcJ3wPgzdSHrc\\nmTIwIdmL5a1Ou30wBXJdBoRxRri+PF/jqjA3UkjwaI4gQulydzSTSfFT1AAtwm7Jl0Gse5p8cgU\\nmp5A4p9ZFv4Kh8ND3wwCX/k4V/2D+tz/N1Syl0MbkBPUFwcT5j7ryRlOg2h/7Am6qzn4PHmcuIo\\nimsPdm4hN5AT6fG9qFXqC6V3NFX5TXL4oma3PqAKTJ5dkVtDbDEEm8rrKfl9Xfisu7ktjVXiWuI\\nH+8SJPM15BxTvkfwZUNfpNRV5PKqAzMyplldhQvrpQjVw7nS4Eu0gkwuujfj7e73Vfpk2+3Euyx\\nCHoMvobDTy96FUG1SAo79JpIfcA+zvTNVAfQYvmQTAGUaQLDb47NeS9D5l/lgu5df06/K63FxP3\\n1HvqTFnPA9fekOY7wnRmGXvvJNqiQ90gWiM5QA4V5WhUpSnrMDPYYMnOHsAPwo0sbunpsDG8Sdp\\nzWT9nRmAAmi9NWN+v+Lk7UXdyUq+QlGarNKx2h/k0pVs5oHaTZKfMn8MYT/scVOskJ4S+UYi1gv\\nCB2j0e0edbYZ4gHoJZTGKbkYvD1XVyd7XV4SJTCvvHN/f62WbwPNjr5uIB755fq6Fsv29yactdh\\n5QajAmDJmBf1sHlhFDA4iDR1EHUb0bZigtfWbSOv\\"}","gasLimit":500000,"krsProvider":"","recoveryDestination":"0x80151ebf635e6ec8a5455258f617be6cda1fbd7e","isTss":true,"userKey":"{\\"iv\\":\\"lBEjacWMuAzqfOHnNLbwGg==\\",\\"v\\":1,\\"iter\\":10000,\\"ks\\":256,\\"ts\\":64,\\"mode\\"\\n:\\"ccm\\",\\"adata\\":\\"\\",\\"cipher\\":\\"aes\\",\\"salt\\":\\"CvWbHDA4WD4=\\",\\"ct\\":\\"6BGRLlzV7mvh71\\nDO9dbzrB0Zd2KX/E64KT3nQSKYEg6AAEJQM1GjQRfwzJmG1TS/XASbk69r4/k2+MOEq+xLPKebb\\nNrzXra/NuI713bWCrgTAJ5ORuX/rWvDHTXzYwwlvPwqghMOcOnqDvIMUPTev2aldZIf0qDMPWuF\\nJ5sb8DVBtOy9ycz7Jmjg2omulTsIuCjQCNBKSyHIDOPdEA5pZQZSk+vxaIrArCxeEX+o4hAY4qK\\nAb4OOGdJSzakiznP8l8NA4HwBD72eCCfa2R/Qy0VrA9HJP4bVsXGi9FeZ3k1wLoaKdZr+FG3WXZ\\n/JdvY0tCjsnHXXU8Te5N25Dv/wuhLj4ntIGiMmK9vW9dvzcspZfcLjWjFnLPyCRBLMEBe/ARykf\\nCHTKMBggE+mJFmvdAiyC6maSlCPtkGmpgpvl+QHhFuYi50dlSVqvfqNJf1Fl1tApL7TSXwdzuMP\\n7KsPHrl2Ovz9EHDerQblbchL4RSLgNW46TsP6dFLGef7W8oONPfcxkCUO8ZdyRHHjofMbLTKYZH\\n4GgAoCDSLvsFr/jtGZJWC+dWq+TDCVJYyLkt7SS96GJgzWS+EpRiTteFk+FvOzGyVtY31UZnVd0\\nEH+aH+YD1tkt2Mg5lyUmeDv26LoH6yInxGI/+vHf0lbppBhaaSPO5vJiABBr8baj1XwHiGGMv/1\\nEwBp/1K47OZUfte+mNQd4mOEBdXCA0h4QGvrMxHPLazINsNThl9Kcrj1A1M4pMt3F3Bez4LKbJA\\nddADhC2reoeVNqkONAdWmZkN1ojPrpVcOTHgfgWHlajDX6DJFAde4LxcGv2nb4lSm9rvoLG+Swi\\n8nv+5H8/xi7glgthCS9S4k4ARO5R7VYYfhGMzqCwMsicxzuDwsHl8sQmyzXYva6Kf+pSbZadlSV\\ntpCdI4MPfLev/SQaV9mCJWPT2ak+HLCuy5XfLxvYiRGaHP930Bt0TQ60hKZ9SdXtJ43SOdPZJo5\\nr2CiuV+KhQtOvlrj7kAdOFYD5IMKtNlV/wsGorixqg4+q7/Q/8vT7nhrdXrmDDDD8KAYwhrXv3I\\n3RPT8X5sXaJNbxADRovWpEjIPQ9VAfXD0P8pX1z6\\"}","walletContractAddress":"0x94e3cf69f473c2496b82389aba8fc4bb0a8cd9ae","walletPassphrase":"uHDm]Qji2@l]Q/&&GJpM","gasPrice":20000000000,"eip1559":{"maxFeePerGas":20000000000,"maxPriorityFeePerGas":10000000000},"replayProtectionOptions":{"chain":42431,"hardfork":"london"},"bitgoKey":"","ignoreAddressTypes":[]}'); // <-- paste your JSON string here
9+
10+
const sdk = new BitGoAPI({ env: 'test' });
11+
sdk.register('tempo', Tempo.createInstance);
12+
sdk.register('ttempo', Tempo.createInstance);
13+
14+
async function main() {
15+
const baseCoin = sdk.coin(coin) as AbstractEthLikeNewCoins;
16+
if (parameters.ethCommonParams) {
17+
parameters.common = EthereumCommon.custom({ ...parameters.ethCommonParams });
18+
}
19+
const openSSLBytes = loadWebAssembly().buffer;
20+
const result = await baseCoin.recover({ ...parameters, openSSLBytes }, openSSLBytes);
21+
console.log('Result:', JSON.stringify(result, null, 2));
22+
}
23+
24+
main().catch(console.error);

src/components/CryptocurrencyIcon/get-dynamic-icon.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable */
22
// DO NOT MODIFY THIS FILE. IT IS AUTO-GENERATED BY /scripts/generate-currency-icons.
3-
import { lazy } from 'react';
3+
import { lazy, type ComponentType } from 'react';
44
export const getDynamicIcon = (name: string) => {
55
switch (name) {
66
case '$edison':
@@ -4494,6 +4494,8 @@ export const getDynamicIcon = (name: string) => {
44944494
case 'zusd':
44954495
return lazy(() => import('@bitgo-forks/cryptocurrency-icons/react/zusd'));
44964496
default:
4497-
return null;
4497+
const modules = import.meta.glob<{ default: ComponentType<any> }>('/node_modules/@bitgo-forks/cryptocurrency-icons/react/*.js');
4498+
const importFn = modules[`/node_modules/@bitgo-forks/cryptocurrency-icons/react/${name}.js`];
4499+
return importFn ? lazy(importFn) : null;
44984500
}
44994501
};

src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ const evmCoins = [
9797
'tunieth',
9898
'h',
9999
'th',
100+
'tempoPathusd',
101+
'ttempoPathusd',
100102
...testEvmUnsignedSweepCoins,
101103
...prodEvmUnsignedSweepCoins,
102104
];
@@ -1018,6 +1020,8 @@ function Form() {
10181020
case 'topethToken':
10191021
case 'polygonToken':
10201022
case 'tpolygonToken':
1023+
case 'tempoPathusd':
1024+
case 'ttempoPathusd':
10211025
return (
10221026
<EthLikeTokenForm
10231027
key={coin}

src/containers/NonBitGoRecoveryCoin/NonBitGoRecoveryCoin.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const evmCoins = [
8989
'tunieth',
9090
'h',
9191
'th',
92+
'tempoPathusd',
93+
'ttempoPathusd',
9294
...testEvmNonBitgoRecoveryCoins,
9395
...prodEvmNonBitgoRecoveryCoins,
9496
];
@@ -1001,6 +1003,8 @@ function Form() {
10011003
case 'topethToken':
10021004
case 'polygonToken':
10031005
case 'tpolygonToken':
1006+
case 'tempoPathusd':
1007+
case 'ttempoPathusd':
10041008
return (
10051009
<EthLikeTokenForm
10061010
key={coin}

0 commit comments

Comments
 (0)