Skip to content

Commit

Permalink
Merge pull request #5410 from BitGo/WIN-4299
Browse files Browse the repository at this point in the history
feat: implement abstract substrate functions
  • Loading branch information
yash-bitgo authored Jan 22, 2025
2 parents b27b91e + 20d831b commit 688e299
Show file tree
Hide file tree
Showing 11 changed files with 1,201 additions and 11 deletions.
14 changes: 12 additions & 2 deletions modules/abstract-substrate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"author": "BitGo SDK Team <[email protected]>",
"license": "MIT",
"engines": {
"node": ">=18 <21"
"node": ">=14 <21"
},
"repository": {
"type": "git",
Expand All @@ -39,6 +39,16 @@
},
"dependencies": {
"@bitgo/sdk-core": "^28.20.0",
"@bitgo/statics": "^50.20.0"
"@bitgo/statics": "^50.20.0",
"@polkadot/keyring": "13.2.3",
"@polkadot/types": "14.1.1",
"@polkadot/util": "13.2.3",
"@polkadot/util-crypto": "13.2.3",
"@substrate/txwrapper-core": "7.5.2",
"@substrate/txwrapper-polkadot": "7.5.2",
"bs58": "^4.0.1",
"hi-base32": "^0.5.1",
"lodash": "^4.17.15",
"tweetnacl": "^1.0.3"
}
}
108 changes: 99 additions & 9 deletions modules/abstract-substrate/src/abstractSubstrateCoin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as _ from 'lodash';
import {
BaseCoin,
BitGoBase,
Expand All @@ -6,14 +7,44 @@ import {
ParsedTransaction,
ParseTransactionOptions,
SignedTransaction,
SignTransactionOptions,
SignTransactionOptions as BaseSignTransactionOptions,
VerifyAddressOptions,
VerifyTransactionOptions,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics';
import { Interface, KeyPair as SubstrateKeyPair, Utils } from './lib';

const utils = Utils.default;

export const DEFAULT_SCAN_FACTOR = 20; // default number of receive addresses to scan for funds

export interface SignTransactionOptions extends BaseSignTransactionOptions {
txPrebuild: TransactionPrebuild;
prv: string;
}

export interface TransactionPrebuild {
txHex: string;
transaction: Interface.TxData;
}

export interface ExplainTransactionOptions {
txPrebuild: TransactionPrebuild;
publicKey: string;
feeInfo: {
fee: string;
};
}

export interface VerifiedTransactionParameters {
txHex: string;
prv: string;
}

export class SubstrateCoin extends BaseCoin {
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
readonly MAX_VALIDITY_DURATION = 2400;

protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>) {
super(bitgo);

Expand All @@ -37,7 +68,7 @@ export class SubstrateCoin extends BaseCoin {

/** @inheritDoc **/
getBaseFactor(): string | number {
throw new Error('Method not implemented');
return Math.pow(10, this._staticsCoin.decimalPlaces);
}

/** @inheritDoc **/
Expand Down Expand Up @@ -67,12 +98,20 @@ export class SubstrateCoin extends BaseCoin {

/** @inheritDoc **/
generateKeyPair(seed?: Buffer): KeyPair {
throw new Error('Method not implemented');
const keyPair = seed ? utils.keyPairFromSeed(new Uint8Array(seed)) : new SubstrateKeyPair();
const keys = keyPair.getKeys();
if (!keys.prv) {
throw new Error('Missing prv in key generation.');
}
return {
pub: keys.pub,
prv: keys.prv,
};
}

/** @inheritDoc **/
isValidPub(pub: string): boolean {
throw new Error('Method not implemented');
return utils.isValidPublicKey(pub);
}

/** @inheritDoc **/
Expand All @@ -86,17 +125,68 @@ export class SubstrateCoin extends BaseCoin {
}

/** @inheritDoc **/
verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
throw new Error('Method not implemented');
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
const { txParams } = params;
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
throw new Error(
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
);
}
return true;
}

/** @inheritDoc **/
isValidAddress(address: string): boolean {
throw new Error('Method not implemented.');
return utils.isValidAddress(address);
}

verifySignTransactionParams(params: SignTransactionOptions): VerifiedTransactionParameters {
const prv = params.prv;

const txHex = params.txPrebuild.txHex;

if (!txHex) {
throw new Error('missing txPrebuild parameter');
}

if (!_.isString(txHex)) {
throw new Error(`txPrebuild must be an object, got type ${typeof txHex}`);
}

if (!prv) {
throw new Error('missing prv parameter to sign transaction');
}

if (!_.isString(prv)) {
throw new Error(`prv must be a string, got type ${typeof prv}`);
}

if (!_.has(params, 'pubs')) {
throw new Error('missing public key parameter to sign transaction');
}

return { txHex, prv };
}

/** @inheritDoc **/
signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
throw new Error('Method not implemented.');
async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
const { txHex, prv } = this.verifySignTransactionParams(params);
const factory = this.getBuilder();
const txBuilder = factory.from(txHex);
const keyPair = new SubstrateKeyPair({ prv: prv });
const { referenceBlock, blockNumber, transactionVersion, sender } = params.txPrebuild.transaction;

txBuilder
.validity({ firstValid: blockNumber, maxDuration: this.MAX_VALIDITY_DURATION })
.referenceBlock(referenceBlock)
.version(transactionVersion)
.sender({ address: sender })
.sign({ key: keyPair.getKeys().prv });
const transaction = await txBuilder.build();
if (!transaction) {
throw new Error('Invalid transaction');
}
const signedTxHex = transaction.toBroadcastFormat();
return { txHex: signedTxHex };
}
}
1 change: 1 addition & 0 deletions modules/abstract-substrate/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './lib';
export { SubstrateCoin } from './abstractSubstrateCoin';
Loading

0 comments on commit 688e299

Please sign in to comment.