@ledgerhq/coin-canton 0.7.0 → 0.8.0-nightly.1

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 (133) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/lib/api/index.d.ts.map +1 -1
  4. package/lib/api/index.js +3 -0
  5. package/lib/api/index.js.map +1 -1
  6. package/lib/api/lastBlock.integ.test.js +0 -15
  7. package/lib/api/lastBlock.integ.test.js.map +1 -1
  8. package/lib/bridge/getTransactionStatus.test.d.ts +2 -0
  9. package/lib/bridge/getTransactionStatus.test.d.ts.map +1 -0
  10. package/lib/bridge/getTransactionStatus.test.js +365 -0
  11. package/lib/bridge/getTransactionStatus.test.js.map +1 -0
  12. package/lib/bridge/index.d.ts.map +1 -1
  13. package/lib/bridge/index.js +5 -5
  14. package/lib/bridge/index.js.map +1 -1
  15. package/lib/bridge/onboard.d.ts +11 -6
  16. package/lib/bridge/onboard.d.ts.map +1 -1
  17. package/lib/bridge/onboard.integ.test.js +49 -27
  18. package/lib/bridge/onboard.integ.test.js.map +1 -1
  19. package/lib/bridge/onboard.js +45 -152
  20. package/lib/bridge/onboard.js.map +1 -1
  21. package/lib/bridge/signOperation.d.ts.map +1 -1
  22. package/lib/bridge/signOperation.js +5 -5
  23. package/lib/bridge/signOperation.js.map +1 -1
  24. package/lib/bridge/sync.d.ts +3 -2
  25. package/lib/bridge/sync.d.ts.map +1 -1
  26. package/lib/bridge/sync.integ.test.js +39 -17
  27. package/lib/bridge/sync.integ.test.js.map +1 -1
  28. package/lib/bridge/sync.js +71 -57
  29. package/lib/bridge/sync.js.map +1 -1
  30. package/lib/common-logic/utils.d.ts.map +1 -1
  31. package/lib/common-logic/utils.js +3 -1
  32. package/lib/common-logic/utils.js.map +1 -1
  33. package/lib/common-logic/utils.test.d.ts +2 -0
  34. package/lib/common-logic/utils.test.d.ts.map +1 -0
  35. package/lib/common-logic/utils.test.js +104 -0
  36. package/lib/common-logic/utils.test.js.map +1 -0
  37. package/lib/config.d.ts +1 -1
  38. package/lib/config.d.ts.map +1 -1
  39. package/lib/network/gateway.d.ts +14 -10
  40. package/lib/network/gateway.d.ts.map +1 -1
  41. package/lib/network/gateway.integ.test.js +31 -17
  42. package/lib/network/gateway.integ.test.js.map +1 -1
  43. package/lib/network/gateway.js +34 -16
  44. package/lib/network/gateway.js.map +1 -1
  45. package/lib/network/gateway.test.d.ts +2 -0
  46. package/lib/network/gateway.test.d.ts.map +1 -0
  47. package/lib/network/gateway.test.js +59 -0
  48. package/lib/network/gateway.test.js.map +1 -0
  49. package/lib/types/bridge.d.ts +6 -16
  50. package/lib/types/bridge.d.ts.map +1 -1
  51. package/lib/types/onboard.d.ts +5 -5
  52. package/lib/types/onboard.d.ts.map +1 -1
  53. package/lib/types/onboard.js +10 -10
  54. package/lib/types/onboard.js.map +1 -1
  55. package/lib-es/api/index.d.ts.map +1 -1
  56. package/lib-es/api/index.js +3 -0
  57. package/lib-es/api/index.js.map +1 -1
  58. package/lib-es/api/lastBlock.integ.test.js +0 -15
  59. package/lib-es/api/lastBlock.integ.test.js.map +1 -1
  60. package/lib-es/bridge/getTransactionStatus.test.d.ts +2 -0
  61. package/lib-es/bridge/getTransactionStatus.test.d.ts.map +1 -0
  62. package/lib-es/bridge/getTransactionStatus.test.js +360 -0
  63. package/lib-es/bridge/getTransactionStatus.test.js.map +1 -0
  64. package/lib-es/bridge/index.d.ts.map +1 -1
  65. package/lib-es/bridge/index.js +6 -6
  66. package/lib-es/bridge/index.js.map +1 -1
  67. package/lib-es/bridge/onboard.d.ts +11 -6
  68. package/lib-es/bridge/onboard.d.ts.map +1 -1
  69. package/lib-es/bridge/onboard.integ.test.js +37 -15
  70. package/lib-es/bridge/onboard.integ.test.js.map +1 -1
  71. package/lib-es/bridge/onboard.js +44 -152
  72. package/lib-es/bridge/onboard.js.map +1 -1
  73. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  74. package/lib-es/bridge/signOperation.js +5 -5
  75. package/lib-es/bridge/signOperation.js.map +1 -1
  76. package/lib-es/bridge/sync.d.ts +3 -2
  77. package/lib-es/bridge/sync.d.ts.map +1 -1
  78. package/lib-es/bridge/sync.integ.test.js +34 -12
  79. package/lib-es/bridge/sync.integ.test.js.map +1 -1
  80. package/lib-es/bridge/sync.js +71 -56
  81. package/lib-es/bridge/sync.js.map +1 -1
  82. package/lib-es/common-logic/utils.d.ts.map +1 -1
  83. package/lib-es/common-logic/utils.js +3 -1
  84. package/lib-es/common-logic/utils.js.map +1 -1
  85. package/lib-es/common-logic/utils.test.d.ts +2 -0
  86. package/lib-es/common-logic/utils.test.d.ts.map +1 -0
  87. package/lib-es/common-logic/utils.test.js +99 -0
  88. package/lib-es/common-logic/utils.test.js.map +1 -0
  89. package/lib-es/config.d.ts +1 -1
  90. package/lib-es/config.d.ts.map +1 -1
  91. package/lib-es/network/gateway.d.ts +14 -10
  92. package/lib-es/network/gateway.d.ts.map +1 -1
  93. package/lib-es/network/gateway.integ.test.js +31 -17
  94. package/lib-es/network/gateway.integ.test.js.map +1 -1
  95. package/lib-es/network/gateway.js +34 -16
  96. package/lib-es/network/gateway.js.map +1 -1
  97. package/lib-es/network/gateway.test.d.ts +2 -0
  98. package/lib-es/network/gateway.test.d.ts.map +1 -0
  99. package/lib-es/network/gateway.test.js +54 -0
  100. package/lib-es/network/gateway.test.js.map +1 -0
  101. package/lib-es/types/bridge.d.ts +6 -16
  102. package/lib-es/types/bridge.d.ts.map +1 -1
  103. package/lib-es/types/onboard.d.ts +5 -5
  104. package/lib-es/types/onboard.d.ts.map +1 -1
  105. package/lib-es/types/onboard.js +9 -9
  106. package/lib-es/types/onboard.js.map +1 -1
  107. package/package.json +8 -7
  108. package/src/api/index.ts +9 -0
  109. package/src/api/lastBlock.integ.test.ts +0 -18
  110. package/src/bridge/getTransactionStatus.test.ts +446 -0
  111. package/src/bridge/index.ts +6 -6
  112. package/src/bridge/onboard.integ.test.ts +44 -31
  113. package/src/bridge/onboard.ts +61 -209
  114. package/src/bridge/signOperation.ts +5 -6
  115. package/src/bridge/sync.integ.test.ts +38 -13
  116. package/src/bridge/sync.ts +90 -72
  117. package/src/common-logic/utils.test.ts +108 -0
  118. package/src/common-logic/utils.ts +4 -1
  119. package/src/config.ts +1 -1
  120. package/src/network/gateway.integ.test.ts +48 -21
  121. package/src/network/gateway.test.ts +66 -0
  122. package/src/network/gateway.ts +60 -37
  123. package/src/types/bridge.ts +8 -19
  124. package/src/types/onboard.ts +5 -5
  125. package/lib/bridge/serialization.d.ts +0 -4
  126. package/lib/bridge/serialization.d.ts.map +0 -1
  127. package/lib/bridge/serialization.js +0 -31
  128. package/lib/bridge/serialization.js.map +0 -1
  129. package/lib-es/bridge/serialization.d.ts +0 -4
  130. package/lib-es/bridge/serialization.d.ts.map +0 -1
  131. package/lib-es/bridge/serialization.js +0 -27
  132. package/lib-es/bridge/serialization.js.map +0 -1
  133. package/src/bridge/serialization.ts +0 -36
