@ledgerhq/coin-canton 0.11.0-nightly.20251206023719 → 0.11.0

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 (84) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +27 -13
  3. package/README.md +1 -1
  4. package/lib/bridge/acceptOffer.d.ts.map +1 -1
  5. package/lib/bridge/acceptOffer.js +0 -8
  6. package/lib/bridge/acceptOffer.js.map +1 -1
  7. package/lib/bridge/getTransactionStatus.d.ts +0 -1
  8. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  9. package/lib/bridge/getTransactionStatus.js +17 -48
  10. package/lib/bridge/getTransactionStatus.js.map +1 -1
  11. package/lib/bridge/onboard.d.ts.map +1 -1
  12. package/lib/bridge/onboard.js +1 -4
  13. package/lib/bridge/onboard.js.map +1 -1
  14. package/lib/bridge/serialization.d.ts.map +1 -1
  15. package/lib/bridge/serialization.js +1 -3
  16. package/lib/bridge/serialization.js.map +1 -1
  17. package/lib/bridge/sync.d.ts.map +1 -1
  18. package/lib/bridge/sync.js +2 -6
  19. package/lib/bridge/sync.js.map +1 -1
  20. package/lib/network/gateway.d.ts +3 -20
  21. package/lib/network/gateway.d.ts.map +1 -1
  22. package/lib/network/gateway.js +16 -75
  23. package/lib/network/gateway.js.map +1 -1
  24. package/lib/test/cantonTestUtils.d.ts +1 -3
  25. package/lib/test/cantonTestUtils.d.ts.map +1 -1
  26. package/lib/test/cantonTestUtils.js +1 -1
  27. package/lib/test/cantonTestUtils.js.map +1 -1
  28. package/lib/types/bridge.d.ts +0 -2
  29. package/lib/types/bridge.d.ts.map +1 -1
  30. package/lib/types/errors.d.ts +0 -3
  31. package/lib/types/errors.d.ts.map +1 -1
  32. package/lib/types/errors.js +1 -2
  33. package/lib/types/errors.js.map +1 -1
  34. package/lib-es/bridge/acceptOffer.d.ts.map +1 -1
  35. package/lib-es/bridge/acceptOffer.js +0 -8
  36. package/lib-es/bridge/acceptOffer.js.map +1 -1
  37. package/lib-es/bridge/getTransactionStatus.d.ts +0 -1
  38. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  39. package/lib-es/bridge/getTransactionStatus.js +17 -47
  40. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  41. package/lib-es/bridge/onboard.d.ts.map +1 -1
  42. package/lib-es/bridge/onboard.js +2 -5
  43. package/lib-es/bridge/onboard.js.map +1 -1
  44. package/lib-es/bridge/serialization.d.ts.map +1 -1
  45. package/lib-es/bridge/serialization.js +1 -3
  46. package/lib-es/bridge/serialization.js.map +1 -1
  47. package/lib-es/bridge/sync.d.ts.map +1 -1
  48. package/lib-es/bridge/sync.js +2 -6
  49. package/lib-es/bridge/sync.js.map +1 -1
  50. package/lib-es/network/gateway.d.ts +3 -20
  51. package/lib-es/network/gateway.d.ts.map +1 -1
  52. package/lib-es/network/gateway.js +15 -70
  53. package/lib-es/network/gateway.js.map +1 -1
  54. package/lib-es/test/cantonTestUtils.d.ts +1 -3
  55. package/lib-es/test/cantonTestUtils.d.ts.map +1 -1
  56. package/lib-es/test/cantonTestUtils.js +1 -1
  57. package/lib-es/test/cantonTestUtils.js.map +1 -1
  58. package/lib-es/types/bridge.d.ts +0 -2
  59. package/lib-es/types/bridge.d.ts.map +1 -1
  60. package/lib-es/types/errors.d.ts +0 -3
  61. package/lib-es/types/errors.d.ts.map +1 -1
  62. package/lib-es/types/errors.js +0 -1
  63. package/lib-es/types/errors.js.map +1 -1
  64. package/package.json +8 -8
  65. package/src/api/getBalance.integ.test.ts +1 -1
  66. package/src/api/lastBlock.integ.test.ts +1 -1
  67. package/src/api/listOperations.integ.test.ts +1 -1
  68. package/src/bridge/acceptOffer.test.ts +4 -43
  69. package/src/bridge/acceptOffer.ts +1 -9
  70. package/src/bridge/getTransactionStatus.test.ts +3 -95
  71. package/src/bridge/getTransactionStatus.ts +24 -61
  72. package/src/bridge/onboard.integ.test.ts +4 -85
  73. package/src/bridge/onboard.test.ts +1 -107
  74. package/src/bridge/onboard.ts +1 -6
  75. package/src/bridge/prepareTransaction.test.ts +1 -1
  76. package/src/bridge/serialization.ts +1 -3
  77. package/src/bridge/sync.integ.test.ts +1 -1
  78. package/src/bridge/sync.ts +2 -6
  79. package/src/network/gateway.integ.test.ts +1 -24
  80. package/src/network/gateway.test.ts +2 -64
  81. package/src/network/gateway.ts +26 -98
  82. package/src/test/cantonTestUtils.ts +1 -1
  83. package/src/types/bridge.ts +0 -2
  84. package/src/types/errors.ts +0 -2
