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

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 (205) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +2 -1
  3. package/CHANGELOG.md +26 -9
  4. package/README.md +75 -0
  5. package/lib/bridge/deviceTransactionConfig.d.ts +1 -1
  6. package/lib/bridge/deviceTransactionConfig.d.ts.map +1 -1
  7. package/lib/bridge/deviceTransactionConfig.js +1 -1
  8. package/lib/bridge/deviceTransactionConfig.js.map +1 -1
  9. package/lib/bridge/estimateMaxSpendable.d.ts +2 -2
  10. package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
  11. package/lib/bridge/estimateMaxSpendable.js +2 -3
  12. package/lib/bridge/estimateMaxSpendable.js.map +1 -1
  13. package/lib/bridge/getTransactionStatus.d.ts +5 -3
  14. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  15. package/lib/bridge/getTransactionStatus.js +27 -10
  16. package/lib/bridge/getTransactionStatus.js.map +1 -1
  17. package/lib/bridge/index.d.ts +2 -2
  18. package/lib/bridge/index.d.ts.map +1 -1
  19. package/lib/bridge/index.js +3 -3
  20. package/lib/bridge/index.js.map +1 -1
  21. package/lib/bridge/onboard.d.ts.map +1 -1
  22. package/lib/bridge/onboard.js +48 -22
  23. package/lib/bridge/onboard.js.map +1 -1
  24. package/lib/bridge/prepareTransaction.js +1 -1
  25. package/lib/bridge/prepareTransaction.js.map +1 -1
  26. package/lib/bridge/serialization.d.ts +4 -0
  27. package/lib/bridge/serialization.d.ts.map +1 -0
  28. package/lib/bridge/serialization.js +36 -0
  29. package/lib/bridge/serialization.js.map +1 -0
  30. package/lib/bridge/signOperation.d.ts.map +1 -1
  31. package/lib/bridge/signOperation.js +11 -4
  32. package/lib/bridge/signOperation.js.map +1 -1
  33. package/lib/bridge/sync.d.ts.map +1 -1
  34. package/lib/bridge/sync.js +14 -6
  35. package/lib/bridge/sync.js.map +1 -1
  36. package/lib/bridge/transaction.js +1 -1
  37. package/lib/bridge/transaction.js.map +1 -1
  38. package/lib/common-logic/account/getBalance.d.ts +5 -1
  39. package/lib/common-logic/account/getBalance.d.ts.map +1 -1
  40. package/lib/common-logic/account/getBalance.js +2 -0
  41. package/lib/common-logic/account/getBalance.js.map +1 -1
  42. package/lib/common-logic/index.d.ts +1 -0
  43. package/lib/common-logic/index.d.ts.map +1 -1
  44. package/lib/common-logic/index.js +3 -1
  45. package/lib/common-logic/index.js.map +1 -1
  46. package/lib/common-logic/transaction/craftTransaction.js +1 -1
  47. package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
  48. package/lib/common-logic/transaction/estimateFees.js +1 -1
  49. package/lib/common-logic/transaction/estimateFees.js.map +1 -1
  50. package/lib/common-logic/transaction/sign.d.ts +8 -0
  51. package/lib/common-logic/transaction/sign.d.ts.map +1 -0
  52. package/lib/common-logic/transaction/sign.js +45 -0
  53. package/lib/common-logic/transaction/sign.js.map +1 -0
  54. package/lib/common-logic/transaction/split.d.ts +9 -0
  55. package/lib/common-logic/transaction/split.d.ts.map +1 -0
  56. package/lib/common-logic/transaction/split.js +119 -0
  57. package/lib/common-logic/transaction/split.js.map +1 -0
  58. package/lib/common-logic/utils.js +2 -2
  59. package/lib/common-logic/utils.js.map +1 -1
  60. package/lib/network/gateway.d.ts +6 -1
  61. package/lib/network/gateway.d.ts.map +1 -1
  62. package/lib/network/gateway.js +6 -9
  63. package/lib/network/gateway.js.map +1 -1
  64. package/lib/test/cantonTestUtils.d.ts +4 -9
  65. package/lib/test/cantonTestUtils.d.ts.map +1 -1
  66. package/lib/test/cantonTestUtils.js +736 -27
  67. package/lib/test/cantonTestUtils.js.map +1 -1
  68. package/lib/test/fixtures.d.ts +5 -0
  69. package/lib/test/fixtures.d.ts.map +1 -0
  70. package/lib/test/fixtures.js +57 -0
  71. package/lib/test/fixtures.js.map +1 -0
  72. package/lib/types/bridge.d.ts +14 -2
  73. package/lib/types/bridge.d.ts.map +1 -1
  74. package/lib/types/errors.d.ts +6 -0
  75. package/lib/types/errors.d.ts.map +1 -1
  76. package/lib/types/errors.js +3 -1
  77. package/lib/types/errors.js.map +1 -1
  78. package/lib/types/index.d.ts +1 -0
  79. package/lib/types/index.d.ts.map +1 -1
  80. package/lib/types/index.js +1 -0
  81. package/lib/types/index.js.map +1 -1
  82. package/lib/types/signer.d.ts +15 -2
  83. package/lib/types/signer.d.ts.map +1 -1
  84. package/lib/types/transaction-proto.json +1238 -0
  85. package/lib-es/bridge/deviceTransactionConfig.d.ts +1 -1
  86. package/lib-es/bridge/deviceTransactionConfig.d.ts.map +1 -1
  87. package/lib-es/bridge/deviceTransactionConfig.js +1 -1
  88. package/lib-es/bridge/deviceTransactionConfig.js.map +1 -1
  89. package/lib-es/bridge/estimateMaxSpendable.d.ts +2 -2
  90. package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
  91. package/lib-es/bridge/estimateMaxSpendable.js +2 -3
  92. package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
  93. package/lib-es/bridge/getTransactionStatus.d.ts +5 -3
  94. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  95. package/lib-es/bridge/getTransactionStatus.js +27 -10
  96. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  97. package/lib-es/bridge/index.d.ts +2 -2
  98. package/lib-es/bridge/index.d.ts.map +1 -1
  99. package/lib-es/bridge/index.js +3 -3
  100. package/lib-es/bridge/index.js.map +1 -1
  101. package/lib-es/bridge/onboard.d.ts.map +1 -1
  102. package/lib-es/bridge/onboard.js +49 -23
  103. package/lib-es/bridge/onboard.js.map +1 -1
  104. package/lib-es/bridge/prepareTransaction.js +1 -1
  105. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  106. package/lib-es/bridge/serialization.d.ts +4 -0
  107. package/lib-es/bridge/serialization.d.ts.map +1 -0
  108. package/lib-es/bridge/serialization.js +32 -0
  109. package/lib-es/bridge/serialization.js.map +1 -0
  110. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  111. package/lib-es/bridge/signOperation.js +11 -4
  112. package/lib-es/bridge/signOperation.js.map +1 -1
  113. package/lib-es/bridge/sync.d.ts.map +1 -1
  114. package/lib-es/bridge/sync.js +14 -6
  115. package/lib-es/bridge/sync.js.map +1 -1
  116. package/lib-es/bridge/transaction.js +1 -1
  117. package/lib-es/bridge/transaction.js.map +1 -1
  118. package/lib-es/common-logic/account/getBalance.d.ts +5 -1
  119. package/lib-es/common-logic/account/getBalance.d.ts.map +1 -1
  120. package/lib-es/common-logic/account/getBalance.js +2 -0
  121. package/lib-es/common-logic/account/getBalance.js.map +1 -1
  122. package/lib-es/common-logic/index.d.ts +1 -0
  123. package/lib-es/common-logic/index.d.ts.map +1 -1
  124. package/lib-es/common-logic/index.js +1 -0
  125. package/lib-es/common-logic/index.js.map +1 -1
  126. package/lib-es/common-logic/transaction/craftTransaction.js +1 -1
  127. package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
  128. package/lib-es/common-logic/transaction/estimateFees.js +1 -1
  129. package/lib-es/common-logic/transaction/estimateFees.js.map +1 -1
  130. package/lib-es/common-logic/transaction/sign.d.ts +8 -0
  131. package/lib-es/common-logic/transaction/sign.d.ts.map +1 -0
  132. package/lib-es/common-logic/transaction/sign.js +42 -0
  133. package/lib-es/common-logic/transaction/sign.js.map +1 -0
  134. package/lib-es/common-logic/transaction/split.d.ts +9 -0
  135. package/lib-es/common-logic/transaction/split.d.ts.map +1 -0
  136. package/lib-es/common-logic/transaction/split.js +83 -0
  137. package/lib-es/common-logic/transaction/split.js.map +1 -0
  138. package/lib-es/common-logic/utils.js +2 -2
  139. package/lib-es/common-logic/utils.js.map +1 -1
  140. package/lib-es/network/gateway.d.ts +6 -1
  141. package/lib-es/network/gateway.d.ts.map +1 -1
  142. package/lib-es/network/gateway.js +5 -10
  143. package/lib-es/network/gateway.js.map +1 -1
  144. package/lib-es/test/cantonTestUtils.d.ts +4 -9
  145. package/lib-es/test/cantonTestUtils.d.ts.map +1 -1
  146. package/lib-es/test/cantonTestUtils.js +697 -21
  147. package/lib-es/test/cantonTestUtils.js.map +1 -1
  148. package/lib-es/test/fixtures.d.ts +5 -0
  149. package/lib-es/test/fixtures.d.ts.map +1 -0
  150. package/lib-es/test/fixtures.js +49 -0
  151. package/lib-es/test/fixtures.js.map +1 -0
  152. package/lib-es/types/bridge.d.ts +14 -2
  153. package/lib-es/types/bridge.d.ts.map +1 -1
  154. package/lib-es/types/errors.d.ts +6 -0
  155. package/lib-es/types/errors.d.ts.map +1 -1
  156. package/lib-es/types/errors.js +2 -0
  157. package/lib-es/types/errors.js.map +1 -1
  158. package/lib-es/types/index.d.ts +1 -0
  159. package/lib-es/types/index.d.ts.map +1 -1
  160. package/lib-es/types/index.js +1 -0
  161. package/lib-es/types/index.js.map +1 -1
  162. package/lib-es/types/signer.d.ts +15 -2
  163. package/lib-es/types/signer.d.ts.map +1 -1
  164. package/lib-es/types/transaction-proto.json +1238 -0
  165. package/package.json +13 -10
  166. package/scripts/generate.js +261 -0
  167. package/src/bridge/deviceTransactionConfig.test.ts +14 -10
  168. package/src/bridge/deviceTransactionConfig.ts +2 -2
  169. package/src/bridge/estimateMaxSpendable.ts +6 -8
  170. package/src/bridge/getTransactionStatus.test.ts +103 -165
  171. package/src/bridge/getTransactionStatus.ts +43 -11
  172. package/src/bridge/index.ts +6 -5
  173. package/src/bridge/onboard.integ.test.ts +8 -51
  174. package/src/bridge/onboard.ts +58 -33
  175. package/src/bridge/prepareTransaction.ts +1 -1
  176. package/src/bridge/serialization.ts +44 -0
  177. package/src/bridge/signOperation.test.ts +123 -0
  178. package/src/bridge/signOperation.ts +13 -6
  179. package/src/bridge/sync.integ.test.ts +157 -132
  180. package/src/bridge/sync.test.ts +5 -1
  181. package/src/bridge/sync.ts +18 -7
  182. package/src/bridge/transaction.ts +1 -1
  183. package/src/common-logic/account/getBalance.ts +12 -2
  184. package/src/common-logic/account/getBalance.unit.test.ts +7 -1
  185. package/src/common-logic/index.ts +1 -0
  186. package/src/common-logic/transaction/craftTransaction.ts +1 -1
  187. package/src/common-logic/transaction/estimateFees.test.ts +10 -0
  188. package/src/common-logic/transaction/estimateFees.ts +1 -1
  189. package/src/common-logic/transaction/sign.test.ts +389 -0
  190. package/src/common-logic/transaction/sign.ts +59 -0
  191. package/src/common-logic/transaction/split.test.ts +50 -0
  192. package/src/common-logic/transaction/split.ts +101 -0
  193. package/src/common-logic/utils.test.ts +22 -30
  194. package/src/common-logic/utils.ts +2 -2
  195. package/src/network/gateway.integ.test.ts +5 -6
  196. package/src/network/gateway.ts +13 -10
  197. package/src/test/cantonTestUtils.ts +789 -24
  198. package/src/test/fixtures.ts +53 -0
  199. package/src/test/prepare-transfer-serialized.json +26 -0
  200. package/src/test/prepare-transfer.json +3298 -0
  201. package/src/types/bridge.ts +15 -2
  202. package/src/types/errors.ts +3 -0
  203. package/src/types/index.ts +1 -0
  204. package/src/types/signer.ts +21 -3
  205. package/src/types/transaction-proto.json +1238 -0