@@ -1,11 +1,14 @@
1
1
  import BigNumber from "bignumber.js";
2
2
  import { Operation, OperationType } from "@ledgerhq/types-live";
3
- import { decodeAccountId, encodeAccountId } from "@ledgerhq/coin-framework/account/index";
3
+ import { encodeAccountId } from "@ledgerhq/coin-framework/account/index";
4
4
  import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
5
5
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
6
+ import { SignerContext } from "@ledgerhq/coin-framework/signer";
6
7
  import { getBalance, getLedgerEnd, getOperations, type OperationInfo } from "../network/gateway";
7
- import { CantonAccount } from "../types";
8
8
  import coinConfig from "../config";
9
+ import resolver from "../signer";
10
+ import { CantonAccount, CantonSigner } from "../types";
11
+ import { isAccountOnboarded, isAccountAuthorized } from "./onboard";
9
12
 
10
13
  const txInfoToOperationAdapter =
11
14
  (accountId: string, partyId: string) =>
@@ -30,7 +33,6 @@ const txInfoToOperationAdapter =
30
33
  } else if (txInfo.type === "Initialize") {
31
34
  type = "PRE_APPROVAL";
32
35
  }
33
-
34
36
  const value = new BigNumber(transferValue);
35
37
  const feeValue = new BigNumber(fee);
