Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 34 additions & 34 deletions modules/bitgo/src/v2/coinFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { Near, TNear, Nep141Token } from '@bitgo/sdk-coin-near';
import { SolToken } from '@bitgo/sdk-coin-sol';
import { TrxToken } from '@bitgo/sdk-coin-trx';
import { CoinFactory, CoinConstructor } from '@bitgo/sdk-core';
import { EthLikeErc20Token } from '@bitgo/sdk-coin-evm';
import { EthLikeErc20Token, EthLikeErc721Token } from '@bitgo/sdk-coin-evm';

import {
CoinMap,
coins,
Expand Down Expand Up @@ -38,7 +39,6 @@ import {
TaoTokenConfig,
PolyxTokenConfig,
JettonTokenConfig,
NetworkType,
} from '@bitgo/statics';
import {
Ada,
Expand Down Expand Up @@ -567,6 +567,20 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
coinFactory.register(name, coinConstructor);
});
});

// Generic ERC721 token registration for coins with SUPPORTS_ERC721 feature
coins
.filter((coin) => coin.features.includes(CoinFeature.SUPPORTS_ERC721) && !coin.isToken)
.forEach((coin) => {
const coinNames = {
Mainnet: `${coin.name}`,
Testnet: `t${coin.name}`,
};

EthLikeErc721Token.createTokenConstructors(coinNames).forEach(({ name, coinConstructor }) => {
coinFactory.register(name, coinConstructor);
});
});
}

export function getCoinConstructor(coinName: string): CoinConstructor | undefined {
Expand Down Expand Up @@ -910,45 +924,31 @@ export function getCoinConstructor(coinName: string): CoinConstructor | undefine
}
}

export const buildEthLikeChainToTestnetMap = (): {
mainnetToTestnetMap: Record<string, string>;
testnetToMainnetMap: Record<string, string>;
} => {
const testnetToMainnetMap: Record<string, string> = {};
const mainnetToTestnetMap: Record<string, string> = {};

const enabledEvmCoins = ['ip', 'hypeevm', 'plume'];

// TODO: remove ip and hypeeevm coins here and remove other evm coins from switch block, once changes are tested (Ticket: https://bitgoinc.atlassian.net/browse/WIN-7835)
coins.forEach((coin) => {
if (coin.network.type === NetworkType.TESTNET && !coin.isToken && enabledEvmCoins.includes(coin.family)) {
if (coins.get(coin.family)?.features.includes(CoinFeature.SUPPORTS_ERC20)) {
mainnetToTestnetMap[coin.family] = `${coin.name}`;
testnetToMainnetMap[coin.name] = `${coin.family}`;
}
}
});

return { mainnetToTestnetMap, testnetToMainnetMap };
};

const { mainnetToTestnetMap, testnetToMainnetMap } = buildEthLikeChainToTestnetMap();

