From 9d621e75632b1bc3e65605dce550896ed6169c84 Mon Sep 17 00:00:00 2001 From: AJ Srinivas Date: Fri, 22 Dec 2023 18:08:40 +0530 Subject: [PATCH] feat(sdk-coin-zketh): add zketh token support Ticket: WIN-1422 --- CODEOWNERS | 3 + modules/bitgo/src/v2/coinFactory.ts | 5 + modules/bitgo/src/v2/coins/index.ts | 4 +- modules/bitgo/test/browser/browser.spec.ts | 1 + modules/sdk-coin-zketh/package.json | 2 +- modules/sdk-coin-zketh/src/index.ts | 1 + modules/sdk-coin-zketh/src/zketh.ts | 12 +-- modules/sdk-coin-zketh/src/zkethToken.ts | 30 ++++++ .../sdk-coin-zketh/test/unit/zkethToken.ts | 32 ++++++ modules/sdk-core/src/bitgo/environments.ts | 6 ++ modules/statics/src/account.ts | 100 ++++++++++++++++++ modules/statics/src/base.ts | 6 ++ modules/statics/src/coins.ts | 18 ++++ modules/statics/src/tokenConfig.ts | 27 +++++ 14 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 modules/sdk-coin-zketh/src/zkethToken.ts create mode 100644 modules/sdk-coin-zketh/test/unit/zkethToken.ts diff --git a/CODEOWNERS b/CODEOWNERS index 9d0ec8b25b..42bde240cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,6 +40,7 @@ # All other coins /modules/sdk-coin-ada/ @BitGo/ethalt-team /modules/sdk-coin-algo/ @BitGo/ethalt-team +/modules/sdk-coin-arbeth/ @BitGo/ethalt-team /modules/sdk-coin-atom/ @BitGo/ethalt-team /modules/sdk-coin-avaxc/ @BitGo/ethalt-team /modules/sdk-coin-avaxp/ @BitGo/ethalt-team @@ -49,6 +50,7 @@ /modules/sdk-coin-eos/ @BitGo/ethalt-team /modules/sdk-coin-hbar/ @BitGo/ethalt-team /modules/sdk-coin-near/ @BitGo/ethalt-team +/modules/sdk-coin-opeth/ @BitGo/ethalt-team /modules/sdk-coin-polygon/ @BitGo/ethalt-team /modules/sdk-coin-sol/ @BitGo/ethalt-team /modules/sdk-coin-stx/ @BitGo/ethalt-team @@ -58,6 +60,7 @@ /modules/sdk-coin-xlm/ @BitGo/ethalt-team /modules/sdk-coin-xrp/ @BitGo/ethalt-team /modules/sdk-coin-xtz/ @BitGo/ethalt-team +/modules/sdk-coin-zketh/ @BitGo/ethalt-team # Trade /modules/sdk-core/src/bitgo/address-book/ @BitGo/prime diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index be8fe9f659..751a9cf463 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -122,6 +122,7 @@ import { Zec, Zeta, Zketh, + ZkethToken, } from './coins'; function registerCoinConstructors(globalCoinFactory: CoinFactory): void { @@ -276,6 +277,10 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register(name, coinConstructor); }); + ZkethToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { + globalCoinFactory.register(name, coinConstructor); + }); + SolToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { globalCoinFactory.register(name, coinConstructor); }); diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index a73db72d32..3cd64906d9 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -45,7 +45,7 @@ import { Txrp, Xrp } from '@bitgo/sdk-coin-xrp'; import { Txtz, Xtz } from '@bitgo/sdk-coin-xtz'; import { Tzec, Zec } from '@bitgo/sdk-coin-zec'; import { Tzeta, Zeta } from '@bitgo/sdk-coin-zeta'; -import { Zketh, Tzketh } from '@bitgo/sdk-coin-zketh'; +import { Zketh, Tzketh, ZkethToken } from '@bitgo/sdk-coin-zketh'; export { AbstractUtxoCoin }; export { Algo, AlgoToken, Talgo }; @@ -94,7 +94,7 @@ export { Txrp, Xrp }; export { Txtz, Xtz }; export { Tzec, Zec }; export { Tzeta, Zeta }; -export { Zketh, Tzketh }; +export { Zketh, Tzketh, ZkethToken }; import { coins } from '@bitgo/sdk-core'; const { Ofc, OfcToken, Susd, FiatUsd, FiatEur, FiatGBP, Tsusd, TfiatUsd, TfiatEur, TfiatGBP } = coins; diff --git a/modules/bitgo/test/browser/browser.spec.ts b/modules/bitgo/test/browser/browser.spec.ts index 49945e2af5..834cee200e 100644 --- a/modules/bitgo/test/browser/browser.spec.ts +++ b/modules/bitgo/test/browser/browser.spec.ts @@ -23,6 +23,7 @@ describe('Coins', () => { BscToken: 1, ArbethToken: 1, OpethToken: 1, + ZkethToken: 1, }; Object.keys(BitGoJS.Coin) .filter((coinName) => !excludedKeys[coinName]) diff --git a/modules/sdk-coin-zketh/package.json b/modules/sdk-coin-zketh/package.json index 0b6d0f7c15..cabe983e9e 100644 --- a/modules/sdk-coin-zketh/package.json +++ b/modules/sdk-coin-zketh/package.json @@ -15,7 +15,7 @@ "coverage": "nyc -- npm run unit-test", "unit-test": "mocha" }, - "author": "Ajay Srinivas ", + "author": "BitGo SDK Team ", "license": "MIT", "engines": { "node": ">=16 <21" diff --git a/modules/sdk-coin-zketh/src/index.ts b/modules/sdk-coin-zketh/src/index.ts index 354941dfde..9fbf131d65 100644 --- a/modules/sdk-coin-zketh/src/index.ts +++ b/modules/sdk-coin-zketh/src/index.ts @@ -1,4 +1,5 @@ export * from './lib'; export * from './zketh'; export * from './tzketh'; +export * from './zkethToken'; export * from './register'; diff --git a/modules/sdk-coin-zketh/src/zketh.ts b/modules/sdk-coin-zketh/src/zketh.ts index 55bd50e3a4..f7a4c66e82 100644 --- a/modules/sdk-coin-zketh/src/zketh.ts +++ b/modules/sdk-coin-zketh/src/zketh.ts @@ -1,7 +1,7 @@ /** * @prettier */ -import { BaseCoin, BitGoBase, MPCAlgorithm } from '@bitgo/sdk-core'; +import { BaseCoin, BitGoBase } from '@bitgo/sdk-core'; import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; import { AbstractEthLikeNewCoins, TransactionBuilder as EthLikeTransactionBuilder } from '@bitgo/abstract-eth'; import { TransactionBuilder } from './lib'; @@ -18,14 +18,4 @@ export class Zketh extends AbstractEthLikeNewCoins { protected getTransactionBuilder(): EthLikeTransactionBuilder { return new TransactionBuilder(coins.get(this.getBaseChain())); } - - /** @inheritDoc */ - supportsTss(): boolean { - return false; - } - - /** @inheritDoc */ - getMPCAlgorithm(): MPCAlgorithm { - return 'ecdsa'; - } } diff --git a/modules/sdk-coin-zketh/src/zkethToken.ts b/modules/sdk-coin-zketh/src/zkethToken.ts new file mode 100644 index 0000000000..518d335783 --- /dev/null +++ b/modules/sdk-coin-zketh/src/zkethToken.ts @@ -0,0 +1,30 @@ +/** + * @prettier + */ +import { EthLikeTokenConfig } from '@bitgo/statics'; +import { BitGoBase, CoinConstructor, NamedCoinConstructor } from '@bitgo/sdk-core'; +import { CoinNames, EthLikeToken } from '@bitgo/abstract-eth'; + +export { EthLikeTokenConfig }; + +export class ZkethToken extends EthLikeToken { + public readonly tokenConfig: EthLikeTokenConfig; + static coinNames: CoinNames = { + Mainnet: 'zketh', + Testnet: 'tzketh', + }; + constructor(bitgo: BitGoBase, tokenConfig: EthLikeTokenConfig) { + super(bitgo, tokenConfig, ZkethToken.coinNames); + } + static createTokenConstructor(config: EthLikeTokenConfig): CoinConstructor { + return super.createTokenConstructor(config, ZkethToken.coinNames); + } + + static createTokenConstructors(): NamedCoinConstructor[] { + return super.createTokenConstructors(ZkethToken.coinNames); + } + + getFullName(): string { + return 'Zketh Token'; + } +} diff --git a/modules/sdk-coin-zketh/test/unit/zkethToken.ts b/modules/sdk-coin-zketh/test/unit/zkethToken.ts new file mode 100644 index 0000000000..bd8523d88b --- /dev/null +++ b/modules/sdk-coin-zketh/test/unit/zkethToken.ts @@ -0,0 +1,32 @@ +import 'should'; + +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { ZkethToken } from '../../src'; +import { BitGoAPI } from '@bitgo/sdk-api'; + +describe('Zketh Token:', function () { + let bitgo: TestBitGoAPI; + let zkethTokenCoin; + const tokenName = 'tzketh:usdt'; + + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + ZkethToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { + bitgo.safeRegister(name, coinConstructor); + }); + bitgo.initializeTestVars(); + zkethTokenCoin = bitgo.coin(tokenName); + }); + + it('should return constants', function () { + zkethTokenCoin.getChain().should.equal('tzketh:usdt'); + zkethTokenCoin.getBaseChain().should.equal('tzketh'); + zkethTokenCoin.getFullName().should.equal('Zketh Token'); + zkethTokenCoin.getBaseFactor().should.equal(1e18); + zkethTokenCoin.type.should.equal(tokenName); + zkethTokenCoin.name.should.equal('zkSync Test USDT'); + zkethTokenCoin.coin.should.equal('tzketh'); + zkethTokenCoin.network.should.equal('Testnet'); + zkethTokenCoin.decimalPlaces.should.equal(18); + }); +}); diff --git a/modules/sdk-core/src/bitgo/environments.ts b/modules/sdk-core/src/bitgo/environments.ts index 46e841a583..c0823973b1 100644 --- a/modules/sdk-core/src/bitgo/environments.ts +++ b/modules/sdk-core/src/bitgo/environments.ts @@ -21,6 +21,8 @@ interface EnvironmentTemplate { arbiscanApiToken?: string; optimisticEtherscanBaseUrl?: string; optimisticEtherscanApiToken?: string; + zksyncExplorerBaseUrl?: string; + zksyncExplorerApiToken?: string; stellarFederationServerUrl?: string; eosNodeUrls: string[]; nearNodeUrls: string[]; @@ -108,6 +110,8 @@ const mainnetBase: EnvironmentTemplate = { arbiscanApiToken: process.env.ARBISCAN_API_TOKEN, optimisticEtherscanBaseUrl: 'https://api-optimistic.etherscan.io', optimisticEtherscanApiToken: process.env.OPTIMISTIC_ETHERSCAN_API_TOKEN, + zksyncExplorerBaseUrl: 'https://block-explorer-api.mainnet.zksync.io', + zksyncExplorerApiToken: process.env.ZKSYNC_EXPLORER_API_TOKEN, eosNodeUrls: ['https://bp.cryptolions.io', 'https://api.eosnewyork.io', 'https://api.eosdetroit.io'], nearNodeUrls: ['https://rpc.mainnet.near.org'], solNodeUrl: 'https://api.mainnet-beta.solana.com', @@ -148,6 +152,8 @@ const testnetBase: EnvironmentTemplate = { arbiscanApiToken: process.env.ARBISCAN_API_TOKEN, optimisticEtherscanBaseUrl: 'https://api-sepolia-optimistic.etherscan.io', optimisticEtherscanApiToken: process.env.OPTIMISTIC_ETHERSCAN_API_TOKEN, + zksyncExplorerBaseUrl: 'https://block-explorer-api.sepolia.zksync.dev', + zksyncExplorerApiToken: process.env.ZKSYNC_EXPLORER_API_TOKEN, // kylin eos endpoints found here // https://github.com/cryptokylin/CryptoKylin-Testnet#http-api-list // https://docs.liquidapps.io/liquidapps-documentation/eosio-guides/testnet-creation-guides/creating-cryptokylin-account#setup diff --git a/modules/statics/src/account.ts b/modules/statics/src/account.ts index 26df14184a..ea4bb31db7 100644 --- a/modules/statics/src/account.ts +++ b/modules/statics/src/account.ts @@ -379,6 +379,16 @@ export class OpethERC20Token extends ContractAddressDefinedToken { } } +/** + * The zkSync network support tokens + * zkSync Tokens are ERC20 tokens + */ +export class ZkethERC20Token extends ContractAddressDefinedToken { + constructor(options: Erc20ConstructorOptions) { + super(options); + } +} + /** * The Xrp network supports tokens * Xrp tokens are identified by their issuer address @@ -1876,6 +1886,96 @@ export function topethErc20( ); } +/** + * Factory function for zkethErc20 token instances. + * + * @param id uuid v4 + * @param name unique identifier of the token + * @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 prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to zkSync mainnet network. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function zkethErc20( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES, + prefix = '', + suffix: string = name.toUpperCase(), + network: AccountNetwork = Networks.main.zkSync, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return Object.freeze( + new ZkethERC20Token({ + id, + name, + fullName, + network, + contractAddress, + prefix, + suffix, + features, + decimalPlaces, + asset, + isToken: true, + primaryKeyCurve, + baseUnit: BaseUnit.ETH, + }) + ); +} + +/** + * Factory function for zkSync Sepolia testnet zkethErc20 token instances. + * + * @param id uuid v4 + * @param name unique identifier of the token + * @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 prefix? Optional token prefix. Defaults to empty string + * @param suffix? Optional token suffix. Defaults to token name. + * @param network? Optional token network. Defaults to the zkSync sepolia test network. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` + * @param primaryKeyCurve The elliptic curve for this chain/token + */ +export function tzkethErc20( + id: string, + name: string, + fullName: string, + decimalPlaces: number, + contractAddress: string, + asset: UnderlyingAsset, + features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES, + prefix = '', + suffix: string = name.toUpperCase(), + network: AccountNetwork = Networks.test.zkSync, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return zkethErc20( + id, + name, + fullName, + decimalPlaces, + contractAddress, + asset, + features, + prefix, + suffix, + network, + primaryKeyCurve + ); +} + /** * Factory function for xrp token instances. * diff --git a/modules/statics/src/base.ts b/modules/statics/src/base.ts index 372496641e..562c77ca50 100644 --- a/modules/statics/src/base.ts +++ b/modules/statics/src/base.ts @@ -1713,6 +1713,12 @@ export enum UnderlyingAsset { // Optimism testnet tokens 'topeth:terc18dp' = 'topeth:terc18dp', + // zkSync mainnet tokens + 'zketh:link' = 'zketh:link', + + // zkSync testnet tokens + 'tzketh:usdt' = 'tzketh:usdt', + ERC721 = 'erc721', ERC1155 = 'erc1155', NONSTANDARD = 'nonstandard', diff --git a/modules/statics/src/coins.ts b/modules/statics/src/coins.ts index cda8a716d6..68548e03f0 100644 --- a/modules/statics/src/coins.ts +++ b/modules/statics/src/coins.ts @@ -35,6 +35,8 @@ import { tstellarToken, ttronToken, txrpToken, + tzkethErc20, + zkethErc20, } from './account'; import { ada } from './ada'; import { avaxp } from './avaxp'; @@ -14082,6 +14084,22 @@ export const coins = CoinMap.fromCoins([ '0xe9df68a54bba438c8a6192e95f0f2c53ac93d997', UnderlyingAsset['topeth:terc18dp'] ), + zkethErc20( + '53f0e845-f415-44d3-8517-7565dc346390', + 'zketh:link', + 'Chainlink Token', + 18, + '0x082fade8b84b18c441d506e1d3a43a387cc59d20', + UnderlyingAsset['zketh:link'] + ), + tzkethErc20( + 'ef49b6d1-b7a7-4c5c-8c53-43d22c15cc17', + 'tzketh:usdt', + 'zkSync Test USDT', + 18, + '0xaddfe63f99fbf176672fb0cdf72edb2cd7aae933', + UnderlyingAsset['tzketh:usdt'] + ), txrpToken( '8ef16158-1015-4a67-b6fe-db669c18ab2b', 'txrp:tst-rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd', diff --git a/modules/statics/src/tokenConfig.ts b/modules/statics/src/tokenConfig.ts index 15ab3739e2..836def3713 100644 --- a/modules/statics/src/tokenConfig.ts +++ b/modules/statics/src/tokenConfig.ts @@ -16,6 +16,7 @@ import { XrpCoin, ArbethERC20Token, OpethERC20Token, + ZkethERC20Token, } from './account'; import { CoinFamily, CoinKind } from './base'; import { coins } from './coins'; @@ -121,6 +122,9 @@ export interface Tokens { xrp: { tokens: XrpTokenConfig[]; }; + zketh: { + tokens: EthLikeTokenConfig[]; + }; }; testnet: { eth: { @@ -171,6 +175,9 @@ export interface Tokens { xrp: { tokens: XrpTokenConfig[]; }; + zketh: { + tokens: EthLikeTokenConfig[]; + }; }; } @@ -346,6 +353,20 @@ const formattedOpethTokens = coins.reduce((acc: EthLikeTokenConfig[], coin) => { return acc; }, []); +const formattedZkethTokens = coins.reduce((acc: EthLikeTokenConfig[], coin) => { + if (coin instanceof ZkethERC20Token) { + acc.push({ + type: coin.name, + coin: coin.network.type === NetworkType.MAINNET ? 'zketh' : 'tzketh', + network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet', + name: coin.fullName, + tokenContractAddress: coin.contractAddress.toString().toLowerCase(), + decimalPlaces: coin.decimalPlaces, + }); + } + return acc; +}, []); + const formattedSolTokens = coins.reduce((acc: SolTokenConfig[], coin) => { if (coin instanceof SolCoin) { acc.push({ @@ -468,6 +489,9 @@ export const tokens: Tokens = { opeth: { tokens: formattedOpethTokens.filter((token) => token.network === 'Mainnet'), }, + zketh: { + tokens: formattedZkethTokens.filter((token) => token.network === 'Mainnet'), + }, sol: { tokens: formattedSolTokens.filter((token) => token.network === 'Mainnet'), }, @@ -519,6 +543,9 @@ export const tokens: Tokens = { opeth: { tokens: formattedOpethTokens.filter((token) => token.network === 'Testnet'), }, + zketh: { + tokens: formattedZkethTokens.filter((token) => token.network === 'Testnet'), + }, sol: { tokens: formattedSolTokens.filter((token) => token.network === 'Testnet'), },