Skip to content

Commit feed663

Browse files
feat(statics): add zama support
TICKET: CHALO-402
1 parent 2392d91 commit feed663

7 files changed

Lines changed: 257 additions & 3 deletions

File tree

modules/statics/src/account.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
CANTON_TOKEN_FEATURES,
99
CELO_TOKEN_FEATURES,
1010
COSMOS_SIDECHAIN_FEATURES,
11+
ERC7984_TOKEN_FEATURES,
1112
TEMPO_FEATURES,
1213
} from './coinFeatures';
1314

@@ -96,6 +97,10 @@ export interface Erc721ConstructorOptions extends AccountConstructorOptions {
9697
contractAddress: string;
9798
}
9899

100+
export interface Erc7984ConstructorOptions extends AccountConstructorOptions {
101+
contractAddress: string;
102+
}
103+
99104
export interface NFTCollectionIdConstructorOptions extends AccountConstructorOptions {
100105
nftCollectionId: string;
101106
}
@@ -294,6 +299,19 @@ export class Erc721Coin extends ContractAddressDefinedToken {}
294299
*/
295300
export class Erc1155Coin extends ContractAddressDefinedToken {}
296301

302+
/**
303+
* ERC-7984 is the confidential token standard for fhEVM-enabled blockchains (Zama).
304+
* Token balances are stored as FHE-encrypted ciphertexts; transfers use confidentialTransfer()
305+
* instead of the standard ERC-20 transfer(). Balance reads require delegated decryption via ACL.
306+
*
307+
* {@link https://eips.ethereum.org/EIPS/eip-7984 EIP-7984}
308+
*/
309+
export class Erc7984Coin extends ContractAddressDefinedToken {
310+
constructor(options: Erc7984ConstructorOptions) {
311+
super(options);
312+
}
313+
}
314+
297315
/**
298316
* The TRON blockchain supports tokens of the ERC20 standard similar to ETH ERC20 tokens.
299317
*/
@@ -1087,6 +1105,99 @@ export function terc20(
10871105
return erc20(id, name, fullName, decimalPlaces, contractAddress, asset, features, prefix, suffix, network);
10881106
}
10891107

