From 0f1f80960437e18e5e7fd5b53870546fbf9998e0 Mon Sep 17 00:00:00 2001 From: Maksym Panchyshyn Date: Thu, 20 Aug 2026 11:41:48 +0200 Subject: [PATCH] feat: add testnet chains (base, btc, tron:nile, optimism, polygon, bsc) --- README.md | 2 +- docs/USAGE.md | 8 ++++++++ src/chains.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++ test/portal.test.ts | 40 +++++++++++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd5ab8c..45e606e 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ await client.sendAssets({ }); ``` -Chain fields (`chain` / `chainId` on sign, sendAssets, evaluate, and the `{chain}` path on build-transaction) take the `WalletChain` enum rather than raw strings. Each value is the chain's CAIP-2 id, and `WALLET_CHAINS[chain]` / `getWalletChainConfig(chain)` expose its `curve` and `requiresRpcUrl`. Primary chains: Monad, Ethereum, Solana, Stellar, Tron, Bitcoin, Arbitrum, Avalanche, Base, Optimism, Polygon, Celo (all `_MAINNET`), plus `ETHEREUM_SEPOLIA` and `SOLANA_DEVNET` (testnets). +Chain fields (`chain` / `chainId` on sign, sendAssets, evaluate, and the `{chain}` path on build-transaction) take the `WalletChain` enum rather than raw strings. Each value is the chain's CAIP-2 id, and `WALLET_CHAINS[chain]` / `getWalletChainConfig(chain)` expose its `curve` and `requiresRpcUrl`. Primary chains: Monad, Ethereum, Solana, Stellar, Tron, Bitcoin, Arbitrum, Avalanche, Base, Optimism, Polygon, Celo, BSC (all `_MAINNET`), plus the testnets `ETHEREUM_SEPOLIA`, `SOLANA_DEVNET`, `BITCOIN_TESTNET`, `ARBITRUM_SEPOLIA`, `BASE_SEPOLIA`, `OPTIMISM_SEPOLIA`, `POLYGON_AMOY`, `BSC_TESTNET`, and `TRON_NILE`. ### Signing diff --git a/docs/USAGE.md b/docs/USAGE.md index 6154aaa..10d73f0 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -149,13 +149,21 @@ const cfg = getWalletChainConfig(WalletChain.SOLANA_MAINNET); | `ETHEREUM_SEPOLIA` | `eip155:11155111` | SECP256K1 | false | | `MONAD_MAINNET` | `eip155:143` | SECP256K1 | false | | `ARBITRUM_MAINNET` | `eip155:42161` | SECP256K1 | false | +| `ARBITRUM_SEPOLIA` | `eip155:421614` | SECP256K1 | false | | `AVALANCHE_MAINNET` | `eip155:43114` | SECP256K1 | false | | `BASE_MAINNET` | `eip155:8453` | SECP256K1 | false | +| `BASE_SEPOLIA` | `eip155:84532` | SECP256K1 | false | | `OPTIMISM_MAINNET` | `eip155:10` | SECP256K1 | false | +| `OPTIMISM_SEPOLIA` | `eip155:11155420` | SECP256K1 | false | | `POLYGON_MAINNET` | `eip155:137` | SECP256K1 | false | +| `POLYGON_AMOY` | `eip155:80002` | SECP256K1 | false | | `CELO_MAINNET` | `eip155:42220` | SECP256K1 | true | +| `BSC_MAINNET` | `eip155:56` | SECP256K1 | false | +| `BSC_TESTNET` | `eip155:97` | SECP256K1 | false | | `BITCOIN_MAINNET` | `bip122:000000000019d6689c085ae165831e93-p2wpkh` | SECP256K1 | true | +| `BITCOIN_TESTNET` | `bip122:000000000933ea01ad0ee984209779ba-p2wpkh` | SECP256K1 | true | | `TRON_MAINNET` | `tron:mainnet` | SECP256K1 | true | +| `TRON_NILE` | `tron:nile` | SECP256K1 | true | | `SOLANA_MAINNET` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | ED25519 | false | | `SOLANA_DEVNET` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | ED25519 | false | | `STELLAR_MAINNET` | `stellar:pubnet` | ED25519 | true | diff --git a/src/chains.ts b/src/chains.ts index 3e0cfff..76f1fa1 100644 --- a/src/chains.ts +++ b/src/chains.ts @@ -15,14 +15,21 @@ export enum WalletChain { SOLANA_DEVNET = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', STELLAR_MAINNET = 'stellar:pubnet', TRON_MAINNET = 'tron:mainnet', + TRON_NILE = 'tron:nile', BITCOIN_MAINNET = 'bip122:000000000019d6689c085ae165831e93-p2wpkh', + BITCOIN_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba-p2wpkh', ARBITRUM_MAINNET = 'eip155:42161', + ARBITRUM_SEPOLIA = 'eip155:421614', AVALANCHE_MAINNET = 'eip155:43114', BASE_MAINNET = 'eip155:8453', + BASE_SEPOLIA = 'eip155:84532', OPTIMISM_MAINNET = 'eip155:10', + OPTIMISM_SEPOLIA = 'eip155:11155420', POLYGON_MAINNET = 'eip155:137', + POLYGON_AMOY = 'eip155:80002', CELO_MAINNET = 'eip155:42220', BSC_MAINNET = 'eip155:56', + BSC_TESTNET = 'eip155:97', } export interface WalletChainConfig { @@ -84,18 +91,36 @@ export const WALLET_CHAINS: Record = { requiresRpcUrl: true, tatumNetwork: 'tron-mainnet', }, + [WalletChain.TRON_NILE]: { + chainId: WalletChain.TRON_NILE, + curve: 'SECP256K1', + requiresRpcUrl: true, + tatumNetwork: 'tron-nile', + }, [WalletChain.BITCOIN_MAINNET]: { chainId: WalletChain.BITCOIN_MAINNET, curve: 'SECP256K1', requiresRpcUrl: true, tatumNetwork: 'bitcoin-mainnet', }, + [WalletChain.BITCOIN_TESTNET]: { + chainId: WalletChain.BITCOIN_TESTNET, + curve: 'SECP256K1', + requiresRpcUrl: true, + tatumNetwork: 'bitcoin-testnet', + }, [WalletChain.ARBITRUM_MAINNET]: { chainId: WalletChain.ARBITRUM_MAINNET, curve: 'SECP256K1', requiresRpcUrl: false, tatumNetwork: 'arb-one-mainnet', }, + [WalletChain.ARBITRUM_SEPOLIA]: { + chainId: WalletChain.ARBITRUM_SEPOLIA, + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'arbitrum-one-sepolia', + }, [WalletChain.AVALANCHE_MAINNET]: { chainId: WalletChain.AVALANCHE_MAINNET, curve: 'SECP256K1', @@ -108,18 +133,36 @@ export const WALLET_CHAINS: Record = { requiresRpcUrl: false, tatumNetwork: 'base-mainnet', }, + [WalletChain.BASE_SEPOLIA]: { + chainId: WalletChain.BASE_SEPOLIA, + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'base-sepolia', + }, [WalletChain.OPTIMISM_MAINNET]: { chainId: WalletChain.OPTIMISM_MAINNET, curve: 'SECP256K1', requiresRpcUrl: false, tatumNetwork: 'optimism-mainnet', }, + [WalletChain.OPTIMISM_SEPOLIA]: { + chainId: WalletChain.OPTIMISM_SEPOLIA, + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'optimism-testnet', + }, [WalletChain.POLYGON_MAINNET]: { chainId: WalletChain.POLYGON_MAINNET, curve: 'SECP256K1', requiresRpcUrl: false, tatumNetwork: 'polygon-mainnet', }, + [WalletChain.POLYGON_AMOY]: { + chainId: WalletChain.POLYGON_AMOY, + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'polygon-amoy', + }, [WalletChain.CELO_MAINNET]: { chainId: WalletChain.CELO_MAINNET, curve: 'SECP256K1', @@ -132,6 +175,12 @@ export const WALLET_CHAINS: Record = { requiresRpcUrl: false, tatumNetwork: 'bsc-mainnet', }, + [WalletChain.BSC_TESTNET]: { + chainId: WalletChain.BSC_TESTNET, + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'bsc-testnet', + }, }; /** Resolve the configuration for a supported chain. */ diff --git a/test/portal.test.ts b/test/portal.test.ts index 1befefe..efc2c4b 100644 --- a/test/portal.test.ts +++ b/test/portal.test.ts @@ -463,7 +463,7 @@ describe('Portal-backed Tatum Wallets SDK', () => { describe('WalletChain primary-chain mapping', () => { it('exposes a Portal config for every primary chain, with the enum value as the chainId', () => { const chains = Object.values(WalletChain); - expect(chains).toHaveLength(15); + expect(chains).toHaveLength(22); for (const chain of chains) { const config = getWalletChainConfig(chain); @@ -503,12 +503,50 @@ describe('WalletChain primary-chain mapping', () => { // Non-obvious Tatum gateway slugs. expect(WALLET_CHAINS[WalletChain.ARBITRUM_MAINNET].tatumNetwork).toBe('arb-one-mainnet'); expect(WALLET_CHAINS[WalletChain.AVALANCHE_MAINNET].tatumNetwork).toBe('avax-mainnet'); + expect(WALLET_CHAINS[WalletChain.ARBITRUM_SEPOLIA].tatumNetwork).toBe('arbitrum-one-sepolia'); expect(WALLET_CHAINS[WalletChain.ETHEREUM_SEPOLIA]).toEqual({ chainId: 'eip155:11155111', curve: 'SECP256K1', requiresRpcUrl: false, tatumNetwork: 'ethereum-sepolia', }); + expect(WALLET_CHAINS[WalletChain.BASE_SEPOLIA]).toEqual({ + chainId: 'eip155:84532', + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'base-sepolia', + }); + expect(WALLET_CHAINS[WalletChain.POLYGON_AMOY]).toEqual({ + chainId: 'eip155:80002', + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'polygon-amoy', + }); + expect(WALLET_CHAINS[WalletChain.OPTIMISM_SEPOLIA]).toEqual({ + chainId: 'eip155:11155420', + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'optimism-testnet', + }); + expect(WALLET_CHAINS[WalletChain.BSC_TESTNET]).toEqual({ + chainId: 'eip155:97', + curve: 'SECP256K1', + requiresRpcUrl: false, + tatumNetwork: 'bsc-testnet', + }); + // Bitcoin and Tron have no Portal-managed RPC on either network. + expect(WALLET_CHAINS[WalletChain.BITCOIN_TESTNET]).toEqual({ + chainId: 'bip122:000000000933ea01ad0ee984209779ba-p2wpkh', + curve: 'SECP256K1', + requiresRpcUrl: true, + tatumNetwork: 'bitcoin-testnet', + }); + expect(WALLET_CHAINS[WalletChain.TRON_NILE]).toEqual({ + chainId: 'tron:nile', + curve: 'SECP256K1', + requiresRpcUrl: true, + tatumNetwork: 'tron-nile', + }); }); });