Skip to content
Merged
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
4 changes: 3 additions & 1 deletion modules/sdk-coin-iota/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"@bitgo/statics": "^58.8.0",
"@iota/bcs": "^1.2.0",
"@iota/iota-sdk": "^1.6.0",
"bignumber.js": "^9.1.2"
"bignumber.js": "^9.1.2",
"lodash": "^4.17.21",
"@bitgo/blake2b": "^3.2.4"
},
"devDependencies": {
"@bitgo/sdk-api": "^1.71.0",
Expand Down
124 changes: 117 additions & 7 deletions modules/sdk-coin-iota/src/iota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
BaseCoin,
BitGoBase,
KeyPair,
ParseTransactionOptions,
ParsedTransaction,
SignTransactionOptions,
SignedTransaction,
Expand All @@ -13,12 +12,23 @@ import {
MPCAlgorithm,
TssVerifyAddressOptions,
MPCType,
PopulatedIntent,
PrebuildTransactionWithIntentOptions,
verifyEddsaTssWalletAddress,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics';
import { BaseCoin as StaticsBaseCoin, CoinFamily, coins } from '@bitgo/statics';
import utils from './lib/utils';
import { KeyPair as IotaKeyPair } from './lib';
import { KeyPair as IotaKeyPair, Transaction, TransactionBuilderFactory } from './lib';
import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
import BigNumber from 'bignumber.js';
import * as _ from 'lodash';
import {
ExplainTransactionOptions,
IotaParseTransactionOptions,
TransactionExplanation,
TransferTxData,
} from './lib/iface';
import { TransferTransaction } from './lib/transferTransaction';

export class Iota extends BaseCoin {
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
Expand Down Expand Up @@ -77,12 +87,44 @@ export class Iota extends BaseCoin {
return utils.isValidAddress(address);
}

/**
* @inheritDoc
*/
async explainTransaction(params: ExplainTransactionOptions): Promise<TransactionExplanation> {
const rawTx = params.txBase64;
if (!rawTx) {
throw new Error('missing required tx prebuild property txBase64');
}
const transaction = await this.rebuildTransaction(rawTx);
if (!transaction) {
throw new Error('failed to explain transaction');
}
return transaction.explainTransaction();
}

/**
* Verifies that a transaction prebuild complies with the original intention
* @param params
*/
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
// TODO: Add IOTA-specific transaction verification logic
const { txPrebuild: txPrebuild, txParams: txParams } = params;
const rawTx = txPrebuild.txBase64;
if (!rawTx) {
throw new Error('missing required tx prebuild property txBase64');
}
const transaction = await this.rebuildTransaction(rawTx);
if (!transaction) {
throw new Error('failed to verify transaction');
}
if (txParams.recipients !== undefined) {
if (!(transaction instanceof TransferTransaction)) {
throw new Error('Tx not a transfer transaction');
}
const txData = transaction.toJson() as TransferTxData;
if (!txData.recipients || !_.isEqual(txParams.recipients, txData.recipients)) {
throw new Error('Tx recipients does not match with expected txParams recipients');
}
}
return true;
}

Expand All @@ -102,9 +144,51 @@ export class Iota extends BaseCoin {
* Parse a transaction
* @param params
*/
async parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
// TODO: Add IOTA-specific transaction parsing logic
return {};
async parseTransaction(params: IotaParseTransactionOptions): Promise<ParsedTransaction> {
const transactionExplanation = await this.explainTransaction({ txBase64: params.txBase64 });

if (!transactionExplanation) {
throw new Error('Invalid transaction');
}

let fee = new BigNumber(0);

if (transactionExplanation.outputs.length <= 0) {
return {
inputs: [],
outputs: [],
fee,
};
}

const senderAddress = transactionExplanation.outputs[0].address;
if (transactionExplanation.fee.fee !== '') {
fee = new BigNumber(transactionExplanation.fee.fee);
}

// assume 1 sender, who is also the fee payer
const inputs = [
{
address: senderAddress,
amount: new BigNumber(transactionExplanation.outputAmount).plus(fee).toFixed(),
},
];

const outputs: {
address: string;
amount: string;
}[] = transactionExplanation.outputs.map((output) => {
return {
address: output.address,
amount: new BigNumber(output.amount).toFixed(),
};
});

return {
inputs,
outputs,
fee,
};
}

/**
Expand Down Expand Up @@ -149,4 +233,30 @@ export class Iota extends BaseCoin {
}
auditEddsaPrivateKey(prv, publicKey ?? '');
}

/** @inheritDoc */
async getSignablePayload(serializedTx: string): Promise<Buffer> {
const rebuiltTransaction = await this.rebuildTransaction(serializedTx);
return rebuiltTransaction.signablePayload;
}

/** inherited doc */
setCoinSpecificFieldsInIntent(intent: PopulatedIntent, params: PrebuildTransactionWithIntentOptions): void {
intent.unspents = params.unspents;
}

private getTxBuilderFactory(): TransactionBuilderFactory {
return new TransactionBuilderFactory(coins.get(this.getChain()));
}

private async rebuildTransaction(txHex: string): Promise<Transaction> {
const txBuilderFactory = this.getTxBuilderFactory();
try {
const txBuilder = txBuilderFactory.from(txHex);
txBuilder.transaction.isSimulateTx = false;
return (await txBuilder.build()) as Transaction;
} catch {
throw new Error('Failed to rebuild transaction');
}
}
}
10 changes: 7 additions & 3 deletions modules/sdk-coin-iota/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export const IOTA_ADDRESS_LENGTH = 64;
export const IOTA_TRANSACTION_DIGEST_LENGTH = 32;
export const IOTA_BLOCK_DIGEST_LENGTH = 32;
export const IOTA_SIGNATURE_LENGTH = 64;
export const ADDRESS_BYTES_LENGTH = 32;
export const AMOUNT_BYTES_LENGTH = 8;
export const SECONDS_PER_WEEK = 7 * 24 * 60 * 60; // 1 week in seconds
export const IOTA_KEY_BYTES_LENGTH = 32; // Ed25519 public key is 32 bytes
export const MAX_INPUT_OBJECTS = 2048;
export const MAX_GAS_PAYMENT_OBJECTS = 256;
export const MAX_GAS_BUDGET = 50000000000;
export const MAX_GAS_PRICE = 100000;
export const MAX_RECIPIENTS = 256; // Maximum number of recipients in a transfer transaction
export const TRANSFER_TRANSACTION_COMMANDS = ['SplitCoins', 'MergeCoins', 'TransferObjects'];
45 changes: 44 additions & 1 deletion modules/sdk-coin-iota/src/lib/iface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
import {
ParseTransactionOptions as BaseParseTransactionOptions,
TransactionExplanation as BaseTransactionExplanation,
TransactionRecipient,
TransactionType as BitGoTransactionType,
TransactionType,
} from '@bitgo/sdk-core';

export interface TransactionExplanation extends BaseTransactionExplanation {
type: BitGoTransactionType;
}

export type TransactionObjectInput = {
objectId: string;
version: string;
digest: string;
};

export type GasData = {
gasBudget?: number;
gasPrice?: number;
gasPaymentObjects?: TransactionObjectInput[];
};

/**
* The transaction data returned from the toJson() function of a transaction
*/
export interface TxData {
id: string;
id?: string;
sender: string;
gasBudget?: number;
gasPrice?: number;
gasPaymentObjects?: TransactionObjectInput[];
gasSponsor?: string;
type: TransactionType;
}

export interface TransferTxData extends TxData {
recipients: TransactionRecipient[];
paymentObjects?: TransactionObjectInput[];
}

export interface ExplainTransactionOptions {
txBase64: string;
}

export interface IotaParseTransactionOptions extends BaseParseTransactionOptions {
txBase64: string;
}
1 change: 1 addition & 0 deletions modules/sdk-coin-iota/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export { KeyPair } from './keyPair';
export { Transaction } from './transaction';
export { TransactionBuilder } from './transactionBuilder';
export { TransferBuilder } from './transferBuilder';
export { TransferTransaction } from './transferTransaction';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { Interface, Utils };
Loading