@@ -8,7 +8,7 @@ describe.skip("devnet", () => {
8
8
  api = createApi({
9
9
  nodeUrl: "https://wallet-validator-devnet-canton.ledger-test.com/v2",
10
10
  networkType: "devnet",
11
- gatewayUrl: "https://canton-gateway-devnet.api.live.ledger-test.com",
11
+ gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
12
12
  useGateway: true,
13
13
  nativeInstrumentId: "Amulet",
14
14
  });
@@ -1,24 +1,17 @@
1
1
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
2
- import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
3
2
  import type { Account } from "@ledgerhq/types-live";
3
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
4
+ import { buildTransferInstruction } from "./acceptOffer";
5
+ import * as gateway from "../network/gateway";
4
6
  import * as signTransactionModule from "../common-logic/transaction/sign";
7
+ import type { CantonSigner, CantonSignature } from "../types/signer";
5
8
  import type { PrepareTransferResponse } from "../network/gateway";
6
- import * as gateway from "../network/gateway";
7
- import type { CantonAccount } from "../types";
8
- import { TopologyChangeError } from "../types/errors";
9
- import type { CantonSignature, CantonSigner } from "../types/signer";
10
- import { buildTransferInstruction } from "./acceptOffer";
11
- import * as getTransactionStatusModule from "./getTransactionStatus";
12
9
 
13
10
  jest.mock("../network/gateway");
14
11
  jest.mock("../common-logic/transaction/sign");
15
- jest.mock("./getTransactionStatus");
16
12
 
17
13
  const mockedGateway = gateway as jest.Mocked<typeof gateway>;
18
14
  const mockedSignTransaction = signTransactionModule as jest.Mocked<typeof signTransactionModule>;
19
- const mockedGetTransactionStatus = getTransactionStatusModule as jest.Mocked<
20
- typeof getTransactionStatusModule
21
- >;
22
15
 
23
16
  describe("acceptOffer", () => {
24
17
  const mockCurrency = {
@@ -102,7 +95,6 @@ describe("acceptOffer", () => {
102
95
  mockedGateway.prepareTransferInstruction.mockResolvedValue(mockPreparedTransaction);
103
96
  mockedSignTransaction.signTransaction.mockResolvedValue(mockSignature);
104
97
  mockedGateway.submitTransferInstruction.mockResolvedValue({ update_id: "test-update-id" });
105
- mockedGetTransactionStatus.validateTopology.mockResolvedValue(null);
106
98
  });
107
99
 
108
100
  describe("buildTransferInstruction", () => {
@@ -304,36 +296,5 @@ describe("acceptOffer", () => {
304
296
  expect(mockedSignTransaction.signTransaction).toHaveBeenCalled();
305
297
  expect(mockedGateway.submitTransferInstruction).toHaveBeenCalled();
306
298
  });
307
-
308
- it("should throw TopologyChangeError when validateTopology returns topology error", async () => {
309
- // GIVEN
310
- const topologyError = new TopologyChangeError("Topology change detected");
311
- const cantonAccount = {
312
- ...mockAccount,
313
- cantonResources: {
314
- publicKey: "test-public-key",
315
- instrumentUtxoCounts: {},
316
- },
317
- } as unknown as CantonAccount;
318
- mockedGetTransactionStatus.validateTopology.mockResolvedValue(topologyError);
319
- const transferInstruction = buildTransferInstruction(mockSignerContext);
320
-
321
- // WHEN & THEN
322
- await expect(
323
- transferInstruction(
324
- mockCurrency,
325
- mockDeviceId,
326
- cantonAccount,
327
- mockPartyId,
328
- mockContractId,
329
- "accept-transfer-instruction",
330
- ),
331
- ).rejects.toThrow(TopologyChangeError);
332
-
333
- expect(mockedGetTransactionStatus.validateTopology).toHaveBeenCalledWith(cantonAccount);
334
- expect(mockedGateway.prepareTransferInstruction).not.toHaveBeenCalled();
335
- expect(mockedSignTransaction.signTransaction).not.toHaveBeenCalled();
336
- expect(mockedGateway.submitTransferInstruction).not.toHaveBeenCalled();
337
- });
338
299
  });
339
300
  });
@@ -1,10 +1,9 @@
1
1
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
2
2
  import type { Account } from "@ledgerhq/types-live";
3
3
  import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
4
- import { validateTopology } from "./getTransactionStatus";
4
+
5
5
  import { prepareTransferInstruction, submitTransferInstruction } from "../network/gateway";
6
6
  import { signTransaction } from "../common-logic/transaction/sign";
7
- import { isCantonAccount } from "./serialization";
8
7
  import type { CantonSigner } from "../types";
9
8
 
10
9
  type TransferInstructionType =
@@ -23,13 +22,6 @@ export const buildTransferInstruction =
23
22
  type: TransferInstructionType,
24
23
  reason?: string,
25
24
  ) => {
26
- if (isCantonAccount(account)) {
27
- const topologyError = await validateTopology(account);
28
- if (topologyError) {
29
- throw topologyError;
30
- }
31
- }
32
-
33
25
  const preparedTransaction = await prepareTransferInstruction(currency, partyId, {
34
26
  type,
35
27
  contract_id: contractId,
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  AmountRequired,
3
3
  FeeNotLoaded,
4
+ FeeRequired,
4
5
  FeeTooHigh,
5
6
  InvalidAddress,
7
+ InvalidAddressBecauseDestinationIsAlsoSource,
6
8
  NotEnoughBalanceBecauseDestinationNotCreated,
7
9
  NotEnoughSpendableBalance,
8
10
  RecipientRequired,
9
11
  } from "@ledgerhq/errors";
10
12
  import BigNumber from "bignumber.js";
11
13
  import coinConfig from "../config";
12
- import * as gateway from "../network/gateway";
13
14
  import { createMockAccount } from "../test/fixtures";
14
15
  import { CantonAccount, TooManyUtxosCritical, TooManyUtxosWarning, Transaction } from "../types";
15
- import { TopologyChangeError } from "../types/errors";
16
16
  import {
17
17
  getTransactionStatus,
18
18
  TO_MANY_UTXOS_CRITICAL_COUNT,
@@ -20,9 +20,7 @@ import {
20
20
  } from "./getTransactionStatus";
21
21
 
22
22
  jest.mock("../config", () => ({ getCoinConfig: jest.fn() }));
23
- jest.mock("../network/gateway");
24
23
  const mockCoinConfig = jest.mocked(coinConfig);
25
- const mockedGateway = gateway as jest.Mocked<typeof gateway>;
26
24
 
27
25
  describe("getTransactionStatus", () => {
28
26
  const mockAccount: CantonAccount = {
@@ -30,11 +28,8 @@ describe("getTransactionStatus", () => {
30
28
  balance: new BigNumber(1000),
31
29
  spendableBalance: new BigNumber(1000),
32
30
  freshAddress: "test::33333333333333333333333333333333333333333333333333333333333333333333",
33
- xpub: "test-party-id",
34
31
  }),
35
32
  cantonResources: {
36
- pendingTransferProposals: [],
37
- publicKey: "test-public-key",
38
33
  instrumentUtxoCounts: {
39
34
  Amulet: 5,
40
35
  },
@@ -49,7 +44,6 @@ describe("getTransactionStatus", () => {
49
44
  status: { type: "active" },
50
45
  nativeInstrumentId: "Amulet",
51
46
  });
52
- mockedGateway.isTopologyChangeRequiredCached.mockResolvedValue(false);
53
47
  });
54
48
 
55
49
  describe("fee validation", () => {
@@ -293,7 +287,6 @@ describe("getTransactionStatus", () => {
293
287
  const accountWithTooManyUtxos = {
294
288
  ...mockAccount,
295
289
  cantonResources: {
296
- ...mockAccount.cantonResources,
297
290
  instrumentUtxoCounts: {
298
291
  Amulet: TO_MANY_UTXOS_CRITICAL_COUNT + 1,
299
292
  },
@@ -318,7 +311,7 @@ describe("getTransactionStatus", () => {
318
311
  const accountWithManyUtxos = {
319
312
  ...mockAccount,
320
313
  cantonResources: {
321
- ...mockAccount.cantonResources,
314
+ pendingTransferProposals: [],
322
315
  instrumentUtxoCounts: {
323
316
  Amulet: TO_MANY_UTXOS_WARNING_COUNT + 1,
324
317
  },
@@ -346,7 +339,6 @@ describe("getTransactionStatus", () => {
346
339
  const accountWithFewUtxos = {
347
340
  ...mockAccount,
348
341
  cantonResources: {
349
- ...mockAccount.cantonResources,
350
342
  instrumentUtxoCounts: {
351
343
  Amulet: TO_MANY_UTXOS_WARNING_COUNT - 1,
352
344
  },
@@ -370,7 +362,6 @@ describe("getTransactionStatus", () => {
370
362
  const accountWithManyUtxos = {
371
363
  ...mockAccount,
372
364
  cantonResources: {
373
- ...mockAccount.cantonResources,
374
365
  instrumentUtxoCounts: {
375
366
  Amulet: 25,
376
367
  },
@@ -391,87 +382,4 @@ describe("getTransactionStatus", () => {
391
382
  expect(result.errors.utxoCount).toBeUndefined();
392
383
  });
393
384
  });
394
-
395
- describe("topology validation", () => {
396
- it("should return TopologyChangeError when isTopologyChangeRequiredCached returns true", async () => {
397
- // GIVEN
398
- const accountWithPartyId: CantonAccount = {
399
- ...mockAccount,
400
- xpub: "test-party-id",
401
- };
402
- mockedGateway.isTopologyChangeRequiredCached.mockResolvedValue(true);
403
-
404
- const transaction: Transaction = {
405
- family: "canton",
406
- amount: new BigNumber(100),
407
- recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
408
- fee: new BigNumber(10),
409
- tokenId: "",
410
- };
411
-
412
- // WHEN
413
- const result = await getTransactionStatus(accountWithPartyId, transaction);
414
-
415
- // THEN
416
- expect(result.errors.topologyChange).toBeInstanceOf(TopologyChangeError);
417
- expect(mockedGateway.isTopologyChangeRequiredCached).toHaveBeenCalledWith(
418
- accountWithPartyId.currency,
419
- "test-public-key",
420
- );
421
- });
422
-
423
- it("should not return topology error when isTopologyChangeRequiredCached returns false", async () => {
424
- // GIVEN
425
- const accountWithPartyId: CantonAccount = {
426
- ...mockAccount,
427
- xpub: "test-party-id",
428
- };
429
- mockedGateway.isTopologyChangeRequiredCached.mockResolvedValue(false);
430
-
431
- const transaction: Transaction = {
432
- family: "canton",
433
- amount: new BigNumber(100),
434
- recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
435
- fee: new BigNumber(10),
436
- tokenId: "",
437
- };
438
-
439
- // WHEN
440
- const result = await getTransactionStatus(accountWithPartyId, transaction);
441
-
442
- // THEN
443
- expect(result.errors.topologyChange).toBeUndefined();
444
- expect(mockedGateway.isTopologyChangeRequiredCached).toHaveBeenCalledWith(
445
- accountWithPartyId.currency,
446
- "test-public-key",
447
- );
448
- });
449
-
450
- it("should not return topology error when isTopologyChangeRequiredCached throws an error", async () => {
451
- // GIVEN
452
- const accountWithPartyId: CantonAccount = {
453
- ...mockAccount,
454
- xpub: "test-party-id",
455
- };
456
- mockedGateway.isTopologyChangeRequiredCached.mockRejectedValue(new Error("Network error"));
457
-
458
- const transaction: Transaction = {
459
- family: "canton",
460
- amount: new BigNumber(100),
461
- recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
462
- fee: new BigNumber(10),
463
- tokenId: "",
464
- };
465
-
466
- // WHEN
467
- const result = await getTransactionStatus(accountWithPartyId, transaction);
468
-
469
- // THEN
470
- expect(result.errors.topologyChange).toBeUndefined();
471
- expect(mockedGateway.isTopologyChangeRequiredCached).toHaveBeenCalledWith(
472
- accountWithPartyId.currency,
473
- "test-public-key",
474
- );
475
- });
476
- });
477
385
  });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AmountRequired,
3
3
  FeeNotLoaded,
4
+ FeeRequired,
4
5
  FeeTooHigh,
5
6
  InvalidAddress,
6
7
  NotEnoughBalanceBecauseDestinationNotCreated,
@@ -20,8 +21,6 @@ import {
20
21
  import { isRecipientValid } from "../common-logic/utils";
21
22
  import coinConfig from "../config";
22
23
  import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets/abandonseed";
23
- import { isTopologyChangeRequiredCached } from "../network/gateway";
24
- import { TopologyChangeError } from "../types/errors";
25
24
 
26
25
  export const TO_MANY_UTXOS_CRITICAL_COUNT = 24;
27
26
  export const TO_MANY_UTXOS_WARNING_COUNT = 10;
@@ -82,15 +81,7 @@ export const getTransactionStatus: AccountBridge<
82
81
  errors.amount = new AmountRequired();
83
82
  }
84
83
 
85
- const utxoWarning = validateUtxoCount(account, transaction);
86
- if (utxoWarning) {
87
- warnings.tooManyUtxos = utxoWarning;
88
- }
89
-
90
- const topologyError = await validateTopology(account);
91
- if (topologyError) {
92
- errors.topologyChange = topologyError;
93
- }
84
+ validateUtxoCount(account, transaction, warnings);
94
85
 
95
86
  return {
96
87
  errors,
@@ -101,57 +92,29 @@ export const getTransactionStatus: AccountBridge<
101
92
  };
102
93
  };
103
94
 
104
- function validateUtxoCount(account: CantonAccount, transaction: Transaction): Error | null {
105
- if (!account.cantonResources?.instrumentUtxoCounts) {
106
- return null;
107
- }
108
-
109
- if (!transaction.recipient) {
110
- return null;
111
- }
112
-
113
- if (!isRecipientValid(transaction.recipient) || account.xpub === transaction.recipient) {
114
- return null;
115
- }
116
-
95
+ function validateUtxoCount(
96
+ account: CantonAccount,
97
+ transaction: Transaction,
98
+ warnings: Record<string, Error>,
99
+ ): void {
117
100
  const abandonSeedAddress = getAbandonSeedAddress(account.currency.id);
118
- if (transaction.recipient.includes(abandonSeedAddress)) {
119
- return null;
120
- }
121
-
122
- const { instrumentUtxoCounts } = account.cantonResources;
123
- const instrumentUtxoCount = instrumentUtxoCounts[transaction.tokenId] || 0;
124
-
125
- if (instrumentUtxoCount > TO_MANY_UTXOS_CRITICAL_COUNT) {
126
- return new TooManyUtxosCritical();
127
- }
128
-
129
- if (instrumentUtxoCount > TO_MANY_UTXOS_WARNING_COUNT) {
130
- return new TooManyUtxosWarning("families.canton.tooManyUtxos.warning");
131
- }
132
-
133
- return null;
134
- }
135
-
136
- export async function validateTopology(account: CantonAccount): Promise<Error | null> {
137
- const publicKey = account.cantonResources?.publicKey;
138
- if (!publicKey) {
139
- return null;
140
- }
141
-
142
- try {
143
- const isTopologyChangeRequired = await isTopologyChangeRequiredCached(
144
- account.currency,
145
- publicKey,
146
- );
147
-
148
- if (!isTopologyChangeRequired) {
149
- return null;
101
+ const isAbandonSeedAddress = transaction.recipient?.includes(abandonSeedAddress);
102
+
103
+ // UTXO count validation - only validate if recipient is valid and not equal to sender
104
+ // Skip validation for abandon seed addresses
105
+ if (
106
+ account?.cantonResources?.instrumentUtxoCounts &&
107
+ transaction.recipient &&
108
+ isRecipientValid(transaction.recipient) &&
109
+ account.xpub !== transaction.recipient &&
110
+ !isAbandonSeedAddress
111
+ ) {
112
+ const { instrumentUtxoCounts } = account.cantonResources;
113
+ const instrumentUtxoCount = instrumentUtxoCounts[transaction.tokenId] || 0;
114
+ if (instrumentUtxoCount > TO_MANY_UTXOS_CRITICAL_COUNT) {
115
+ warnings.tooManyUtxos = new TooManyUtxosCritical();
116
+ } else if (instrumentUtxoCount > TO_MANY_UTXOS_WARNING_COUNT) {
117
+ warnings.tooManyUtxos = new TooManyUtxosWarning("families.canton.tooManyUtxos.warning");
150
118
  }
151
-
152
- return new TopologyChangeError("Topology change detected. Re-onboarding required.");
153
- } catch {
154
- // If topology check fails, don't block the transaction
155
- return null;
156
119
  }
157
120
  }
@@ -1,5 +1,5 @@
1
+ import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
1
2
  import { firstValueFrom, toArray } from "rxjs";
2
- import { getEnv, setEnv } from "@ledgerhq/live-env";
3
3
  import coinConfig from "../config";
4
4
  import { createMockSigner, generateMockKeyPair } from "../test/cantonTestUtils";
5
5
  import { createMockAccount, createMockCantonCurrency } from "../test/fixtures";
@@ -12,12 +12,8 @@ import {
12
12
  OnboardStatus,
13
13
  } from "../types/onboard";
14
14
  import { buildAuthorizePreapproval, buildOnboardAccount, isAccountOnboarded } from "./onboard";
15
- import {
16
- isTopologyChangeRequiredCached,
17
- clearIsTopologyChangeRequiredCache,
18
- } from "../network/gateway";
19
15
 
20
- describe("onboard (devnet)", () => {
16
+ describe.skip("onboard (devnet)", () => {
21
17
  const mockDeviceId = "test-device-id";
22
18
  const mockCurrency = createMockCantonCurrency();
23
19
  const mockAccount = createMockAccount();
@@ -31,7 +27,7 @@ describe("onboard (devnet)", () => {
31
27
 
32
28
  beforeAll(() => {
33
29
  coinConfig.setCoinConfig(() => ({
34
- gatewayUrl: "https://canton-gateway-devnet.api.live.ledger-test.com",
30
+ gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
35
31
  useGateway: true,
36
32
  networkType: "devnet",
37
33
  nativeInstrumentId: "Amulet",
@@ -170,7 +166,7 @@ describe("onboard (devnet)", () => {
170
166
  }, 30000);
171
167
  });
172
168
 
173
- describe.skip("buildAuthorizePreapproval", () => {
169
+ describe("buildAuthorizePreapproval", () => {
174
170
  it("should complete preapproval flow for onboarded account", async () => {
175
171
  // GIVEN
176
172
  const { mockSignerContext, onboardResult } = getOnboardedAccount();
@@ -203,81 +199,4 @@ describe("onboard (devnet)", () => {
203
199
  expect(typeof finalResult.isApproved).toBe("boolean");
204
200
  }, 30000);
205
201
  });
206
-
207
- describe("TopologyChangeError", () => {
208
- it("should require topology change and complete re-onboarding when accessing account from different node", async () => {
209
- // GIVEN
210
- const originalNodeId = getEnv("CANTON_NODE_ID_OVERRIDE");
211
- setEnv("CANTON_NODE_ID_OVERRIDE", "devnet");
212
- const keyPair = generateMockKeyPair();
213
- const mockAccount = createMockAccount({ xpub: keyPair.publicKeyHex });
214
- const mockSigner = createMockSigner(keyPair);
215
- const mockSignerContext = jest.fn().mockImplementation((_, callback) => {
216
- return callback(mockSigner);
217
- });
218
-
219
- const onboardObservable = buildOnboardAccount(mockSignerContext);
220
- const onboardValues = await firstValueFrom(
221
- onboardObservable(mockCurrency, mockDeviceId, mockAccount).pipe(toArray()),
222
- );
223
- const onboardResult = onboardValues.find(
224
- (value): value is CantonOnboardResult => "partyId" in value,
225
- );
226
-
227
- if (!onboardResult) {
228
- throw new Error("Failed to onboard account");
229
- }
230
-
231
- const partyId = onboardResult.partyId;
232
- expect(partyId).toBeDefined();
233
-
234
- // Verify account is accessible on devnet node
235
- const isTopologyChangeRequiredOnDevnet = await isTopologyChangeRequiredCached(
236
- mockCurrency,
237
- keyPair.publicKeyHex,
238
- );
239
- expect(isTopologyChangeRequiredOnDevnet).toBe(false);
240
-
241
- // WHEN: Switch to different node
242
- setEnv("CANTON_NODE_ID_OVERRIDE", "devnet-replicated");
243
- clearIsTopologyChangeRequiredCache(mockCurrency, keyPair.publicKeyHex);
244
-
245
- // THEN: Verify topology change is required
246
- const isTopologyChangeRequiredOnReplicated = await isTopologyChangeRequiredCached(
247
- mockCurrency,
248
- keyPair.publicKeyHex,
249
- );
250
- expect(isTopologyChangeRequiredOnReplicated).toBe(true);
251
-
252
- // AND: Verify re-onboarding proceeds through full onboarding flow
253
- const reonboardObservable = buildOnboardAccount(mockSignerContext);
254
- const reonboardValues = await firstValueFrom(
255
- reonboardObservable(mockCurrency, mockDeviceId, mockAccount).pipe(toArray()),
256
- );
257
- const progressValues = reonboardValues.filter(
258
- (value): value is CantonOnboardProgress => "status" in value && !("partyId" in value),
259
- );
260
- const resultValues = reonboardValues.filter(
261
- (value): value is CantonOnboardResult => "partyId" in value,
262
- );
263
-
264
- // Check expected status progression
265
- expect(progressValues.some(p => p.status === OnboardStatus.INIT)).toBe(true);
266
- expect(progressValues.some(p => p.status === OnboardStatus.PREPARE)).toBe(true);
267
- expect(progressValues.some(p => p.status === OnboardStatus.SIGN)).toBe(true);
268
- expect(progressValues.some(p => p.status === OnboardStatus.SUBMIT)).toBe(true);
269
-
270
- // Check final result
271
- expect(resultValues.length).toBeGreaterThan(0);
272
- const finalResult = resultValues[resultValues.length - 1];
273
- expect(finalResult.partyId).toBeDefined();
274
- expect(typeof finalResult.partyId).toBe("string");
275
-
276
- if (originalNodeId) {
277
- setEnv("CANTON_NODE_ID_OVERRIDE", originalNodeId);
278
- } else {
279
- setEnv("CANTON_NODE_ID_OVERRIDE", "");
280
- }
281
- }, 60000);
282
- });
283
202
  });
@@ -1,17 +1,9 @@
1
1
  import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
2
- import { firstValueFrom, toArray } from "rxjs";
3
- import { SignerContext } from "@ledgerhq/coin-framework/signer";
4
- import { buildOnboardAccount, isCantonCoinPreapproved } from "./onboard";
2
+ import { isCantonCoinPreapproved } from "./onboard";
5
3
  import * as gateway from "../network/gateway";
6
- import * as signTransactionModule from "../common-logic/transaction/sign";
7
- import { createMockAccount } from "../test/fixtures";
8
- import type { CantonSigner, CantonSignature } from "../types";
9
- import { OnboardStatus, CantonOnboardProgress, CantonOnboardResult } from "../types/onboard";
10
4
 
11
5
  jest.mock("../network/gateway");
12
- jest.mock("../common-logic/transaction/sign");
13
6
  const mockedGateway = gateway as jest.Mocked<typeof gateway>;
14
- const mockedSignTransaction = signTransactionModule as jest.Mocked<typeof signTransactionModule>;
15
7
 
16
8
  describe("onboard", () => {
17
9
  const mockPartyId = "test-party-id";
@@ -64,102 +56,4 @@ describe("onboard", () => {
64
56
  expect(mockedGateway.getTransferPreApproval).toHaveBeenCalledWith(mockCurrency, mockPartyId);
65
57
  });
66
58
  });
67
-
68
- describe("buildOnboardAccount", () => {
69
- const mockDeviceId = "test-device-id";
70
- const mockPublicKey = "test-public-key";
71
- const mockPartyId = "test-party-id";
72
- const mockSignature: CantonSignature = {
73
- signature: "test-signature",
74
- };
75
-
76
- const mockSigner: CantonSigner = {
77
- getAddress: jest.fn().mockResolvedValue({
78
- address: "test-address",
79
- publicKey: mockPublicKey,
80
- path: "44'/6767'/0'/0/0",
81
- }),
82
- signTransaction: jest.fn().mockResolvedValue(mockSignature),
83
- } as unknown as CantonSigner;
84
-
85
- const mockSignerContext: SignerContext<CantonSigner> = jest.fn(
86
- async (deviceId: string, callback: (signer: CantonSigner) => Promise<CantonSignature>) => {
87
- return callback(mockSigner);
88
- },
89
- ) as unknown as SignerContext<CantonSigner>;
90
-
91
- beforeEach(() => {
92
- jest.clearAllMocks();
93
- });
94
-
95
- it("should skip submission when account is onboarded on network but has no local xpub", async () => {
96
- // GIVEN
97
- const account = createMockAccount({ xpub: undefined });
98
- mockedGateway.getPartyByPubKey.mockResolvedValue({ party_id: mockPartyId });
99
-
100
- const onboardObservable = buildOnboardAccount(mockSignerContext);
101
- const values = await firstValueFrom(
102
- onboardObservable(mockCurrency, mockDeviceId, account).pipe(toArray()),
103
- );
104
-
105
- // THEN
106
- const result = values.find((v): v is CantonOnboardResult => "partyId" in v);
107
- expect(result).toBeDefined();
108
- expect(result?.partyId).toBe(mockPartyId);
109
- expect(result?.account.xpub).toBe(mockPartyId);
110
-
111
- // Should NOT call prepareOnboarding or submitOnboarding
112
- expect(mockedGateway.prepareOnboarding).not.toHaveBeenCalled();
113
- expect(mockedGateway.submitOnboarding).not.toHaveBeenCalled();
114
- expect(mockedSignTransaction.signTransaction).not.toHaveBeenCalled();
115
- });
116
-
117
- it("should proceed with submission when account has xpub (re-onboarding scenario)", async () => {
118
- // GIVEN - account already has xpub (re-onboarding)
119
- const existingPartyId = "existing-party-id";
120
- const account = createMockAccount({ xpub: existingPartyId });
121
- const newPartyId = "new-party-id";
122
-
123
- mockedGateway.getPartyByPubKey.mockResolvedValue({ party_id: existingPartyId });
124
- mockedGateway.prepareOnboarding.mockResolvedValue({
125
- party_id: newPartyId,
126
- transactions: {},
127
- });
128
- mockedGateway.submitOnboarding.mockResolvedValue({
129
- party: {
130
- party_id: newPartyId,
131
- public_key: mockPublicKey,
132
- },
133
- });
134
- mockedSignTransaction.signTransaction.mockResolvedValue(mockSignature);
135
-
136
- const onboardObservable = buildOnboardAccount(mockSignerContext);
137
- const values = await firstValueFrom(
138
- onboardObservable(mockCurrency, mockDeviceId, account).pipe(toArray()),
139
- );
140
-
141
- // THEN - should proceed through full onboarding flow
142
- const statuses = values
143
- .filter((v): v is CantonOnboardProgress => "status" in v)
144
- .map(v => v.status);
145
- expect(statuses).toContain(OnboardStatus.PREPARE);
146
- expect(statuses).toContain(OnboardStatus.SIGN);
147
- expect(statuses).toContain(OnboardStatus.SUBMIT);
148
-
149
- // Should call prepareOnboarding and submitOnboarding
150
- expect(mockedGateway.prepareOnboarding).toHaveBeenCalledWith(mockCurrency, mockPublicKey);
151
- expect(mockedGateway.submitOnboarding).toHaveBeenCalled();
152
- expect(mockedSignTransaction.signTransaction).toHaveBeenCalled();
153
-
154
- // Should clear topology change cache
155
- expect(mockedGateway.clearIsTopologyChangeRequiredCache).toHaveBeenCalledWith(
156
- mockCurrency,
157
- mockPublicKey,
158
- );
159
-
160
- const result = values.find((v): v is CantonOnboardResult => "partyId" in v);
161
- expect(result).toBeDefined();
162
- expect(result?.partyId).toBe(newPartyId);
163
- });
164
- });
165
59
  });
@@ -16,7 +16,6 @@ import {
16
16
  preparePreApprovalTransaction,
17
17
  submitPreApprovalTransaction,
18
18
  getTransferPreApproval,
19
- clearIsTopologyChangeRequiredCache,
20
19
  } from "../network/gateway";
21
20
  import { signTransaction } from "../common-logic/transaction/sign";
22
21
  import {
@@ -91,9 +90,7 @@ export const buildOnboardAccount =
91
90
 
92
91
  let { partyId } = await isAccountOnboarded(currency, publicKey);
93
92
 
94
- // Skip submission only if account is onboarded on network but has no local xpub.
95
- // For re-onboarding (account has xpub), always proceed to submit a new onboarding transaction.
96
- if (partyId && !account.xpub) {
93
+ if (partyId) {
97
94
  const onboardedAccount = createOnboardedAccount(account, partyId, currency);
98
95
  o.next({ partyId, account: onboardedAccount }); // success
99
96
  return;
@@ -112,8 +109,6 @@ export const buildOnboardAccount =
112
109
 
113
110
  await submitOnboarding(currency, publicKey, preparedTransaction, signature);
114
111
 
115
- clearIsTopologyChangeRequiredCache(currency, publicKey);
116
-
117
112
  const onboardedAccount = createOnboardedAccount(account, partyId, currency);
118
113
  o.next({ partyId, account: onboardedAccount }); // success
119
114
  }
@@ -16,7 +16,7 @@ describe("prepareTransaction", () => {
16
16
 
17
17
  beforeAll(async () => {
18
18
  coinConfig.setCoinConfig(() => ({
19
- gatewayUrl: "https://canton-gateway-devnet.api.live.ledger-test.com",
19
+ gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
20
20
  useGateway: true,
21
21
  networkType: "devnet",
22
22
  nativeInstrumentId: "Amulet",