Skip to content

Commit 0c1af36

Browse files
committed
fix: canton raw prepared transaction parsing
Ticket: COIN-6482
1 parent c75db75 commit 0c1af36

File tree

3 files changed

+98
-47
lines changed

3 files changed

+98
-47
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class Transaction extends BaseTransaction {
150150
// TODO: extract other required data (utxo used, request time, execute before etc)
151151
let parsedInfo: PreparedTxnParsedInfo;
152152
try {
153-
parsedInfo = utils.parseRawCantonTransactionData(this._prepareCommand.preparedTransaction);
153+
parsedInfo = utils.parseRawCantonTransactionData(this._prepareCommand.preparedTransaction, this.type);
154154
} catch (e) {
155155
throw new InvalidTransactionError(`Failed to parse transaction hash: ${e instanceof Error ? e.message : e}`);
156156
}

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

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import BigNumber from 'bignumber.js';
22
import crypto from 'crypto';
33

4-
import { BaseUtils, isValidEd25519PublicKey } from '@bitgo/sdk-core';
4+
import { BaseUtils, isValidEd25519PublicKey, TransactionType } from '@bitgo/sdk-core';
55

66
import { computePreparedTransaction } from '../../resources/hash/hash.js';
77
import { PreparedTransaction } from '../../resources/proto/preparedTransaction.js';
@@ -79,66 +79,92 @@ export class Utils implements BaseUtils {
7979
/**
8080
* Method to parse raw canton transaction & get required data
8181
* @param {String} rawData base64 encoded string
82+
* @param {TransactionType} txType the transaction type
8283
* @returns {PreparedTxnParsedInfo}
8384
*/
84-
parseRawCantonTransactionData(rawData: string): PreparedTxnParsedInfo {
85+
parseRawCantonTransactionData(rawData: string, txType: TransactionType): PreparedTxnParsedInfo {
8586
const decodedData = this.decodePreparedTransaction(rawData);
8687
let sender = '';
8788
let receiver = '';
8889
let amount = '';
89-
decodedData.transaction?.nodes?.forEach((node) => {
90+
let preApprovalNode: RecordField[] = [];
91+
let transferNode: RecordField[] = [];
92+
let transferAcceptRejectNode: RecordField[] = [];
93+
const nodes = decodedData.transaction?.nodes;
94+
95+
nodes?.forEach((node) => {
9096
const versionedNode = node.versionedNode;
9197
if (!versionedNode || versionedNode.oneofKind !== 'v1') return;
92-
9398
const v1Node = versionedNode.v1;
9499
const nodeType = v1Node.nodeType;
95-
96100
if (nodeType.oneofKind !== 'create') return;
97-
98101
const createNode = nodeType.create;
99-
100-
const getField = (fields: RecordField[], label: string) => fields.find((f) => f.label === label)?.value?.sum;
101-
102-
// Check if it's the correct template
103102
const template = createNode.templateId;
104103
const argSum = createNode.argument?.sum;
105104
if (!argSum || argSum.oneofKind !== 'record') return;
106105
const fields = argSum.record?.fields;
107106
if (!fields) return;
108-
if (template?.entityName === 'AmuletTransferInstruction') {
109-
const transferField = fields.find((f) => f.label === 'transfer');
110-
const transferSum = transferField?.value?.sum;
111-
if (!transferSum || transferSum.oneofKind !== 'record') return;
107+
if (
108+
template?.entityName === 'TransferPreapprovalProposal' &&
109+
!preApprovalNode.length &&
110+
txType === TransactionType.OneStepPreApproval
111+
) {
112+
preApprovalNode = fields;
113+
}
114+
if (
115+
template?.entityName === 'Amulet' &&
116+
!transferAcceptRejectNode.length &&
117+
(txType === TransactionType.TransferAccept || txType === TransactionType.TransferReject)
118+
) {
119+
transferAcceptRejectNode = fields;
120+
}
121+
if (
122+
template?.entityName === 'AmuletTransferInstruction' &&
123+
!transferNode.length &&
124+
txType === TransactionType.Send
125+
) {
126+
transferNode = fields;
127+
}
128+
});
129+
130+
const getField = (fields: RecordField[], label: string) => fields.find((f) => f.label === label)?.value?.sum;
131+
132+
if (preApprovalNode.length) {
133+
const receiverData = getField(preApprovalNode, 'receiver');
134+
if (receiverData?.oneofKind === 'party') receiver = receiverData.party ?? '';
135+
const providerData = getField(preApprovalNode, 'provider');
136+
if (providerData?.oneofKind === 'party') sender = providerData.party ?? '';
137+
amount = '0';
138+
} else if (transferNode.length) {
139+
const transferField = transferNode.find((f) => f.label === 'transfer');
140+
const transferSum = transferField?.value?.sum;
141+
if (transferSum && transferSum.oneofKind === 'record') {
112142
const transferRecord = transferSum.record?.fields;
113-
if (!transferRecord) return;
114-
const senderData = getField(transferRecord, 'sender');
115-
if (senderData?.oneofKind === 'party') sender = senderData.party ?? '';
116-
117-
const receiverData = getField(transferRecord, 'receiver');
118-
if (receiverData?.oneofKind === 'party') receiver = receiverData.party ?? '';
119-
120-
const amountData = getField(transferRecord, 'amount');
121-
if (amountData?.oneofKind === 'numeric') amount = amountData.numeric ?? '';
122-
} else if (template?.entityName === 'Amulet') {
123-
const dsoData = getField(fields, 'dso');
124-
if (dsoData?.oneofKind === 'party') sender = dsoData.party ?? '';
125-
const ownerData = getField(fields, 'owner');
126-
if (ownerData?.oneofKind === 'party') receiver = ownerData.party ?? '';
127-
const amountField = getField(fields, 'amount');
128-
if (!amountField || amountField.oneofKind !== 'record') return;
143+
if (transferRecord?.length) {
144+
const senderData = getField(transferRecord, 'sender');
145+
if (senderData?.oneofKind === 'party') sender = senderData.party ?? '';
146+
147+
const receiverData = getField(transferRecord, 'receiver');
148+
if (receiverData?.oneofKind === 'party') receiver = receiverData.party ?? '';
129149

150+
const amountData = getField(transferRecord, 'amount');
151+
if (amountData?.oneofKind === 'numeric') amount = amountData.numeric ?? '';
152+
}
153+
}
154+
} else if (transferAcceptRejectNode.length) {
155+
const dsoData = getField(transferAcceptRejectNode, 'dso');
156+
if (dsoData?.oneofKind === 'party') sender = dsoData.party ?? '';
157+
const ownerData = getField(transferAcceptRejectNode, 'owner');
158+
if (ownerData?.oneofKind === 'party') receiver = ownerData.party ?? '';
159+
const amountField = getField(transferAcceptRejectNode, 'amount');
160+
if (amountField && amountField.oneofKind === 'record') {
130161
const amountRecord = amountField.record?.fields;
131-
if (!amountRecord) return;
132-
const initialAmountData = getField(amountRecord, 'initialAmount');
133-
if (initialAmountData?.oneofKind === 'numeric') amount = initialAmountData.numeric ?? '';
134-
} else if (template?.entityName === 'TransferPreapprovalProposal') {
135-
const receiverData = getField(fields, 'receiver');
136-
if (receiverData?.oneofKind === 'party') receiver = receiverData.party ?? '';
137-
const providerData = getField(fields, 'provider');
138-
if (providerData?.oneofKind === 'party') sender = providerData.party ?? '';
139-
amount = '0';
162+
if (amountRecord?.length) {
163+
const initialAmountData = getField(amountRecord, 'initialAmount');
164+
if (initialAmountData?.oneofKind === 'numeric') amount = initialAmountData.numeric ?? '';
165+
}
140166
}
141-
});
167+
}
142168
if (!sender || !receiver || !amount) {
143169
const missingFields: string[] = [];
144170
if (!sender) missingFields.push('sender');

modules/sdk-coin-canton/test/unit/utils.ts

Lines changed: 31 additions & 6 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)