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

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 (117) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/lib/api/lastBlock.integ.test.js +0 -15
  4. package/lib/api/lastBlock.integ.test.js.map +1 -1
  5. package/lib/bridge/getTransactionStatus.test.d.ts +2 -0
  6. package/lib/bridge/getTransactionStatus.test.d.ts.map +1 -0
  7. package/lib/bridge/getTransactionStatus.test.js +365 -0
  8. package/lib/bridge/getTransactionStatus.test.js.map +1 -0
  9. package/lib/bridge/index.d.ts.map +1 -1
  10. package/lib/bridge/index.js +2 -5
  11. package/lib/bridge/index.js.map +1 -1
  12. package/lib/bridge/onboard.d.ts +11 -6
  13. package/lib/bridge/onboard.d.ts.map +1 -1
  14. package/lib/bridge/onboard.integ.test.js +49 -27
  15. package/lib/bridge/onboard.integ.test.js.map +1 -1
  16. package/lib/bridge/onboard.js +45 -152
  17. package/lib/bridge/onboard.js.map +1 -1
  18. package/lib/bridge/signOperation.d.ts.map +1 -1
  19. package/lib/bridge/signOperation.js +5 -5
  20. package/lib/bridge/signOperation.js.map +1 -1
  21. package/lib/bridge/sync.d.ts +3 -2
  22. package/lib/bridge/sync.d.ts.map +1 -1
  23. package/lib/bridge/sync.integ.test.js +31 -14
  24. package/lib/bridge/sync.integ.test.js.map +1 -1
  25. package/lib/bridge/sync.js +71 -57
  26. package/lib/bridge/sync.js.map +1 -1
  27. package/lib/common-logic/utils.d.ts.map +1 -1
  28. package/lib/common-logic/utils.js +3 -1
  29. package/lib/common-logic/utils.js.map +1 -1
  30. package/lib/common-logic/utils.test.d.ts +2 -0
  31. package/lib/common-logic/utils.test.d.ts.map +1 -0
  32. package/lib/common-logic/utils.test.js +104 -0
  33. package/lib/common-logic/utils.test.js.map +1 -0
  34. package/lib/config.d.ts +1 -1
  35. package/lib/config.d.ts.map +1 -1
  36. package/lib/network/gateway.d.ts +9 -9
  37. package/lib/network/gateway.d.ts.map +1 -1
  38. package/lib/network/gateway.integ.test.js +31 -17
  39. package/lib/network/gateway.integ.test.js.map +1 -1
  40. package/lib/network/gateway.js +33 -15
  41. package/lib/network/gateway.js.map +1 -1
  42. package/lib/types/bridge.d.ts +6 -16
  43. package/lib/types/bridge.d.ts.map +1 -1
  44. package/lib/types/onboard.d.ts +5 -5
  45. package/lib/types/onboard.d.ts.map +1 -1
  46. package/lib/types/onboard.js +10 -10
  47. package/lib/types/onboard.js.map +1 -1
  48. package/lib-es/api/lastBlock.integ.test.js +0 -15
  49. package/lib-es/api/lastBlock.integ.test.js.map +1 -1
  50. package/lib-es/bridge/getTransactionStatus.test.d.ts +2 -0
  51. package/lib-es/bridge/getTransactionStatus.test.d.ts.map +1 -0
  52. package/lib-es/bridge/getTransactionStatus.test.js +360 -0
  53. package/lib-es/bridge/getTransactionStatus.test.js.map +1 -0
  54. package/lib-es/bridge/index.d.ts.map +1 -1
  55. package/lib-es/bridge/index.js +3 -6
  56. package/lib-es/bridge/index.js.map +1 -1
  57. package/lib-es/bridge/onboard.d.ts +11 -6
  58. package/lib-es/bridge/onboard.d.ts.map +1 -1
  59. package/lib-es/bridge/onboard.integ.test.js +37 -15
  60. package/lib-es/bridge/onboard.integ.test.js.map +1 -1
  61. package/lib-es/bridge/onboard.js +44 -152
  62. package/lib-es/bridge/onboard.js.map +1 -1
  63. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  64. package/lib-es/bridge/signOperation.js +5 -5
  65. package/lib-es/bridge/signOperation.js.map +1 -1
  66. package/lib-es/bridge/sync.d.ts +3 -2
  67. package/lib-es/bridge/sync.d.ts.map +1 -1
  68. package/lib-es/bridge/sync.integ.test.js +26 -9
  69. package/lib-es/bridge/sync.integ.test.js.map +1 -1
  70. package/lib-es/bridge/sync.js +71 -56
  71. package/lib-es/bridge/sync.js.map +1 -1
  72. package/lib-es/common-logic/utils.d.ts.map +1 -1
  73. package/lib-es/common-logic/utils.js +3 -1
  74. package/lib-es/common-logic/utils.js.map +1 -1
  75. package/lib-es/common-logic/utils.test.d.ts +2 -0
  76. package/lib-es/common-logic/utils.test.d.ts.map +1 -0
  77. package/lib-es/common-logic/utils.test.js +99 -0
  78. package/lib-es/common-logic/utils.test.js.map +1 -0
  79. package/lib-es/config.d.ts +1 -1
  80. package/lib-es/config.d.ts.map +1 -1
  81. package/lib-es/network/gateway.d.ts +9 -9
  82. package/lib-es/network/gateway.d.ts.map +1 -1
  83. package/lib-es/network/gateway.integ.test.js +31 -17
  84. package/lib-es/network/gateway.integ.test.js.map +1 -1
  85. package/lib-es/network/gateway.js +33 -15
  86. package/lib-es/network/gateway.js.map +1 -1
  87. package/lib-es/types/bridge.d.ts +6 -16
  88. package/lib-es/types/bridge.d.ts.map +1 -1
  89. package/lib-es/types/onboard.d.ts +5 -5
  90. package/lib-es/types/onboard.d.ts.map +1 -1
  91. package/lib-es/types/onboard.js +9 -9
  92. package/lib-es/types/onboard.js.map +1 -1
  93. package/package.json +5 -4
  94. package/src/api/lastBlock.integ.test.ts +0 -18
  95. package/src/bridge/getTransactionStatus.test.ts +446 -0
  96. package/src/bridge/index.ts +3 -6
  97. package/src/bridge/onboard.integ.test.ts +44 -31
  98. package/src/bridge/onboard.ts +61 -209
  99. package/src/bridge/signOperation.ts +5 -6
  100. package/src/bridge/sync.integ.test.ts +30 -10
  101. package/src/bridge/sync.ts +87 -72
  102. package/src/common-logic/utils.test.ts +108 -0
  103. package/src/common-logic/utils.ts +4 -1
  104. package/src/config.ts +1 -1
  105. package/src/network/gateway.integ.test.ts +48 -21
  106. package/src/network/gateway.ts +49 -34
  107. package/src/types/bridge.ts +8 -19
  108. package/src/types/onboard.ts +5 -5
  109. package/lib/bridge/serialization.d.ts +0 -4
  110. package/lib/bridge/serialization.d.ts.map +0 -1
  111. package/lib/bridge/serialization.js +0 -31
  112. package/lib/bridge/serialization.js.map +0 -1
  113. package/lib-es/bridge/serialization.d.ts +0 -4
  114. package/lib-es/bridge/serialization.d.ts.map +0 -1
  115. package/lib-es/bridge/serialization.js +0 -27
  116. package/lib-es/bridge/serialization.js.map +0 -1
  117. package/src/bridge/serialization.ts +0 -36
