Skip to content

Commit 9854def

Browse files
committed
feat(sdk-coin-canton): forward token and make choiceArgument optional
Ticket: SCAAS-9624
1 parent 142d72d commit 9854def

5 files changed

Lines changed: 106 additions & 3 deletions

File tree

modules/sdk-coin-canton/src/lib/cantonCommandBuilder.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
CantonCommand,
66
CantonCommandResolveContractSpec,
77
} from '@bitgo/sdk-core';
8-
import { BaseCoin as CoinConfig } from '@bitgo/statics';
8+
import { BaseCoin as CoinConfig, coins } from '@bitgo/statics';
99
import { CANTON_COMMAND_KEYS, CantonCommandRequest, CantonPrepareCommandResponse } from './iface';
1010
import { TransactionBuilder } from './transactionBuilder';
1111
import { Transaction } from './transaction/transaction';
@@ -17,6 +17,7 @@ export class CantonCommandBuilder extends TransactionBuilder {
1717
private _readAs: string[] = [];
1818
private _command: CantonCommand;
1919
private _resolveContracts: CantonCommandResolveContractSpec[] = [];
20+
private _token?: string;
2021

2122
constructor(_coinConfig: Readonly<CoinConfig>) {
2223
super(_coinConfig);
@@ -137,6 +138,31 @@ export class CantonCommandBuilder extends TransactionBuilder {
137138
return this;
138139
}
139140

141+
/**
142+
* Sets the Canton token identifier (e.g. 'tcanton:stgusd1') forwarded to IMS for
143+
* choice-context resolution on token-specific commands such as mint and burn.
144+
*
145+
* @param name - Registered BitGo canton token name
146+
* @returns The current builder instance for chaining.
147+
*/
148+
token(name: string): this {
149+
if (typeof name !== 'string' || !name.trim()) {
150+
throw new Error('token must be a non-empty string');
151+
}
152+
const tokenName = name.trim();
153+
let coinConfig: ReturnType<typeof coins.get>;
154+
try {
155+
coinConfig = coins.get(tokenName);
156+
} catch {
157+
throw new Error(`token is not a registered coin: ${tokenName}`);
158+
}
159+
if (coinConfig.family !== 'canton' || !coinConfig.isToken) {
160+
throw new Error(`token must be a registered canton token: ${tokenName}`);
161+
}
162+
this._token = tokenName;
163+
return this;
164+
}
165+
140166
/**
141167
* Builds and returns the CantonCommandRequest from the builder's internal state.
142168
*
@@ -146,13 +172,17 @@ export class CantonCommandBuilder extends TransactionBuilder {
146172
toRequestObject(): CantonCommandRequest {
147173
this.validate();
148174

149-
return {
175+
const req: CantonCommandRequest = {
150176
commandId: this._commandId,
151177
actAs: this._actAs,
152178
readAs: this._readAs ?? [],
153179
command: this._command,
154180
resolveContracts: this._resolveContracts ?? [],
155181
};
182+
if (this._token) {
183+
req.token = this._token;
184+
}
185+
return req;
156186
}
157187

158188
private validate(): void {

modules/sdk-coin-canton/src/lib/iface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export interface CantonCommandRequest {
231231
readAs?: string[];
232232
command: CantonCommand;
233233
resolveContracts?: CantonCommandResolveContractSpec[];
234+
token?: string;
234235
}
235236

236237
// Root command decoded from the prepared Canton transaction protobuf, used during verifyTransaction.

modules/sdk-coin-canton/test/unit/builder/cantonCommand/cantonCommandBuilder.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,59 @@ describe('CantonCommandBuilder', () => {
138138
});
139139
});
140140

141+
describe('token()', () => {
142+
it('should set the token', function () {
143+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
144+
const tx = new Transaction(coins.get('tcanton'));
145+
builder.initBuilder(tx);
146+
builder.commandId('cmd-tok-1').actAs([PARTY_A]).command(sampleExerciseCommand).token('tcanton:testtoken');
147+
assert.equal(builder.toRequestObject().token, 'tcanton:testtoken');
148+
});
149+
150+
it('should trim whitespace', function () {
151+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
152+
const tx = new Transaction(coins.get('tcanton'));
153+
builder.initBuilder(tx);
154+
builder.commandId('cmd-tok-2').actAs([PARTY_A]).command(sampleExerciseCommand).token(' tcanton:testtoken ');
155+
assert.equal(builder.toRequestObject().token, 'tcanton:testtoken');
156+
});
157+
158+
it('should throw on empty string', function () {
159+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
160+
const tx = new Transaction(coins.get('tcanton'));
161+
builder.initBuilder(tx);
162+
assert.throws(() => builder.token(''), /token must be a non-empty string/);
163+
});
164+
165+
it('should throw on whitespace-only string', function () {
166+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
167+
const tx = new Transaction(coins.get('tcanton'));
168+
builder.initBuilder(tx);
169+
assert.throws(() => builder.token(' '), /token must be a non-empty string/);
170+
});
171+
172+
it('should throw on an unregistered coin name', function () {
173+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
174+
const tx = new Transaction(coins.get('tcanton'));
175+
builder.initBuilder(tx);
176+
assert.throws(() => builder.token('tcanton:fakecoin'), /token is not a registered coin/);
177+
});
178+
179+
it('should throw when token is not a canton family token', function () {
180+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
181+
const tx = new Transaction(coins.get('tcanton'));
182+
builder.initBuilder(tx);
183+
assert.throws(() => builder.token('eth'), /token must be a registered canton token/);
184+
});
185+
186+
it('should throw when token is the base canton coin (not a token)', function () {
187+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
188+
const tx = new Transaction(coins.get('tcanton'));
189+
builder.initBuilder(tx);
190+
assert.throws(() => builder.token('tcanton'), /token must be a registered canton token/);
191+
});
192+
});
193+
141194
describe('resolveContracts()', () => {
142195
it('should set the spec array', function () {
143196
const spec = [{ templateId: TEMPLATE_ID, actAs: [PARTY_A], injectAs: 'command.ExerciseCommand.contractId' }];
@@ -206,6 +259,24 @@ describe('CantonCommandBuilder', () => {
206259
const req = builder.toRequestObject();
207260
assert.deepEqual(req.resolveContracts, []);
208261
});
262+
263+
it('should include token when set', function () {
264+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
265+
const tx = new Transaction(coins.get('tcanton'));
266+
builder.initBuilder(tx);
267+
builder.commandId('cmd-003').actAs([PARTY_A]).command(sampleExerciseCommand).token('tcanton:testtoken');
268+
const req = builder.toRequestObject();
269+
assert.equal(req.token, 'tcanton:testtoken');
270+
});
271+
272+
it('should not include token key when not set', function () {
273+
const builder = new CantonCommandBuilder(coins.get('tcanton'));
274+
const tx = new Transaction(coins.get('tcanton'));
275+
builder.initBuilder(tx);
276+
builder.commandId('cmd-004').actAs([PARTY_A]).command(sampleExerciseCommand);
277+
const req = builder.toRequestObject();
278+
assert.ok(!('token' in req));
279+
});
209280
});
210281

211282
describe('initBuilder()', () => {

modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export interface CantonExerciseCommand {
9393
templateId: string;
9494
contractId?: string;
9595
choice: string;
96-
choiceArgument: Record<string, unknown>;
96+
choiceArgument?: Record<string, unknown>;
9797
};
9898
}
9999

modules/sdk-core/src/bitgo/wallet/wallet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4301,6 +4301,7 @@ export class Wallet implements IWallet {
43014301
reqId,
43024302
intentType: 'cantonCommand',
43034303
cantonCommandParams: params.cantonCommandParams,
4304+
tokenName: params.tokenName,
43044305
sequenceId: params.sequenceId,
43054306
comment: params.comment,
43064307
},

0 commit comments

Comments
 (0)