Skip to content

Commit 0621452

Browse files
committed
fix(sdk-coin-canton): correct calculation of balance
Ticket: BG-123
1 parent ed4835c commit 0621452

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import * as querystring from 'querystring';
2828
import { TransactionBuilderFactory } from './lib';
2929
import { KeyPair as CantonKeyPair } from './lib/keyPair';
3030
import utils from './lib/utils';
31+
import { WalletInitBroadcastData, TransactionBroadcastData } from './lib/iface';
3132

3233
export interface TransactionExplanation extends BaseTransactionExplanation {
3334
type: TransactionType;
@@ -102,6 +103,108 @@ export class Canton extends BaseCoin {
102103
return 'eddsa';
103104
}
104105

106+
/**
107+
* Returns the extended payload that needs to be signed for Canton EdDSA operations.
108+
* Canton requires signing a structured payload containing transaction metadata and signable hash,
109+
* not just the serialized transaction.
110+
*
111+
* @param {string} serializedTx - the unsigned transaction in broadcast format (base64)
112+
* @returns {Promise<Buffer>} - the extended payload (topology + hash, or prepared tx + hash)
113+
*/
114+
async getSignablePayload(serializedTx: string): Promise<Buffer> {
115+
try {
116+
// Decode the serialized transaction
117+
const decoded = JSON.parse(Buffer.from(serializedTx, 'base64').toString('utf8')) as
118+
| WalletInitBroadcastData
119+
| TransactionBroadcastData;
120+
121+
// Extract the signable payload (preparedTransactionHash in base64 format)
122+
let signableHex = '';
123+
if ('prepareCommandResponse' in decoded && decoded.prepareCommandResponse) {
124+
signableHex = Buffer.from(decoded.prepareCommandResponse.preparedTransactionHash, 'base64').toString('hex');
125+
} else {
126+
// Fallback: if unable to extract, return empty buffer
127+
signableHex = '';
128+
}
129+
130+
// Build extended payload based on transaction type
131+
if ('preparedParty' in decoded && decoded.preparedParty && decoded.preparedParty.topologyTransactions) {
132+
// WalletInitBuilder format: [txnType] || itemCount || [lenOfTx || tx]... || signableHex
133+
return this.buildWalletInitPayload(decoded as WalletInitBroadcastData, signableHex);
134+
}
135+
136+
// TransactionBuilder format: itemCount || lenOfTx || preparedTransaction || signableHex
137+
if ('prepareCommandResponse' in decoded && decoded.prepareCommandResponse?.preparedTransaction) {
138+
return this.buildTransactionPayload(decoded as TransactionBroadcastData, signableHex);
139+
}
140+
141+
// Fallback: return signableHex only if no extended format detected
142+
return Buffer.from(signableHex, 'hex');
143+
} catch (e) {
144+
// If parsing fails, fall back to base implementation
145+
return Buffer.from(serializedTx);
146+
}
147+
}
148+
149+
/**
150+
* Build WalletInitBuilder extended payload format.
151+
* Format: [txnType (optional)] || itemCount (4 bytes LE) || [lenOfTx (4 bytes LE) || tx]... || signableHex
152+
*/
153+
private buildWalletInitPayload(decoded: WalletInitBroadcastData, signableHex: string): Buffer {
154+
const shouldIncludeTxnType = decoded.preparedParty.shouldIncludeTxnType ?? false;
155+
const topologyTransactions = decoded.preparedParty.topologyTransactions;
156+
const itemCount = topologyTransactions.length + 1;
157+
158+
const parts: Buffer[] = [];
159+
160+
// Add txnType if required (version >0.5.x)
161+
if (shouldIncludeTxnType) {
162+
const txnTypeBuff = Buffer.alloc(4);
163+
txnTypeBuff.writeUInt32LE(0, 0);
164+
parts.push(txnTypeBuff);
165+
}
166+
167+
// Add item count
168+
const itemCountBuff = Buffer.alloc(4);
169+
itemCountBuff.writeUInt32LE(itemCount, 0);
170+
parts.push(itemCountBuff);
171+
172+
// Add topology transactions with length prefixes
173+
for (const tx of topologyTransactions) {
174+
const txBuffer = Buffer.from(tx, 'base64');
175+
const lenBuff = Buffer.alloc(4);
176+
lenBuff.writeUInt32LE(txBuffer.length, 0);
177+
parts.push(lenBuff, txBuffer);
178+
}
179+
180+
// Add signable hash
181+
parts.push(Buffer.from(signableHex, 'hex'));
182+
183+
return Buffer.concat(parts);
184+
}
185+
186+
/**
187+
* Build TransactionBuilder extended payload format.
188+
* Format: itemCount (4 bytes LE) || lenOfTx (4 bytes LE) || preparedTransaction || signableHex
189+
*/
190+
private buildTransactionPayload(decoded: TransactionBroadcastData, signableHex: string): Buffer {
191+
const preparedTx = decoded.prepareCommandResponse?.preparedTransaction;
192+
if (!preparedTx) {
193+
return Buffer.from(signableHex, 'hex');
194+
}
195+
196+
const preparedTxBuffer = Buffer.from(preparedTx, 'base64');
197+
const itemCount = 2; // prepared transaction & signable payload
198+
199+
const itemCountBuff = Buffer.alloc(4);
200+
itemCountBuff.writeUInt32LE(itemCount, 0);
201+
202+
const lenBuff = Buffer.alloc(4);
203+
lenBuff.writeUInt32LE(preparedTxBuffer.length, 0);
204+
205+
return Buffer.concat([itemCountBuff, lenBuff, preparedTxBuffer, Buffer.from(signableHex, 'hex')]);
206+
}
207+
105208
/** @inheritDoc */
106209
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
107210
const coinConfig = coins.get(this.getChain());

0 commit comments

Comments
 (0)