Skip to content

Commit 5854dab

Browse files
Merge pull request #6306 from BitGo/WIN-5910
feat(sdk-coin-evm): add EvmCoin & transaction builder
2 parents 951428d + 0682aea commit 5854dab

File tree

11 files changed

+544
-90
lines changed

11 files changed

+544
-90
lines changed

modules/bitgo/test/browser/browser.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('Coins', () => {
4343
Polyx: 1,
4444
Tpolyx: 1,
4545
CoredaoToken: 1,
46+
EvmCoin: 1,
4647
Nep141Token: 1,
4748
};
4849
Object.keys(BitGoJS.Coin)

modules/sdk-coin-evm/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"unit-test": "mocha",
1616
"prepare": "npm run build"
1717
},
18+
"dependencies": {
19+
"@bitgo/abstract-eth": "^24.5.2",
20+
"@bitgo/sdk-core": "^35.3.0",
21+
"@bitgo/statics": "^54.4.0",
22+
"@ethereumjs/common": "^2.6.5"
23+
},
24+
"devDependencies": {},
1825
"author": "BitGo SDK Team <[email protected]>",
1926
"license": "MIT",
2027
"repository": {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @prettier
3+
*/
4+
import { BaseCoin, BitGoBase, common, MPCAlgorithm, MultisigType, multisigTypes } from '@bitgo/sdk-core';
5+
import { BaseCoin as StaticsBaseCoin, CoinFeature, coins } from '@bitgo/statics';
6+
import {
7+
AbstractEthLikeNewCoins,
8+
OfflineVaultTxInfo,
9+
RecoverOptions,
10+
recoveryBlockchainExplorerQuery,
11+
TransactionBuilder as EthLikeTransactionBuilder,
12+
UnsignedSweepTxMPCv2,
13+
} from '@bitgo/abstract-eth';
14+
import { TransactionBuilder } from './lib';
15+
import assert from 'assert';
16+
17+
export class EvmCoin extends AbstractEthLikeNewCoins {
18+
protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>) {
19+
super(bitgo, staticsCoin);
20+
}
21+
22+
static createInstance(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>): BaseCoin {
23+
return new EvmCoin(bitgo, staticsCoin);
24+
}
25+
26+
protected getTransactionBuilder(): EthLikeTransactionBuilder {
27+
return new TransactionBuilder(coins.get(this.getBaseChain()));
28+
}
29+
30+
/** @inheritDoc */
31+
supportsTss(): boolean {
32+
return this.staticsCoin?.features.includes(CoinFeature.TSS) ?? false;
33+
}
34+
35+
/** inherited doc */
36+
getDefaultMultisigType(): MultisigType {
37+
return this.staticsCoin?.features.includes(CoinFeature.TSS) ? multisigTypes.tss : multisigTypes.onchain;
38+
}
39+
40+
/** @inheritDoc */
41+
getMPCAlgorithm(): MPCAlgorithm {
42+
return 'ecdsa';
43+
}
44+
45+
protected async buildUnsignedSweepTxnTSS(params: RecoverOptions): Promise<OfflineVaultTxInfo | UnsignedSweepTxMPCv2> {
46+
if (this.staticsCoin?.features.includes(CoinFeature.MPCV2)) {
47+
return this.buildUnsignedSweepTxnMPCv2(params);
48+
}
49+
return super.buildUnsignedSweepTxnTSS(params);
50+
}
51+
52+
/**
53+
* Make a query to chain explorer for information such as balance, token balance, solidity calls
54+
* @param {Object} query key-value pairs of parameters to append after /api
55+
* @returns {Promise<Object>} response from chain explorer
56+
*/
57+
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
58+
const evmConfig = common.Environments[this.bitgo.getEnv()].evm;
59+
assert(
60+
evmConfig && this.getFamily() in evmConfig,
61+
`env config is missing for ${this.getFamily()} in ${this.bitgo.getEnv()}`
62+
);
63+
64+
const apiToken = evmConfig[this.getFamily()].apiToken;
65+
const explorerUrl = evmConfig[this.getFamily()].baseUrl;
66+
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken as string);
67+
}
68+
}

