@ledgerhq/coin-canton 0.9.0-nightly.1 → 0.9.0-nightly.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +75 -0
  4. package/lib/bridge/deviceTransactionConfig.d.ts +1 -1
  5. package/lib/bridge/deviceTransactionConfig.d.ts.map +1 -1
  6. package/lib/bridge/deviceTransactionConfig.js +1 -1
  7. package/lib/bridge/deviceTransactionConfig.js.map +1 -1
  8. package/lib/bridge/onboard.d.ts.map +1 -1
  9. package/lib/bridge/onboard.js +48 -22
  10. package/lib/bridge/onboard.js.map +1 -1
  11. package/lib/bridge/signOperation.d.ts.map +1 -1
  12. package/lib/bridge/signOperation.js +10 -3
  13. package/lib/bridge/signOperation.js.map +1 -1
  14. package/lib/common-logic/index.d.ts +1 -0
  15. package/lib/common-logic/index.d.ts.map +1 -1
  16. package/lib/common-logic/index.js +3 -1
  17. package/lib/common-logic/index.js.map +1 -1
  18. package/lib/common-logic/transaction/sign.d.ts +8 -0
  19. package/lib/common-logic/transaction/sign.d.ts.map +1 -0
  20. package/lib/common-logic/transaction/sign.js +27 -0
  21. package/lib/common-logic/transaction/sign.js.map +1 -0
  22. package/lib/common-logic/transaction/split.d.ts +9 -0
  23. package/lib/common-logic/transaction/split.d.ts.map +1 -0
  24. package/lib/common-logic/transaction/split.js +119 -0
  25. package/lib/common-logic/transaction/split.js.map +1 -0
  26. package/lib/common-logic/utils.js +2 -2
  27. package/lib/common-logic/utils.js.map +1 -1
  28. package/lib/network/gateway.d.ts +1 -0
  29. package/lib/network/gateway.d.ts.map +1 -1
  30. package/lib/network/gateway.js +2 -7
  31. package/lib/network/gateway.js.map +1 -1
  32. package/lib/test/cantonTestUtils.d.ts +4 -9
  33. package/lib/test/cantonTestUtils.d.ts.map +1 -1
  34. package/lib/test/cantonTestUtils.js +736 -27
  35. package/lib/test/cantonTestUtils.js.map +1 -1
  36. package/lib/types/signer.d.ts +10 -1
  37. package/lib/types/signer.d.ts.map +1 -1
  38. package/lib/types/transaction-proto.json +1238 -0
  39. package/lib-es/bridge/deviceTransactionConfig.d.ts +1 -1
  40. package/lib-es/bridge/deviceTransactionConfig.d.ts.map +1 -1
  41. package/lib-es/bridge/deviceTransactionConfig.js +1 -1
  42. package/lib-es/bridge/deviceTransactionConfig.js.map +1 -1
  43. package/lib-es/bridge/onboard.d.ts.map +1 -1
  44. package/lib-es/bridge/onboard.js +49 -23
  45. package/lib-es/bridge/onboard.js.map +1 -1
  46. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  47. package/lib-es/bridge/signOperation.js +10 -3
  48. package/lib-es/bridge/signOperation.js.map +1 -1
  49. package/lib-es/common-logic/index.d.ts +1 -0
  50. package/lib-es/common-logic/index.d.ts.map +1 -1
  51. package/lib-es/common-logic/index.js +1 -0
  52. package/lib-es/common-logic/index.js.map +1 -1
  53. package/lib-es/common-logic/transaction/sign.d.ts +8 -0
  54. package/lib-es/common-logic/transaction/sign.d.ts.map +1 -0
  55. package/lib-es/common-logic/transaction/sign.js +24 -0
  56. package/lib-es/common-logic/transaction/sign.js.map +1 -0
  57. package/lib-es/common-logic/transaction/split.d.ts +9 -0
  58. package/lib-es/common-logic/transaction/split.d.ts.map +1 -0
  59. package/lib-es/common-logic/transaction/split.js +83 -0
  60. package/lib-es/common-logic/transaction/split.js.map +1 -0
  61. package/lib-es/common-logic/utils.js +2 -2
  62. package/lib-es/common-logic/utils.js.map +1 -1
  63. package/lib-es/network/gateway.d.ts +1 -0
  64. package/lib-es/network/gateway.d.ts.map +1 -1
  65. package/lib-es/network/gateway.js +1 -8
  66. package/lib-es/network/gateway.js.map +1 -1
  67. package/lib-es/test/cantonTestUtils.d.ts +4 -9
  68. package/lib-es/test/cantonTestUtils.d.ts.map +1 -1
  69. package/lib-es/test/cantonTestUtils.js +697 -21
  70. package/lib-es/test/cantonTestUtils.js.map +1 -1
  71. package/lib-es/types/signer.d.ts +10 -1
  72. package/lib-es/types/signer.d.ts.map +1 -1
  73. package/lib-es/types/transaction-proto.json +1238 -0
  74. package/package.json +10 -7
  75. package/scripts/generate.js +261 -0
  76. package/src/bridge/deviceTransactionConfig.test.ts +14 -10
  77. package/src/bridge/deviceTransactionConfig.ts +2 -2
  78. package/src/bridge/getTransactionStatus.test.ts +19 -19
  79. package/src/bridge/onboard.integ.test.ts +0 -20
  80. package/src/bridge/onboard.ts +57 -33
  81. package/src/bridge/signOperation.test.ts +114 -0
  82. package/src/bridge/signOperation.ts +12 -5
  83. package/src/bridge/sync.integ.test.ts +157 -132
  84. package/src/common-logic/index.ts +1 -0
  85. package/src/common-logic/transaction/sign.test.ts +317 -0
  86. package/src/common-logic/transaction/sign.ts +33 -0
  87. package/src/common-logic/transaction/split.test.ts +50 -0
  88. package/src/common-logic/transaction/split.ts +101 -0
  89. package/src/common-logic/utils.test.ts +22 -30
  90. package/src/common-logic/utils.ts +2 -2
  91. package/src/network/gateway.integ.test.ts +2 -0
  92. package/src/network/gateway.ts +3 -8
  93. package/src/test/cantonTestUtils.ts +789 -24
  94. package/src/test/prepare-transfer-serialized.json +26 -0
  95. package/src/test/prepare-transfer.json +3298 -0
  96. package/src/types/signer.ts +17 -3
  97. package/src/types/transaction-proto.json +1238 -0