export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor | undefined {
const testnetCoin = mainnetToTestnetMap[tokenConfig.coin];
if (testnetCoin) {
const coin = coins.get(tokenConfig.coin);

if (
'network' in tokenConfig &&
tokenConfig.network === 'Mainnet' &&
coin?.features.includes(CoinFeature.SUPPORTS_ERC20)
) {
return EthLikeErc20Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, {
Mainnet: tokenConfig.coin,
Testnet: testnetCoin,
Testnet: `t${tokenConfig.coin}`,
});
}
const mainnetCoin = testnetToMainnetMap[tokenConfig.coin];
if (mainnetCoin) {
return EthLikeErc20Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, {
Mainnet: mainnetCoin,
Testnet: tokenConfig.coin,

if (
'network' in tokenConfig &&
tokenConfig.network === 'Mainnet' &&
coin?.features.includes(CoinFeature.SUPPORTS_ERC721)
) {
return EthLikeErc721Token.createTokenConstructor(tokenConfig as EthLikeTokenConfig, {
Mainnet: tokenConfig.coin,
Testnet: `t${tokenConfig.coin}`,
});
}

switch (tokenConfig.coin) {
case 'eth':
case 'hteth':
Expand Down
4 changes: 2 additions & 2 deletions modules/bitgo/src/v2/coins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Dot, Tdot } from '@bitgo/sdk-coin-dot';
import { Eos, EosToken, Teos } from '@bitgo/sdk-coin-eos';
import { Etc, Tetc } from '@bitgo/sdk-coin-etc';
import { Erc20Token, Erc721Token, Eth, Gteth, Hteth, Teth } from '@bitgo/sdk-coin-eth';
import { EvmCoin, EthLikeErc20Token } from '@bitgo/sdk-coin-evm';
import { EvmCoin, EthLikeErc20Token, EthLikeErc721Token } from '@bitgo/sdk-coin-evm';
import { Flr, Tflr, FlrToken } from '@bitgo/sdk-coin-flr';
import { Flrp } from '@bitgo/sdk-coin-flrp';
import { Ethw } from '@bitgo/sdk-coin-ethw';
Expand Down Expand Up @@ -109,7 +109,7 @@ export { Erc20Token, Erc721Token, Eth, Gteth, Hteth, Teth };
export { Ethw };
export { EthLikeCoin, TethLikeCoin };
export { Etc, Tetc };
export { EvmCoin, EthLikeErc20Token };
export { EvmCoin, EthLikeErc20Token, EthLikeErc721Token };
export { Flr, Tflr, FlrToken };
export { Flrp };
export { Hash, Thash, HashToken };
Expand Down
1 change: 1 addition & 0 deletions modules/bitgo/test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('Coins', () => {
CosmosSharedCoin: 1,
VetToken: 1,
EthLikeErc20Token: 1,
EthLikeErc721Token: 1,
HashToken: 1,
FlrToken: 1,
JettonToken: 1,
Expand Down
54 changes: 54 additions & 0 deletions modules/sdk-coin-evm/src/ethLikeErc721Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @prettier
*/
import { coins, EthLikeTokenConfig } from '@bitgo/statics';
import { BitGoBase, CoinConstructor, common, MPCAlgorithm, NamedCoinConstructor } from '@bitgo/sdk-core';
import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
import { TransactionBuilder } from './lib';
import assert from 'assert';

export class EthLikeErc721Token extends EthLikeToken {
public readonly tokenConfig: EthLikeTokenConfig;
private readonly coinNames: CoinNames;

constructor(bitgo: BitGoBase, tokenConfig: EthLikeTokenConfig, coinNames: CoinNames) {
super(bitgo, tokenConfig, coinNames);
this.coinNames = coinNames;
}

static createTokenConstructor(config: EthLikeTokenConfig, coinNames: CoinNames): CoinConstructor {
return (bitgo: BitGoBase) => new this(bitgo, config, coinNames);
}

static createTokenConstructors(coinNames: CoinNames): NamedCoinConstructor[] {
return super.createTokenConstructors(coinNames);
}

protected getTransactionBuilder(): TransactionBuilder {
return new TransactionBuilder(coins.get(this.getBaseChain()));
}

getMPCAlgorithm(): MPCAlgorithm {
return 'ecdsa';
}

supportsTss(): boolean {
return true;
}

async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
const family = this.getFamily();
const evmConfig = common.Environments[this.bitgo.getEnv()].evm;
assert(
evmConfig && this.getFamily() in evmConfig,
`env config is missing for ${this.getFamily()} in ${this.bitgo.getEnv()}`
);
const explorerUrl = evmConfig[family].baseUrl;
const apiToken = evmConfig[family].apiToken;
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
}

getFullName(): string {
return 'ERC721 Token';
}
}
1 change: 1 addition & 0 deletions modules/sdk-coin-evm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './evmCoin';
export * from './lib';
export * from './register';
export * from './ethLikeErc20Token';
export * from './ethLikeErc721Token';
13 changes: 13 additions & 0 deletions modules/sdk-coin-evm/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BitGoBase } from '@bitgo/sdk-core';
import { CoinFeature, coins, NetworkType } from '@bitgo/statics';
import { EvmCoin } from './evmCoin';
import { EthLikeErc20Token } from './ethLikeErc20Token';
import { EthLikeErc721Token } from './ethLikeErc721Token';

export const registerAll = (sdk: BitGoBase): void => {
coins
Expand Down Expand Up @@ -35,5 +36,17 @@ export const register = (coinFamily: string, sdk: BitGoBase): void => {
sdk.register(name, coinConstructor);
});
}

// Handle SUPPORTS_ERC721 registration
if (coinFeatures.includes(CoinFeature.SUPPORTS_ERC721)) {
const coinNames = {
Mainnet: `${coin.name}`,
Testnet: `t${coin.name}`,
};

EthLikeErc721Token.createTokenConstructors(coinNames).forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});
}
});
};
53 changes: 53 additions & 0 deletions modules/statics/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export interface Erc20ConstructorOptions extends AccountConstructorOptions {
contractAddress: string;
}

