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
11 changes: 9 additions & 2 deletions modules/sdk-coin-canton/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ export interface WalletInitRequest {
observingParticipantUids: string[];
}

export interface OneStepEnablementRequest {
export interface CantonPrepareCommandRequest {
commandId: string;
receiverId: string;
verboseHashing: boolean;
actAs: string[];
readAs: string[];
Expand All @@ -81,3 +80,11 @@ export interface WalletInitBroadcastData {
onboardingTransactions: OnboardingTransaction[];
multiHashSignatures: MultiHashSignature[];
}

export interface CantonOneStepEnablementRequest extends CantonPrepareCommandRequest {
receiverId: string;
}

export interface CantonTransferAcceptRequest extends CantonPrepareCommandRequest {
contractId: string;
}
2 changes: 2 additions & 0 deletions modules/sdk-coin-canton/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as Utils from './utils';
import * as Interface from './iface';

export { KeyPair } from './keyPair';
export { OneStepPreApprovalBuilder } from './oneStepPreApprovalBuilder';
export { Transaction } from './transaction/transaction';
export { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
export { TransactionBuilder } from './transactionBuilder';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { WalletInitBuilder } from './walletInitBuilder';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, OneStepEnablementRequest } from './iface';
import { CantonPrepareCommandResponse, CantonOneStepEnablementRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';

Expand Down Expand Up @@ -62,15 +62,15 @@ export class OneStepPreApprovalBuilder extends TransactionBuilder {
}

/**
* Builds and returns the OneStepEnablementRequest object from the builder's internal state.
* Builds and returns the CantonOneStepEnablementRequest object from the builder's internal state.
*
* This method performs validation before constructing the object. If required fields are
* missing or invalid, it throws an error.
*
* @returns {OneStepEnablementRequest} - A fully constructed and validated request object for 1-step enablement.
* @returns {CantonOneStepEnablementRequest} - A fully constructed and validated request object for 1-step enablement.
* @throws {Error} If any required field is missing or fails validation.
*/
toRequestObject(): OneStepEnablementRequest {
toRequestObject(): CantonOneStepEnablementRequest {
this.validate();

return {
Expand Down
19 changes: 16 additions & 3 deletions modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TransactionType,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { Transaction } from './transaction/transaction';
Expand All @@ -24,13 +25,25 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
} catch {
const tx = new Transaction(this._coinConfig);
tx.fromRawTransaction(raw);
if (tx.type === TransactionType.Send) {
return this.getTransferBuilder(tx);
switch (tx.type) {
case TransactionType.Send: {
return this.getTransferBuilder(tx);
}
case TransactionType.TransferAccept: {
return this.getTransferAcceptanceBuilder(tx);
}
default: {
throw new InvalidTransactionError('unsupported transaction');
}
}
throw new InvalidTransactionError('unsupported transaction');
}
}

/** @inheritdoc */
getTransferAcceptanceBuilder(tx?: Transaction): TransferAcceptanceBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferAcceptanceBuilder(this._coinConfig));
}

/** @inheritdoc */
getTransferBuilder(tx?: Transaction): TransferBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferBuilder(this._coinConfig));
Expand Down
111 changes: 111 additions & 0 deletions modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, CantonTransferAcceptRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';

export class TransferAcceptanceBuilder extends TransactionBuilder {
private _commandId: string;
private _contractId: string;
private _actAsPartyId: string;
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}

initBuilder(tx: Transaction): void {
super.initBuilder(tx);
this.setTransactionType();
}

get transactionType(): TransactionType {
return TransactionType.TransferAccept;
}

setTransactionType(): void {
this.transaction.transactionType = TransactionType.TransferAccept;
}

setTransaction(transaction: CantonPrepareCommandResponse): void {
this.transaction.prepareCommand = transaction;
}

/**
* Sets the unique id for the transfer acceptance
* Also sets the _id of the transaction
*
* @param id - A uuid
* @returns The current builder instance for chaining.
* @throws Error if id is empty.
*/
commandId(id: string): this {
if (!id.trim()) {
throw new Error('commandId must be a non-empty string');
}
this._commandId = id.trim();
// also set the transaction _id
this.transaction.id = id.trim();
return this;
}

/**
* Sets the acceptance contract id the receiver needs to accept
* @param id - canton acceptance contract id
* @returns The current builder instance for chaining.
* @throws Error if id is empty.
*/
contractId(id: string): this {
if (!id.trim()) {
throw new Error('contractId must be a non-empty string');
}
this._contractId = id.trim();
return this;
}

/**
* Sets the receiver of the acceptance
*
* @param id - the receiver party id (address)
* @returns The current builder instance for chaining.
* @throws Error if id is empty.
*/
actAs(id: string): this {
if (!id.trim()) {
throw new Error('actAsPartyId must be a non-empty string');
}
this._actAsPartyId = id.trim();
return this;
}

/**
* Builds and returns the CantonTransferAcceptRequest object from the builder's internal state.
*
* This method performs validation before constructing the object. If required fields are
* missing or invalid, it throws an error.
*
* @returns {CantonTransferAcceptRequest} - A fully constructed and validated request object for transfer acceptance.
* @throws {Error} If any required field is missing or fails validation.
*/
toRequestObject(): CantonTransferAcceptRequest {
this.validate();

return {
commandId: this._commandId,
contractId: this._contractId,
verboseHashing: false,
actAs: [this._actAsPartyId],
readAs: [],
};
}

/**
* Validates the internal state of the builder before building the request object.
*
* @private
* @throws {Error} If any required field is missing or invalid.
*/
private validate(): void {
if (!this._commandId) throw new Error('commandId is missing');
if (!this._contractId) throw new Error('contractId is missing');
if (!this._actAsPartyId) throw new Error('receiver partyId is missing');
}
}
15 changes: 15 additions & 0 deletions modules/sdk-coin-canton/test/resources.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import should from 'should';
import { coins } from '@bitgo/statics';

import { Transaction } from '../../../../src';
import { OneStepEnablementRequest } from '../../../../src/lib/iface';
import { CantonOneStepEnablementRequest } from '../../../../src/lib/iface';
import { OneStepPreApprovalBuilder } from '../../../../src/lib/oneStepPreApprovalBuilder';

import {
Expand All @@ -14,13 +14,13 @@ import {
} from '../../../resources';

describe('Wallet Pre-approval Enablement Builder', () => {
it('should get the wallet init request object', function () {
it('should get the one step enablement request object', function () {
const txBuilder = new OneStepPreApprovalBuilder(coins.get('tcanton'));
const oneStepEnablementTx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(oneStepEnablementTx);
const { commandId, partyId } = OneStepEnablement;
txBuilder.commandId(commandId).receiverPartyId(partyId);
const requestObj: OneStepEnablementRequest = txBuilder.toRequestObject();
const requestObj: CantonOneStepEnablementRequest = txBuilder.toRequestObject();
should.exist(requestObj);
assert.equal(requestObj.commandId, commandId);
assert.equal(requestObj.receiverId, partyId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import assert from 'assert';
import should from 'should';

import { coins } from '@bitgo/statics';

import { TransferAcceptanceBuilder, Transaction } from '../../../../src';
import { CantonTransferAcceptRequest } from '../../../../src/lib/iface';

import { TransferAcceptance, TransferAcceptancePrepareResponse } from '../../../resources';

describe('Transfer Acceptance Builder', () => {
it('should get the transfer acceptance request object', function () {
const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton'));
const transferAcceptanceTx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(transferAcceptanceTx);
const { commandId, contractId, partyId } = TransferAcceptance;
txBuilder.commandId(commandId).contractId(contractId).actAs(partyId);
const requestObj: CantonTransferAcceptRequest = txBuilder.toRequestObject();
should.exist(requestObj);
assert.equal(requestObj.commandId, commandId);
assert.equal(requestObj.contractId, contractId);
assert.equal(requestObj.actAs.length, 1);
const actAs = requestObj.actAs[0];
assert.equal(actAs, partyId);
});

it('should validate raw transaction', function () {
const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton'));
const transferAcceptanceTx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(transferAcceptanceTx);
txBuilder.setTransaction(TransferAcceptancePrepareResponse);
txBuilder.validateRawTransaction(TransferAcceptancePrepareResponse.preparedTransaction);
});

it('should validate the transaction', function () {
const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton'));
const transferAcceptanceTx = new Transaction(coins.get('tcanton'));
transferAcceptanceTx.prepareCommand = TransferAcceptancePrepareResponse;
txBuilder.initBuilder(transferAcceptanceTx);
txBuilder.setTransaction(TransferAcceptancePrepareResponse);
txBuilder.validateTransaction(transferAcceptanceTx);
});

it('should throw error in validating raw transaction', function () {
const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton'));
const transferAcceptanceTx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(transferAcceptanceTx);
const invalidPrepareResponse = TransferAcceptancePrepareResponse;
invalidPrepareResponse.preparedTransactionHash = '+vlIXv6Vgd2ypPXD0mrdn7RlcSH4c2hCRj2/tXqqUVs=';
txBuilder.setTransaction(invalidPrepareResponse);
try {
txBuilder.validateRawTransaction(invalidPrepareResponse.preparedTransaction);
} catch (e) {
assert.equal(e.message, 'invalid raw transaction, hash not matching');
}
});

it('should throw error in validating raw transaction', function () {
const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton'));
const oneStepEnablementTx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(oneStepEnablementTx);
const invalidPrepareResponse = TransferAcceptancePrepareResponse;
invalidPrepareResponse.preparedTransactionHash = '+vlIXv6Vgd2ypPXD0mrdn7RlcSH4c2hCRj2/tXqqUVs=';
oneStepEnablementTx.prepareCommand = invalidPrepareResponse;
try {
txBuilder.validateTransaction(oneStepEnablementTx);
} catch (e) {
assert.equal(e.message, 'invalid transaction');
}
});
});
2 changes: 2 additions & 0 deletions modules/sdk-core/src/account-lib/baseCoin/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export enum TransactionType {
FlushERC1155,
// Set up 1-step pre-approval for canton
OneStepPreApproval,
// canton transfer accept, 2-step
TransferAccept,

// trx
FREEZE,
Expand Down