@@ -1,23 +1,45 @@
1
+ import BigNumber from "bignumber.js";
1
2
  import { firstValueFrom, toArray } from "rxjs";
3
+ import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
4
+ import { emptyHistoryCache } from "@ledgerhq/coin-framework/account/index";
2
5
  import { generateMockKeyPair, createMockSigner } from "../test/cantonTestUtils";
3
- import { buildOnboardAccount, isAccountOnboarded, buildAuthorizePreapproval } from "./onboard";
4
6
  import {
7
+ AuthorizeStatus,
5
8
  OnboardStatus,
6
- PreApprovalStatus,
9
+ CantonAuthorizeProgress,
10
+ CantonAuthorizeResult,
7
11
  CantonOnboardProgress,
8
12
  CantonOnboardResult,
9
- CantonPreApprovalProgress,
10
- CantonPreApprovalResult,
11
13
  } from "../types/onboard";
12
14
  import coinConfig from "../config";
13
- import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
15
+ import { buildOnboardAccount, isAccountOnboarded, buildAuthorizePreapproval } from "./onboard";
14
16
 
15
17
  describe("onboard (devnet)", () => {
16
18
  const mockDeviceId = "test-device-id";
17
- const mockDerivationPath = "44'/6767'/0'/0'/0'";
18
19
  const mockCurrency = {
19
20
  id: "canton_network",
20
21
  } as unknown as CryptoCurrency;
22
+ const mockAccount = {
23
+ type: "Account" as const,
24
+ id: "js:2:canton_network:canton_3f5c9d9a:canton",
25
+ seedIdentifier: "canton_3f5c9d9a",
26
+ derivationMode: "canton" as const,
27
+ index: 0,
28
+ freshAddress: "canton_3f5c9d9a",
29
+ freshAddressPath: "44'/6767'/0'/0'/0'",
30
+ used: false,
31
+ balance: BigNumber(10000),
32
+ spendableBalance: BigNumber(10000),
33
+ creationDate: new Date(),
34
+ blockHeight: 1,
35
+ currency: mockCurrency,
36
+ operationsCount: 0,
37
+ operations: [],
38
+ pendingOperations: [],
39
+ lastSyncDate: new Date(),
40
+ balanceHistoryCache: emptyHistoryCache,
41
+ swapHistory: [],
42
+ };
21
43
 
22
44
  let onboardedAccount: {
23
45
  keyPair: ReturnType<typeof generateMockKeyPair>;
@@ -58,7 +80,7 @@ describe("onboard (devnet)", () => {
58
80
 
59
81
  const onboardObservable = buildOnboardAccount(mockSignerContext);
60
82
  const onboardValues = await firstValueFrom(
61
- onboardObservable(mockCurrency, mockDeviceId, mockDerivationPath).pipe(toArray()),
83
+ onboardObservable(mockCurrency, mockDeviceId, mockAccount).pipe(toArray()),
62
84
  );
63
85
  const onboardResult = onboardValues.find(
64
86
  (value): value is CantonOnboardResult => "partyId" in value,
@@ -82,10 +104,10 @@ describe("onboard (devnet)", () => {
82
104
  // THEN
83
105
  expect(result).not.toBe(false);
84
106
  if (typeof result === "object") {
85
- expect(result.party_id).toBeDefined();
86
- expect(result.party_id).toBe(onboardResult!.partyId);
107
+ expect(result.partyId).toBeDefined();
108
+ expect(result.partyId).toBe(onboardResult.partyId);
87
109
  }
88
- }, 30000);
110
+ }, 40000);
89
111
 
90
112
  it("should return false for non-onboarded account with fresh keypair", async () => {
91
113
  // GIVEN
@@ -122,7 +144,7 @@ describe("onboard (devnet)", () => {
122
144
 
123
145
  // WHEN
124
146
  const allValues = await firstValueFrom(
125
- onboardObservable(mockCurrency, mockDeviceId, mockDerivationPath).pipe(toArray()),
147
+ onboardObservable(mockCurrency, mockDeviceId, mockAccount).pipe(toArray()),
126
148
  );
127
149
  const progressValues = allValues.filter(
128
150
  (value): value is CantonOnboardProgress => "status" in value && !("partyId" in value),
@@ -149,12 +171,12 @@ describe("onboard (devnet)", () => {
149
171
 
150
172
  it("should complete full onboarding flow with already onboarded account", async () => {
151
173
  // GIVEN
152
- const { keyPair, mockSignerContext, onboardResult: firstResult } = getOnboardedAccount();
174
+ const { mockSignerContext, onboardResult: firstResult } = getOnboardedAccount();
153
175
  const secondOnboardObservable = buildOnboardAccount(mockSignerContext);
154
176
 
155
177
  // WHEN
156
178
  const secondOnboardValues = await firstValueFrom(
157
- secondOnboardObservable(mockCurrency, mockDeviceId, mockDerivationPath).pipe(toArray()),
179
+ secondOnboardObservable(mockCurrency, mockDeviceId, mockAccount).pipe(toArray()),
158
180
  );
159
181
  const secondResult = secondOnboardValues.find(
160
182
  (value): value is CantonOnboardResult => "partyId" in value,
@@ -175,27 +197,23 @@ describe("onboard (devnet)", () => {
175
197
 
176
198
  // WHEN
177
199
  const preapprovalValues = await firstValueFrom(
178
- preapprovalObservable(
179
- mockCurrency,
180
- mockDeviceId,
181
- mockDerivationPath,
182
- onboardResult.partyId,
183
- ).pipe(toArray()),
200
+ preapprovalObservable(mockCurrency, mockDeviceId, mockAccount, onboardResult.partyId).pipe(
201
+ toArray(),
202
+ ),
184
203
  );
185
204
 
186
205
  const progressValues = preapprovalValues.filter(
187
- (value): value is CantonPreApprovalProgress =>
188
- "status" in value && !("isApproved" in value),
206
+ (value): value is CantonAuthorizeProgress => "status" in value && !("isApproved" in value),
189
207
  );
190
208
  const resultValues = preapprovalValues.filter(
191
- (value): value is CantonPreApprovalResult => "isApproved" in value,
209
+ (value): value is CantonAuthorizeResult => "isApproved" in value,
192
210
  );
193
211
 
194
212
  // THEN
195
213
  // Check expected status progression
196
- expect(progressValues.some(p => p.status === PreApprovalStatus.PREPARE)).toBe(true);
197
- expect(progressValues.some(p => p.status === PreApprovalStatus.SIGN)).toBe(true);
198
- expect(progressValues.some(p => p.status === PreApprovalStatus.SUBMIT)).toBe(true);
214
+ expect(progressValues.some(p => p.status === AuthorizeStatus.PREPARE)).toBe(true);
215
+ expect(progressValues.some(p => p.status === AuthorizeStatus.SIGN)).toBe(true);
216
+ expect(progressValues.some(p => p.status === AuthorizeStatus.SUBMIT)).toBe(true);
199
217
 
200
218
  // Check final result (should be approved)
201
219
  expect(resultValues.length).toBeGreaterThan(0);
@@ -216,12 +234,7 @@ describe("onboard (devnet)", () => {
216
234
  // WHEN & THEN
217
235
  try {
218
236
  await firstValueFrom(
219
- preapprovalObservable(
220
- mockCurrency,
221
- mockDeviceId,
222
- mockDerivationPath,
223
- "invalid-party-id-123",
224
- ),
237
+ preapprovalObservable(mockCurrency, mockDeviceId, mockAccount, "invalid-party-id-123"),
225
238
  );
226
239
  expect(true).toBe(true);
227
240
  } catch (error) {
@@ -1,10 +1,8 @@
1
1
  import { Observable } from "rxjs";
2
2
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
3
- import { emptyHistoryCache } from "@ledgerhq/coin-framework/account/index";
4
- import { getDerivationModesForCurrency } from "@ledgerhq/coin-framework/derivation";
5
- import { getAccountShape } from "./sync";
6
- import { CantonAccount, CantonSigner } from "../types";
7
- import type { Account, DerivationMode } from "@ledgerhq/types-live";
3
+ import type { Account, Operation } from "@ledgerhq/types-live";
4
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
5
+ import { log } from "@ledgerhq/logs";
8
6
  import {
9
7
  prepareOnboarding,
10
8
  submitOnboarding,
@@ -16,148 +14,84 @@ import {
16
14
  } from "../network/gateway";
17
15
  import {
18
16
  OnboardStatus,
19
- PreApprovalStatus,
17
+ AuthorizeStatus,
20
18
  CantonOnboardProgress,
21
19
  CantonOnboardResult,
22
- CantonPreApprovalProgress,
23
- CantonPreApprovalResult,
24
- PrepareTransactionResponse,
20
+ CantonAuthorizeProgress,
21
+ CantonAuthorizeResult,
25
22
  } from "../types/onboard";
26
23
  import resolver from "../signer";
27
- import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
24
+ import type { CantonSigner } from "../types";
28
25
 
29
- async function _getKeypair(
30
- signerContext: SignerContext<CantonSigner>,
31
- deviceId: string,
32
- derivationPath: string,
33
- ) {
34
- return signerContext(deviceId, async signer => {
35
- const { publicKey, address } = await signer.getAddress(derivationPath);
36
- return { signer, publicKey: publicKey.replace("0x", ""), address };
37
- });
38
- }
39
-
40
- export const isAccountOnboarded = async (
41
- currency: CryptoCurrency,
42
- publicKey: string,
43
- ): Promise<{ isOnboarded: boolean; party_id?: string }> => {
26
+ export const isAccountOnboarded = async (currency: CryptoCurrency, publicKey: string) => {
44
27
  try {
45
28
  const { party_id } = await getPartyByPubKey(currency, publicKey);
46
29
 
47
30
  if (party_id) {
48
- return { isOnboarded: true, party_id };
31
+ return { isOnboarded: true, partyId: party_id };
49
32
  } else {
50
33
  return { isOnboarded: false };
51
34
  }
52
35
  } catch (err) {
53
- log("[isAccountOnboarded] Error checking party status (likely not onboarded):", err);
54
36
  return { isOnboarded: false };
55
37
  }
56
38
  };
57
39
 
40
+ export const isAccountAuthorized = async (operations: Operation[], partyId: string) => {
41
+ // temporary solution to check if the account is authorized
42
+ return operations.some(operation => operation.senders.includes(partyId));
43
+ };
44
+
58
45
  export const buildOnboardAccount =
59
46
  (signerContext: SignerContext<CantonSigner>) =>
60
47
  (
61
48
  currency: CryptoCurrency,
62
49
  deviceId: string,
63
- derivationPath: string,
50
+ account: Account,
64
51
  ): Observable<CantonOnboardProgress | CantonOnboardResult> =>
65
- new Observable(observer => {
52
+ new Observable(o => {
66
53
  async function main() {
67
- observer.next({
68
- status: OnboardStatus.INIT,
69
- });
70
- const derivationMode = getDerivationModesForCurrency(currency)[0];
54
+ o.next({ status: OnboardStatus.INIT });
55
+
71
56
  const getAddress = resolver(signerContext);
72
- const { address, publicKey } = await getAddress(deviceId, {
73
- path: derivationPath,
57
+ const { publicKey } = await getAddress(deviceId, {
58
+ path: account.freshAddressPath,
74
59
  currency,
75
- derivationMode: derivationMode || "",
60
+ derivationMode: account.derivationMode,
76
61
  });
77
62
 
78
- observer.next({
79
- status: OnboardStatus.PREPARE,
80
- });
63
+ o.next({ status: OnboardStatus.PREPARE });
81
64
 
82
- const { party_id: partyId } = await isAccountOnboarded(currency, publicKey);
65
+ let { partyId } = await isAccountOnboarded(currency, publicKey);
83
66
  if (partyId) {
84
- const account = await createAccount({
85
- address,
86
- derivationPath,
87
- partyId,
88
- currency,
89
- derivationMode,
90
- });
91
- observer.next({
92
- partyId,
93
- account,
94
- });
95
- observer.complete();
67
+ o.next({ partyId, account }); // success
96
68
  return;
97
69
  }
98
70
 
99
- const preparedTransaction = await prepareOnboarding(currency, publicKey, "ed25519");
71
+ const preparedTransaction = await prepareOnboarding(currency, publicKey);
72
+ partyId = preparedTransaction.party_id;
100
73
 
101
- observer.next({
102
- status: OnboardStatus.SIGN,
103
- });
74
+ o.next({ status: OnboardStatus.SIGN });
104
75
 
105
76
  const signature = await signerContext(deviceId, signer =>
106
- signer.signTransaction(derivationPath, preparedTransaction.transactions.combined_hash),
77
+ signer.signTransaction(
78
+ account.freshAddressPath,
79
+ preparedTransaction.transactions.combined_hash,
80
+ ),
107
81
  );
108
82
 
109
- observer.next({
110
- status: OnboardStatus.SUBMIT,
111
- });
83
+ o.next({ status: OnboardStatus.SUBMIT });
112
84
 
113
- const result = await submitOnboarding(
114
- currency,
115
- { public_key: publicKey, public_key_type: "ed25519" },
116
- preparedTransaction,
117
- signature,
118
- ).catch(async err => {
119
- if (err.type === "PARTY_ALREADY_EXISTS") {
120
- const account = await createAccount({
121
- address,
122
- derivationPath,
123
- partyId: preparedTransaction.party_id,
124
- currency,
125
- derivationMode,
126
- });
127
- observer.next({
128
- partyId: preparedTransaction.party_id,
129
- account,
130
- });
131
- return observer.complete();
132
- }
133
- throw err;
134
- });
135
-
136
- if (result) {
137
- observer.next({
138
- status: OnboardStatus.SUCCESS,
139
- });
140
- const account = await createAccount({
141
- address,
142
- derivationPath,
143
- partyId: result.party.party_id,
144
- currency,
145
- derivationMode,
146
- });
147
- observer.next({
148
- partyId: result.party.party_id,
149
- account,
150
- });
151
- }
85
+ await submitOnboarding(currency, publicKey, preparedTransaction, signature);
152
86
 
153
- observer.complete();
87
+ o.next({ partyId, account }); // success
154
88
  }
155
89
 
156
90
  main().then(
157
- () => observer.complete(),
91
+ () => o.complete(),
158
92
  error => {
159
- log("[onboardAccount] Error:", error);
160
- observer.error(error);
93
+ log("[canton:onboard] onboardAccount failed:", error);
94
+ o.error(error);
161
95
  },
162
96
  );
163
97
  });
@@ -167,146 +101,64 @@ export const buildAuthorizePreapproval =
167
101
  (
168
102
  currency: CryptoCurrency,
169
103
  deviceId: string,
170
- derivationPath: string,
104
+ account: Account,
171
105
  partyId: string,
172
- ): Observable<CantonPreApprovalProgress | CantonPreApprovalResult> =>
173
- new Observable(observer => {
106
+ ): Observable<CantonAuthorizeProgress | CantonAuthorizeResult> =>
107
+ new Observable(o => {
174
108
  async function main() {
175
- observer.next({
176
- status: PreApprovalStatus.PREPARE,
177
- });
109
+ o.next({ status: AuthorizeStatus.INIT });
178
110
 
179
- const preparedTransaction: PrepareTransactionResponse = await preparePreApprovalTransaction(
180
- currency,
181
- partyId,
182
- );
111
+ const isAuthorized = await isAccountAuthorized(account.operations, partyId);
183
112
 
184
- observer.next({
185
- status: PreApprovalStatus.SIGN,
186
- });
113
+ if (!isAuthorized) {
114
+ o.next({ status: AuthorizeStatus.PREPARE });
187
115
 
188
- const signature = await signerContext(deviceId, signer =>
189
- signer.signTransaction(derivationPath, preparedTransaction.hash),
190
- );
116
+ const preparedTransaction = await preparePreApprovalTransaction(currency, partyId);
191
117
 
192
- observer.next({
193
- status: PreApprovalStatus.SUBMIT,
194
- });
118
+ o.next({ status: AuthorizeStatus.SIGN });
195
119
 
196
- const { isApproved } = await submitPreApprovalTransaction(
197
- currency,
198
- partyId,
199
- preparedTransaction,
200
- signature,
201
- );
120
+ const signature = await signerContext(deviceId, signer =>
121
+ signer.signTransaction(account.freshAddressPath, preparedTransaction.hash),
122
+ );
202
123
 
203
- observer.next({
204
- status: PreApprovalStatus.SUCCESS,
205
- });
124
+ o.next({ status: AuthorizeStatus.SUBMIT });
206
125
 
207
- observer.next({
208
- isApproved,
209
- });
126
+ await submitPreApprovalTransaction(currency, partyId, preparedTransaction, signature);
127
+ }
128
+
129
+ o.next({ isApproved: true }); // success
210
130
 
211
131
  const handleTapRequest = async () => {
212
132
  try {
213
- const { serialized, hash } = await prepareTapRequest(currency, {
214
- partyId,
215
- });
133
+ const { serialized, hash } = await prepareTapRequest(currency, { partyId });
216
134
 
217
135
  if (serialized && hash) {
218
- observer.next({
219
- status: PreApprovalStatus.SIGN,
220
- });
136
+ o.next({ status: AuthorizeStatus.SIGN });
221
137
 
222
138
  const signature = await signerContext(deviceId, signer =>
223
- signer.signTransaction(derivationPath, hash),
139
+ signer.signTransaction(account.freshAddressPath, hash),
224
140
  );
225
141
 
226
- observer.next({
227
- status: PreApprovalStatus.SUBMIT,
228
- });
142
+ o.next({ status: AuthorizeStatus.SUBMIT });
229
143
 
230
144
  await submitTapRequest(currency, {
231
145
  partyId,
232
146
  serialized,
233
147
  signature,
234
148
  });
235
-
236
- observer.next({
237
- status: PreApprovalStatus.SUCCESS,
238
- });
239
149
  }
240
150
  } catch (err) {
241
151
  // Tap request failure should not break the pre-approval flow
242
152
  }
243
153
  };
244
154
  await handleTapRequest();
245
-
246
- observer.complete();
247
155
  }
248
156
 
249
157
  main().then(
250
- () => observer.complete(),
158
+ () => o.complete(),
251
159
  error => {
252
- log("[buildAuthorizePreapproval] Error:", error);
253
- observer.error(error);
160
+ log("[canton:onboard] authorizePreapproval failed:", error);
161
+ o.error(error);
254
162
  },
255
163
  );
256
164
  });
257
-
258
- const createAccount = async ({
259
- address,
260
- partyId,
261
- derivationPath,
262
- currency,
263
- derivationMode,
264
- index = 0,
265
- }: {
266
- address: string;
267
- derivationPath: string;
268
- partyId: string;
269
- currency: CryptoCurrency;
270
- derivationMode: DerivationMode;
271
- index?: number;
272
- }): Promise<Partial<Account>> => {
273
- const accountShape = await getAccountShape(
274
- {
275
- address,
276
- currency,
277
- derivationMode,
278
- derivationPath,
279
- index,
280
- rest: {
281
- cantonResources: {
282
- partyId,
283
- },
284
- },
285
- },
286
- { paginationConfig: {} },
287
- );
288
-
289
- const account: Partial<CantonAccount> = {
290
- ...accountShape,
291
- type: "Account",
292
- xpub: partyId.replace(/:/g, "_"),
293
- index,
294
- // operations: [],
295
- currency,
296
- derivationMode,
297
- lastSyncDate: new Date(),
298
- pendingOperations: [],
299
- seedIdentifier: address,
300
- balanceHistoryCache: emptyHistoryCache,
301
- cantonResources: {
302
- partyId,
303
- },
304
- };
305
-
306
- return account;
307
- };
308
-
309
- const log = (message: string, ...rest: unknown[]) => {
310
- // eslint-disable-next-line no-console
311
- console.log(message, ...rest);
312
- };
@@ -4,6 +4,7 @@ import { FeeNotLoaded } from "@ledgerhq/errors";
4
4
  import { AccountBridge, Operation } from "@ledgerhq/types-live";
5
5
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
6
6
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
7
+ import { decodeAccountId } from "@ledgerhq/coin-framework/account";
7
8
  import { combine, craftTransaction } from "../common-logic";
8
9
  import { Transaction, CantonSigner } from "../types";
9
10
 
@@ -22,10 +23,8 @@ export const buildSignOperation =
22
23
  });
23
24
 
24
25
  const signature = await signerContext(deviceId, async signer => {
25
- const { freshAddressPath: derivationPath } = account;
26
- const partyId = (account as unknown as { cantonResources: { partyId: string } })
27
- .cantonResources.partyId;
28
-
26
+ const { id, freshAddressPath: derivationPath, xpub } = account;
27
+ const address = xpub ?? decodeAccountId(id).xpubOrAddress;
29
28
  const params: {
30
29
  recipient?: string;
31
30
  amount: BigNumber;
@@ -45,13 +44,13 @@ export const buildSignOperation =
45
44
  const { hash, serializedTransaction } = await craftTransaction(
46
45
  account.currency,
47
46
  {
48
- address: partyId,
47
+ address,
49
48
  },
50
49
  params,
51
50
  );
52
51
  const transactionSignature = await signer.signTransaction(derivationPath, hash);
53
52
 
54
- return combine(serializedTransaction, `${transactionSignature}__PARTY__${partyId}`);
53
+ return combine(serializedTransaction, `${transactionSignature}__PARTY__${address}`);
55
54
  });
56
55
 
57
56
  o.next({
@@ -9,7 +9,9 @@ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
9
9
  import { Account, Operation } from "@ledgerhq/types-live";
10
10
  import coinConfig from "../config";
11
11
  import * as gateway from "../network/gateway";
12
- import { getAccountShape } from "./sync";
12
+ import { generateMockKeyPair, createMockSigner } from "../test/cantonTestUtils";
13
+ import { CantonAccount } from "../types";
14
+ import { makeGetAccountShape } from "./sync";
13
15
 
14
16
  const TEST_ADDRESS =
15
17
  "b6400f93ea1c74aea86be39b0ccc846fc5de01f12b2ad0d7c31848d6fb6eb6d9::1220c81315e2bf2524a9141bcc6cbf19b61c151e0dcaa95343c0ccf53aed7415c4ec";
@@ -22,14 +24,25 @@ const derivationPath = runDerivationScheme(
22
24
  account: 0,
23
25
  },
24
26
  );
25
- const ACCOUNT_SHAPE_INFO: AccountShapeInfo = {
27
+ const xpub = TEST_ADDRESS;
28
+ const ACCOUNT_SHAPE_INFO: AccountShapeInfo<CantonAccount> = {
26
29
  address: TEST_ADDRESS,
27
30
  currency,
28
31
  derivationMode,
29
32
  derivationPath,
30
33
  index: 0,
34
+ initialAccount: {
35
+ xpub,
36
+ } as CantonAccount,
31
37
  };
32
38
 
39
+ // Mock signer context for testing
40
+ const keyPair = generateMockKeyPair();
41
+ const mockSigner = createMockSigner(keyPair);
42
+ const mockSignerContext = jest.fn().mockImplementation((deviceId, callback) => {
43
+ return callback(mockSigner);
44
+ });
45
+
33
46
  describe("sync (devnet)", () => {
34
47
  beforeAll(async () => {
35
48
  coinConfig.setCoinConfig(() => ({
@@ -43,13 +56,14 @@ describe("sync (devnet)", () => {
43
56
  }));
44
57
  });
45
58
 
46
- describe("getAccountShape", () => {
59
+ describe("makeGetAccountShape", () => {
47
60
  it("should fetch account shape for a valid address", async () => {
61
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
48
62
  const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
49
63
 
50
64
  expect(result).toBeDefined();
51
65
  expect(result.id).toBeDefined();
52
- expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
66
+ expect(result.xpub).toBe(TEST_ADDRESS);
53
67
  expect(result.blockHeight).toBeGreaterThan(0);
54
68
  expect(result.balance).toBeDefined();
55
69
  expect(result.spendableBalance).toBeDefined();
@@ -67,12 +81,15 @@ describe("sync (devnet)", () => {
67
81
  });
68
82
 
69
83
  it("should handle address with colons correctly", async () => {
84
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
70
85
  const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
71
86
 
72
- expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
87
+ expect(result.xpub).toContain("::");
73
88
  });
74
89
 
75
90
  it("should merge operations correctly with initial account", async () => {
91
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
92
+
76
93
  const operations: Operation[] = [
77
94
  {
78
95
  id: "test-op-1",
@@ -95,7 +112,7 @@ describe("sync (devnet)", () => {
95
112
  {
96
113
  ...ACCOUNT_SHAPE_INFO,
97
114
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
98
- initialAccount: { operations } as Account,
115
+ initialAccount: { xpub, operations } as Account,
99
116
  },
100
117
  { paginationConfig: {} },
101
118
  );
@@ -117,6 +134,7 @@ describe("sync (devnet)", () => {
117
134
  },
118
135
  ]);
119
136
 
137
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
120
138
  const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
121
139
 
122
140
  expect(result.balance?.toNumber()).toBe(1000000);
@@ -125,7 +143,7 @@ describe("sync (devnet)", () => {
125
143
  mockGetBalance.mockRestore();
126
144
  });
127
145
 
128
- it("should call getOperations with correct cursor based with initial account", async () => {
146
+ it("should call getOperations with correct cursor based on initial account", async () => {
129
147
  const mockGetOperations = jest.spyOn(gateway, "getOperations");
130
148
  const operation: Operation = {
131
149
  id: "test-op-1",
@@ -144,8 +162,9 @@ describe("sync (devnet)", () => {
144
162
  };
145
163
 
146
164
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
147
- const initialAccount = { operations: [operation] } as Account;
165
+ const initialAccount = { xpub, operations: [operation] } as Account;
148
166
 
167
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
149
168
  const result = await getAccountShape(
150
169
  {
151
170
  ...ACCOUNT_SHAPE_INFO,
@@ -154,7 +173,7 @@ describe("sync (devnet)", () => {
154
173
  { paginationConfig: {} },
155
174
  );
156
175
 
157
- expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
176
+ expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
158
177
  cursor: (operation.blockHeight || 0) + 1,
159
178
  limit: 100,
160
179
  });
@@ -165,12 +184,13 @@ describe("sync (devnet)", () => {
165
184
  it("should call getOperations with cursor 0 when no initial account", async () => {
166
185
  const mockGetOperations = jest.spyOn(gateway, "getOperations");
167
186
 
187
+ const getAccountShape = makeGetAccountShape(mockSignerContext);
168
188
  const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
169
189
 
170
190
  expect(result.operations).toBeDefined();
171
191
  expect(result.operationsCount).toBeGreaterThanOrEqual(1);
172
192
 
173
- expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
193
+ expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
174
194
  cursor: 0,
175
195
  limit: 100,
176
196
  });