36
38
  const memo = details.metadata.reason;
@@ -65,76 +67,92 @@ const filterOperations = (
65
67
  return transactions.map(txInfoToOperationAdapter(accountId, partyId));
66
68
  };
67
69
 
68
- export const getAccountShape: GetAccountShape<CantonAccount> = async info => {
69
- const { address, initialAccount, currency, derivationMode, derivationPath, rest } = info;
70
-
71
- const xpubOrAddress = (
72
- (initialAccount?.id && decodeAccountId(initialAccount.id).xpubOrAddress) ||
73
- ""
74
- ).replace(/:/g, "_");
75
- const partyId =
76
- rest?.cantonResources?.partyId ||
77
- initialAccount?.cantonResources?.partyId ||
78
- xpubOrAddress.replace(/_/g, ":");
79
-
80
- const accountId = encodeAccountId({
81
- type: "js",
82
- version: "2",
83
- currencyId: currency.id,
84
- xpubOrAddress,
85
- derivationMode,
86
- });
87
-
88
- // Account info retrieval + spendable balance calculation
89
- // const accountInfo = await getAccountInfo(address);
90
- const balances = await getBalance(currency, partyId);
91
-
92
- const balanceData = balances.find(
93
- balance => balance.instrument_id === coinConfig.getCoinConfig(currency).nativeInstrumentId,
94
- ) || {
95
- instrument_id: coinConfig.getCoinConfig(currency).nativeInstrumentId,
96
- amount: 0,
97
- locked: false,
98
- };
70
+ export function makeGetAccountShape(
71
+ signerContext: SignerContext<CantonSigner>,
72
+ ): GetAccountShape<CantonAccount> {
73
+ return async info => {
74
+ const { address, currency, derivationMode, derivationPath, initialAccount } = info;
75
+
76
+ let xpubOrAddress = initialAccount?.xpub || "";
77
+
78
+ if (!xpubOrAddress) {
79
+ const getAddress = resolver(signerContext);
80
+ const { publicKey } = await getAddress(info.deviceId || "", {
81
+ path: derivationPath,
82
+ currency: currency,
83
+ derivationMode: derivationMode,
84
+ verify: false,
85
+ });
86
+
87
+ const { isOnboarded, partyId } = await isAccountOnboarded(currency, publicKey);
88
+ if (isOnboarded && partyId) {
89
+ xpubOrAddress = partyId;
90
+ }
91
+ }
99
92
 
100
- const balance = new BigNumber(balanceData.amount);
101
- const reserveMin = coinConfig.getCoinConfig(currency).minReserve || 0;
102
- const lockedAmount = balanceData.locked ? balance : new BigNumber(0);
103
- const spendableBalance = BigNumber.max(
104
- 0,
105
- balance.minus(lockedAmount).minus(BigNumber(reserveMin)),
106
- );
107
-
108
- let operations: Operation[] = [];
109
- // Tx history fetching if xpubOrAddress is not empty
110
- if (xpubOrAddress) {
111
- const oldOperations = initialAccount?.operations || [];
112
- const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0;
113
- const transactionData = await getOperations(currency, partyId, {
114
- cursor: startAt,
115
- limit: 100,
93
+ const accountId = encodeAccountId({
94
+ type: "js",
95
+ version: "2",
96
+ currencyId: currency.id,
97
+ xpubOrAddress: xpubOrAddress,
98
+ derivationMode,
116
99
  });
117
100
 
118
- const newOperations = filterOperations(transactionData.operations, accountId, partyId);
119
- operations = mergeOps(oldOperations, newOperations);
120
- }
121
- // blockheight retrieval
122
- const blockHeight = await getLedgerEnd(currency);
123
- // We return the new account shape
124
- const shape = {
125
- id: accountId,
126
- xpub: xpubOrAddress,
127
- blockHeight,
128
- balance,
129
- spendableBalance,
130
- operations,
131
- operationsCount: operations.length,
132
- freshAddress: address,
133
- freshAddressPath: derivationPath,
134
- cantonResources: {
135
- partyId,
136
- },
137
- };
101
+ const { nativeInstrumentId } = coinConfig.getCoinConfig(currency);
102
+ const balances = xpubOrAddress ? await getBalance(currency, xpubOrAddress) : [];
138
103
 
139
- return shape;
140
- };
104
+ const balancesData = (balances || []).reduce(
105
+ (acc, { amount, instrument_id, locked }) => {
106
+ acc[instrument_id] = { amount, locked };
107
+ return acc;
108
+ },
109
+ {} as Record<string, { amount: string; locked: boolean }>,
110
+ );
111
+
112
+ const unlockedAmount = new BigNumber(balancesData[nativeInstrumentId]?.amount || "0");
113
+ const lockedAmount = new BigNumber(balancesData[`Locked${nativeInstrumentId}`]?.amount || "0");
114
+ const totalBalance = unlockedAmount.plus(lockedAmount);
115
+ const reserveMin = new BigNumber(coinConfig.getCoinConfig(currency).minReserve || 0);
116
+ const spendableBalance = BigNumber.max(0, unlockedAmount.minus(reserveMin));
117
+
118
+ let operations: Operation[] = [];
119
+ if (xpubOrAddress) {
120
+ const oldOperations = initialAccount?.operations || [];
121
+ const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0;
122
+ const transactionData = await getOperations(currency, xpubOrAddress, {
123
+ cursor: startAt,
124
+ limit: 100,
125
+ });
126
+ const newOperations = filterOperations(transactionData.operations, accountId, xpubOrAddress);
127
+ operations = mergeOps(oldOperations, newOperations);
128
+ }
129
+
130
+ const isAuthorized = await isAccountAuthorized(operations, xpubOrAddress);
131
+ const used = isAuthorized && totalBalance.gt(0);
132
+
133
+ const blockHeight = await getLedgerEnd(currency);
134
+
135
+ const creationDate =
136
+ operations.length > 0
137
+ ? new Date(Math.min(...operations.map(op => op.date.getTime())))
138
+ : new Date();
139
+
140
+ const shape = {
141
+ id: accountId,
142
+ type: "Account" as const,
143
+ balance: totalBalance,
144
+ blockHeight,
145
+ creationDate,
146
+ lastSyncDate: new Date(),
147
+ freshAddress: address,
148
+ seedIdentifier: address,
149
+ operations,
150
+ operationsCount: operations.length,
151
+ spendableBalance,
152
+ xpub: xpubOrAddress,
153
+ used,
154
+ };
155
+
156
+ return shape;
157
+ };
158
+ }
@@ -0,0 +1,108 @@
1
+ import { isRecipientValid, validateTag } from "./utils";
2
+ import BigNumber from "bignumber.js";
3
+
4
+ describe("utils", () => {
5
+ describe("isRecipientValid", () => {
6
+ it("should return true for valid Canton addresses", () => {
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
20
+ ];
21
+
22
+ validAddresses.forEach(address => {
23
+ expect(isRecipientValid(address)).toBe(true);
24
+ });
25
+ });
26
+
27
+ it("should return false for invalid Canton addresses", () => {
28
+ const invalidAddresses = [
29
+ "", // empty string
30
+ "::123", // no characters before ::
31
+ "abc::", // no characters after ::
32
+ "abc:123", // single colon instead of double
33
+ "abc::", // empty after ::
34
+ "::", // only colons
35
+ "abc", // no colons
36
+ "123", // no colons
37
+ "abc::123.456", // decimal numbers
38
+ "abc::-123", // negative numbers
39
+ "abc::+123", // positive sign
40
+ "abc:: 123", // space before alphanumeric
41
+ "abc::123 ", // space after alphanumeric
42
+ "abc:: 123", // space after ::
43
+ "abc::123-abc", // dash in alphanumeric part
44
+ "abc::123_abc", // underscore in alphanumeric part
45
+ "abc::123.abc", // dot in alphanumeric part
46
+ "abc::123 abc", // space in alphanumeric part
47
+ "abc::", // empty after ::
48
+ ];
49
+
50
+ invalidAddresses.forEach(address => {
51
+ expect(isRecipientValid(address)).toBe(false);
52
+ });
53
+ });
54
+
55
+ 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
66
+ });
67
+
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
76
+ });
77
+ });
78
+
79
+ describe("validateTag", () => {
80
+ it("should return true for valid tags", () => {
81
+ const validTags = [
82
+ new BigNumber(1),
83
+ new BigNumber(42),
84
+ new BigNumber(4294967295), // UINT32_MAX
85
+ new BigNumber(0),
86
+ ];
87
+
88
+ validTags.forEach(tag => {
89
+ expect(validateTag(tag)).toBe(true);
90
+ });
91
+ });
92
+
93
+ it("should return false for invalid tags", () => {
94
+ const invalidTags = [
95
+ new BigNumber(-1), // negative
96
+ new BigNumber(4294967296), // greater than UINT32_MAX
97
+ new BigNumber(1.5), // decimal
98
+ new BigNumber(NaN), // NaN
99
+ new BigNumber(Infinity), // Infinity
100
+ new BigNumber(-Infinity), // -Infinity
101
+ ];
102
+
103
+ invalidTags.forEach(tag => {
104
+ expect(validateTag(tag)).toBe(false);
105
+ });
106
+ });
107
+ });
108
+ });
@@ -8,6 +8,9 @@ export const validateTag = (tag: BigNumber) => {
8
8
  );
