Skip to content

Commit cdb05b6

Browse files
Merge pull request #7348 from BitGo/WIN-6890
feat(sdk-coin-iota): transaction builder for iota
2 parents 48b7c67 + 0df3476 commit cdb05b6

19 files changed

+3194
-71
lines changed

modules/sdk-coin-iota/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"@bitgo/statics": "^58.8.0",
4545
"@iota/bcs": "^1.2.0",
4646
"@iota/iota-sdk": "^1.6.0",
47-
"bignumber.js": "^9.1.2"
47+
"bignumber.js": "^9.1.2",
48+
"lodash": "^4.17.21",
49+
"@bitgo/blake2b": "^3.2.4"
4850
},
4951
"devDependencies": {
5052
"@bitgo/sdk-api": "^1.71.0",

modules/sdk-coin-iota/src/iota.ts

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
BaseCoin,
44
BitGoBase,
55
KeyPair,
6-
ParseTransactionOptions,
76
ParsedTransaction,
87
SignTransactionOptions,
98
SignedTransaction,
@@ -13,12 +12,23 @@ import {
1312
MPCAlgorithm,
1413
TssVerifyAddressOptions,
1514
MPCType,
15+
PopulatedIntent,
16+
PrebuildTransactionWithIntentOptions,
1617
verifyEddsaTssWalletAddress,
1718
} from '@bitgo/sdk-core';
18-
import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics';
19+
import { BaseCoin as StaticsBaseCoin, CoinFamily, coins } from '@bitgo/statics';
1920
import utils from './lib/utils';
20-
import { KeyPair as IotaKeyPair } from './lib';
21+
import { KeyPair as IotaKeyPair, Transaction, TransactionBuilderFactory } from './lib';
2122
import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
23+
import BigNumber from 'bignumber.js';
24+
import * as _ from 'lodash';
25+
import {
26+
ExplainTransactionOptions,
27+
IotaParseTransactionOptions,
28+
TransactionExplanation,
29+
TransferTxData,
30+
} from './lib/iface';
31+
import { TransferTransaction } from './lib/transferTransaction';
2232

2333
export class Iota extends BaseCoin {
2434
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -77,12 +87,44 @@ export class Iota extends BaseCoin {
7787
return utils.isValidAddress(address);
7888
}
7989

90+
/**
91+
* @inheritDoc
92+
*/
93+
async explainTransaction(params: ExplainTransactionOptions): Promise<TransactionExplanation> {
94+
const rawTx = params.txBase64;
95+
if (!rawTx) {
96+
throw new Error('missing required tx prebuild property txBase64');
97+
}
98+
const transaction = await this.rebuildTransaction(rawTx);
99+
if (!transaction) {
100+
throw new Error('failed to explain transaction');
101+
}
102+
return transaction.explainTransaction();
103+
}
104+
80105
/**
81106
* Verifies that a transaction prebuild complies with the original intention
82107
* @param params
83108
*/
84109
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
85-
// TODO: Add IOTA-specific transaction verification logic
110+
const { txPrebuild: txPrebuild, txParams: txParams } = params;
111+
const rawTx = txPrebuild.txBase64;
112+
if (!rawTx) {
113+
throw new Error('missing required tx prebuild property txBase64');
114+
}
115+
const transaction = await this.rebuildTransaction(rawTx);
116+
if (!transaction) {
117+
throw new Error('failed to verify transaction');
118+
}
119+
if (txParams.recipients !== undefined) {
120+
if (!(transaction instanceof TransferTransaction)) {
121+
throw new Error('Tx not a transfer transaction');
122+
}
123+
const txData = transaction.toJson() as TransferTxData;
124+
if (!txData.recipients || !_.isEqual(txParams.recipients, txData.recipients)) {
125+
throw new Error('Tx recipients does not match with expected txParams recipients');
126+
}
127+
}
86128
return true;
87129
}
88130

@@ -102,9 +144,51 @@ export class Iota extends BaseCoin {
102144
* Parse a transaction
103145
* @param params
104146
*/
105-
async parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
106-
// TODO: Add IOTA-specific transaction parsing logic
107-
return {};
147+
async parseTransaction(params: IotaParseTransactionOptions): Promise<ParsedTransaction> {
148+
const transactionExplanation = await this.explainTransaction({ txBase64: params.txBase64 });
149+
150+
if (!transactionExplanation) {
151+
throw new Error('Invalid transaction');
152+
}
153+
154+
let fee = new BigNumber(0);
155+
156+
if (transactionExplanation.outputs.length <= 0) {
157+
return {
158+
inputs: [],
159+
outputs: [],
160+
fee,
161+
};
162+
}
163+
164+
const senderAddress = transactionExplanation.outputs[0].address;
165+
if (transactionExplanation.fee.fee !== '') {
166+
fee = new BigNumber(transactionExplanation.fee.fee);
167+
}
168+
169+
// assume 1 sender, who is also the fee payer
170+
const inputs = [
171+
{
172+
address: senderAddress,
173+
amount: new BigNumber(transactionExplanation.outputAmount).plus(fee).toFixed(),
174+
},
175+
];
176+
177+
const outputs: {
178+
address: string;
179+
amount: string;
180+
}[] = transactionExplanation.outputs.map((output) => {
181+
return {
182+
address: output.address,
183+
amount: new BigNumber(output.amount).toFixed(),
184+
};
185+
});
186+
187+
return {
188+
inputs,
189+
outputs,
190+
fee,
191+
};
108192
}
109193

110194
/**
@@ -149,4 +233,30 @@ export class Iota extends BaseCoin {
149233
}
150234
auditEddsaPrivateKey(prv, publicKey ?? '');
151235
}
236+
237+
/** @inheritDoc */
238+
async getSignablePayload(serializedTx: string): Promise<Buffer> {
239+
const rebuiltTransaction = await this.rebuildTransaction(serializedTx);
240+
return rebuiltTransaction.signablePayload;
241+
}
242+
243+
/** inherited doc */
244+
setCoinSpecificFieldsInIntent(intent: PopulatedIntent, params: PrebuildTransactionWithIntentOptions): void {
245+
intent.unspents = params.unspents;
246+
}
247+
248+
private getTxBuilderFactory(): TransactionBuilderFactory {
249+
return new TransactionBuilderFactory(coins.get(this.getChain()));
250+
}
251+
252+
private async rebuildTransaction(txHex: string): Promise<Transaction> {
253+
const txBuilderFactory = this.getTxBuilderFactory();
254+
try {
255+
const txBuilder = txBuilderFactory.from(txHex);
256+
txBuilder.transaction.isSimulateTx = false;
257+
return (await txBuilder.build()) as Transaction;
258+
} catch {
259+
throw new Error('Failed to rebuild transaction');
260+
}
261+
}
152262
}

modules/sdk-coin-iota/src/lib/constants.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ export const IOTA_ADDRESS_LENGTH = 64;
22
export const IOTA_TRANSACTION_DIGEST_LENGTH = 32;
33
export const IOTA_BLOCK_DIGEST_LENGTH = 32;
44
export const IOTA_SIGNATURE_LENGTH = 64;
5-
export const ADDRESS_BYTES_LENGTH = 32;
6-
export const AMOUNT_BYTES_LENGTH = 8;
7-
export const SECONDS_PER_WEEK = 7 * 24 * 60 * 60; // 1 week in seconds
5+
export const IOTA_KEY_BYTES_LENGTH = 32; // Ed25519 public key is 32 bytes
6+
export const MAX_INPUT_OBJECTS = 2048;
7+
export const MAX_GAS_PAYMENT_OBJECTS = 256;
8+
export const MAX_GAS_BUDGET = 50000000000;
9+
export const MAX_GAS_PRICE = 100000;
10+
export const MAX_RECIPIENTS = 256; // Maximum number of recipients in a transfer transaction
11+
export const TRANSFER_TRANSACTION_COMMANDS = ['SplitCoins', 'MergeCoins', 'TransferObjects'];
Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
1+
import {
2+
ParseTransactionOptions as BaseParseTransactionOptions,
3+
TransactionExplanation as BaseTransactionExplanation,
4+
TransactionRecipient,
5+
TransactionType as BitGoTransactionType,
6+
TransactionType,
7+
} from '@bitgo/sdk-core';
8+
9+
export interface TransactionExplanation extends BaseTransactionExplanation {
10+
type: BitGoTransactionType;
11+
}
12+
13+
export type TransactionObjectInput = {
14+
objectId: string;
15+
version: string;
16+
digest: string;
17+
};
18+
19+
export type GasData = {
20+
gasBudget?: number;
21+
gasPrice?: number;
22+
gasPaymentObjects?: TransactionObjectInput[];
23+
};
24+
125
/**
226
* The transaction data returned from the toJson() function of a transaction
327
*/
428
export interface TxData {
5-
id: string;
29+
id?: string;
30+
sender: string;
31+
gasBudget?: number;
32+
gasPrice?: number;
33+
gasPaymentObjects?: TransactionObjectInput[];
34+
gasSponsor?: string;
35+
type: TransactionType;
36+
}
37+
38+
export interface TransferTxData extends TxData {
39+
recipients: TransactionRecipient[];
40+
paymentObjects?: TransactionObjectInput[];
41+
}
42+
43+
export interface ExplainTransactionOptions {
44+
txBase64: string;
45+
}
46+
47+
export interface IotaParseTransactionOptions extends BaseParseTransactionOptions {
48+
txBase64: string;
649
}

modules/sdk-coin-iota/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export { KeyPair } from './keyPair';
55
export { Transaction } from './transaction';
66
export { TransactionBuilder } from './transactionBuilder';
77
export { TransferBuilder } from './transferBuilder';
8+
export { TransferTransaction } from './transferTransaction';
89
export { TransactionBuilderFactory } from './transactionBuilderFactory';
910
export { Interface, Utils };

0 commit comments

Comments
 (0)