@@ -3,17 +3,21 @@ import { SignerContext } from "@ledgerhq/coin-framework/signer";
3
3
  import type { Account } from "@ledgerhq/types-live";
4
4
  import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
5
5
  import { log } from "@ledgerhq/logs";
6
+ import { TransportStatusError, UserRefusedOnDevice, LockedDeviceError } from "@ledgerhq/errors";
6
7
  import { encodeAccountId } from "@ledgerhq/coin-framework/account/accountId";
8
+
7
9
  import {
10
+ getNetworkType,
8
11
  prepareOnboarding,
9
12
  submitOnboarding,
10
13
  getPartyByPubKey,
11
- preparePreApprovalTransaction,
12
- submitPreApprovalTransaction,
13
14
  prepareTapRequest,
14
15
  submitTapRequest,
16
+ preparePreApprovalTransaction,
17
+ submitPreApprovalTransaction,
15
18
  getTransferPreApproval,
16
19
  } from "../network/gateway";
20
+ import { signTransaction } from "../common-logic/transaction/sign";
17
21
  import {
18
22
  OnboardStatus,
19
23
  AuthorizeStatus,
@@ -85,6 +89,7 @@ export const buildOnboardAccount =
85
89
  o.next({ status: OnboardStatus.PREPARE });
86
90
 
87
91
  let { partyId } = await isAccountOnboarded(currency, publicKey);
92
+
88
93
  if (partyId) {
89
94
  const onboardedAccount = createOnboardedAccount(account, partyId, currency);
90
95
  o.next({ partyId, account: onboardedAccount }); // success
@@ -96,12 +101,9 @@ export const buildOnboardAccount =
96
101
 
97
102
  o.next({ status: OnboardStatus.SIGN });
98
103
 
99
- const signature = await signerContext(deviceId, signer =>
100
- signer.signTransaction(
101
- account.freshAddressPath,
102
- preparedTransaction.transactions.combined_hash,
103
- ),
104
- );
104
+ const signature = await signerContext(deviceId, async signer => {
105
+ return await signTransaction(signer, account.freshAddressPath, preparedTransaction);
106
+ });
105
107
 
106
108
  o.next({ status: OnboardStatus.SUBMIT });
107
109
 
@@ -115,7 +117,9 @@ export const buildOnboardAccount =
115
117
  () => o.complete(),
116
118
  error => {
117
119
  log("[canton:onboard] onboardAccount failed:", error);
118
- o.error(error);
120
+
121
+ const handledError = handleDeviceErrors(error);
122
+ o.error(handledError || error);
119
123
  },
120
124
  );
121
125
  });
