From badd8539b7fd6bb1188b9057d6831310790c9687 Mon Sep 17 00:00:00 2001 From: Shubham Damkondwar Date: Fri, 13 Mar 2026 17:03:15 +0530 Subject: [PATCH] feat(statics): add DynamicNetwork and DynamicCoin for AMS-discovered chains Ticket: CECHO-439 --- modules/statics/src/base.ts | 46 +++++++++++++++++ modules/statics/src/coins.ts | 34 ++++++++++++- modules/statics/src/networks.ts | 89 +++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/modules/statics/src/base.ts b/modules/statics/src/base.ts index cfc367e67b..255788fe90 100644 --- a/modules/statics/src/base.ts +++ b/modules/statics/src/base.ts @@ -3931,3 +3931,49 @@ export abstract class BaseCoin { return baseFeatures.filter((feature) => !excludedFeatures.includes(feature)); } } + +export interface DynamicCoinConstructorOptions { + id: string; + fullName: string; + name: string; + alias?: string; + prefix?: string; + suffix?: string; + denom?: string; + baseUnit: string; + kind: string; + isToken: boolean; + features: string[]; + decimalPlaces: number; + asset: string; + network: BaseNetwork; + primaryKeyCurve: string; +} + +/** + * Concrete coin class for AMS-discovered chains not yet registered in local statics. + * + * Extends {@link BaseCoin} directly with empty required/disallowed + * feature sets — AMS is the source of truth for features. Accepts string-typed enum + * fields and casts internally (safe since CoinKind, CoinFeature, UnderlyingAsset, + * KeyCurve are all string enums). + */ +export class DynamicCoin extends BaseCoin { + protected requiredFeatures(): Set { + return new Set(); + } + + protected disallowedFeatures(): Set { + return new Set(); + } + + constructor(options: DynamicCoinConstructorOptions) { + super({ + ...options, + kind: options.kind as CoinKind, + features: options.features as CoinFeature[], + asset: options.asset as UnderlyingAsset, + primaryKeyCurve: options.primaryKeyCurve as KeyCurve, + }); + } +} diff --git a/modules/statics/src/coins.ts b/modules/statics/src/coins.ts index 4ea9e62458..9e8681545b 100644 --- a/modules/statics/src/coins.ts +++ b/modules/statics/src/coins.ts @@ -30,10 +30,10 @@ import { erc721Token, } from './account'; import { ofcToken } from './ofc'; -import { BaseCoin, CoinFeature } from './base'; +import { BaseCoin, CoinFeature, DynamicCoin } from './base'; import { AmsTokenConfig, TrimmedAmsTokenConfig } from './tokenConfig'; import { CoinMap } from './map'; -import { Networks, NetworkType } from './networks'; +import { DynamicNetwork, DynamicNetworkOptions, Networks, NetworkType } from './networks'; import { networkFeatureMapForTokens } from './networkFeatureMapForTokens'; import { ofcErc20Coins, tOfcErc20Coins } from './coins/ofcErc20Coins'; import { ofcCoins } from './coins/ofcCoins'; @@ -133,6 +133,9 @@ export function createToken(token: AmsTokenConfig): Readonly | undefin const family = token.family; const initializer = initializerMap[family] as (...args: unknown[]) => Readonly; if (!initializer) { + if (!token.isToken) { + return buildDynamicCoin(token); + } return undefined; } @@ -352,6 +355,33 @@ export function createToken(token: AmsTokenConfig): Readonly | undefin } } +/** + * Build a real DynamicCoin + DynamicNetwork instance for AMS-discovered base chains + * whose family is not yet registered in the SDK's initializerMap. + * Called from createToken() as a fallback when no initializer exists and isToken is false. + */ +function buildDynamicCoin(token: AmsTokenConfig): Readonly { + const network = Object.freeze(new DynamicNetwork(token.network as DynamicNetworkOptions)); + return Object.freeze( + new DynamicCoin({ + id: token.id, + name: token.name, + fullName: token.fullName, + decimalPlaces: token.decimalPlaces, + asset: token.asset as string, + isToken: token.isToken, + features: token.features as string[], + network, + primaryKeyCurve: (token.primaryKeyCurve as string) ?? 'secp256k1', + prefix: token.prefix ?? '', + suffix: token.suffix ?? token.name.toUpperCase(), + baseUnit: (token.baseUnit as string) ?? 'wei', + kind: (token.kind as string) ?? 'crypto', + alias: token.alias, + }) + ); +} + function getAptTokenInitializer(token: AmsTokenConfig) { if (token.assetId) { // used for fungible-assets / legacy coins etc. diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 6c2fcd5931..63c2f827d7 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -2539,6 +2539,95 @@ class TempoTestnet extends Testnet implements EthereumNetwork { tokenOperationHashPrefix = '42431'; } +/** + * Constructor options for {@link DynamicNetwork}. + * Accepts string-typed `type` and `family` so AMS JSON can be passed directly. + * Fields mirror BaseNetwork + AccountNetwork + EthereumNetwork. + */ +export interface DynamicNetworkOptions { + // BaseNetwork + name: string; + type: string; + family: string; + explorerUrl?: string; + // AccountNetwork + accountExplorerUrl?: string; + blockExplorerUrl?: string; + // EthereumNetwork + chainId?: number; + batcherContractAddress?: string; + forwarderFactoryAddress?: string; + forwarderImplementationAddress?: string; + walletFactoryAddress?: string; + walletImplementationAddress?: string; + walletV2FactoryAddress?: string; + walletV2ImplementationAddress?: string; + walletV4FactoryAddress?: string; + walletV4ImplementationAddress?: string; + walletV2ForwarderFactoryAddress?: string; + walletV2ForwarderImplementationAddress?: string; + walletV4ForwarderFactoryAddress?: string; + walletV4ForwarderImplementationAddress?: string; + nativeCoinOperationHashPrefix?: string; + tokenOperationHashPrefix?: string; +} + +/** + * Concrete network class for AMS-discovered chains not yet registered in local statics. + * Accepts string-typed type/family and casts to enums internally (safe since both are string enums). + * Currently covers BaseNetwork + AccountNetwork + EthereumNetwork fields. + */ +export class DynamicNetwork extends BaseNetwork { + public readonly name: string; + public readonly type: NetworkType; + public readonly family: CoinFamily; + public readonly explorerUrl: string | undefined; + public readonly accountExplorerUrl?: string; + public readonly blockExplorerUrl?: string; + public readonly chainId?: number; + public readonly batcherContractAddress?: string; + public readonly forwarderFactoryAddress?: string; + public readonly forwarderImplementationAddress?: string; + public readonly walletFactoryAddress?: string; + public readonly walletImplementationAddress?: string; + public readonly walletV2FactoryAddress?: string; + public readonly walletV2ImplementationAddress?: string; + public readonly walletV4FactoryAddress?: string; + public readonly walletV4ImplementationAddress?: string; + public readonly walletV2ForwarderFactoryAddress?: string; + public readonly walletV2ForwarderImplementationAddress?: string; + public readonly walletV4ForwarderFactoryAddress?: string; + public readonly walletV4ForwarderImplementationAddress?: string; + public readonly nativeCoinOperationHashPrefix?: string; + public readonly tokenOperationHashPrefix?: string; + + constructor(options: DynamicNetworkOptions) { + super(); + this.name = options.name; + this.type = options.type as NetworkType; + this.family = options.family as CoinFamily; + this.explorerUrl = options.explorerUrl; + this.accountExplorerUrl = options.accountExplorerUrl; + this.blockExplorerUrl = options.blockExplorerUrl; + this.chainId = options.chainId; + this.batcherContractAddress = options.batcherContractAddress; + this.forwarderFactoryAddress = options.forwarderFactoryAddress; + this.forwarderImplementationAddress = options.forwarderImplementationAddress; + this.walletFactoryAddress = options.walletFactoryAddress; + this.walletImplementationAddress = options.walletImplementationAddress; + this.walletV2FactoryAddress = options.walletV2FactoryAddress; + this.walletV2ImplementationAddress = options.walletV2ImplementationAddress; + this.walletV4FactoryAddress = options.walletV4FactoryAddress; + this.walletV4ImplementationAddress = options.walletV4ImplementationAddress; + this.walletV2ForwarderFactoryAddress = options.walletV2ForwarderFactoryAddress; + this.walletV2ForwarderImplementationAddress = options.walletV2ForwarderImplementationAddress; + this.walletV4ForwarderFactoryAddress = options.walletV4ForwarderFactoryAddress; + this.walletV4ForwarderImplementationAddress = options.walletV4ForwarderImplementationAddress; + this.nativeCoinOperationHashPrefix = options.nativeCoinOperationHashPrefix; + this.tokenOperationHashPrefix = options.tokenOperationHashPrefix; + } +} + export const Networks = { main: { ada: Object.freeze(new Ada()),