From feed663a719af399f1ddfde0984ea9002be1dffa Mon Sep 17 00:00:00 2001 From: Mohammed Ryaan Date: Mon, 11 May 2026 12:11:39 +0530 Subject: [PATCH] feat(statics): add zama support TICKET: CHALO-402 --- modules/statics/src/account.ts | 111 +++++++++++++++++++++ modules/statics/src/allCoinsAndTokens.ts | 2 + modules/statics/src/base.ts | 18 ++++ modules/statics/src/coinFeatures.ts | 9 ++ modules/statics/src/coins/erc7984Tokens.ts | 55 ++++++++++ modules/statics/src/index.ts | 1 + modules/statics/src/tokenConfig.ts | 64 +++++++++++- 7 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 modules/statics/src/coins/erc7984Tokens.ts diff --git a/modules/statics/src/account.ts b/modules/statics/src/account.ts index a735e876a4..7adfb776eb 100644 --- a/modules/statics/src/account.ts +++ b/modules/statics/src/account.ts @@ -8,6 +8,7 @@ import { CANTON_TOKEN_FEATURES, CELO_TOKEN_FEATURES, COSMOS_SIDECHAIN_FEATURES, + ERC7984_TOKEN_FEATURES, TEMPO_FEATURES, } from './coinFeatures'; @@ -96,6 +97,10 @@ export interface Erc721ConstructorOptions extends AccountConstructorOptions { contractAddress: string; } +export interface Erc7984ConstructorOptions extends AccountConstructorOptions { + contractAddress: string; +} + export interface NFTCollectionIdConstructorOptions extends AccountConstructorOptions { nftCollectionId: string; } @@ -294,6 +299,19 @@ export class Erc721Coin extends ContractAddressDefinedToken {} */ export class Erc1155Coin extends ContractAddressDefinedToken {} +/** + * ERC-7984 is the confidential token standard for fhEVM-enabled blockchains (Zama). + * Token balances are stored as FHE-encrypted ciphertexts; transfers use confidentialTransfer() + * instead of the standard ERC-20 transfer(). Balance reads require delegated decryption via ACL. + * + * {@link https://eips.ethereum.org/EIPS/eip-7984 EIP-7984} + */ +export class Erc7984Coin extends ContractAddressDefinedToken { + constructor(options: Erc7984ConstructorOptions) { + super(options); + } +} + /** * The TRON blockchain supports tokens of the ERC20 standard similar to ETH ERC20 tokens. */ @@ -1087,6 +1105,99 @@ export function terc20( return erc20(id, name, fullName, decimalPlaces, contractAddress, asset, features, prefix, suffix, network); } +/** + * Factory function for ERC-7984 confidential token instances (Zama fhEVM). + * + * ERC-7984 tokens store balances as FHE-encrypted ciphertexts. Transfers use + * confidentialTransfer() and balance reads require ACL delegation to BitGo. + * + * @param id uuid v4 + * @param name unique identifier of the token (e.g. 'eth:ctkn') + * @param fullName Complete human-readable name of the token + * @param decimalPlaces Number of decimal places this token supports (divisibility exponent) + * @param contractAddress Contract address of this token + * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param features? Features of this coin. Defaults to ERC7984_TOKEN_FEATURES + * @param prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to Ethereum main network. + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function erc7984( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = ERC7984_TOKEN_FEATURES, + prefix = '', + suffix: string = name.toUpperCase(), + network: EthereumNetwork = Networks.main.ethereum, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return Object.freeze( + new Erc7984Coin({ + id, + name, + fullName, + network, + contractAddress, + prefix, + suffix, + features, + decimalPlaces, + asset, + isToken: true, + primaryKeyCurve, + baseUnit: BaseUnit.ETH, + }) + ); +} + +/** + * Factory function for testnet ERC-7984 confidential token instances (Zama fhEVM). + * + * @param id uuid v4 + * @param name unique identifier of the token (e.g. 'hteth:ctkn') + * @param fullName Complete human-readable name of the token + * @param decimalPlaces Number of decimal places this token supports (divisibility exponent) + * @param contractAddress Contract address of this token + * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param features? Features of this coin. Defaults to ERC7984_TOKEN_FEATURES + * @param prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to Hoodi test network. + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function terc7984( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = ERC7984_TOKEN_FEATURES, + prefix = '', + suffix: string = name.toUpperCase(), + network: EthereumNetwork = Networks.test.hoodi, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return erc7984( + id, + name, + fullName, + decimalPlaces, + contractAddress, + asset, + features, + prefix, + suffix, + network, + primaryKeyCurve + ); +} + /** * Factory function for erc721 token instances. * diff --git a/modules/statics/src/allCoinsAndTokens.ts b/modules/statics/src/allCoinsAndTokens.ts index 5874dc9804..2169d08791 100644 --- a/modules/statics/src/allCoinsAndTokens.ts +++ b/modules/statics/src/allCoinsAndTokens.ts @@ -72,6 +72,7 @@ import { nep141Tokens } from './coins/nep141Tokens'; import { vetTokens } from './coins/vetTokens'; import { cosmosTokens } from './coins/cosmosTokens'; import { jettonTokens } from './coins/jettonTokens'; +import { erc7984Tokens } from './coins/erc7984Tokens'; import { polyxTokens } from './coins/polyxTokens'; import { cantonTokens } from './coins/cantonTokens'; import { flrp } from './flrp'; @@ -164,6 +165,7 @@ export const allCoinsAndTokens = [ ...botTokens, ...adaTokens, ...jettonTokens, + ...erc7984Tokens, ...polyxTokens, ...cantonTokens, avaxp( diff --git a/modules/statics/src/base.ts b/modules/statics/src/base.ts index 9367965017..5088c250af 100644 --- a/modules/statics/src/base.ts +++ b/modules/statics/src/base.ts @@ -559,6 +559,18 @@ export enum CoinFeature { * This coin allows negative fees in transactions */ ALLOWS_NEGATIVE_FEE = 'allows-negative-fee', + + /** + * This token uses fully homomorphic encryption (FHE) for confidential transfers (ERC-7984). + * Balances are stored as encrypted ciphertexts; transfers use confidentialTransfer() instead of transfer(). + */ + CONFIDENTIAL_TRANSFER = 'confidential-transfer', + + /** + * Reading the balance of this token requires the wallet owner to delegate decryption access + * to BitGo via ACL.delegateForUserDecryption() before balances can be displayed. + */ + REQUIRES_DECRYPTION_DELEGATION = 'requires-decryption-delegation', } /** @@ -3794,6 +3806,12 @@ export enum UnderlyingAsset { 'eth:drv' = 'eth:drv', 'eth:prn' = 'eth:prn', 'eth:zama' = 'eth:zama', + // ERC-7984 confidential tokens (Zama fhEVM - mainnet, contract addresses TBD pending Zama mainnet launch) + 'eth:ctkn' = 'eth:ctkn', + 'eth:cusdt' = 'eth:cusdt', + // ERC-7984 confidential tokens (Zama fhEVM - testnet / hteth) + 'hteth:ctkn' = 'hteth:ctkn', + 'hteth:cusdt' = 'hteth:cusdt', 'eth:mony' = 'eth:mony', 'eth:architectgvi' = 'eth:architectgvi', 'eth:zk' = 'eth:zk', diff --git a/modules/statics/src/coinFeatures.ts b/modules/statics/src/coinFeatures.ts index c65d1b2c6e..9a018a14e1 100644 --- a/modules/statics/src/coinFeatures.ts +++ b/modules/statics/src/coinFeatures.ts @@ -789,3 +789,12 @@ export const CANTON_TOKEN_FEATURES = [ CoinFeature.REQUIRES_DEPOSIT_ACCEPTANCE_TRANSACTION, CoinFeature.ALPHANUMERIC_MEMO_ID, ]; + +export const ERC7984_TOKEN_FEATURES = [ + ...ACCOUNT_COIN_DEFAULT_FEATURES, + CoinFeature.BULK_TRANSACTION, + CoinFeature.TSS, + CoinFeature.TSS_COLD, + CoinFeature.CONFIDENTIAL_TRANSFER, + CoinFeature.REQUIRES_DECRYPTION_DELEGATION, +]; diff --git a/modules/statics/src/coins/erc7984Tokens.ts b/modules/statics/src/coins/erc7984Tokens.ts new file mode 100644 index 0000000000..8a8e3bdadb --- /dev/null +++ b/modules/statics/src/coins/erc7984Tokens.ts @@ -0,0 +1,55 @@ +import { erc7984, terc7984 } from '../account'; +import { UnderlyingAsset } from '../base'; + +/** + * ERC-7984 confidential tokens (Zama fhEVM). + * + * These tokens use fully homomorphic encryption (FHE) for on-chain confidential transfers. + * Balances are stored as encrypted ciphertexts; plaintext amounts require ACL-delegated + * decryption via the Zama Gateway before they can be displayed. + * + * Mainnet contract addresses are TBD pending Zama fhEVM mainnet launch. + * Testnet tokens (hteth:*) are deployed on the BitGo-supported ETH testnet (Hoodi). + * + * Sandbox development contracts (Sepolia): + * CTKN: 0x94167129172A35ab093B44b8b96213DDbc3cD387 + * cUSDT: 0x4E7B06D78965594eB5EF5414c357ca21E1554491 + */ +export const erc7984Tokens = [ + // Mainnet tokens (contract addresses TBD pending Zama fhEVM mainnet launch) + erc7984( + 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + 'eth:ctkn', + 'Confidential Test Token', + 6, + '0x0000000000000000000000000000000000000000', // TODO: update with mainnet contract address + UnderlyingAsset['eth:ctkn'] + ), + erc7984( + 'f47ac10b-58cc-4372-a567-0e02b2c3d480', + 'eth:cusdt', + 'Confidential USDT', + 6, + '0x0000000000000000000000000000000000000000', // TODO: update with mainnet contract address + UnderlyingAsset['eth:cusdt'] + ), + + // Testnet tokens (hteth / Hoodi) + // Note: sandbox development contracts are on Ethereum Sepolia; deploy to Hoodi for BitGo testnet support + terc7984( + 'f47ac10b-58cc-4372-a567-0e02b2c3d481', + 'hteth:ctkn', + 'Confidential Test Token', + 6, + '0x0000000000000000000000000000000000000000', // TODO: deploy to Hoodi and update address (Sepolia dev: 0x94167129172A35ab093B44b8b96213DDbc3cD387) + UnderlyingAsset['hteth:ctkn'] + ), + terc7984( + 'f47ac10b-58cc-4372-a567-0e02b2c3d482', + 'hteth:cusdt', + 'Confidential USDT', + 6, + '0x0000000000000000000000000000000000000000', // TODO: deploy to Hoodi and update address (Sepolia dev: 0x4E7B06D78965594eB5EF5414c357ca21E1554491) + UnderlyingAsset['hteth:cusdt'] + ), +]; diff --git a/modules/statics/src/index.ts b/modules/statics/src/index.ts index 2adb89bfda..5361c89114 100644 --- a/modules/statics/src/index.ts +++ b/modules/statics/src/index.ts @@ -35,6 +35,7 @@ export { AdaToken, JettonToken, CantonToken, + Erc7984Coin, } from './account'; export { CoinMap } from './map'; export { networkFeatureMapForTokens, registerNetworkFeatures, getNetworkFeatures } from './networkFeatureMapForTokens'; diff --git a/modules/statics/src/tokenConfig.ts b/modules/statics/src/tokenConfig.ts index b76b11592f..8220ffe28f 100644 --- a/modules/statics/src/tokenConfig.ts +++ b/modules/statics/src/tokenConfig.ts @@ -13,6 +13,7 @@ import { Erc1155Coin, Erc20Coin, Erc721Coin, + Erc7984Coin, EthLikeERC20Token, EthLikeERC721Token, FlrERC20Token, @@ -68,6 +69,7 @@ export type EosTokenConfig = BaseContractAddressConfig & { contractAddress: string; }; export type Erc20TokenConfig = BaseContractAddressConfig; +export type Erc7984TokenConfig = BaseContractAddressConfig; export type TrxTokenConfig = BaseContractAddressConfig; export type StellarTokenConfig = BaseNetworkConfig; @@ -191,12 +193,14 @@ export type TokenConfig = | PolyxTokenConfig | JettonTokenConfig | EthLikeERC721TokenConfig - | Tip20TokenConfig; + | Tip20TokenConfig + | Erc7984TokenConfig; export interface TokenNetwork { eth: { tokens: Erc20TokenConfig[]; nfts: EthLikeTokenConfig[]; + confidentialTokens: Erc7984TokenConfig[]; }; xlm: { tokens: StellarTokenConfig[] }; algo: { tokens: AlgoTokenConfig[] }; @@ -342,6 +346,44 @@ export const getFormattedErc20Tokens = (customCoinMap = coins) => return acc; }, []); +function getErc7984TokenConfig(coin: Erc7984Coin): Erc7984TokenConfig { + let baseCoin: string; + switch (coin.network.name) { + case Networks.main.ethereum.name: + baseCoin = 'eth'; + break; + case Networks.test.kovan.name: + baseCoin = 'teth'; + break; + case Networks.test.goerli.name: + baseCoin = 'gteth'; + break; + case Networks.test.holesky.name: + case Networks.test.hoodi.name: + baseCoin = 'hteth'; + break; + default: + throw new Error(`ERC-7984 token ${coin.name} has an unsupported network`); + } + return { + type: coin.name, + coin: baseCoin, + network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet', + name: coin.fullName, + tokenContractAddress: coin.contractAddress.toString().toLowerCase(), + decimalPlaces: coin.decimalPlaces, + }; +} + +// Get the list of ERC-7984 confidential tokens from statics and format it properly +export const getFormattedErc7984Tokens = (customCoinMap = coins) => + customCoinMap.reduce((acc: Erc7984TokenConfig[], coin) => { + if (coin instanceof Erc7984Coin) { + acc.push(getErc7984TokenConfig(coin)); + } + return acc; + }, []); + export const ethGasConfigs = { minimumGasPrice: 1000000000, // minimum gas price a user can provide (1 Gwei) defaultGasPrice: 20000000000, // default gas price if estimation fails (20 Gwei) @@ -1152,6 +1194,7 @@ type EthLikeTokenMap = { export enum TokenTypeEnum { ERC20 = 'erc20', ERC721 = 'erc721', + ERC7984 = 'erc7984', } function getEthLikeTokenConfig(coin: EthLikeERC20Token): EthLikeTokenConfig { @@ -1257,6 +1300,7 @@ export const getFormattedTokensByNetwork = (network: 'Mainnet' | 'Testnet', coin eth: { tokens: getFormattedErc20Tokens(coinMap).filter((token) => token.network === network), nfts: getFormattedErc721Tokens(coinMap).filter((token) => token.network === network), + confidentialTokens: getFormattedErc7984Tokens(coinMap).filter((token) => token.network === network), }, xlm: { tokens: getFormattedStellarTokens(coinMap).filter((token) => token.network === network), @@ -1443,13 +1487,25 @@ export const formattedAlgoTokens = getFormattedAlgoTokens(); const mainnetErc20Tokens = verifyTokens(tokens.bitcoin.eth.tokens); const mainnetErc721Tokens = verifyTokens(tokens.bitcoin.eth.nfts); +const mainnetErc7984Tokens = verifyTokens(tokens.bitcoin.eth.confidentialTokens); const mainnetStellarTokens = verifyTokens(tokens.bitcoin.xlm.tokens); -export const mainnetTokens = { ...mainnetErc20Tokens, ...mainnetErc721Tokens, ...mainnetStellarTokens }; +export const mainnetTokens = { + ...mainnetErc20Tokens, + ...mainnetErc721Tokens, + ...mainnetErc7984Tokens, + ...mainnetStellarTokens, +}; const testnetErc20Tokens = verifyTokens(tokens.testnet.eth.tokens); const testnetErc721Tokens = verifyTokens(tokens.testnet.eth.nfts); +const testnetErc7984Tokens = verifyTokens(tokens.testnet.eth.confidentialTokens); const testnetStellarTokens = verifyTokens(tokens.testnet.xlm.tokens); -export const testnetTokens = { ...testnetErc20Tokens, ...testnetErc721Tokens, ...testnetStellarTokens }; +export const testnetTokens = { + ...testnetErc20Tokens, + ...testnetErc721Tokens, + ...testnetErc7984Tokens, + ...testnetStellarTokens, +}; /** * Get formatted token configuration for a single coin @@ -1459,6 +1515,8 @@ export const testnetTokens = { ...testnetErc20Tokens, ...testnetErc721Tokens, .. export function getFormattedTokenConfigForCoin(coin: Readonly): TokenConfig | undefined { if (coin instanceof Erc20Coin) { return getErc20TokenConfig(coin); + } else if (coin instanceof Erc7984Coin) { + return getErc7984TokenConfig(coin); } else if (coin instanceof StellarCoin) { return getStellarTokenConfig(coin); } else if (coin instanceof OfcCoin) {