export interface Erc721ConstructorOptions extends AccountConstructorOptions {
contractAddress: string;
}

export interface NFTCollectionIdConstructorOptions extends AccountConstructorOptions {
nftCollectionId: string;
}
Expand Down Expand Up @@ -430,6 +434,12 @@ export class EthLikeERC20Token extends ContractAddressDefinedToken {
}
}

export class EthLikeERC721Token extends ContractAddressDefinedToken {
constructor(options: Erc721ConstructorOptions) {
super(options);
}
}

/**
* The AVAX C Chain network support tokens
* AVAX C Chain Tokens are ERC20 coins
Expand Down Expand Up @@ -895,6 +905,49 @@ export function erc20Token(
);
}

/**
* Factory function for erc721 token instances.
*
* @param id uuid v4
* @param name unique identifier of the token
* @param fullName Complete human-readable name of the token
* @param contractAddress Contract address of this token
* @param network network
* @param features Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
* @param prefix Optional token prefix
* @param suffix Optional token suffix
* @param primaryKeyCurve The elliptic curve for this chain/token
*/
export function erc721Token(
id: string,
name: string,
fullName: string,
contractAddress: string,
network: AccountNetwork,
features: CoinFeature[] = [...AccountCoin.DEFAULT_FEATURES, CoinFeature.EIP1559],
prefix = '',
suffix: string = name.toUpperCase(),
primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1
): Readonly<EthLikeERC721Token> {
return Object.freeze(
new EthLikeERC721Token({
id,
name,
fullName,
network,
contractAddress,
decimalPlaces: 0, // ERC721 tokens are non-divisible
asset: UnderlyingAsset.ERC721,
features,
prefix,
suffix,
primaryKeyCurve,
isToken: true,
baseUnit: BaseUnit.ETH,
})
);
}

/**
* Factory function for erc20 token instances.
*
Expand Down
15 changes: 13 additions & 2 deletions modules/statics/src/allCoinsAndTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
erc20CompatibleAccountCoin,
erc20Token,
erc721,
erc721Token,
fiat,
flrErc20,
gasTankAccount,
Expand Down Expand Up @@ -2157,7 +2158,7 @@ export const allCoinsAndTokens = [
Networks.main.hederaEVM,
8,
UnderlyingAsset.HBAREVM,
BaseUnit.HBAR,
BaseUnit.ETH,
[
...EVM_FEATURES,
CoinFeature.SHARED_EVM_SIGNING,
Expand All @@ -2168,6 +2169,7 @@ export const allCoinsAndTokens = [
CoinFeature.EVM_NON_BITGO_RECOVERY,
CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY,
CoinFeature.SUPPORTS_ERC20,
CoinFeature.SUPPORTS_ERC721,
]
),
account(
Expand All @@ -2177,7 +2179,7 @@ export const allCoinsAndTokens = [
Networks.test.hederaEVM,
8,
UnderlyingAsset.HBAREVM,
BaseUnit.HBAR,
BaseUnit.ETH,
[
...EVM_FEATURES,
CoinFeature.SHARED_EVM_SIGNING,
Expand All @@ -2189,6 +2191,15 @@ export const allCoinsAndTokens = [
CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY,
]
),

erc721Token(
'a7604e03-7f40-41f0-8efa-2e7673ac2a9f',
'thbarevmnft',
'Testnet Hedera EVM NFT',
'0x00000000000000000000000000000000007103a5',
Networks.test.hederaEVM
),

account(
'8f6ed7e4-cce2-4686-bdab-ae8f54e2c05e',
'tfluenteth',
Expand Down
5 changes: 5 additions & 0 deletions modules/statics/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ export enum CoinFeature {
*/
SUPPORTS_ERC20 = 'supports-erc20-token',

/**
* This coin supports erc721 tokens
*/
SUPPORTS_ERC721 = 'supports-erc721-token',

/**
* This coin is a Cosmos coin and should use shared Cosmos SDK module
*/
Expand Down
Loading