1108+
/**
1109+
* Factory function for ERC-7984 confidential token instances (Zama fhEVM).
1110+
*
1111+
* ERC-7984 tokens store balances as FHE-encrypted ciphertexts. Transfers use
1112+
* confidentialTransfer() and balance reads require ACL delegation to BitGo.
1113+
*
1114+
* @param id uuid v4
1115+
* @param name unique identifier of the token (e.g. 'eth:ctkn')
1116+
* @param fullName Complete human-readable name of the token
1117+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
1118+
* @param contractAddress Contract address of this token
1119+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
1120+
* @param features? Features of this coin. Defaults to ERC7984_TOKEN_FEATURES
1121+
* @param prefix? Optional token prefix. Defaults to empty string
1122+
* @param suffix? Optional token suffix. Defaults to token name.
1123+
* @param network? Optional token network. Defaults to Ethereum main network.
1124+
* @param primaryKeyCurve The elliptic curve for this chain/token
1125+
*/
1126+
export function erc7984(
1127+
id: string,
1128+
name: string,
1129+
fullName: string,
1130+
decimalPlaces: number,
1131+
contractAddress: string,
1132+
asset: UnderlyingAsset,
1133+
features: CoinFeature[] = ERC7984_TOKEN_FEATURES,
1134+
prefix = '',
1135+
suffix: string = name.toUpperCase(),
1136+
network: EthereumNetwork = Networks.main.ethereum,
1137+
primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1
1138+
) {
1139+
return Object.freeze(
1140+
new Erc7984Coin({
1141+
id,
1142+
name,
1143+
fullName,
1144+
network,
1145+
contractAddress,
1146+
prefix,
1147+
suffix,
1148+
features,
1149+
decimalPlaces,
1150+
asset,
1151+
isToken: true,
1152+
primaryKeyCurve,
1153+
baseUnit: BaseUnit.ETH,
1154+
})
1155+
);
1156+
}
1157+
1158+
/**
1159+
* Factory function for testnet ERC-7984 confidential token instances (Zama fhEVM).
1160+
*
1161+
* @param id uuid v4
1162+
* @param name unique identifier of the token (e.g. 'hteth:ctkn')
1163+
* @param fullName Complete human-readable name of the token
1164+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
1165+
* @param contractAddress Contract address of this token
1166+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
1167+
* @param features? Features of this coin. Defaults to ERC7984_TOKEN_FEATURES
1168+
* @param prefix? Optional token prefix. Defaults to empty string
1169+
* @param suffix? Optional token suffix. Defaults to token name.
1170+
* @param network? Optional token network. Defaults to Hoodi test network.
1171+
* @param primaryKeyCurve The elliptic curve for this chain/token
1172+
*/
1173+
export function terc7984(
1174+
id: string,
1175+
name: string,
1176+
fullName: string,
1177+
decimalPlaces: number,
1178+
contractAddress: string,
1179+
asset: UnderlyingAsset,
1180+
features: CoinFeature[] = ERC7984_TOKEN_FEATURES,
1181+
prefix = '',
1182+
suffix: string = name.toUpperCase(),
1183+
network: EthereumNetwork = Networks.test.hoodi,
1184+
primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1
1185+
) {
1186+
return erc7984(
1187+
id,
1188+
name,
1189+
fullName,
1190+
decimalPlaces,
1191+
contractAddress,
1192+
asset,
1193+
features,
1194+
prefix,
1195+
suffix,
1196+
network,
1197+
primaryKeyCurve
1198+
);
1199+
}
1200+
10901201
/**
10911202
* Factory function for erc721 token instances.
10921203
*

modules/statics/src/allCoinsAndTokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { nep141Tokens } from './coins/nep141Tokens';
7272
import { vetTokens } from './coins/vetTokens';
7373
import { cosmosTokens } from './coins/cosmosTokens';
7474
import { jettonTokens } from './coins/jettonTokens';
75+
import { erc7984Tokens } from './coins/erc7984Tokens';
7576
import { polyxTokens } from './coins/polyxTokens';
7677
import { cantonTokens } from './coins/cantonTokens';
7778
import { flrp } from './flrp';
@@ -164,6 +165,7 @@ export const allCoinsAndTokens = [
164165
...botTokens,
165166
...adaTokens,
166167
...jettonTokens,
168+
...erc7984Tokens,
167169
...polyxTokens,
168170
...cantonTokens,
169171
avaxp(

modules/statics/src/base.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,18 @@ export enum CoinFeature {
559559
* This coin allows negative fees in transactions
560560
*/
561561
ALLOWS_NEGATIVE_FEE = 'allows-negative-fee',
562+
563+
/**
564+
* This token uses fully homomorphic encryption (FHE) for confidential transfers (ERC-7984).
565+
* Balances are stored as encrypted ciphertexts; transfers use confidentialTransfer() instead of transfer().
566+
*/
567+
CONFIDENTIAL_TRANSFER = 'confidential-transfer',
568+
569+
/**
570+
* Reading the balance of this token requires the wallet owner to delegate decryption access
571+
* to BitGo via ACL.delegateForUserDecryption() before balances can be displayed.
572+
*/
573+
REQUIRES_DECRYPTION_DELEGATION = 'requires-decryption-delegation',
562574
}
563575