@@ -141,10 +145,9 @@ export const buildAuthorizePreapproval =
141
145
 
142
146
  o.next({ status: AuthorizeStatus.SIGN });
143
147
 
144
- const signature = await signerContext(deviceId, signer =>
145
- signer.signTransaction(account.freshAddressPath, preparedTransaction.hash),
146
- );
147
-
148
+ const { signature } = await signerContext(deviceId, async signer => {
149
+ return await signTransaction(signer, account.freshAddressPath, preparedTransaction);
150
+ });
148
151
  o.next({ status: AuthorizeStatus.SUBMIT });
149
152
 
150
153
  await submitPreApprovalTransaction(currency, partyId, preparedTransaction, signature);
@@ -152,37 +155,59 @@ export const buildAuthorizePreapproval =
152
155
 
153
156
  o.next({ isApproved: true }); // success
154
157
 
155
- const handleTapRequest = async () => {
156
- try {
157
- const { serialized, hash } = await prepareTapRequest(currency, { partyId });
158
+ if (getNetworkType(currency) !== "mainnet") {
159
+ const handleTapRequest = async () => {
160
+ try {
161
+ const { serialized, hash } = await prepareTapRequest(currency, { partyId });
158
162
 
159
- if (serialized && hash) {
160
- o.next({ status: AuthorizeStatus.SIGN });
163
+ if (serialized && hash) {
164
+ o.next({ status: AuthorizeStatus.SIGN });
161
165
 
162
- const signature = await signerContext(deviceId, signer =>
163
- signer.signTransaction(account.freshAddressPath, hash),
164
- );
166
+ const { signature } = await signerContext(deviceId, signer =>
167
+ signer.signTransaction(account.freshAddressPath, hash),
168
+ );
165
169
 
166
- o.next({ status: AuthorizeStatus.SUBMIT });
170
+ o.next({ status: AuthorizeStatus.SUBMIT });
167
171
 
168
- await submitTapRequest(currency, {
169
- partyId,
170
- serialized,
171
- signature,
172
- });
172
+ await submitTapRequest(currency, {
173
+ partyId,
174
+ serialized,
175
+ signature,
176
+ });
177
+ }
178
+ } catch (err) {
179
+ // Tap request failure should not break the pre-approval flow
173
180
  }
174
- } catch (err) {
175
- // Tap request failure should not break the pre-approval flow
176
- }
177
- };
178
- await handleTapRequest();
181
+ };
182
+ await handleTapRequest();
183
+ }
179
184
  }
