diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index 3137bdd999..b601e4871b 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -21,6 +21,7 @@ import { AvaxP, Bch, Bera, + BeraToken, Bld, Bsc, BscToken, @@ -296,6 +297,10 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register(name, coinConstructor); }); + BeraToken.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 520df70027..c7b09d0834 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -8,7 +8,7 @@ import { AvaxC, AvaxCToken, TavaxC } from '@bitgo/sdk-coin-avaxc'; import { AvaxP, TavaxP } from '@bitgo/sdk-coin-avaxp'; import { Bch, Tbch } from '@bitgo/sdk-coin-bch'; import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha'; -import { Bera, Tbera } from '@bitgo/sdk-coin-bera'; +import { Bera, Tbera, BeraToken } from '@bitgo/sdk-coin-bera'; import { Bld, Tbld } from '@bitgo/sdk-coin-bld'; import { Bsc, BscToken, Tbsc } from '@bitgo/sdk-coin-bsc'; import { Bsv, Tbsv } from '@bitgo/sdk-coin-bsv'; @@ -59,7 +59,7 @@ export { Atom, Tatom }; export { AvaxC, AvaxCToken, TavaxC }; export { AvaxP, TavaxP }; export { Bch, Tbch }; -export { Bera, Tbera }; +export { Bera, Tbera, BeraToken }; export { Bsc, BscToken, Tbsc }; export { Bsv, Tbsv }; export { Btc, Tbtc, Tbtcsig, Tbtc4, Tbtcbgsig }; diff --git a/modules/bitgo/test/browser/browser.spec.ts b/modules/bitgo/test/browser/browser.spec.ts index c9a718db29..46703e2b96 100644 --- a/modules/bitgo/test/browser/browser.spec.ts +++ b/modules/bitgo/test/browser/browser.spec.ts @@ -28,6 +28,7 @@ describe('Coins', () => { OpethToken: 1, ZkethToken: 1, SuiToken: 1, + BeraToken: 1, }; Object.keys(BitGoJS.Coin) .filter((coinName) => !excludedKeys[coinName]) diff --git a/modules/sdk-coin-bera/src/beraToken.ts b/modules/sdk-coin-bera/src/beraToken.ts new file mode 100644 index 0000000000..440ff8e759 --- /dev/null +++ b/modules/sdk-coin-bera/src/beraToken.ts @@ -0,0 +1,47 @@ +/** + * @prettier + */ +import { coins, EthLikeTokenConfig } from '@bitgo/statics'; +import { BitGoBase, CoinConstructor, common, NamedCoinConstructor } from '@bitgo/sdk-core'; +import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth'; + +import { TransactionBuilder } from './lib'; + +export { EthLikeTokenConfig }; + +export class BeraToken extends EthLikeToken { + public readonly tokenConfig: EthLikeTokenConfig; + static coinNames: CoinNames = { + Mainnet: 'bera', + Testnet: 'tbera', + }; + constructor(bitgo: BitGoBase, tokenConfig: EthLikeTokenConfig) { + super(bitgo, tokenConfig, BeraToken.coinNames); + } + static createTokenConstructor(config: EthLikeTokenConfig): CoinConstructor { + return super.createTokenConstructor(config, BeraToken.coinNames); + } + + static createTokenConstructors(): NamedCoinConstructor[] { + return super.createTokenConstructors(BeraToken.coinNames); + } + + protected getTransactionBuilder(): TransactionBuilder { + return new TransactionBuilder(coins.get(this.getBaseChain())); + } + + /** + * Make a query to Berachain explorer for information such as balance, token balance, solidity calls + * @param {Object} query key-value pairs of parameters to append after /api + * @returns {Promise} response Berachain explorer + */ + async recoveryBlockchainExplorerQuery(query: Record): Promise> { + const apiToken = common.Environments[this.bitgo.getEnv()].beraExplorerApiToken; + const explorerUrl = common.Environments[this.bitgo.getEnv()].beraExplorerBaseUrl; + return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken); + } + + getFullName(): string { + return 'Bera Token'; + } +} diff --git a/modules/sdk-coin-bera/src/index.ts b/modules/sdk-coin-bera/src/index.ts index 4642ce88b0..c418426694 100644 --- a/modules/sdk-coin-bera/src/index.ts +++ b/modules/sdk-coin-bera/src/index.ts @@ -1,4 +1,5 @@ export * from './bera'; export * from './tbera'; +export * from './beraToken'; export * from './lib'; export * from './register'; diff --git a/modules/sdk-coin-bera/src/register.ts b/modules/sdk-coin-bera/src/register.ts index d3e21c4f2c..4b6c2400ca 100644 --- a/modules/sdk-coin-bera/src/register.ts +++ b/modules/sdk-coin-bera/src/register.ts @@ -1,8 +1,12 @@ import { BitGoBase } from '@bitgo/sdk-core'; import { Bera } from './bera'; import { Tbera } from './tbera'; +import { BeraToken } from './beraToken'; export const register = (sdk: BitGoBase): void => { sdk.register('bera', Bera.createInstance); sdk.register('tbera', Tbera.createInstance); + BeraToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { + sdk.register(name, coinConstructor); + }); }; diff --git a/modules/sdk-coin-bera/test/unit/beraToken.ts b/modules/sdk-coin-bera/test/unit/beraToken.ts new file mode 100644 index 0000000000..b424ed28cc --- /dev/null +++ b/modules/sdk-coin-bera/test/unit/beraToken.ts @@ -0,0 +1,31 @@ +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { BitGoAPI } from '@bitgo/sdk-api'; + +import { BeraToken } from '../../src'; + +describe('Bera Token:', function () { + let bitgo: TestBitGoAPI; + let bgtTokenCoin; + const tokenName = 'tbera:bgt'; + + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + BeraToken.createTokenConstructors().forEach(({ name, coinConstructor }) => { + bitgo.safeRegister(name, coinConstructor); + }); + bitgo.initializeTestVars(); + bgtTokenCoin = bitgo.coin(tokenName); + }); + + it('should return constants', function () { + bgtTokenCoin.getChain().should.equal('tbera:bgt'); + bgtTokenCoin.getBaseChain().should.equal('tbera'); + bgtTokenCoin.getFullName().should.equal('Bera Token'); + bgtTokenCoin.getBaseFactor().should.equal(1e18); + bgtTokenCoin.type.should.equal(tokenName); + bgtTokenCoin.name.should.equal('Bera Testnet BGT'); + bgtTokenCoin.coin.should.equal('tbera'); + bgtTokenCoin.network.should.equal('Testnet'); + bgtTokenCoin.decimalPlaces.should.equal(18); + }); +}); diff --git a/modules/statics/src/account.ts b/modules/statics/src/account.ts index e6310ab55b..fe403c2434 100644 --- a/modules/statics/src/account.ts +++ b/modules/statics/src/account.ts @@ -395,6 +395,16 @@ export class ZkethERC20Token extends ContractAddressDefinedToken { } } +/** + * The Bera Chain network support tokens + * Bera Chain Tokens are ERC20 tokens + */ +export class BeraERC20Token extends ContractAddressDefinedToken { + constructor(options: Erc20ConstructorOptions) { + super(options); + } +} + /** * The Xrp network supports tokens * Xrp tokens are identified by their issuer address @@ -1998,6 +2008,96 @@ export function tzkethErc20( ); } +/** + * Factory function for beraErc20 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 bera 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 beraErc20( + 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.bera, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return Object.freeze( + new BeraERC20Token({ + id, + name, + fullName, + network, + contractAddress, + prefix, + suffix, + features, + decimalPlaces, + asset, + isToken: true, + primaryKeyCurve, + baseUnit: BaseUnit.ETH, + }) + ); +} + +/** + * Factory function for zkSync Sepolia testnet beraErc20 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 bera 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 tberaErc20( + 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.bera, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1 +) { + return beraErc20( + 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 9befdc96a4..1c3ac6a791 100644 --- a/modules/statics/src/base.ts +++ b/modules/statics/src/base.ts @@ -2123,6 +2123,12 @@ export enum UnderlyingAsset { // Celo mainnet tokens 'celo:pact' = 'celo:pact', + // bera mainnet tokens + 'bera:bgt' = 'bera:bgt', + + // bera testnet tokens + 'tbera:bgt' = 'tbera:bgt', + ERC721 = 'erc721', ERC1155 = 'erc1155', NONSTANDARD = 'nonstandard', diff --git a/modules/statics/src/coins.ts b/modules/statics/src/coins.ts index 75e5ae7ec7..036b6cc798 100644 --- a/modules/statics/src/coins.ts +++ b/modules/statics/src/coins.ts @@ -4,6 +4,7 @@ import { algoToken, arbethErc20, avaxErc20, + beraErc20, bscToken, celoToken, eosToken, @@ -24,6 +25,7 @@ import { tarbethErc20, tavaxErc20, tbscToken, + tberaErc20, tceloToken, teosToken, terc1155, @@ -17966,6 +17968,23 @@ export const coins = CoinMap.fromCoins([ '0xcccb29bac5ad81290383643c6fb38130cda9d881', UnderlyingAsset['tzketh:link'] ), + beraErc20( + 'ef833f4e-7617-4c6d-8a1f-1fef0dd1dd0e', + 'bera:bgt', + 'BGT Token', + 18, + // TODO: the mainnet contract address is still not available, adding placeholder here + '0xbda130737bdd9618301681329bf2e46a016ff9aa', + UnderlyingAsset['bera:bgt'] + ), + tberaErc20( + '24af5e18-ab4b-43e5-80db-0ddb9beb01b3', + 'tbera:bgt', + 'Bera Testnet BGT', + 18, + '0xbda130737bdd9618301681329bf2e46a016ff9ad', + UnderlyingAsset['tbera:bgt'] + ), txrpToken( '8ef16158-1015-4a67-b6fe-db669c18ab2b', 'txrp:tst-rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd', diff --git a/modules/statics/src/tokenConfig.ts b/modules/statics/src/tokenConfig.ts index 0e4fcb7fab..3e5a0a893b 100644 --- a/modules/statics/src/tokenConfig.ts +++ b/modules/statics/src/tokenConfig.ts @@ -18,6 +18,7 @@ import { OpethERC20Token, ZkethERC20Token, SuiCoin, + BeraERC20Token, } from './account'; import { CoinFamily, CoinKind } from './base'; import { coins } from './coins'; @@ -135,6 +136,9 @@ export interface Tokens { sui: { tokens: SuiTokenConfig[]; }; + bera: { + tokens: EthLikeTokenConfig[]; + }; }; testnet: { eth: { @@ -191,6 +195,9 @@ export interface Tokens { sui: { tokens: SuiTokenConfig[]; }; + bera: { + tokens: EthLikeTokenConfig[]; + }; }; } @@ -382,6 +389,20 @@ const formattedZkethTokens = coins.reduce((acc: EthLikeTokenConfig[], coin) => { return acc; }, []); +const formattedBeraTokens = coins.reduce((acc: EthLikeTokenConfig[], coin) => { + if (coin instanceof BeraERC20Token) { + acc.push({ + type: coin.name, + coin: coin.network.type === NetworkType.MAINNET ? 'bera' : 'tbera', + 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({ @@ -541,6 +562,9 @@ export const tokens: Tokens = { sui: { tokens: formattedSuiTokens.filter((token) => token.network === 'Mainnet'), }, + bera: { + tokens: formattedBeraTokens.filter((token) => token.network === 'Mainnet'), + }, }, // network name for test environments testnet: { @@ -598,6 +622,9 @@ export const tokens: Tokens = { sui: { tokens: formattedSuiTokens.filter((token) => token.network === 'Testnet'), }, + bera: { + tokens: formattedBeraTokens.filter((token) => token.network === 'Testnet'), + }, }, };