564576
/**
@@ -3794,6 +3806,12 @@ export enum UnderlyingAsset {
37943806
'eth:drv' = 'eth:drv',
37953807
'eth:prn' = 'eth:prn',
37963808
'eth:zama' = 'eth:zama',
3809+
// ERC-7984 confidential tokens (Zama fhEVM - mainnet, contract addresses TBD pending Zama mainnet launch)
3810+
'eth:ctkn' = 'eth:ctkn',
3811+
'eth:cusdt' = 'eth:cusdt',
3812+
// ERC-7984 confidential tokens (Zama fhEVM - testnet / hteth)
3813+
'hteth:ctkn' = 'hteth:ctkn',
3814+
'hteth:cusdt' = 'hteth:cusdt',
37973815
'eth:mony' = 'eth:mony',
37983816
'eth:architectgvi' = 'eth:architectgvi',
37993817
'eth:zk' = 'eth:zk',

modules/statics/src/coinFeatures.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,3 +789,12 @@ export const CANTON_TOKEN_FEATURES = [
789789
CoinFeature.REQUIRES_DEPOSIT_ACCEPTANCE_TRANSACTION,
790790
CoinFeature.ALPHANUMERIC_MEMO_ID,
791791
];
792+
793+
export const ERC7984_TOKEN_FEATURES = [
794+
...ACCOUNT_COIN_DEFAULT_FEATURES,
795+
CoinFeature.BULK_TRANSACTION,
796+
CoinFeature.TSS,
797+
CoinFeature.TSS_COLD,
798+
CoinFeature.CONFIDENTIAL_TRANSFER,
799+
CoinFeature.REQUIRES_DECRYPTION_DELEGATION,
800+
];
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { erc7984, terc7984 } from '../account';
2+
import { UnderlyingAsset } from '../base';
3+
4+
/**
5+
* ERC-7984 confidential tokens (Zama fhEVM).
6+
*
7+
* These tokens use fully homomorphic encryption (FHE) for on-chain confidential transfers.
8+
* Balances are stored as encrypted ciphertexts; plaintext amounts require ACL-delegated
9+
* decryption via the Zama Gateway before they can be displayed.
10+
*
11+
* Mainnet contract addresses are TBD pending Zama fhEVM mainnet launch.
12+
* Testnet tokens (hteth:*) are deployed on the BitGo-supported ETH testnet (Hoodi).
13+
*
14+
* Sandbox development contracts (Sepolia):
15+
* CTKN: 0x94167129172A35ab093B44b8b96213DDbc3cD387
16+
* cUSDT: 0x4E7B06D78965594eB5EF5414c357ca21E1554491
17+
*/
18+
export const erc7984Tokens = [
19+
// Mainnet tokens (contract addresses TBD pending Zama fhEVM mainnet launch)
20+
erc7984(
21+
'f47ac10b-58cc-4372-a567-0e02b2c3d479',
22+
'eth:ctkn',
23+
'Confidential Test Token',
24+
6,
25+
'0x0000000000000000000000000000000000000000', // TODO: update with mainnet contract address
26+
UnderlyingAsset['eth:ctkn']
27+
),
28+
erc7984(
29+
'f47ac10b-58cc-4372-a567-0e02b2c3d480',
30+
'eth:cusdt',
31+
'Confidential USDT',
32+
6,
33+
'0x0000000000000000000000000000000000000000', // TODO: update with mainnet contract address
34+
UnderlyingAsset['eth:cusdt']
35+
),
36+
37+
// Testnet tokens (hteth / Hoodi)
38+
// Note: sandbox development contracts are on Ethereum Sepolia; deploy to Hoodi for BitGo testnet support
39+
terc7984(
40+
'f47ac10b-58cc-4372-a567-0e02b2c3d481',
41+
'hteth:ctkn',
42+
'Confidential Test Token',
43+
6,
44+
'0x0000000000000000000000000000000000000000', // TODO: deploy to Hoodi and update address (Sepolia dev: 0x94167129172A35ab093B44b8b96213DDbc3cD387)
45+
UnderlyingAsset['hteth:ctkn']
46+
),
47+
terc7984(
48+
'f47ac10b-58cc-4372-a567-0e02b2c3d482',
49+
'hteth:cusdt',
50+
'Confidential USDT',
51+
6,
52+
'0x0000000000000000000000000000000000000000', // TODO: deploy to Hoodi and update address (Sepolia dev: 0x4E7B06D78965594eB5EF5414c357ca21E1554491)
53+
UnderlyingAsset['hteth:cusdt']
54+
),
55+
];

modules/statics/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export {
3535
AdaToken,
3636
JettonToken,
3737
CantonToken,
38+
Erc7984Coin,
3839
} from './account';
3940
export { CoinMap } from './map';
4041
export { networkFeatureMapForTokens, registerNetworkFeatures, getNetworkFeatures } from './networkFeatureMapForTokens';

modules/statics/src/tokenConfig.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Erc1155Coin,
1414
Erc20Coin,
1515
Erc721Coin,
16+
Erc7984Coin,
1617
EthLikeERC20Token,
1718
EthLikeERC721Token,
1819
FlrERC20Token,
@@ -68,6 +69,7 @@ export type EosTokenConfig = BaseContractAddressConfig & {
6869
contractAddress: string;
6970
};
7071
export type Erc20TokenConfig = BaseContractAddressConfig;
72+
export type Erc7984TokenConfig = BaseContractAddressConfig;
7173
export type TrxTokenConfig = BaseContractAddressConfig;
7274
export type StellarTokenConfig = BaseNetworkConfig;
7375

@@ -191,12 +193,14 @@ export type TokenConfig =
191193
| PolyxTokenConfig
192194
| JettonTokenConfig
193195
| EthLikeERC721TokenConfig
194-
| Tip20TokenConfig;
196+
| Tip20TokenConfig
197+
| Erc7984TokenConfig;
195198

196199
export interface TokenNetwork {
197200
eth: {
198201
tokens: Erc20TokenConfig[];
199202
nfts: EthLikeTokenConfig[];
203+
confidentialTokens: Erc7984TokenConfig[];
200204
};
201205
xlm: { tokens: StellarTokenConfig[] };
202206
algo: { tokens: AlgoTokenConfig[] };
@@ -342,6 +346,44 @@ export const getFormattedErc20Tokens = (customCoinMap = coins) =>
342346
return acc;
343347
}, []);
344348