@@ -0,0 +1,317 @@
1
+ import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
2
+ import { CantonPreparedTransaction, CantonSigner } from "../../types/signer";
3
+ import { signTransaction } from "./sign";
4
+
5
+ class MockCantonSigner implements CantonSigner {
6
+ async getAddress(path: string, display?: boolean) {
7
+ return {
8
+ publicKey: "mock-public-key",
9
+ address: "mock-address",
10
+ path,
11
+ };
12
+ }
13
+
14
+ async signTransaction(
15
+ path: string,
16
+ data: CantonPreparedTransaction | { transactions: string[] },
17
+ ) {
18
+ if ("transactions" in data) {
19
+ return `untyped-signature-${data.transactions.length}`;
20
+ } else {
21
+ return `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`;
22
+ }
23
+ }
24
+ }
25
+
26
+ describe("signTransaction", () => {
27
+ const mockSigner = new MockCantonSigner();
28
+ const mockDerivationPath = "44'/6767'/0'/0'/0'";
29
+
30
+ it("should sign prepared transaction", async () => {
31
+ // GIVEN
32
+ const mockPrepareTransferResponse: PrepareTransferResponse = {
33
+ json: {
34
+ transaction: {
35
+ version: "2.1",
36
+ roots: ["0"],
37
+ nodes: [
38
+ {
39
+ nodeId: "0",
40
+ v1: {
41
+ create: {
42
+ lfVersion: "2.1",
43
+ contractId: "test-contract-id",
44
+ packageName: "test-package",
45
+ templateId: {
46
+ packageId: "test-package-id",
47
+ moduleName: "TestModule",
48
+ entityName: "TestEntity",
49
+ },
50
+ argument: {
51
+ record: {
52
+ recordId: {
53
+ packageId: "test-package-id",
54
+ moduleName: "TestModule",
55
+ entityName: "TestEntity",
56
+ },
57
+ fields: [],
58
+ },
59
+ },
60
+ },
61
+ },
62
+ },
63
+ ],
64
+ },
65
+ metadata: {
66
+ submitterInfo: {
67
+ actAs: ["test::party"],
68
+ commandId: "test-command-id",
69
+ },
70
+ synchronizerId: "test-synchronizer-id",
71
+ transactionUuid: "test-transaction-uuid",
72
+ submissionTime: "1234567890",
73
+ inputContracts: [],
74
+ },
75
+ },
76
+ serialized: "serialized-transaction",
77
+ hash: "test-hash",
78
+ };
79
+
80
+ // WHEN
81
+ const result = await signTransaction(
82
+ mockSigner,
83
+ mockDerivationPath,
84
+ mockPrepareTransferResponse,
85
+ );
86
+
87
+ // THEN
88
+ expect(result).toBe("prepared-transaction-signature-10-1");
89
+ });
90
+
91
+ it("should sign untyped versioned message", async () => {
92
+ // GIVEN
93
+ const mockOnboardingPrepareResponse: OnboardingPrepareResponse = {
94
+ party_id: "test-party-id",
95
+ party_name: "test-party-name",
96
+ public_key_fingerprint: "test-fingerprint",
97
+ transactions: {
98
+ namespace_transaction: {
99
+ serialized: "namespace-transaction-data",
100
+ json: {},
101
+ hash: "namespace-hash",
102
+ },
103
+ party_to_key_transaction: {
104
+ serialized: "party-to-key-transaction-data",
105
+ json: {},
106
+ hash: "party-to-key-hash",
107
+ },
108
+ party_to_participant_transaction: {
109
+ serialized: "party-to-participant-transaction-data",
110
+ json: {},
111
+ hash: "party-to-participant-hash",
112
+ },
113
+ combined_hash: "combined-hash",
114
+ },
115
+ };
116
+
117
+ // WHEN
118
+ const result = await signTransaction(
119
+ mockSigner,
120
+ mockDerivationPath,
121
+ mockOnboardingPrepareResponse,
122
+ );
123
+
124
+ // THEN
125
+ expect(result).toBe("untyped-signature-3");
126
+ });
127
+
128
+ it("should handle empty signature from signer", async () => {
129
+ // GIVEN
130
+ const mockSignerWithEmptySignature = {
131
+ ...mockSigner,
132
+ signTransaction: jest.fn().mockResolvedValue(""),
133
+ } as unknown as CantonSigner;
134
+
135
+ const mockPrepareTransferResponse: PrepareTransferResponse = {
136
+ json: {
137
+ transaction: {
138
+ version: "2.1",
139
+ roots: ["0"],
140
+ nodes: [],
141
+ },
142
+ metadata: {
143
+ submitterInfo: {
144
+ actAs: ["test::party"],
145
+ commandId: "test-command-id",
146
+ },
147
+ synchronizerId: "test-synchronizer-id",
148
+ transactionUuid: "test-transaction-uuid",
149
+ submissionTime: "1234567890",
150
+ inputContracts: [],
151
+ },
152
+ },
153
+ serialized: "serialized-transaction",
154
+ hash: "test-hash",
155
+ };
156
+
157
+ // WHEN & THEN
158
+ await expect(
159
+ signTransaction(
160
+ mockSignerWithEmptySignature,
161
+ mockDerivationPath,
162
+ mockPrepareTransferResponse,
163
+ ),
164
+ ).rejects.toThrow("Device returned empty signature");
165
+ });
166
+
167
+ it("should handle signer errors", async () => {
168
+ // GIVEN
169
+ const mockSignerWithError = {
170
+ ...mockSigner,
171
+ signTransaction: jest.fn().mockRejectedValue(new Error("Signer error")),
172
+ } as unknown as CantonSigner;
173
+
174
+ const mockPrepareTransferResponse: PrepareTransferResponse = {
175
+ json: {
176
+ transaction: {
177
+ version: "2.1",
178
+ roots: ["0"],
179
+ nodes: [],
180
+ },
181
+ metadata: {
182
+ submitterInfo: {
183
+ actAs: ["test::party"],
184
+ commandId: "test-command-id",
185
+ },
186
+ synchronizerId: "test-synchronizer-id",
187
+ transactionUuid: "test-transaction-uuid",
188
+ submissionTime: "1234567890",
189
+ inputContracts: [],
190
+ },
191
+ },
192
+ serialized: "serialized-transaction",
193
+ hash: "test-hash",
194
+ };
195
+
196
+ // WHEN & THEN
197
+ await expect(
198
+ signTransaction(mockSignerWithError, mockDerivationPath, mockPrepareTransferResponse),
199
+ ).rejects.toThrow("Signer error");
200
+ });
201
+
202
+ it("should call signer with correct parameters for prepared transaction", async () => {
203
+ // GIVEN
204
+ const mockSignerSpy = jest.fn().mockResolvedValue("test-signature");
205
+ const mockSignerWithSpy = {
206
+ ...mockSigner,
207
+ signTransaction: mockSignerSpy,
208
+ } as unknown as CantonSigner;
209
+
210
+ const mockPrepareTransferResponse: PrepareTransferResponse = {
211
+ json: {
212
+ transaction: {
213
+ version: "2.1",
214
+ roots: ["0"],
215
+ nodes: [
216
+ {
217
+ nodeId: "0",
218
+ v1: {
219
+ create: {
220
+ lfVersion: "2.1",
221
+ contractId: "test-contract-id",
222
+ packageName: "test-package",
223
+ templateId: {
224
+ packageId: "test-package-id",
225
+ moduleName: "TestModule",
226
+ entityName: "TestEntity",
227
+ },
228
+ argument: {
229
+ record: {
230
+ recordId: {
231
+ packageId: "test-package-id",
232
+ moduleName: "TestModule",
233
+ entityName: "TestEntity",
234
+ },
235
+ fields: [],
236
+ },
237
+ },
238
+ },
239
+ },
240
+ },
241
+ ],
242
+ },
243
+ metadata: {
244
+ submitterInfo: {
245
+ actAs: ["test::party"],
246
+ commandId: "test-command-id",
247
+ },
248
+ synchronizerId: "test-synchronizer-id",
249
+ transactionUuid: "test-transaction-uuid",
250
+ submissionTime: "1234567890",
251
+ inputContracts: [],
252
+ },
253
+ },
254
+ serialized: "serialized-transaction",
255
+ hash: "test-hash",
256
+ };
257
+
258
+ // WHEN
259
+ await signTransaction(mockSignerWithSpy, mockDerivationPath, mockPrepareTransferResponse);
260
+
261
+ // THEN
262
+ expect(mockSignerSpy).toHaveBeenCalledWith(
263
+ mockDerivationPath,
264
+ expect.objectContaining({
265
+ damlTransaction: expect.any(Uint8Array),
266
+ nodes: expect.any(Array),
267
+ metadata: expect.any(Uint8Array),
268
+ inputContracts: expect.any(Array),
269
+ }),
270
+ );
271
+ });
272
+
273
+ it("should call signer with correct parameters for untyped versioned message", async () => {
274
+ // GIVEN
275
+ const mockSignerSpy = jest.fn().mockResolvedValue("test-signature");
276
+ const mockSignerWithSpy = {
277
+ ...mockSigner,
278
+ signTransaction: mockSignerSpy,
279
+ } as unknown as CantonSigner;
280
+
281
+ const mockOnboardingPrepareResponse: OnboardingPrepareResponse = {
282
+ party_id: "test-party-id",
283
+ party_name: "test-party-name",
284
+ public_key_fingerprint: "test-fingerprint",
285
+ transactions: {
286
+ namespace_transaction: {
287
+ serialized: "namespace-transaction-data",
288
+ json: {},
289
+ hash: "namespace-hash",
290
+ },
291
+ party_to_key_transaction: {
292
+ serialized: "party-to-key-transaction-data",
293
+ json: {},
294
+ hash: "party-to-key-hash",
295
+ },
296
+ party_to_participant_transaction: {
297
+ serialized: "party-to-participant-transaction-data",
298
+ json: {},
299
+ hash: "party-to-participant-hash",
300
+ },
301
+ combined_hash: "combined-hash",
302
+ },
303
+ };
304
+
305
+ // WHEN
306
+ await signTransaction(mockSignerWithSpy, mockDerivationPath, mockOnboardingPrepareResponse);
307
+
308
+ // THEN
309
+ expect(mockSignerSpy).toHaveBeenCalledWith(mockDerivationPath, {
310
+ transactions: [
311
+ "namespace-transaction-data",
312
+ "party-to-key-transaction-data",
313
+ "party-to-participant-transaction-data",
314
+ ],
315
+ });
316
+ });
317
+ });
@@ -0,0 +1,33 @@
1
+ import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
2
+ import { PrepareTransactionResponse } from "../../types/onboard";
3
+ import { CantonSigner } from "../../types/signer";
4
+ import { splitTransaction } from "./split";
5
+
6
+ /**
7
+ * Sign a Canton transaction - handles both prepared transactions and untyped versioned messages
8
+ */
9
+ export async function signTransaction(
10
+ signer: CantonSigner,
11
+ derivationPath: string,
12
+ transactionData: PrepareTransferResponse | OnboardingPrepareResponse | PrepareTransactionResponse,
13
+ ): Promise<string> {
14
+ let signature: string;
15
+
16
+ if ("json" in transactionData) {
17
+ const components = splitTransaction(transactionData.json);
18
+ signature = await signer.signTransaction(derivationPath, components);
19
+ } else {
20
+ const transactions = [
21
+ transactionData.transactions.namespace_transaction.serialized,
22
+ transactionData.transactions.party_to_key_transaction.serialized,
23
+ transactionData.transactions.party_to_participant_transaction.serialized,
24
+ ];
25
+ signature = await signer.signTransaction(derivationPath, { transactions });
26
+ }
27
+
28
+ if (!signature || signature.length === 0) {
29
+ throw new Error("Device returned empty signature");
30
+ }
31
+
32
+ return signature;
33
+ }
@@ -0,0 +1,50 @@
1
+ import prepareTransferMockSerialized from "../../test/prepare-transfer-serialized.json";
2
+ import prepareTransferMock from "../../test/prepare-transfer.json";
3
+ import { splitTransaction } from "./split";
4
+
5
+ function uint8ArrayToHex(bytes: Uint8Array): string {
6
+ return Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
7
+ }
8
+
9
+ describe("splitTransaction", () => {
10
+ it("should split transaction correctly", () => {
11
+ const transactionData = prepareTransferMock;
12
+ const result = splitTransaction(transactionData);
13
+
14
+ expect(result).toBeDefined();
15
+ expect(result.damlTransaction).toBeInstanceOf(Uint8Array);
16
+ expect(result.nodes).toBeInstanceOf(Array);
17
+ expect(result.metadata).toBeInstanceOf(Uint8Array);
18
+ expect(result.inputContracts).toBeInstanceOf(Array);
19
+ });
20
+
21
+ it("should properly serialize damlTransaction", () => {
22
+ const transactionData = prepareTransferMock;
23
+ const { damlTransaction } = splitTransaction(transactionData);
24
+
25
+ expect(uint8ArrayToHex(damlTransaction)).toEqual(prepareTransferMockSerialized.damlTransaction);
26
+ });
27
+
28
+ it("should properly serialize nodes", () => {
29
+ const transactionData = prepareTransferMock;
30
+ const { nodes } = splitTransaction(transactionData);
31
+
32
+ expect(nodes.map(uint8ArrayToHex)).toEqual(prepareTransferMockSerialized.nodes);
33
+ });
34
+
35
+ it("should properly serialize metadata", () => {
36
+ const transactionData = prepareTransferMock;
37
+ const { metadata } = splitTransaction(transactionData);
38
+
39
+ expect(uint8ArrayToHex(metadata)).toEqual(prepareTransferMockSerialized.metadata);
40
+ });
41
+
42
+ it("should properly serialize inputContracts", () => {
43
+ const transactionData = prepareTransferMock;
44
+ const { inputContracts } = splitTransaction(transactionData);
45
+
46
+ expect(inputContracts.map(uint8ArrayToHex)).toEqual(
47
+ prepareTransferMockSerialized.inputContracts,
48
+ );
49
+ });
50
+ });
@@ -0,0 +1,101 @@
1
+ import * as protobuf from "protobufjs";
2
+ import { PrepareTransferResponse } from "../../network/gateway";
3
+ import { CantonPreparedTransaction } from "../../types/signer";
4
+ import * as transactionProto from "../../types/transaction-proto.json";
5
+
6
+ const root: { [key: string]: any } = protobuf.Root.fromJSON(transactionProto) || {};
7
+
8
+ const RESERVED_WORDS = {
9
+ bool: "bool_",
10
+ enum: "enum_",
11
+ constructor: "constructor_",
12
+ };
13
+
14
+ const replaceReservedWords = (obj: any): any => {
15
+ if (obj === null || typeof obj !== "object") return obj;
16
+ if (Array.isArray(obj)) return obj.map(replaceReservedWords);
17
+
18
+ const transformed: any = {};
19
+ for (const [key, value] of Object.entries(obj)) {
20
+ transformed[RESERVED_WORDS[key as keyof typeof RESERVED_WORDS] || key] =
21
+ replaceReservedWords(value);
22
+ }
23
+ return transformed;
24
+ };
25
+
26
+ /**
27
+ * Splits a Canton transaction into components for prepared transaction signing.
28
+ * Converts protobuf transaction data into structured components that can be
29
+ * sent to the Ledger device for signing.
30
+ */
31
+ export function splitTransaction(
32
+ transaction: PrepareTransferResponse["json"],
33
+ ): CantonPreparedTransaction {
34
+ const { transaction: transactionData, metadata } = transaction;
35
+
36
+ // Process DAML transaction
37
+ const DeviceDamlTransaction = root.lookupType(
38
+ "com.daml.ledger.api.v2.interactive.DeviceDamlTransaction",
39
+ );
40
+
41
+ const damlTransactionBytes = DeviceDamlTransaction.encode({
42
+ version: transactionData.version,
43
+ roots: transactionData.roots,
44
+ nodesCount: transactionData.nodes?.length || 0,
45
+ nodeSeeds: (transactionData.nodeSeeds || []).map((seed: any) => ({
46
+ seed: Uint8Array.from(Buffer.from(seed.seed, "base64")),
47
+ ...(seed.nodeId && seed.nodeId !== 0 && { nodeId: seed.nodeId }),
48
+ })),
49
+ }).finish();
50
+
51
+ // Process input contracts
52
+ const inputContracts = (metadata.inputContracts || []).map((contract: any) => {
53
+ const { eventBlob, ...contractWithoutBlob } = contract;
54
+ const InputContract = root.lookupType(
55
+ "com.daml.ledger.api.v2.interactive.DeviceMetadata.InputContract",
56
+ );
57
+ const contractPb = InputContract.fromObject(replaceReservedWords(contractWithoutBlob));
58
+ return InputContract.encode(contractPb).finish();
59
+ });
60
+
61
+ // Process metadata
62
+ const metadataData = {
63
+ submitterInfo: {
64
+ actAs: metadata.submitterInfo.actAs,
65
+ commandId: metadata.submitterInfo.commandId,
66
+ },
67
+ synchronizerId: metadata.synchronizerId,
68
+ ...(metadata.mediatorGroup !== undefined && { mediatorGroup: metadata.mediatorGroup }),
69
+ transactionUuid: metadata.transactionUuid,
70
+ submissionTime: Number.parseInt(metadata.preparationTime, 10),
71
+ inputContractsCount: metadata.inputContracts?.length || 0,
72
+ ...(metadata.minLedgerEffectiveTime && {
73
+ minLedgerEffectiveTime: Number.parseInt(metadata.minLedgerEffectiveTime, 10),
74
+ }),
75
+ ...(metadata.maxLedgerEffectiveTime && {
76
+ maxLedgerEffectiveTime: Number.parseInt(metadata.maxLedgerEffectiveTime, 10),
77
+ }),
78
+ };
79
+
80
+ const DeviceMetadata = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceMetadata");
81
+ const metadataBytes = DeviceMetadata.encode(metadataData).finish();
82
+
83
+ // Process nodes
84
+ const nodesArray = transactionData.nodes || [];
85
+ const nodes = new Array(nodesArray.length);
86
+
87
+ for (const node of nodesArray) {
88
+ const nodeId = Number.parseInt(node.nodeId || "0", 10);
89
+ const Node = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceDamlTransaction.Node");
90
+ const nodePb = Node.fromObject(replaceReservedWords(node));
91
+ const pos = nodesArray.length - 1 - nodeId;
92
+ nodes[pos] = Node.encode(nodePb).finish();
93
+ }
94
+
95
+ return {
96
+ damlTransaction: damlTransactionBytes,
97
+ nodes,
98
+ metadata: metadataBytes,
99
+ inputContracts,
100
+ };
101
+ }
@@ -5,18 +5,9 @@ describe("utils", () => {
5
5
  describe("isRecipientValid", () => {
6
6
  it("should return true for valid Canton addresses", () => {
7
7
  const validAddresses = [
8
- "abc::123",
9
- "hello::1",
10
- "test123::456",
11
- "a::0",
12
- "party::999",
13
- "user123::42",
14
- "canton_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z::123",
15
- "test::123456789",
16
- "abc::abc", // letters after ::
17
- "test::ABC123", // mixed case letters and numbers
18
- "user::a1b2c3", // alphanumeric after ::
19
- "contract::XyZ789", // mixed case with numbers
8
+ "ldg::1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e8",
9
+ "ldg-with-dash::1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e8",
10
+ "ldg-with-number-1::1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e8",
20
11
  ];
21
12
 
22
13
  validAddresses.forEach(address => {
@@ -26,6 +17,8 @@ describe("utils", () => {
26
17
 
27
18
  it("should return false for invalid Canton addresses", () => {
28
19
  const invalidAddresses = [
20
+ "ldg::1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e", // too short fingerprint
21
+ "ldg::1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e80", // too long fingerprint
29
22
  "", // empty string
30
23
  "::123", // no characters before ::
31
24
  "abc::", // no characters after ::
@@ -53,26 +46,25 @@ describe("utils", () => {
53
46
  });
54
47
 
55
48
  it("should handle edge cases", () => {
56
- expect(isRecipientValid("a::1")).toBe(true); // minimum valid case with number
57
- expect(isRecipientValid("a::a")).toBe(true); // minimum valid case with letter
58
- expect(isRecipientValid("1::1")).toBe(true); // number before ::
59
- expect(isRecipientValid("1::a")).toBe(true); // number before ::, letter after
60
- expect(isRecipientValid("_::1")).toBe(true); // underscore before ::
61
- expect(isRecipientValid("_::a")).toBe(true); // underscore before ::, letter after
62
- expect(isRecipientValid("-::1")).toBe(true); // dash before ::
63
- expect(isRecipientValid("-::a")).toBe(true); // dash before ::, letter after
64
- expect(isRecipientValid(".::1")).toBe(true); // dot before ::
65
- expect(isRecipientValid(".::a")).toBe(true); // dot before ::, letter after
49
+ const validHex68 = "1220691e945dc1b210f3b6be9fbad73efaf642bfb96022552f66c9e2b83b00cb20e8";
50
+ expect(isRecipientValid(`a::${validHex68}`)).toBe(true); // single letter prefix
51
+ expect(isRecipientValid(`1::${validHex68}`)).toBe(true); // single number prefix
52
+ expect(isRecipientValid(`a-1::${validHex68}`)).toBe(true); // prefix with dash
53
+
54
+ // Invalid edge cases (should be false)
55
+ expect(isRecipientValid("a::1")).toBe(false); // too short hex
56
+ expect(isRecipientValid("a::a")).toBe(false); // too short hex
57
+ expect(isRecipientValid("_::1")).toBe(false); // invalid prefix character
58
+ expect(isRecipientValid(".::1")).toBe(false); // invalid prefix character
66
59
  });
67
60
 
68
- it("should handle addresses with spaces and multiple colons", () => {
69
- // These are valid according to our regex but might not be ideal Canton addresses
70
- expect(isRecipientValid(" abc::123")).toBe(true); // space before address
71
- expect(isRecipientValid(" abc::abc")).toBe(true); // space before address with letters
72
- expect(isRecipientValid("abc ::123")).toBe(true); // space before ::
73
- expect(isRecipientValid("abc ::abc")).toBe(true); // space before :: with letters
74
- expect(isRecipientValid("abc::123::456")).toBe(true); // multiple ::
75
- expect(isRecipientValid("abc::abc::def")).toBe(true); // multiple :: with letters
61
+ it("should reject addresses with spaces and multiple colons", () => {
62
+ expect(isRecipientValid(" abc::123")).toBe(false); // space before address
63
+ expect(isRecipientValid(" abc::abc")).toBe(false); // space before address with letters
64
+ expect(isRecipientValid("abc ::123")).toBe(false); // space before ::
65
+ expect(isRecipientValid("abc ::abc")).toBe(false); // space before :: with letters
66
+ expect(isRecipientValid("abc::123::456")).toBe(false); // multiple ::
67
+ expect(isRecipientValid("abc::abc::def")).toBe(false); // multiple :: with letters
76
68
  });
77
69
  });
78
70
 
@@ -8,9 +8,9 @@ export const validateTag = (tag: BigNumber) => {
8
8
  );
9
9
  };
10
10
 
11
- const CANTON_ADDRESS_REGEX = /^.+::[a-zA-Z0-9]+$/;
11
+ const CANTON_ADDRESS_REGEX = /^[a-zA-Z0-9-]+::[a-fA-F0-9]{68}$/;
12
12
 
13
13
  export function isRecipientValid(recipient: string): boolean {
14
- // Canton address format: at least 1 character :: at least 1 alphanumeric character
14
+ // Canton address format: alphanumeric with dashes :: 68 hexadecimal characters
15
15
  return CANTON_ADDRESS_REGEX.test(recipient);
16
16
  }
@@ -107,6 +107,8 @@ describe("gateway (devnet)", () => {
107
107
  testIfPrepared(
108
108
  "should not throw when already onboarded",
109
109
  async () => {
110
+ // Add delay to ensure previous operations are complete
111
+ await new Promise(resolve => setTimeout(resolve, 10000));
110
112
  // GIVEN
111
113
  const { keyPair } = getOnboardedAccount();
112
114
  const signature = keyPair.sign(prepareResponse!.transactions.combined_hash);
@@ -283,7 +283,8 @@ export type OperationInfo =
283
283
  const getGatewayUrl = (currency: CryptoCurrency) => coinConfig.getCoinConfig(currency).gatewayUrl;
284
284
  const getNodeId = (currency: CryptoCurrency) =>
285
285
  coinConfig.getCoinConfig(currency).nodeId || "ledger-live-devnet";
286
- const getNetworkType = (currency: CryptoCurrency) => coinConfig.getCoinConfig(currency).networkType;
286
+ export const getNetworkType = (currency: CryptoCurrency) =>
287
+ coinConfig.getCoinConfig(currency).networkType;
287
288
 
288
289
  const gatewayNetwork = <T, U = unknown>(req: LiveNetworkRequest<U>) => {
289
290
  const API_KEY = getEnv("CANTON_API_KEY");
@@ -448,13 +449,6 @@ export async function prepareTapRequest(
448
449
  currency: CryptoCurrency,
449
450
  { partyId, amount = 1000000 }: PrepareTapRequest,
450
451
  ) {
451
- if (getNetworkType(currency) === "mainnet") {
452
- return {
453
- serialized: "",
454
- json: null,
455
- hash: "",
456
- };
457
- }
458
452
  const { data } = await gatewayNetwork<PrepareTapResponse, { amount: string; type: string }>({
459
453
  method: "POST",
460
454
  url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/prepare`,
@@ -505,6 +499,7 @@ export async function prepareTransferRequest(
505
499
  url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/prepare`,
506
500
  data: params,
507
501
  });
502
+
508
503
  return data;
509
504
  }
510
505