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
22 changes: 21 additions & 1 deletion modules/sdk-coin-canton/src/lib/cantonCommandBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class CantonCommandBuilder extends TransactionBuilder {
private _readAs: string[] = [];
private _command: CantonCommand;
private _resolveContracts: CantonCommandResolveContractSpec[] = [];
private _token?: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
Expand Down Expand Up @@ -137,6 +138,21 @@ export class CantonCommandBuilder extends TransactionBuilder {
return this;
}

/**
* Sets the Canton token identifier (e.g. 'tcanton:stgusd1') forwarded to IMS for
* choice-context resolution on token-specific commands such as mint and burn.
*
* @param name - Registered BitGo canton token name
* @returns The current builder instance for chaining.
*/
token(name: string): this {
if (typeof name !== 'string' || !name.trim()) {
throw new Error('token must be a non-empty string');
}
this._token = name.trim();
return this;
}

/**
* Builds and returns the CantonCommandRequest from the builder's internal state.
*
Expand All @@ -146,13 +162,17 @@ export class CantonCommandBuilder extends TransactionBuilder {
toRequestObject(): CantonCommandRequest {
this.validate();

return {
const req: CantonCommandRequest = {
commandId: this._commandId,
actAs: this._actAs,
readAs: this._readAs ?? [],
command: this._command,
resolveContracts: this._resolveContracts ?? [],
};
if (this._token) {
req.token = this._token;
}
return req;
}

private validate(): void {
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-canton/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export interface CantonCommandRequest {
readAs?: string[];
command: CantonCommand;
resolveContracts?: CantonCommandResolveContractSpec[];
token?: string;
}

// Root command decoded from the prepared Canton transaction protobuf, used during verifyTransaction.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,38 @@ describe('CantonCommandBuilder', () => {
});
});

describe('token()', () => {
it('should set the token', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
builder.commandId('cmd-tok-1').actAs([PARTY_A]).command(sampleExerciseCommand).token('tcanton:testtoken');
assert.equal(builder.toRequestObject().token, 'tcanton:testtoken');
});

it('should trim whitespace', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
builder.commandId('cmd-tok-2').actAs([PARTY_A]).command(sampleExerciseCommand).token(' tcanton:testtoken ');
assert.equal(builder.toRequestObject().token, 'tcanton:testtoken');
});

it('should throw on empty string', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
assert.throws(() => builder.token(''), /token must be a non-empty string/);
});

it('should throw on whitespace-only string', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
assert.throws(() => builder.token(' '), /token must be a non-empty string/);
});
});

describe('resolveContracts()', () => {
it('should set the spec array', function () {
const spec = [{ templateId: TEMPLATE_ID, actAs: [PARTY_A], injectAs: 'command.ExerciseCommand.contractId' }];
Expand Down Expand Up @@ -206,6 +238,24 @@ describe('CantonCommandBuilder', () => {
const req = builder.toRequestObject();
assert.deepEqual(req.resolveContracts, []);
});

it('should include token when set', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
builder.commandId('cmd-003').actAs([PARTY_A]).command(sampleExerciseCommand).token('tcanton:testtoken');
const req = builder.toRequestObject();
assert.equal(req.token, 'tcanton:testtoken');
});

it('should not include token key when not set', function () {
const builder = new CantonCommandBuilder(coins.get('tcanton'));
const tx = new Transaction(coins.get('tcanton'));
builder.initBuilder(tx);
builder.commandId('cmd-004').actAs([PARTY_A]).command(sampleExerciseCommand);
const req = builder.toRequestObject();
assert.ok(!('token' in req));
});
});

describe('initBuilder()', () => {
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface CantonExerciseCommand {
templateId: string;
contractId?: string;
choice: string;
choiceArgument: Record<string, unknown>;
choiceArgument?: Record<string, unknown>;
};
}

Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4301,6 +4301,7 @@ export class Wallet implements IWallet {
reqId,
intentType: 'cantonCommand',
cantonCommandParams: params.cantonCommandParams,
tokenName: params.tokenName,
sequenceId: params.sequenceId,
Comment thread
abhijeet848 marked this conversation as resolved.
comment: params.comment,
},
Expand Down
Loading