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
2 changes: 1 addition & 1 deletion modules/sdk-coin-canton/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface CantonOneStepEnablementRequest extends CantonPrepareCommandRequ
receiverId: string;
}

export interface CantonTransferAcceptRequest extends CantonPrepareCommandRequest {
export interface CantonTransferAcceptRejectRequest extends CantonPrepareCommandRequest {
contractId: string;
}

Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-canton/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
export { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder';
export { TransactionBuilder } from './transactionBuilder';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { TransferRejectionBuilder } from './transferRejectionBuilder';
export { WalletInitBuilder } from './walletInitBuilder';
export { WalletInitTransaction } from './walletInitialization/walletInitTransaction';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
import { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder';
import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { TransferRejectionBuilder } from './transferRejectionBuilder';
import { Transaction } from './transaction/transaction';
import { WalletInitBuilder } from './walletInitBuilder';
import { WalletInitTransaction } from './walletInitialization/walletInitTransaction';
Expand Down Expand Up @@ -36,6 +37,9 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
case TransactionType.TransferAcknowledge: {
return this.getTransferAcknowledgeBuilder(tx);
}
case TransactionType.TransferReject: {
return this.getTransferRejectBuilder(tx);
}
default: {
throw new InvalidTransactionError('unsupported transaction');
}
Expand All @@ -51,6 +55,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferAcknowledgeBuilder(this._coinConfig));
}

getTransferRejectBuilder(tx?: Transaction): TransferRejectionBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferRejectionBuilder(this._coinConfig));
}

/** @inheritdoc */
getTransferBuilder(tx?: Transaction): TransferBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferBuilder(this._coinConfig));
Expand Down
14 changes: 7 additions & 7 deletions modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, CantonTransferAcceptRequest } from './iface';
import { CantonPrepareCommandResponse, CantonTransferAcceptRejectRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';
import utils from './utils';
Expand Down Expand Up @@ -50,7 +50,7 @@ export class TransferAcceptanceBuilder extends TransactionBuilder {
* @throws Error if id is empty.
*/
commandId(id: string): this {
if (!id.trim()) {
if (!id || !id.trim()) {
throw new Error('commandId must be a non-empty string');
}
this._commandId = id.trim();
Expand All @@ -66,7 +66,7 @@ export class TransferAcceptanceBuilder extends TransactionBuilder {
* @throws Error if id is empty.
*/
contractId(id: string): this {
if (!id.trim()) {
if (!id || !id.trim()) {
throw new Error('contractId must be a non-empty string');
}
this._contractId = id.trim();
Expand All @@ -81,23 +81,23 @@ export class TransferAcceptanceBuilder extends TransactionBuilder {
* @throws Error if id is empty.
*/
actAs(id: string): this {
if (!id.trim()) {
if (!id || !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.
* Builds and returns the CantonTransferAcceptRejectRequest 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.
* @returns {CantonTransferAcceptRejectRequest} - A fully constructed and validated request object for transfer acceptance.
* @throws {Error} If any required field is missing or fails validation.
*/
toRequestObject(): CantonTransferAcceptRequest {
toRequestObject(): CantonTransferAcceptRejectRequest {
this.validate();

return {
Expand Down
123 changes: 123 additions & 0 deletions modules/sdk-coin-canton/src/lib/transferRejectionBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, CantonTransferAcceptRejectRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';
import utils from './utils';

export class TransferRejectionBuilder 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.TransferReject;
}

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

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

/** @inheritDoc */
addSignature(publicKey: PublicKey, signature: Buffer): void {
if (!this.transaction) {
throw new InvalidTransactionError('transaction is empty!');
}
this._signatures.push({ publicKey, signature });
const pubKeyBase64 = utils.getBase64FromHex(publicKey.pub);
this.transaction.signerFingerprint = utils.getAddressFromPublicKey(pubKeyBase64);
this.transaction.signatures = signature.toString('base64');
}

/**
* Sets the unique id for the transfer rejection
* 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 || !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 rejection contract id the receiver needs to accept
* @param id - canton rejection contract id
* @returns The current builder instance for chaining.
* @throws Error if id is empty.
*/
contractId(id: string): this {
if (!id || !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 || !id.trim()) {
throw new Error('actAsPartyId must be a non-empty string');
}
this._actAsPartyId = id.trim();
return this;
}

/**
* Builds and returns the CantonTransferAcceptRejectRequest 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 {CantonTransferAcceptRejectRequest} - A fully constructed and validated request object for transfer acceptance.
* @throws {Error} If any required field is missing or fails validation.
*/
toRequestObject(): CantonTransferAcceptRejectRequest {
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 { TransferAcceptanceBuilder, Transaction } from '../../../../src';
import { CantonTransferAcceptRequest } from '../../../../src/lib/iface';
import { CantonTransferAcceptRejectRequest } from '../../../../src/lib/iface';

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

Expand All @@ -15,7 +15,7 @@ describe('Transfer Acceptance Builder', () => {
txBuilder.initBuilder(transferAcceptanceTx);
const { commandId, contractId, partyId } = TransferAcceptance;
txBuilder.commandId(commandId).contractId(contractId).actAs(partyId);
const requestObj: CantonTransferAcceptRequest = txBuilder.toRequestObject();
const requestObj: CantonTransferAcceptRejectRequest = txBuilder.toRequestObject();
should.exist(requestObj);
assert.equal(requestObj.commandId, commandId);
assert.equal(requestObj.contractId, contractId);
Expand Down Expand Up @@ -54,18 +54,4 @@ describe('Transfer Acceptance Builder', () => {
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');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import assert from 'assert';
import should from 'should';

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

import { Transaction, TransferRejectionBuilder } from '../../../../src';
import { CantonTransferAcceptRejectRequest } from '../../../../src/lib/iface';

import { TransferRejection, TransferRejectionPrepareResponse } from '../../../resources';

describe('Transfer Rejection Builder', () => {
it('should get the transfer rejection request object', function () {
const txBuilder = new TransferRejectionBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(tx);
const { commandId, contractId, partyId } = TransferRejection;
txBuilder.commandId(commandId).contractId(contractId).actAs(partyId);
const requestObj: CantonTransferAcceptRejectRequest = 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 TransferRejectionBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(tx);
txBuilder.setTransaction(TransferRejectionPrepareResponse);
txBuilder.validateRawTransaction(TransferRejectionPrepareResponse.preparedTransaction);
});

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

it('should throw error in validating raw transaction', function () {
const txBuilder = new TransferRejectionBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
txBuilder.initBuilder(tx);
const invalidPrepareResponse = TransferRejectionPrepareResponse;
invalidPrepareResponse.preparedTransactionHash = 'QFxX1WBdq7lZbSc45iKA3J/oOF9mrVLc3DeKphAjb15=';
txBuilder.setTransaction(invalidPrepareResponse);
try {
txBuilder.validateRawTransaction(invalidPrepareResponse.preparedTransaction);
} catch (e) {
assert.equal(e.message, 'invalid raw transaction, hash not matching');
}
});
});
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 @@ -93,6 +93,8 @@ export enum TransactionType {
TransferAccept,
// canton transfer acknowledgement
TransferAcknowledge,
// canton transfer reject, 2-step
TransferReject,

// trx
FREEZE,
Expand Down