9
9
  };
10
10
 
11
+ const CANTON_ADDRESS_REGEX = /^.+::[a-zA-Z0-9]+$/;
12
+
11
13
  export function isRecipientValid(recipient: string): boolean {
12
- return recipient.length > 0;
14
+ // Canton address format: at least 1 character :: at least 1 alphanumeric character
15
+ return CANTON_ADDRESS_REGEX.test(recipient);
13
16
  }
package/src/config.ts CHANGED
@@ -7,7 +7,7 @@ export type CantonConfig = {
7
7
  gatewayUrl?: string;
8
8
  // TODELETE
9
9
  minReserve?: number;
10
- networkType: "mainnet" | "devnet" | "localnet";
10
+ networkType: "mainnet" | "devnet" | "testnet" | "localnet";
11
11
  useGateway?: boolean;
12
12
  nativeInstrumentId: string;
13
13
  fee?: number;
@@ -12,6 +12,7 @@ import {
12
12
  submitTapRequest,
13
13
  preparePreApprovalTransaction,
14
14
  submitPreApprovalTransaction,
15
+ type OnboardingPrepareResponse,
15
16
  } from "./gateway";
16
17
  import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
17
18
 
@@ -25,6 +26,8 @@ describe("gateway (devnet)", () => {
25
26
  partyId: string;
26
27
  } | null = null;
27
28
 
29
+ let prepareResponse: OnboardingPrepareResponse | null = null;
30
+
28
31
  beforeAll(async () => {
29
32
  coinConfig.setCoinConfig(() => ({
30
33
  gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
@@ -35,7 +38,7 @@ describe("gateway (devnet)", () => {
35
38
  type: "active",
36
39
  },
37
40
  }));
38
- }, 60000);
41
+ });
39
42
 
40
43
  const getOnboardedAccount = () => {
41
44
  if (!onboardedAccount) {
@@ -56,7 +59,7 @@ describe("gateway (devnet)", () => {
56
59
  };
57
60
 
58
61
  // WHEN
59
- const response = await prepareOnboarding(mockCurrency, keyPair.publicKeyHex, "ed25519");
62
+ const response = await prepareOnboarding(mockCurrency, keyPair.publicKeyHex);
60
63
 
61
64
  // THEN
62
65
  expect(response).toHaveProperty("party_id");
@@ -68,30 +71,26 @@ describe("gateway (devnet)", () => {
68
71
  expect(typeof response.party_name).toBe("string");
69
72
 
70
73
  expect(response.public_key_fingerprint).toBe(keyPair.fingerprint);
71
- }, 30000);
74
+ });
72
75
  });
