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
15 changes: 7 additions & 8 deletions modules/sdk-coin-iota/src/iota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export class Iota extends BaseCoin {
* @inheritDoc
*/
async explainTransaction(params: ExplainTransactionOptions): Promise<TransactionExplanation> {
const rawTx = params.txBase64;
const rawTx = params.txHex;
if (!rawTx) {
throw new Error('missing required tx prebuild property txBase64');
throw new Error('missing required tx prebuild property txHex');
}
const transaction = await this.rebuildTransaction(rawTx);
if (!transaction) {
Expand All @@ -108,9 +108,9 @@ export class Iota extends BaseCoin {
*/
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
const { txPrebuild: txPrebuild, txParams: txParams } = params;
const rawTx = txPrebuild.txBase64;
const rawTx = txPrebuild.txHex;
if (!rawTx) {
throw new Error('missing required tx prebuild property txBase64');
throw new Error('missing required tx prebuild property txHex');
}
const transaction = await this.rebuildTransaction(rawTx);
if (!transaction) {
Expand Down Expand Up @@ -145,7 +145,7 @@ export class Iota extends BaseCoin {
* @param params
*/
async parseTransaction(params: IotaParseTransactionOptions): Promise<ParsedTransaction> {
const transactionExplanation = await this.explainTransaction({ txBase64: params.txBase64 });
const transactionExplanation = await this.explainTransaction({ txHex: params.txHex });

if (!transactionExplanation) {
throw new Error('Invalid transaction');
Expand Down Expand Up @@ -262,10 +262,9 @@ export class Iota extends BaseCoin {
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');
} catch (err) {
throw new Error(`Failed to rebuild transaction ${err.toString()}`);
}
}
}
4 changes: 2 additions & 2 deletions modules/sdk-coin-iota/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ export interface TransferTxData extends TxData {
}

export interface ExplainTransactionOptions {
txBase64: string;
txHex: string;
}

export interface IotaParseTransactionOptions extends BaseParseTransactionOptions {
txBase64: string;
txHex: string;
}
6 changes: 4 additions & 2 deletions modules/sdk-coin-iota/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { Transaction } from './transaction';
import { TRANSFER_TRANSACTION_COMMANDS } from './constants';
import utils from './utils';
import { Transaction as IotaTransaction } from '@iota/iota-sdk/transactions';
import { BaseCoin as CoinConfig } from '@bitgo/statics';

Expand All @@ -22,11 +23,12 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {

from(rawTx: string | Uint8Array): TransactionBuilder {
let builder: TransactionBuilder;
const txType: TransactionType = this.identifyTxTypeFromRawTx(rawTx);
const rawTxBase64 = utils.getBase64String(rawTx);
const txType: TransactionType = this.identifyTxTypeFromRawTx(rawTxBase64);
switch (txType) {
case TransactionType.Send:
builder = new TransferBuilder(this._coinConfig);
builder.fromImplementation(rawTx);
builder.fromImplementation(rawTxBase64);
return builder;
}
throw new InvalidTransactionError('Unsupported transaction');
Expand Down
10 changes: 9 additions & 1 deletion modules/sdk-coin-iota/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './constants';
import { Ed25519PublicKey } from '@iota/iota-sdk/keypairs/ed25519';
import { Transaction as IotaTransaction } from '@iota/iota-sdk/transactions';
import { fromBase64 } from '@iota/bcs';
import { toBase64, fromBase64 } from '@iota/iota-sdk/utils';

export class Utils implements BaseUtils {
/** @inheritdoc */
Expand Down Expand Up @@ -49,6 +49,14 @@ export class Utils implements BaseUtils {
return regex.test(value);
}

getBase64String(value: string | Uint8Array): string {
if (value instanceof Uint8Array) {
return toBase64(value);
} else {
return toBase64(Buffer.from(value, 'hex'));
}
}

getAddressFromPublicKey(publicKey: string): string {
const iotaPublicKey = new Ed25519PublicKey(Buffer.from(publicKey, 'hex'));
return iotaPublicKey.toIotaAddress();
Expand Down
60 changes: 33 additions & 27 deletions modules/sdk-coin-iota/test/unit/iota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,16 @@ describe('IOTA:', function () {
});

describe('explainTransaction', () => {
it('should throw error for missing txBase64', async function () {
it('should throw error for missing txHex', async function () {
await assert.rejects(
async () => await basecoin.explainTransaction({ txBase64: '' }),
/missing required tx prebuild property txBase64/
async () => await basecoin.explainTransaction({ txHex: '' }),
/missing required tx prebuild property txHex/
);
});

it('should throw error for invalid transaction', async function () {
await assert.rejects(
async () => await basecoin.explainTransaction({ txBase64: 'invalidTxBase64' }),
async () => await basecoin.explainTransaction({ txHex: 'invalidTxHex' }),
/Failed to rebuild transaction/
);
});
Expand All @@ -171,14 +171,14 @@ describe('IOTA:', function () {
});

describe('verifyTransaction', () => {
it('should throw error for missing txBase64', async function () {
it('should throw error for missing txHex', async function () {
await assert.rejects(
async () =>
await basecoin.verifyTransaction({
txPrebuild: {},
txParams: { recipients: testData.recipients },
}),
/missing required tx prebuild property txBase64/
/missing required tx prebuild property txHex/
);
});

Expand All @@ -191,16 +191,14 @@ describe('IOTA:', function () {
txBuilder.gasData(testData.gasData);

const tx = (await txBuilder.build()) as TransferTransaction;
const txJson = tx.toJson();

// Rebuild from JSON to simulate what would happen in verification
const rebuiltTxBuilder = factory.getTransferBuilder();
(rebuiltTxBuilder.transaction as TransferTransaction).parseFromJSON(txJson);
const rebuiltTx = (await rebuiltTxBuilder.build()) as TransferTransaction;

// Verify the rebuilt transaction matches
should.equal(rebuiltTx.sender, testData.sender.address);
should.deepEqual(rebuiltTx.recipients, testData.recipients);
const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex');
should.equal(
await basecoin.verifyTransaction({
txPrebuild: { txHex: txHex },
txParams: { recipients: testData.recipients },
}),
true
);
});

it('should detect mismatched recipients', async function () {
Expand All @@ -211,12 +209,15 @@ describe('IOTA:', function () {
txBuilder.gasData(testData.gasData);

const tx = (await txBuilder.build()) as TransferTransaction;
const txJson = tx.toJson();

const differentRecipients = [{ address: testData.addresses.validAddresses[0], amount: '9999' }];

// Recipients don't match
should.notDeepEqual(txJson.recipients, differentRecipients);
const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex');
assert.rejects(
async () =>
await basecoin.verifyTransaction({
txPrebuild: { txHex: txHex },
txParams: { recipients: testData.recipients },
}),
/Tx recipients does not match with expected txParams recipients/
);
});

it('should verify transaction without recipients parameter', async function () {
Expand All @@ -227,17 +228,22 @@ describe('IOTA:', function () {
txBuilder.gasData(testData.gasData);

const tx = (await txBuilder.build()) as TransferTransaction;

// Verification should still work even if no recipients are provided for comparison
should.exist(tx);
should.equal(tx.type, TransactionType.Send);
const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex');

should.equal(
await basecoin.verifyTransaction({
txPrebuild: { txHex: txHex },
txParams: {},
}),
true
);
});
});

describe('parseTransaction', () => {
it('should throw error for invalid transaction', async function () {
await assert.rejects(
async () => await basecoin.parseTransaction({ txBase64: 'invalidTxBase64' }),
async () => await basecoin.parseTransaction({ txHex: 'invalidTxHex' }),
/Failed to rebuild transaction/
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ describe('Iota Transaction Builder Factory', () => {

const tx = (await txBuilder.build()) as TransferTransaction;
const rawTx = await tx.toBroadcastFormat();
const txHex = Buffer.from(rawTx, 'base64').toString('hex');

const rebuiltBuilder = factory.from(rawTx);
const rebuiltBuilder = factory.from(txHex);
should.exist(rebuiltBuilder);
should(rebuiltBuilder instanceof TransferBuilder).be.true();
const rebuiltTx = (await rebuiltBuilder.build()) as TransferTransaction;
Expand Down
41 changes: 41 additions & 0 deletions modules/sdk-coin-iota/test/unit/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,47 @@ describe('Iota util library', function () {
});
});

describe('getBase64String', function () {
it('should convert Uint8Array to base64', function () {
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
const result = utils.getBase64String(uint8Array);
should.exist(result);
should.equal(typeof result, 'string');
should.equal(result.length > 0, true);
});

it('should convert hex string to base64', function () {
const hexString = '48656c6c6f'; // "Hello" in hex
const result = utils.getBase64String(hexString);
should.exist(result);
should.equal(typeof result, 'string');
should.equal(result.length > 0, true);
});

it('should handle empty Uint8Array', function () {
const emptyArray = new Uint8Array([]);
const result = utils.getBase64String(emptyArray);
should.exist(result);
should.equal(typeof result, 'string');
});

it('should handle Buffer conversion to base64', function () {
const buffer = Buffer.from('Hello World');
const uint8Array = new Uint8Array(buffer);
const result = utils.getBase64String(uint8Array);
should.exist(result);
should.equal(typeof result, 'string');
should.equal(result.length > 0, true);
});

it('should consistently convert same input to same output', function () {
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const result1 = utils.getBase64String(uint8Array);
const result2 = utils.getBase64String(new Uint8Array([1, 2, 3, 4, 5]));
should.equal(result1, result2);
});
});

describe('isValidRawTransaction', function () {
it('should validate proper raw transactions', async function () {
const factory = new TransactionBuilderFactory(coins.get('tiota'));
Expand Down