Skip to content

Commit 3ed05a5

Browse files
committed
feat(sdk-coin-canton): added transaction class
Ticket: COIN-5871
1 parent ef0a892 commit 3ed05a5

File tree

6 files changed

+249
-63
lines changed

6 files changed

+249
-63
lines changed

modules/sdk-coin-canton/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"dependencies": {
4343
"@bitgo/sdk-core": "^36.10.1",
4444
"@bitgo/statics": "^58.2.0",
45-
"@canton-network/wallet-sdk": "^0.9.0",
45+
"@canton-network/wallet-sdk": "^0.10.0",
4646
"bignumber.js": "^9.1.1"
4747
},
4848
"devDependencies": {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
1+
import { TransactionType } from '@bitgo/sdk-core';
2+
import { PartyId } from '@canton-network/core-types';
3+
14
/**
25
* The transaction data returned from the toJson() function of a transaction
36
*/
47
export interface TxData {
58
id: string;
9+
type: TransactionType;
10+
sender: string;
11+
receiver: string;
12+
}
13+
14+
export interface PreparedTrxParsedInfo {
15+
sender: string;
16+
receiver: string;
17+
amount: string;
18+
}
19+
20+
export interface WalletInitializationDataTxData {
21+
id: string;
22+
type: TransactionType;
23+
}
24+
25+
export interface CantonPrepareCommandResponse {
26+
preparedTransaction?: string;
27+
preparedTransactionHash: string;
28+
hashingSchemeVersion: string;
29+
hashingDetails?: string;
30+
}
31+
32+
export interface PreparedParty {
33+
partyTransactions: Uint8Array<ArrayBufferLike>[];
34+
combinedHash: string;
35+
txHashes: Buffer<ArrayBuffer>[];
36+
namespace: string;
37+
partyId: PartyId;
638
}
Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,49 @@
1-
import { BaseKey, BaseTransaction } from '@bitgo/sdk-core';
2-
import { TxData } from './iface';
1+
import { BaseKey, BaseTransaction, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
2+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3+
import { CantonPrepareCommandResponse, TxData } from './iface';
4+
import utils from './utils';
35

46
export class Transaction extends BaseTransaction {
7+
private _transaction: CantonPrepareCommandResponse;
8+
9+
constructor(coinConfig: Readonly<CoinConfig>) {
10+
super(coinConfig);
11+
}
12+
13+
get transaction(): CantonPrepareCommandResponse {
14+
return this._transaction;
15+
}
16+
17+
set transaction(transaction: CantonPrepareCommandResponse) {
18+
this._transaction = transaction;
19+
this._id = transaction.preparedTransactionHash;
20+
}
21+
522
canSign(key: BaseKey): boolean {
623
return false;
724
}
825

926
toBroadcastFormat(): string {
10-
throw new Error('Method not implemented.');
27+
if (!this._transaction) {
28+
throw new InvalidTransactionError('Empty transaction data');
29+
}
30+
return this._transaction.preparedTransactionHash;
1131
}
1232

1333
toJson(): TxData {
14-
throw new Error('Method not implemented.');
34+
if (!this._transaction || !this._transaction.preparedTransaction) {
35+
throw new InvalidTransactionError('Empty transaction data');
36+
}
37+
const result: TxData = {
38+
id: this.id,
39+
type: this._type as TransactionType,
40+
sender: '',
41+
receiver: '',
42+
};
43+
// TODO: extract other required data (utxo used, request time, execute before etc)
44+
const parsedInfo = utils.parseRawCantonTransactionData(this._transaction.preparedTransaction);
45+
result.sender = parsedInfo.sender;
46+
result.receiver = parsedInfo.receiver;
47+
return result;
1548
}
1649
}

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { BaseUtils, isValidEd25519PublicKey } from '@bitgo/sdk-core';
2-
import { TopologyController } from '@canton-network/wallet-sdk';
2+
import { RecordField } from '@canton-network/core-ledger-proto';
3+
import { decodePreparedTransaction, TopologyController } from '@canton-network/wallet-sdk';
4+
import { PreparedTrxParsedInfo } from './iface';
35

46
export class Utils implements BaseUtils {
57
/** @inheritdoc */
@@ -40,6 +42,82 @@ export class Utils implements BaseUtils {
4042
getAddressFromPublicKey(publicKey: string): string {
4143
return TopologyController.createFingerprintFromPublicKey(publicKey);
4244
}
45+
46+
/**
47+
* Method to parse raw canton transaction & get required data
48+
* @param {String} rawData base64 encoded string
49+
* @returns {PreparedTrxParsedInfo}
50+
*/
51+
parseRawCantonTransactionData(rawData: string): PreparedTrxParsedInfo {
52+
const decodedData = decodePreparedTransaction(rawData);
53+
let sender = '';
54+
let receiver = '';
55+
let amount = '';
56+
decodedData.transaction?.nodes?.map((node) => {
57+
const versionedNode = node.versionedNode;
58+
if (!versionedNode || versionedNode.oneofKind !== 'v1') return;
59+
60+
const v1Node = versionedNode.v1;
61+
const nodeType = v1Node.nodeType;
62+
63+
if (nodeType.oneofKind !== 'create') return;
64+
65+
const createNode = nodeType.create;
66+
67+
// Check if it's the correct template
68+
const template = createNode.templateId;
69+
if (template?.entityName !== 'AmuletTransferInstruction') return;
70+
71+
// Now parse the 'create' argument
72+
if (createNode.argument?.sum?.oneofKind !== 'record') return;
73+
const fields = createNode.argument?.sum?.record?.fields;
74+
if (!fields) return;
75+
76+
// Find the 'transfer' field
77+
const transferField = fields.find((f) => f.label === 'transfer');
78+
if (transferField?.value?.sum?.oneofKind !== 'record') return;
79+
const transferRecord = transferField?.value?.sum?.record?.fields;
80+
if (!transferRecord) return;
81+
82+
const getField = (fields: RecordField[], label: string) => fields.find((f) => f.label === label)?.value?.sum;
83+
84+
const senderData = getField(transferRecord, 'sender');
85+
if (!senderData || senderData.oneofKind !== 'party') return;
86+
sender = senderData.party;
87+
const receiverData = getField(transferRecord, 'receiver');
88+
if (!receiverData || receiverData.oneofKind !== 'party') return;
89+
receiver = receiverData.party;
90+
const amountData = getField(transferRecord, 'amount');
91+
if (!amountData || amountData.oneofKind !== 'numeric') return;
92+
amount = amountData.numeric;
93+
});
94+
if (!sender || !receiver || !amount) {
95+
throw new Error('invalid transaction data');
96+
}
97+
return {
98+
sender,
99+
receiver,
100+
amount,
101+
};
102+
}
103+
104+
/**
105+
* Method to compute the prepared transaction hash offline
106+
* @param {String} preparedTransaction base64 encoded prepared transaction
107+
* @returns {Promise<string>} the computed hash
108+
*/
109+
async computeHashFromPreparedTransaction(preparedTransaction: string): Promise<string> {
110+
return await TopologyController.createTransactionHash(preparedTransaction);
111+
}
112+
113+
/**
114+
* Method to compute the topology transactions hash offline
115+
* @param {Uint8Array<ArrayBufferLike>[]} preparedTransactions the prepared topology transactions
116+
* @returns {Promise<String>} the computed hash
117+
*/
118+
async computeHashFromTopologyTransaction(preparedTransactions: Uint8Array<ArrayBufferLike>[]): Promise<string> {
119+
return await TopologyController.computeTopologyTxHash(preparedTransactions);
120+
}
43121
}
44122

45123
const utils = new Utils();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { BaseKey, BaseTransaction, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
2+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3+
import { PreparedParty, WalletInitializationDataTxData } from '../iface';
4+
5+
export class Transaction extends BaseTransaction {
6+
private _transaction: PreparedParty;
7+
8+
constructor(coinConfig: Readonly<CoinConfig>) {
9+
super(coinConfig);
10+
}
11+
12+
get transaction(): PreparedParty {
13+
return this._transaction;
14+
}
15+
16+
set transaction(transaction: PreparedParty) {
17+
this._transaction = transaction;
18+
this._id = transaction.combinedHash;
19+
}
20+
21+
canSign(key: BaseKey): boolean {
22+
return false;
23+
}
24+
25+
toBroadcastFormat(): string {
26+
if (!this._transaction) {
27+
throw new InvalidTransactionError('Empty transaction data');
28+
}
29+
return this._transaction.combinedHash;
30+
}
31+
32+
toJson(): WalletInitializationDataTxData {
33+
if (!this._transaction) {
34+
throw new InvalidTransactionError('Empty transaction data');
35+
}
36+
const result: WalletInitializationDataTxData = {
37+
id: this.id,
38+
type: this._type as TransactionType,
39+
};
40+
// Add logic to parse the preparedTransaction & extract sender, receiver
41+
return result;
42+
}
43+
}

0 commit comments

Comments
 (0)