73
76
 
74
77
  describe("submitOnboarding", () => {
75
78
  it("should submit onboarding with proper signature", async () => {
76
79
  // GIVEN
77
80
  const { keyPair } = getOnboardedAccount();
78
- const prepareRequest = { public_key: keyPair.publicKeyHex, public_key_type: "ed25519" };
79
- const prepareResponse = await prepareOnboarding(
80
- mockCurrency,
81
- keyPair.publicKeyHex,
82
- "ed25519",
83
- );
81
+ // Save prepare response for next test
82
+ prepareResponse = await prepareOnboarding(mockCurrency, keyPair.publicKeyHex);
84
83
  const signature = keyPair.sign(prepareResponse.transactions.combined_hash);
85
84
 
86
85
  // WHEN
87
86
  const response = await submitOnboarding(
88
87
  mockCurrency,
89
- prepareRequest,
88
+ keyPair.publicKeyHex,
90
89
  prepareResponse,
91
90
  signature,
92
91
  );
93
92
 
94
- // Save onboarded account for all tests that need a valid party ID
93
+ // Save onboarded account for next tests that need a valid party ID
95
94
  onboardedAccount = {
96
95
  keyPair,
97
96
  partyId: response.party.party_id,
@@ -103,6 +102,31 @@ describe("gateway (devnet)", () => {
103
102
  expect(response.party).toHaveProperty("public_key");
104
103
  expect(response.party.public_key).toBe(keyPair.publicKeyHex);
105
104
  }, 30000);
105
+
106
+ const testIfPrepared = prepareResponse ? it.skip : it;
107
+ testIfPrepared(
108
+ "should not throw when already onboarded",
109
+ async () => {
110
+ // GIVEN
111
+ const { keyPair } = getOnboardedAccount();
112
+ const signature = keyPair.sign(prepareResponse!.transactions.combined_hash);
113
+
114
+ // WHEN
115
+ const response = await submitOnboarding(
116
+ mockCurrency,
117
+ keyPair.publicKeyHex,
118
+ prepareResponse!,
119
+ signature,
120
+ );
121
+
122
+ // THEN
123
+ expect(response).toHaveProperty("party");
124
+ expect(response.party).toHaveProperty("party_id");
125
+ expect(response.party).toHaveProperty("public_key");
126
+ expect(response.party.public_key).toBe(keyPair.publicKeyHex);
127
+ },
128
+ 30000,
129
+ );
106
130
  });
107
131
 
108
132
  describe("getLedgerEnd", () => {
@@ -125,17 +149,20 @@ describe("gateway (devnet)", () => {
125
149
  });
126
150
 
127
151
  describe("getPartyById", () => {
128
- it.skip("should return party info", async () => {
129
- const party = await getPartyById(mockCurrency, "4f2e1485107adf5f");
152
+ it("should return party info", async () => {
153
+ const party = await getPartyById(
154
+ mockCurrency,
155
+ "ldg::12208b12fa34be8a079bcbb68bba828e58313046c4208855b39885fab48661322e68",
156
+ );
130
157
  expect(party).toBeDefined();
131
158
  });
132
159
  });
133
160
 
134
161
  describe("getPartyByPubKey", () => {
135
- it.skip("should return party info", async () => {
162
+ it("should return party info", async () => {
136
163
  const party = await getPartyByPubKey(
137
164
  mockCurrency,
138
- "122027c6dbbbdbffe0fa3122ae05175f3b9328e879e9ce96b670354deb64a45683c1",
165
+ "c5cdb19624833f9a929a0125978c886ec4297320c14cea6bf667dc1d23a8e650",
139
166
  );
140
167
  expect(party).toBeDefined();
141
168
  });
@@ -153,7 +180,7 @@ describe("gateway (devnet)", () => {
153
180
  });
154
181
 
155
182
  describe("prepareTapRequest", () => {
156
- it("should prepare tap request for onboarded party", async () => {
183
+ it.skip("should prepare tap request for onboarded party", async () => {
157
184
  // GIVEN
158
185
  const { partyId } = getOnboardedAccount();
159
186
  const amount = 1000;
@@ -166,11 +193,11 @@ describe("gateway (devnet)", () => {
166
193
  expect(response).toHaveProperty("hash");
167
194
  expect(typeof response.serialized).toBe("string");
168
195
  expect(typeof response.hash).toBe("string");
169
- }, 30000);
196
+ });
170
197
  });
171
198
 
172
199
  describe("submitTapRequest", () => {
173
- it("should submit tap request with proper signature", async () => {
200
+ it.skip("should submit tap request with proper signature", async () => {
174
201
  // GIVEN
175
202
  const { keyPair, partyId } = getOnboardedAccount();
176
203
  const tapPrepareResponse = await prepareTapRequest(mockCurrency, {
@@ -191,7 +218,7 @@ describe("gateway (devnet)", () => {
191
218
  expect(response).toHaveProperty("update_id");
192
219
  expect(typeof response.submission_id).toBe("string");
193
220
  expect(typeof response.update_id).toBe("string");
194
- }, 30000);
221
+ });
195
222
  });
196
223
 
197
224
  describe("preparePreApprovalTransaction", () => {
@@ -207,7 +234,7 @@ describe("gateway (devnet)", () => {
207
234
  expect(response).toHaveProperty("hash");
208
235
  expect(typeof response.serialized).toBe("string");
209
236
  expect(typeof response.hash).toBe("string");
210
- }, 30000);
237
+ });
211
238
  });
212
239
 
213
240
  describe("submitPreApprovalTransaction", () => {
@@ -232,6 +259,6 @@ describe("gateway (devnet)", () => {
232
259
  expect(response.isApproved).toBe(true);
233
260
  expect(typeof response.submissionId).toBe("string");
234
261
  expect(typeof response.updateId).toBe("string");
235
- }, 30000);
262
+ });
236
263
  });
237
264
  });
@@ -0,0 +1,66 @@
1
+ import { getBalance, type GetBalanceResponse, type InstrumentBalance } from "./gateway";
2
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
3
+ import coinConfig from "../config";
4
+
5
+ jest.mock("@ledgerhq/live-network", () => ({
6
+ __esModule: true,
7
+ default: jest.fn(),
8
+ }));
9
+
10
+ import network from "@ledgerhq/live-network";
11
+
12
+ const mockBalances: InstrumentBalance[] = [
13
+ {
14
+ instrument_id: "Amulet",
15
+ amount: "10000000000000000000000000000000000000000",
16
+ locked: false,
17
+ },
18
+ {
19
+ instrument_id: "LockedAmulet",
20
+ amount: "5000000000000000000000000000000000000000",
21
+ locked: true,
22
+ },
23
+ ];
24
+
25
+ describe("getBalance", () => {
26
+ const mockCurrency = {
27
+ id: "canton_network",
28
+ } as unknown as CryptoCurrency;
29
+
30
+ const mockNetwork = network as jest.MockedFunction<typeof network>;
31
+
32
+ beforeAll(() => {
33
+ coinConfig.setCoinConfig(() => ({
34
+ gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
35
+ useGateway: true,
36
+ networkType: "devnet",
37
+ nativeInstrumentId: "Amulet",
38
+ status: {
39
+ type: "active",
40
+ },
41
+ }));
42
+ });
43
+
44
+ beforeEach(() => {
45
+ jest.clearAllMocks();
46
+ });
47
+
48
+ it("should return an array of balances (backwards compatibility)", async () => {
49
+ mockNetwork.mockResolvedValue({ data: mockBalances, status: 200 });
50
+ const result = await getBalance(mockCurrency, "test-party-id");
51
+
52
+ expect(result).toEqual(mockBalances);
53
+ });
54
+
55
+ it("should return and object with balances property", async () => {
56
+ const mockResponse: GetBalanceResponse = {
57
+ at_round: 123,
58
+ balances: mockBalances,
59
+ };
60
+
61
+ mockNetwork.mockResolvedValue({ data: mockResponse, status: 200 });
62
+ const result = await getBalance(mockCurrency, "test-party-id");
63
+
64
+ expect(result).toEqual(mockResponse.balances);
65
+ });
66
+ });