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
45 changes: 44 additions & 1 deletion modules/sdk-coin-canton/src/canton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
import { TransactionBuilderFactory } from './lib';
import { KeyPair as CantonKeyPair } from './lib/keyPair';
import { CantonCommandKind, TxData } from './lib/iface';
import { BroadridgeCommandParams, CantonCommandKind, TxData } from './lib/iface';
import { broadridgeDescriptorFromCommand } from './lib/broadridge';
import { Transaction } from './lib/transaction/transaction';
import utils from './lib/utils';

Expand All @@ -45,6 +46,7 @@ export interface ExplainTransactionOptions {

export interface CantonTransactionParams extends TransactionParams {
cantonCommandParams?: CantonCommandParams;
broadridgeCommandParams?: BroadridgeCommandParams;
}

export class Canton extends BaseCoin {
Expand Down Expand Up @@ -123,6 +125,10 @@ export class Canton extends BaseCoin {
const txBuilder = new TransactionBuilderFactory(coinConfig).from(rawTx);
const transaction = txBuilder.transaction;
const explainedTx = transaction.explainTransaction();
const broadridgeParams = (txParams as CantonTransactionParams).broadridgeCommandParams;
if (broadridgeParams) {
return this.verifyBroadridgeCommandTransaction(transaction, broadridgeParams);
}
switch (transaction.type) {
case TransactionType.WalletInitialization:
case TransactionType.TransferAccept:
Expand Down Expand Up @@ -237,6 +243,43 @@ export class Canton extends BaseCoin {
}
}
}
private verifyBroadridgeCommandTransaction(transaction: BaseTransaction, params: BroadridgeCommandParams): boolean {
const rawPrepared = (transaction as Transaction).prepareCommand?.preparedTransaction;
if (!rawPrepared) {
throw new Error('Broadridge verifyTransaction: missing preparedTransaction protobuf');
}
const actual = utils.extractCantonCommandInfo(rawPrepared);
const actualDescriptor = broadridgeDescriptorFromCommand(actual);
if (!actualDescriptor) {
throw new Error('Broadridge verifyTransaction: prepared command is not a supported Broadridge template');
}
const expected = params.descriptor;
const expectedTemplate = utils.parseCantonTemplateId(expected.templateId);
if (!expectedTemplate || expected.protocol !== 'broadridge') {
throw new Error('Broadridge verifyTransaction: invalid protocol descriptor');
}
if (
expected.standard !== actualDescriptor.standard ||
expectedTemplate.pkg !== actualDescriptor.packageId ||
expectedTemplate.moduleName !== actualDescriptor.moduleName ||
expectedTemplate.entityName !== actualDescriptor.entityName ||
expected.choice !== actualDescriptor.choice ||
expected.role !== actualDescriptor.role
) {
throw new Error('Broadridge verifyTransaction: template, package, protocol, choice, or role mismatch');
}
if (!utils.sameElements(expected.actors, actualDescriptor.actors)) {
throw new Error('Broadridge verifyTransaction: actor set mismatch');
}
const expectedContractId = expected.contractId ?? expected.requestCid ?? expected.agreementCid;
if (expectedContractId !== undefined && expectedContractId !== actual.contractId) {
throw new Error('Broadridge verifyTransaction: contract/request/agreement CID mismatch');
}
if (params.argument !== undefined) {
utils.assertDeepCantonMatch(params.argument, actual.argument, new Set());
}
return true;
}

private verifyCantonCommandTransaction(
transaction: BaseTransaction,
Expand Down
27 changes: 13 additions & 14 deletions modules/sdk-coin-canton/src/lib/allocationRequestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { Transaction } from './transaction/transaction';
import { normalizeCantonAmount } from './amount';

/**
* Builder for an AllocationRequest txRequest — an internal, non-signable transaction
* that surfaces the DvP trade leg details to the allocating party. The party reviews
* this request and then submits a separate AllocationAllocate to lock their asset.
* Builder for an AllocationRequest txRequest — an internal request that surfaces the DvP trade
* leg details to the allocating party. The party reviews this request and then submits a
* separate AllocationAllocate to lock their asset.
*
* setTransaction and addSignature are intentionally not implemented because this
* transaction type is never signed or broadcast directly.
* Allocation requests are not normally signed or broadcast as standalone transactions, but the
* builder still supports the standard prepared-transaction and signature hooks for transaction
* builder compatibility.
*/
export class AllocationRequestBuilder extends TransactionBuilder {
private _updateId: string;
Expand Down Expand Up @@ -49,12 +50,15 @@ export class AllocationRequestBuilder extends TransactionBuilder {
}

setTransaction(transaction: CantonPrepareCommandResponse): void {
throw new Error('Not implemented!');
this.transaction.prepareCommand = transaction;
}

/** @inheritDoc */
addSignature(publicKey: PublicKey, signature: Buffer): void {
throw new Error('Not implemented!');
if (!this.transaction) {
throw new Error('transaction is empty!');
}
this._signatures.push({ publicKey, signature });
}

/**
Expand Down Expand Up @@ -294,13 +298,8 @@ export class AllocationRequestBuilder extends TransactionBuilder {
if (!this._receiverPartyId) throw new Error('receiverPartyId is missing');
if (this._amount === undefined || this._amount === null) throw new Error('amount is missing');
if (!this._token) throw new Error('token is missing');
if (this._legType !== 'fee') {
// Trade legs (explicit or legacy) always carry the receive side of the settlement;
// fee legs are payment-only — the fee payer receives nothing — so the receive fields are omitted.
if (!this._receiveToken) throw new Error('receiveToken is missing');
if (this._receiveAmount === undefined || this._receiveAmount === null)
throw new Error('receiveAmount is missing');
}
// CIP-56 payment-only requests legitimately have no receive leg. Keep receive
// fields optional rather than inventing a counter-leg for the wallet.
if (!this._allocateBefore) throw new Error('allocateBefore is missing');
if (!this._settleBefore) throw new Error('settleBefore is missing');
}
Expand Down
Loading
Loading