modules/sdk-coin-evm/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './evmCoin';
2+
export * from './lib';
3+
export * from './register';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as Utils from './utils';
2+
3+
export { TransactionBuilder } from './transactionBuilder';
4+
export { TransferBuilder } from './transferBuilder';
5+
export { Transaction, KeyPair } from '@bitgo/abstract-eth';
6+
export { Utils };
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
2+
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
3+
import { TransactionBuilder as AbstractTransactionBuilder, Transaction } from '@bitgo/abstract-eth';
4+
import { getCommon } from './utils';
5+
import { TransferBuilder } from './transferBuilder';
6+
7+
export class TransactionBuilder extends AbstractTransactionBuilder {
8+
protected _transfer: TransferBuilder;
9+
private _signatures: any;
10+
11+
constructor(_coinConfig: Readonly<CoinConfig>) {
12+
super(_coinConfig);
13+
this._common = getCommon(this._coinConfig);
14+
this.transaction = new Transaction(this._coinConfig, this._common);
15+
}
16+
17+
/** @inheritdoc */
18+
transfer(data?: string): TransferBuilder {
19+
if (this._type !== TransactionType.Send) {
20+
throw new BuildTransactionError('Transfers can only be set for send transactions');
21+
}
22+
if (!this._transfer) {
23+
this._transfer = new TransferBuilder(data);
24+
}
25+
return this._transfer;
26+
}
27+
28+
addSignature(publicKey, signature) {
29+
this._signatures = [];
30+
this._signatures.push({ publicKey, signature });
31+
}
32+
33+
protected getContractData(addresses: string[]): string {
34+
throw new Error('Method not implemented.');
35+
}
36+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { TransferBuilder } from '@bitgo/abstract-eth';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { CoinFeature, NetworkType, BaseCoin, EthereumNetwork } from '@bitgo/statics';
2+
import EthereumCommon from '@ethereumjs/common';
3+
import { InvalidTransactionError } from '@bitgo/sdk-core';
4+
5+
/**
6+
* @param {NetworkType} network either mainnet or testnet
7+
* @returns {EthereumCommon} Ethereum common configuration object
8+
*/
9+
export function getCommon(coin: Readonly<BaseCoin>): EthereumCommon {
10+
if (!coin.features.includes(CoinFeature.SHARED_EVM_SDK)) {
11+
throw new InvalidTransactionError(`Cannot use common sdk module for the coin ${coin.name}`);
12+
}
13+
return EthereumCommon.custom(
14+
{
15+
name: coin.network.name,
16+
networkId: (coin.network as EthereumNetwork).chainId,
17+
chainId: (coin.network as EthereumNetwork).chainId,
18+
},
19+
{
20+
baseChain: coin.network.type === NetworkType.MAINNET ? 'mainnet' : 'sepolia',
21+
hardfork: coin.features.includes(CoinFeature.EIP1559) ? 'london' : undefined,
22+
eips: coin.features.includes(CoinFeature.EIP1559) ? [1559] : undefined,
23+
}
24+
);
25+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { BitGoBase } from '@bitgo/sdk-core';
2+
import { CoinFeature, coins } from '@bitgo/statics';
3+
import { EvmCoin } from './evmCoin';
4+
5+
export const register = (sdk: BitGoBase): void => {
6+
coins
7+
.filter((coin) => coin.features.includes(CoinFeature.SHARED_EVM_SDK))
8+
.forEach((coin) => {
9+
sdk.register(coin.name, EvmCoin.createInstance);
10+
});
11+
};

modules/sdk-core/src/bitgo/environments.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ interface EnvironmentTemplate {
8686
soneiumExplorerApiToken?: string;
8787
stxNodeUrl: string;
8888
vetNodeUrl: string;
89+
evm?: {
90+
[key: string]: {
91+
baseUrl: string;
92+
apiToken?: string;
93+
};
94+
};
8995
}
9096

9197
export interface Environment extends EnvironmentTemplate {

0 commit comments

Comments
 (0)