180
185
 
181
186
  main().then(
182
187
  () => o.complete(),
183
188
  error => {
184
189
  log("[canton:onboard] authorizePreapproval failed:", error);
185
- o.error(error);
190
+
191
+ const handledError = handleDeviceErrors(error);
192
+ o.error(handledError || error);
186
193
  },
187
194
  );
188
195
  });
196
+
197
+ /**
198
+ * Check if an error is a LockedDeviceError or UserRefusedOnDevice and create user-friendly error messages
199
+ */
200
+ const handleDeviceErrors = (error: Error): Error | null => {
201
+ if (error instanceof TransportStatusError) {
202
+ if (error.statusCode === 0x6985) {
203
+ const userRefusedError = new UserRefusedOnDevice("errors.UserRefusedOnDevice.description");
204
+ return userRefusedError;
205
+ }
206
+ if (error.statusCode === 0x5515) {
207
+ const lockedDeviceError = new LockedDeviceError("errors.LockedDeviceError.description");
208
+ return lockedDeviceError;
209
+ }
210
+ }
211
+
212
+ return null;
213
+ };
@@ -11,7 +11,7 @@ export const prepareTransaction: AccountBridge<Transaction>["prepareTransaction"
11
11
  ) => {
12
12
  const amount = transaction.amount || BigNumber(0);
13
13
  const fee = BigNumber(
14
- (await estimateFees(account.currency, BigInt(amount.toString()))).toString(),
14
+ (await estimateFees(account.currency, BigInt(amount.toFixed()))).toString(),
15
15
  );
16
16
 
17
17
  if (!transaction.tokenId) {
@@ -0,0 +1,44 @@
1
+ import type { Account, AccountRaw } from "@ledgerhq/types-live";
2
+ import {
3
+ type CantonAccount,
4
+ type CantonAccountRaw,
5
+ type CantonResources,
6
+ type CantonResourcesRaw,
7
+ } from "../types";
8
+
9
+ function isCantonAccount(account: Account): account is CantonAccount {
10
+ return "cantonResources" in account;
11
+ }
12
+
13
+ function isCantonAccountRaw(accountRaw: AccountRaw): accountRaw is CantonAccountRaw {
14
+ return "cantonResources" in accountRaw;
15
+ }
16
+
17
+ function toResourcesRaw(r: CantonResources): CantonResourcesRaw {
18
+ const { instrumentUtxoCounts } = r;
19
+ return {
20
+ instrumentUtxoCounts,
21
+ };
22
+ }
23
+
24
+ function fromResourcesRaw(r: CantonResourcesRaw): CantonResources {
25
+ return {
26
+ instrumentUtxoCounts: r.instrumentUtxoCounts,
27
+ };
28
+ }
29
+
30
+ export function assignToAccountRaw(account: Account, accountRaw: AccountRaw): void {
31
+ if (isCantonAccount(account) && isCantonAccountRaw(accountRaw)) {
32
+ if (account.cantonResources) {
33
+ accountRaw.cantonResources = toResourcesRaw(account.cantonResources);
34
+ }
35
+ }
36
+ }
37
+
38
+ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account): void {
39
+ if (isCantonAccountRaw(accountRaw) && isCantonAccount(account)) {
40
+ if (accountRaw.cantonResources) {
41
+ account.cantonResources = fromResourcesRaw(accountRaw.cantonResources);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,123 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import {
3
+ CantonSigner,
4
+ CantonPreparedTransaction,
5
+ CantonSignature,
6
+ CantonUntypedVersionedMessage,
7
+ } from "../types";
8
+ import { Transaction } from "../types";
9
+ import { craftTransaction } from "../common-logic";
10
+ import prepareTransferMock from "../test/prepare-transfer.json";
11
+ import { buildSignOperation } from "./signOperation";
12
+ import { createMockAccount } from "../test/fixtures";
13
+
14
+ jest.mock("../common-logic", () => {
15
+ const actual = jest.requireActual("../common-logic");
16
+ return {
17
+ ...actual,
18
+ craftTransaction: jest.fn(),
19
+ };
20
+ });
21
+
22
+ const mockCraftTransaction = craftTransaction as jest.MockedFunction<typeof craftTransaction>;
23
+
24
+ class MockCantonSigner implements CantonSigner {
25
+ async getAddress(path: string, display?: boolean) {
26
+ return {
27
+ publicKey: "mock-public-key",
28
+ address: "mock-address",
29
+ path,
30
+ };
31
+ }
32
+
33
+ async signTransaction(
34
+ path: string,
35
+ data: CantonPreparedTransaction | CantonUntypedVersionedMessage | string,
36
+ ): Promise<CantonSignature> {
37
+ if (typeof data === "string") {
38
+ return { signature: `txhash-signature-${data}` };
39
+ } else if ("transactions" in data) {
40
+ const result: CantonSignature = {
41
+ signature: `untyped-signature-${data.transactions.length}`,
42
+ };
43
+ if (data.challenge) {
44
+ result.applicationSignature = `challenge-signature-${data.challenge}`;
45
+ }
46
+ return result;
47
+ } else {
48
+ return {
49
+ signature: `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`,
50
+ };
51
+ }
52
+ }
53
+ }
54
+
55
+ describe("buildSignOperation", () => {
56
+ const mockDeviceId = "test-device-id";
57
+ const mockDerivationPath = "44'/6767'/0'/0'/0'";
58
+
59
+ beforeEach(() => {
60
+ jest.clearAllMocks();
61
+ mockCraftTransaction.mockReset();
62
+ });
63
+
64
+ const mockAccount = createMockAccount({
65
+ id: "js:2:canton_network:test-party-id:",
66
+ freshAddress: "test-address",
67
+ freshAddressPath: mockDerivationPath,
68
+ xpub: "test-party-id",
69
+ });
70
+
71
+ const mockTransaction: Transaction = {
72
+ family: "canton",
73
+ recipient: "bob::44444444444444444444444444444444444444444444444444444444444444444444",
74
+ amount: new BigNumber("1000000"),
75
+ tokenId: "Amulet",
76
+ fee: new BigNumber("1000"),
77
+ memo: "Test transaction",
78
+ };
79
+
80
+ it("should handle prepared transaction signing", async () => {
81
+ // GIVEN
82
+ const mockSigner = new MockCantonSigner();
83
+ const mockSignerContext = jest.fn().mockImplementation(async (deviceId, callback) => {
84
+ return await callback(mockSigner);
85
+ });
86
+
87
+ mockCraftTransaction.mockResolvedValue({
88
+ nativeTransaction: {
89
+ // @ts-expect-error fix types
90
+ transaction: prepareTransferMock.transaction,
91
+ metadata: prepareTransferMock.metadata,
92
+ },
93
+ serializedTransaction: "serialized-transaction",
94
+ hash: "mock-hash",
95
+ });
96
+
97
+ const signOperation = buildSignOperation(mockSignerContext);
98
+
99
+ // WHEN
100
+ const result = await new Promise((resolve, reject) => {
101
+ signOperation({
102
+ account: mockAccount,
103
+ deviceId: mockDeviceId,
104
+ transaction: mockTransaction,
105
+ }).subscribe({
106
+ next: value => {
107
+ if (value.type === "signed") {
108
+ resolve(value);
109
+ }
110
+ },
111
+ error: reject,
112
+ });
113
+ });
114
+
115
+ // THEN
116
+ expect(mockCraftTransaction).toHaveBeenCalled();
117
+ expect(result).toMatchObject({
118
+ signedOperation: {
119
+ signature: expect.stringContaining("prepared-transaction-signature-"),
120
+ },
121
+ });
122
+ });
123
+ });
@@ -6,6 +6,7 @@ import { SignerContext } from "@ledgerhq/coin-framework/signer";
6
6
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
7
7
  import { decodeAccountId } from "@ledgerhq/coin-framework/account";
8
8
  import { combine, craftTransaction } from "../common-logic";
9
+ import { signTransaction } from "../common-logic/transaction/sign";
9
10
  import { Transaction, CantonSigner } from "../types";
10
11
 
11
12
  export const buildSignOperation =
@@ -41,16 +42,21 @@ export const buildSignOperation =
41
42
  params.memo = transaction.memo;
42
43
  }
43
44
 
44
- const { hash, serializedTransaction } = await craftTransaction(
45
+ const { nativeTransaction, serializedTransaction, hash } = await craftTransaction(
45
46
  account.currency,
46
47
  {
47
48
  address,
48
49
  },
49
50
  params,
50
51
  );
51
- const transactionSignature = await signer.signTransaction(derivationPath, hash);
52
52
 
53
- return combine(serializedTransaction, `${transactionSignature}__PARTY__${address}`);
53
+ const { signature } = await signTransaction(signer, derivationPath, {
54
+ json: nativeTransaction,
55
+ serialized: serializedTransaction,
56
+ hash: hash,
57
+ });
58
+
59
+ return combine(serializedTransaction, `${signature}__PARTY__${address}`);
54
60
  });
55
61
 
56
62
  o.next({
@@ -83,9 +89,10 @@ export const buildSignOperation =
83
89
  });
84
90
  } catch (e) {
85
91
  if (e instanceof Error) {
86
- throw new Error(
87
- (e as Error & { data?: { resultMessage?: string } })?.data?.resultMessage,
88
- );
92
+ const errorMessage =
93
+ (e as Error & { data?: { resultMessage?: string } })?.data?.resultMessage ||
94
+ e.message;
95
+ throw new Error(errorMessage);
89
96
  }
90
97
 
91
98
  throw e;
@@ -35,6 +35,7 @@ const ACCOUNT_SHAPE_INFO: AccountShapeInfo<CantonAccount> = {
35
35
  xpub,
36
36
  } as CantonAccount,
37
37
  };
38
+ const TIMEOUT = 30000;
38
39
 
39
40
  // Mock signer context for testing
40
41
  const keyPair = generateMockKeyPair();
@@ -57,41 +58,118 @@ describe("sync (devnet)", () => {
57
58
  });
58
59
 
59
60
  describe("makeGetAccountShape", () => {
60
- it("should fetch account shape for a valid address", async () => {
61
- const getAccountShape = makeGetAccountShape(mockSignerContext);
62
- const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
63
-
64
- expect(result).toBeDefined();
65
- expect(result.id).toBeDefined();
66
- expect(result.xpub).toBe(TEST_ADDRESS);
67
- expect(result.blockHeight).toBeGreaterThan(0);
68
- expect(result.balance).toBeDefined();
69
- expect(result.spendableBalance).toBeDefined();
70
- expect(result.operations).toBeDefined();
71
- expect(result.operationsCount).toBeGreaterThanOrEqual(0);
72
-
73
- expect(result.balance).toBeInstanceOf(Object);
74
- expect(result.balance?.toNumber).toBeDefined();
75
- expect(result.spendableBalance).toBeInstanceOf(Object);
76
- expect(result.spendableBalance?.toNumber).toBeDefined();
77
-
78
- expect(result.spendableBalance?.toNumber()).toBeLessThanOrEqual(
79
- result.balance?.toNumber() || 0,
80
- );
81
- });
82
-
83
- it("should handle address with colons correctly", async () => {
84
- const getAccountShape = makeGetAccountShape(mockSignerContext);
85
- const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
86
-
87
- expect(result.xpub).toContain("::");
88
- });
89
-
90
- it("should merge operations correctly with initial account", async () => {
91
- const getAccountShape = makeGetAccountShape(mockSignerContext);
92
-
93
- const operations: Operation[] = [
94
- {
61
+ it(
62
+ "should fetch account shape for a valid address",
63
+ async () => {
64
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
65
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
66
+
67
+ expect(result).toBeDefined();
68
+ expect(result.id).toBeDefined();
69
+ expect(result.xpub).toBe(TEST_ADDRESS);
70
+ expect(result.blockHeight).toBeGreaterThan(0);
71
+ expect(result.balance).toBeDefined();
72
+ expect(result.spendableBalance).toBeDefined();
73
+ expect(result.operations).toBeDefined();
74
+ expect(result.operationsCount).toBeGreaterThanOrEqual(0);
75
+
76
+ expect(result.balance).toBeInstanceOf(Object);
77
+ expect(result.balance?.toNumber).toBeDefined();
78
+ expect(result.spendableBalance).toBeInstanceOf(Object);
79
+ expect(result.spendableBalance?.toNumber).toBeDefined();
80
+
81
+ expect(result.spendableBalance?.toNumber()).toBeLessThanOrEqual(
82
+ result.balance?.toNumber() || 0,
83
+ );
84
+ },
85
+ TIMEOUT,
86
+ );
87
+
88
+ it(
89
+ "should handle address with colons correctly",
90
+ async () => {
91
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
92
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
93
+
94
+ expect(result.xpub).toContain("::");
95
+ },
96
+ TIMEOUT,
97
+ );
98
+
99
+ it(
100
+ "should merge operations correctly with initial account",
101
+ async () => {
102
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
103
+
104
+ const operations: Operation[] = [
105
+ {
106
+ id: "test-op-1",
107
+ hash: "test-hash-1",
108
+ accountId: "test-account",
109
+ type: "OUT" as const,
110
+ value: new BigNumber(1000000),
111
+ fee: new BigNumber(100000),
112
+ blockHash: "block-hash-1",
113
+ blockHeight: 100,
114
+ senders: [TEST_ADDRESS],
115
+ recipients: ["recipient-1"],
116
+ date: new Date("2023-01-01"),
117
+ transactionSequenceNumber: 100,
118
+ extra: { uid: "uid-1" },
119
+ },
120
+ ];
121
+
122
+ const result = await getAccountShape(
123
+ {
124
+ ...ACCOUNT_SHAPE_INFO,
125
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
126
+ initialAccount: { xpub, operations } as Account,
127
+ },
128
+ { paginationConfig: {} },
129
+ );
130
+
131
+ expect(result.operations).toBeDefined();
132
+ expect(result.operationsCount).toBeGreaterThanOrEqual(1);
133
+ const initialOp = result.operations?.find(op => op.id === "test-op-1");
134
+ expect(initialOp).toBeDefined();
135
+ },
136
+ TIMEOUT,
137
+ );
138
+
139
+ it(
140
+ "should take locked balance into account when calculating spendable balance",
141
+ async () => {
142
+ const mockGetBalance = jest.spyOn(gateway, "getBalance");
143
+
144
+ mockGetBalance.mockResolvedValue([
145
+ {
146
+ instrument_id: "Amulet",
147
+ amount: "500",
148
+ locked: false,
149
+ },
150
+ {
151
+ instrument_id: "LockedAmulet",
152
+ amount: "1000000",
153
+ locked: true,
154
+ },
155
+ ]);
156
+
157
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
158
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
159
+
160
+ expect(result.balance?.toNumber()).toBe(1000500);
161
+ expect(result.spendableBalance?.toNumber()).toBe(500);
162
+
163
+ mockGetBalance.mockRestore();
164
+ },
165
+ TIMEOUT,
166
+ );
167
+
168
+ it(
169
+ "should call getOperations with correct cursor based on initial account",
170
+ async () => {
171
+ const mockGetOperations = jest.spyOn(gateway, "getOperations");
172
+ const operation: Operation = {
95
173
  id: "test-op-1",
96
174
  hash: "test-hash-1",
97
175
  accountId: "test-account",
@@ -105,102 +183,49 @@ describe("sync (devnet)", () => {
105
183
  date: new Date("2023-01-01"),
106
184
  transactionSequenceNumber: 100,
107
185
  extra: { uid: "uid-1" },
108
- },
109
- ];
110
-
111
- const result = await getAccountShape(
112
- {
113
- ...ACCOUNT_SHAPE_INFO,
114
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
115
- initialAccount: { xpub, operations } as Account,
116
- },
117
- { paginationConfig: {} },
118
- );
119
-
120
- expect(result.operations).toBeDefined();
121
- expect(result.operationsCount).toBeGreaterThanOrEqual(1);
122
- const initialOp = result.operations?.find(op => op.id === "test-op-1");
123
- expect(initialOp).toBeDefined();
124
- });
125
-
126
- it("should take locked balance into account when calculating spendable balance", async () => {
127
- const mockGetBalance = jest.spyOn(gateway, "getBalance");
128
-
129
- mockGetBalance.mockResolvedValue([
130
- {
131
- instrument_id: "Amulet",
132
- amount: "500",
133
- locked: false,
134
- },
135
- {
136
- instrument_id: "LockedAmulet",
137
- amount: "1000000",
138
- locked: true,
139
- },
140
- ]);
141
-
142
- const getAccountShape = makeGetAccountShape(mockSignerContext);
143
- const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
144
-
145
- expect(result.balance?.toNumber()).toBe(1000500);
146
- expect(result.spendableBalance?.toNumber()).toBe(500);
147
-
148
- mockGetBalance.mockRestore();
149
- });
150
-
151
- it("should call getOperations with correct cursor based on initial account", async () => {
152
- const mockGetOperations = jest.spyOn(gateway, "getOperations");
153
- const operation: Operation = {
154
- id: "test-op-1",
155
- hash: "test-hash-1",
156
- accountId: "test-account",
157
- type: "OUT" as const,
158
- value: new BigNumber(1000000),
159
- fee: new BigNumber(100000),
160
- blockHash: "block-hash-1",
161
- blockHeight: 100,
162
- senders: [TEST_ADDRESS],
163
- recipients: ["recipient-1"],
164
- date: new Date("2023-01-01"),
165
- transactionSequenceNumber: 100,
166
- extra: { uid: "uid-1" },
167
- };
168
-
169
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
170
- const initialAccount = { xpub, operations: [operation] } as Account;
171
-
172
- const getAccountShape = makeGetAccountShape(mockSignerContext);
173
- const result = await getAccountShape(
174
- {
175
- ...ACCOUNT_SHAPE_INFO,
176
- initialAccount,
177
- },
178
- { paginationConfig: {} },
179
- );
180
-
181
- expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
182
- cursor: (operation.blockHeight || 0) + 1,
183
- limit: 100,
184
- });
185
- expect(result.operations).toBeDefined();
186
- expect(result.operationsCount).toBeGreaterThan(1);
187
- });
188
-
189
- it("should call getOperations with cursor 0 when no initial account", async () => {
190
- const mockGetOperations = jest.spyOn(gateway, "getOperations");
191
-
192
- const getAccountShape = makeGetAccountShape(mockSignerContext);
193
- const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
194
-
195
- expect(result.operations).toBeDefined();
196
- expect(result.operationsCount).toBeGreaterThanOrEqual(1);
197
-
198
- expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
199
- cursor: 0,
200
- limit: 100,
201
- });
202
-
203
- mockGetOperations.mockRestore();
204
- });
186
+ };
187
+
188
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
189
+ const initialAccount = { xpub, operations: [operation] } as Account;
190
+
191
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
192
+ const result = await getAccountShape(
193
+ {
194
+ ...ACCOUNT_SHAPE_INFO,
195
+ initialAccount,
196
+ },
197
+ { paginationConfig: {} },
198
+ );
199
+
200
+ expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
201
+ cursor: (operation.blockHeight || 0) + 1,
202
+ limit: 100,
203
+ });
204
+ expect(result.operations).toBeDefined();
205
+ expect(result.operationsCount).toBeGreaterThan(1);
206
+ },
207
+ TIMEOUT,
208
+ );
209
+
210
+ it(
211
+ "should call getOperations with cursor 0 when no initial account",
212
+ async () => {
213
+ const mockGetOperations = jest.spyOn(gateway, "getOperations");
214
+
215
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
216
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
217
+
218
+ expect(result.operations).toBeDefined();
219
+ expect(result.operationsCount).toBeGreaterThanOrEqual(1);
220
+
221
+ expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
222
+ cursor: 0,
223
+ limit: 100,
224
+ });
225
+
226
+ mockGetOperations.mockRestore();
227
+ },
228
+ TIMEOUT,
229
+ );
205
230
  });
206
231
  });