349+
function getErc7984TokenConfig(coin: Erc7984Coin): Erc7984TokenConfig {
350+
let baseCoin: string;
351+
switch (coin.network.name) {
352+
case Networks.main.ethereum.name:
353+
baseCoin = 'eth';
354+
break;
355+
case Networks.test.kovan.name:
356+
baseCoin = 'teth';
357+
break;
358+
case Networks.test.goerli.name:
359+
baseCoin = 'gteth';
360+
break;
361+
case Networks.test.holesky.name:
362+
case Networks.test.hoodi.name:
363+
baseCoin = 'hteth';
364+
break;
365+
default:
366+
throw new Error(`ERC-7984 token ${coin.name} has an unsupported network`);
367+
}
368+
return {
369+
type: coin.name,
370+
coin: baseCoin,
371+
network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
372+
name: coin.fullName,
373+
tokenContractAddress: coin.contractAddress.toString().toLowerCase(),
374+
decimalPlaces: coin.decimalPlaces,
375+
};
376+
}
377+
378+
// Get the list of ERC-7984 confidential tokens from statics and format it properly
379+
export const getFormattedErc7984Tokens = (customCoinMap = coins) =>
380+
customCoinMap.reduce((acc: Erc7984TokenConfig[], coin) => {
381+
if (coin instanceof Erc7984Coin) {
382+
acc.push(getErc7984TokenConfig(coin));
383+
}
384+
return acc;
385+
}, []);
386+
345387
export const ethGasConfigs = {
346388
minimumGasPrice: 1000000000, // minimum gas price a user can provide (1 Gwei)
347389
defaultGasPrice: 20000000000, // default gas price if estimation fails (20 Gwei)
@@ -1152,6 +1194,7 @@ type EthLikeTokenMap = {
11521194
export enum TokenTypeEnum {
11531195
ERC20 = 'erc20',
11541196
ERC721 = 'erc721',
1197+
ERC7984 = 'erc7984',
11551198
}
11561199

11571200
function getEthLikeTokenConfig(coin: EthLikeERC20Token): EthLikeTokenConfig {
@@ -1257,6 +1300,7 @@ export const getFormattedTokensByNetwork = (network: 'Mainnet' | 'Testnet', coin
12571300
eth: {
12581301
tokens: getFormattedErc20Tokens(coinMap).filter((token) => token.network === network),
12591302
nfts: getFormattedErc721Tokens(coinMap).filter((token) => token.network === network),
1303+
confidentialTokens: getFormattedErc7984Tokens(coinMap).filter((token) => token.network === network),
12601304
},
12611305
xlm: {
12621306
tokens: getFormattedStellarTokens(coinMap).filter((token) => token.network === network),
@@ -1443,13 +1487,25 @@ export const formattedAlgoTokens = getFormattedAlgoTokens();
14431487

14441488
const mainnetErc20Tokens = verifyTokens(tokens.bitcoin.eth.tokens);
14451489
const mainnetErc721Tokens = verifyTokens(tokens.bitcoin.eth.nfts);
1490+
const mainnetErc7984Tokens = verifyTokens(tokens.bitcoin.eth.confidentialTokens);
14461491
const mainnetStellarTokens = verifyTokens(tokens.bitcoin.xlm.tokens);
1447-
export const mainnetTokens = { ...mainnetErc20Tokens, ...mainnetErc721Tokens, ...mainnetStellarTokens };
1492+
export const mainnetTokens = {
1493+
...mainnetErc20Tokens,
1494+
...mainnetErc721Tokens,
1495+
...mainnetErc7984Tokens,
1496+
...mainnetStellarTokens,
1497+
};
14481498

14491499
const testnetErc20Tokens = verifyTokens(tokens.testnet.eth.tokens);
14501500
const testnetErc721Tokens = verifyTokens(tokens.testnet.eth.nfts);
1501+
const testnetErc7984Tokens = verifyTokens(tokens.testnet.eth.confidentialTokens);
14511502
const testnetStellarTokens = verifyTokens(tokens.testnet.xlm.tokens);
1452-
export const testnetTokens = { ...testnetErc20Tokens, ...testnetErc721Tokens, ...testnetStellarTokens };
1503+
export const testnetTokens = {
1504+
...testnetErc20Tokens,
1505+
...testnetErc721Tokens,
1506+
...testnetErc7984Tokens,
1507+
...testnetStellarTokens,
1508+
};
14531509

14541510
/**
14551511
* Get formatted token configuration for a single coin
@@ -1459,6 +1515,8 @@ export const testnetTokens = { ...testnetErc20Tokens, ...testnetErc721Tokens, ..
14591515
export function getFormattedTokenConfigForCoin(coin: Readonly<BaseCoin>): TokenConfig | undefined {
14601516
if (coin instanceof Erc20Coin) {
14611517
return getErc20TokenConfig(coin);
1518+
} else if (coin instanceof Erc7984Coin) {
1519+
return getErc7984TokenConfig(coin);
14621520
} else if (coin instanceof StellarCoin) {
14631521
return getStellarTokenConfig(coin);
14641522
} else if (coin instanceof OfcCoin) {

0 commit comments

Comments
 (0)