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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +12 -4
  3. package/CHANGELOG.md +21 -0
  4. package/lib/api/getBalance.integ.test.js +1 -1
  5. package/lib/api/getBalance.integ.test.js.map +1 -1
  6. package/lib/api/index.d.ts.map +1 -1
  7. package/lib/api/index.js +11 -8
  8. package/lib/api/index.js.map +1 -1
  9. package/lib/bridge/createTransaction.test.js +1 -1
  10. package/lib/bridge/createTransaction.test.js.map +1 -1
  11. package/lib/bridge/index.d.ts +3 -3
  12. package/lib/bridge/index.d.ts.map +1 -1
  13. package/lib/bridge/index.js +9 -1
  14. package/lib/bridge/index.js.map +1 -1
  15. package/lib/bridge/onboard.d.ts +10 -0
  16. package/lib/bridge/onboard.d.ts.map +1 -0
  17. package/lib/bridge/onboard.integ.test.d.ts +2 -0
  18. package/lib/bridge/onboard.integ.test.d.ts.map +1 -0
  19. package/lib/bridge/onboard.integ.test.js +156 -0
  20. package/lib/bridge/onboard.integ.test.js.map +1 -0
  21. package/lib/bridge/onboard.js +139 -0
  22. package/lib/bridge/onboard.js.map +1 -0
  23. package/lib/bridge/prepareTransaction.d.ts.map +1 -1
  24. package/lib/bridge/prepareTransaction.js +5 -7
  25. package/lib/bridge/prepareTransaction.js.map +1 -1
  26. package/lib/bridge/signOperation.d.ts.map +1 -1
  27. package/lib/bridge/signOperation.js +2 -1
  28. package/lib/bridge/signOperation.js.map +1 -1
  29. package/lib/bridge/sync.d.ts.map +1 -1
  30. package/lib/bridge/sync.integ.test.d.ts +2 -0
  31. package/lib/bridge/sync.integ.test.d.ts.map +1 -0
  32. package/lib/bridge/sync.integ.test.js +175 -0
  33. package/lib/bridge/sync.integ.test.js.map +1 -0
  34. package/lib/bridge/sync.js +39 -36
  35. package/lib/bridge/sync.js.map +1 -1
  36. package/lib/bridge/updateTransaction.d.ts.map +1 -1
  37. package/lib/bridge/updateTransaction.js +0 -4
  38. package/lib/bridge/updateTransaction.js.map +1 -1
  39. package/lib/common-logic/history/listOperations.d.ts.map +1 -1
  40. package/lib/common-logic/history/listOperations.js +19 -31
  41. package/lib/common-logic/history/listOperations.js.map +1 -1
  42. package/lib/common-logic/transaction/craftTransaction.d.ts +4 -3
  43. package/lib/common-logic/transaction/craftTransaction.d.ts.map +1 -1
  44. package/lib/common-logic/transaction/craftTransaction.js +10 -12
  45. package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
  46. package/lib/network/gateway.d.ts +194 -3
  47. package/lib/network/gateway.d.ts.map +1 -1
  48. package/lib/network/gateway.integ.test.js +121 -11
  49. package/lib/network/gateway.integ.test.js.map +1 -1
  50. package/lib/network/gateway.js +96 -18
  51. package/lib/network/gateway.js.map +1 -1
  52. package/lib/network/node.d.ts +2 -2
  53. package/lib/network/node.d.ts.map +1 -1
  54. package/lib/network/node.js.map +1 -1
  55. package/lib/network/types.d.ts +1 -1
  56. package/lib/network/types.d.ts.map +1 -1
  57. package/lib/signer/getAddress.d.ts.map +1 -1
  58. package/lib/signer/getAddress.js +2 -2
  59. package/lib/signer/getAddress.js.map +1 -1
  60. package/lib/test/cantonTestUtils.d.ts +33 -0
  61. package/lib/test/cantonTestUtils.d.ts.map +1 -0
  62. package/lib/test/cantonTestUtils.js +159 -0
  63. package/lib/test/cantonTestUtils.js.map +1 -0
  64. package/lib/types/bridge.d.ts +7 -1
  65. package/lib/types/bridge.d.ts.map +1 -1
  66. package/lib/types/index.d.ts +1 -10
  67. package/lib/types/index.d.ts.map +1 -1
  68. package/lib/types/index.js +1 -0
  69. package/lib/types/index.js.map +1 -1
  70. package/lib/types/onboard.d.ts +55 -0
  71. package/lib/types/onboard.d.ts.map +1 -0
  72. package/lib/types/onboard.js +22 -0
  73. package/lib/types/onboard.js.map +1 -0
  74. package/lib-es/api/getBalance.integ.test.js +1 -1
  75. package/lib-es/api/getBalance.integ.test.js.map +1 -1
  76. package/lib-es/api/index.d.ts.map +1 -1
  77. package/lib-es/api/index.js +12 -9
  78. package/lib-es/api/index.js.map +1 -1
  79. package/lib-es/bridge/createTransaction.test.js +1 -1
  80. package/lib-es/bridge/createTransaction.test.js.map +1 -1
  81. package/lib-es/bridge/index.d.ts +3 -3
  82. package/lib-es/bridge/index.d.ts.map +1 -1
  83. package/lib-es/bridge/index.js +9 -1
  84. package/lib-es/bridge/index.js.map +1 -1
  85. package/lib-es/bridge/onboard.d.ts +10 -0
  86. package/lib-es/bridge/onboard.d.ts.map +1 -0
  87. package/lib-es/bridge/onboard.integ.test.d.ts +2 -0
  88. package/lib-es/bridge/onboard.integ.test.d.ts.map +1 -0
  89. package/lib-es/bridge/onboard.integ.test.js +151 -0
  90. package/lib-es/bridge/onboard.integ.test.js.map +1 -0
  91. package/lib-es/bridge/onboard.js +133 -0
  92. package/lib-es/bridge/onboard.js.map +1 -0
  93. package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
  94. package/lib-es/bridge/prepareTransaction.js +6 -8
  95. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  96. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  97. package/lib-es/bridge/signOperation.js +2 -1
  98. package/lib-es/bridge/signOperation.js.map +1 -1
  99. package/lib-es/bridge/sync.d.ts.map +1 -1
  100. package/lib-es/bridge/sync.integ.test.d.ts +2 -0
  101. package/lib-es/bridge/sync.integ.test.d.ts.map +1 -0
  102. package/lib-es/bridge/sync.integ.test.js +137 -0
  103. package/lib-es/bridge/sync.integ.test.js.map +1 -0
  104. package/lib-es/bridge/sync.js +38 -35
  105. package/lib-es/bridge/sync.js.map +1 -1
  106. package/lib-es/bridge/updateTransaction.d.ts.map +1 -1
  107. package/lib-es/bridge/updateTransaction.js +0 -4
  108. package/lib-es/bridge/updateTransaction.js.map +1 -1
  109. package/lib-es/common-logic/history/listOperations.d.ts.map +1 -1
  110. package/lib-es/common-logic/history/listOperations.js +20 -29
  111. package/lib-es/common-logic/history/listOperations.js.map +1 -1
  112. package/lib-es/common-logic/transaction/craftTransaction.d.ts +4 -3
  113. package/lib-es/common-logic/transaction/craftTransaction.d.ts.map +1 -1
  114. package/lib-es/common-logic/transaction/craftTransaction.js +10 -12
  115. package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
  116. package/lib-es/network/gateway.d.ts +194 -3
  117. package/lib-es/network/gateway.d.ts.map +1 -1
  118. package/lib-es/network/gateway.integ.test.js +122 -12
  119. package/lib-es/network/gateway.integ.test.js.map +1 -1
  120. package/lib-es/network/gateway.js +90 -17
  121. package/lib-es/network/gateway.js.map +1 -1
  122. package/lib-es/network/node.d.ts +2 -2
  123. package/lib-es/network/node.d.ts.map +1 -1
  124. package/lib-es/network/node.js.map +1 -1
  125. package/lib-es/network/types.d.ts +1 -1
  126. package/lib-es/network/types.d.ts.map +1 -1
  127. package/lib-es/signer/getAddress.d.ts.map +1 -1
  128. package/lib-es/signer/getAddress.js +2 -2
  129. package/lib-es/signer/getAddress.js.map +1 -1
  130. package/lib-es/test/cantonTestUtils.d.ts +33 -0
  131. package/lib-es/test/cantonTestUtils.d.ts.map +1 -0
  132. package/lib-es/test/cantonTestUtils.js +151 -0
  133. package/lib-es/test/cantonTestUtils.js.map +1 -0
  134. package/lib-es/types/bridge.d.ts +7 -1
  135. package/lib-es/types/bridge.d.ts.map +1 -1
  136. package/lib-es/types/index.d.ts +1 -10
  137. package/lib-es/types/index.d.ts.map +1 -1
  138. package/lib-es/types/index.js +1 -0
  139. package/lib-es/types/index.js.map +1 -1
  140. package/lib-es/types/onboard.d.ts +55 -0
  141. package/lib-es/types/onboard.d.ts.map +1 -0
  142. package/lib-es/types/onboard.js +19 -0
  143. package/lib-es/types/onboard.js.map +1 -0
  144. package/package.json +6 -6
  145. package/src/api/getBalance.integ.test.ts +1 -2
  146. package/src/api/index.ts +33 -26
  147. package/src/bridge/createTransaction.test.ts +1 -1
  148. package/src/bridge/index.ts +14 -4
  149. package/src/bridge/onboard.integ.test.ts +219 -0
  150. package/src/bridge/onboard.ts +220 -0
  151. package/src/bridge/prepareTransaction.ts +6 -15
  152. package/src/bridge/signOperation.ts +3 -2
  153. package/src/bridge/sync.integ.test.ts +180 -0
  154. package/src/bridge/sync.ts +57 -49
  155. package/src/bridge/updateTransaction.ts +0 -5
  156. package/src/common-logic/history/listOperations.ts +20 -31
  157. package/src/common-logic/transaction/craftTransaction.ts +13 -17
  158. package/src/network/gateway.integ.test.ts +156 -17
  159. package/src/network/gateway.ts +333 -26
  160. package/src/network/node.ts +3 -3
  161. package/src/network/types.ts +1 -1
  162. package/src/signer/getAddress.ts +3 -5
  163. package/src/test/cantonTestUtils.ts +181 -0
  164. package/src/types/bridge.ts +20 -0
  165. package/src/types/index.ts +1 -11
  166. package/src/types/onboard.ts +65 -0
@@ -0,0 +1,220 @@
1
+ import { Observable } from "rxjs";
2
+ import { SignerContext } from "@ledgerhq/coin-framework/signer";
3
+ import { CantonSigner } from "../types/signer";
4
+ import {
5
+ prepareOnboarding,
6
+ submitOnboarding,
7
+ getPartyByPubKey,
8
+ preparePreApprovalTransaction,
9
+ submitPreApprovalTransaction,
10
+ prepareTapRequest,
11
+ submitTapRequest,
12
+ } from "../network/gateway";
13
+ import {
14
+ OnboardStatus,
15
+ PreApprovalStatus,
16
+ CantonOnboardProgress,
17
+ CantonOnboardResult,
18
+ CantonPreApprovalProgress,
19
+ CantonPreApprovalResult,
20
+ PrepareTransactionResponse,
21
+ } from "../types/onboard";
22
+
23
+ async function getKeypair(
24
+ signerContext: SignerContext<CantonSigner>,
25
+ deviceId: string,
26
+ derivationPath: string,
27
+ ) {
28
+ return signerContext(deviceId, async signer => {
29
+ const { publicKey, address } = await signer.getAddress(derivationPath);
30
+ return { signer, publicKey: publicKey.replace("0x", ""), address };
31
+ });
32
+ }
33
+
34
+ export const isAccountOnboarded = async (
35
+ publicKey: string,
36
+ ): Promise<false | { party_id: string }> => {
37
+ try {
38
+ const { party_id } = await getPartyByPubKey(publicKey);
39
+
40
+ if (party_id) {
41
+ return { party_id };
42
+ } else {
43
+ return false;
44
+ }
45
+ } catch (err) {
46
+ log("[isAccountOnboarded] Error checking party status (likely not onboarded):", err);
47
+ return false;
48
+ }
49
+ };
50
+
51
+ export const buildOnboardAccount =
52
+ (signerContext: SignerContext<CantonSigner>) =>
53
+ (
54
+ deviceId: string,
55
+ derivationPath: string,
56
+ ): Observable<CantonOnboardProgress | CantonOnboardResult> =>
57
+ new Observable(observer => {
58
+ async function main() {
59
+ observer.next({
60
+ status: OnboardStatus.INIT,
61
+ });
62
+ const keypair = await getKeypair(signerContext, deviceId, derivationPath);
63
+
64
+ observer.next({
65
+ status: OnboardStatus.PREPARE,
66
+ });
67
+
68
+ const isOnboardedResult = await isAccountOnboarded(keypair.publicKey);
69
+ if (isOnboardedResult && isOnboardedResult.party_id) {
70
+ observer.next({
71
+ partyId: isOnboardedResult.party_id,
72
+ });
73
+ observer.complete();
74
+ return;
75
+ }
76
+
77
+ const preparedTransaction = await prepareOnboarding(keypair.publicKey, "ed25519");
78
+
79
+ observer.next({
80
+ status: OnboardStatus.SIGN,
81
+ });
82
+
83
+ const signature = await signerContext(deviceId, signer =>
84
+ signer.signTransaction(derivationPath, preparedTransaction.transactions.combined_hash),
85
+ );
86
+
87
+ observer.next({
88
+ status: OnboardStatus.SUBMIT,
89
+ });
90
+
91
+ const result = await submitOnboarding(
92
+ { public_key: keypair.publicKey, public_key_type: "ed25519" },
93
+ preparedTransaction,
94
+ signature,
95
+ ).catch(err => {
96
+ if (err.type === "PARTY_ALREADY_EXISTS") {
97
+ observer.next({
98
+ partyId: preparedTransaction.party_id,
99
+ });
100
+ return observer.complete();
101
+ }
102
+ throw err;
103
+ });
104
+
105
+ observer.next({
106
+ status: OnboardStatus.SUCCESS,
107
+ });
108
+
109
+ observer.next({
110
+ partyId: result?.party?.party_id || "unknown",
111
+ });
112
+
113
+ observer.complete();
114
+ }
115
+
116
+ main().then(
117
+ () => observer.complete(),
118
+ error => {
119
+ log("[onboardAccount] Error:", error);
120
+ observer.error(error);
121
+ },
122
+ );
123
+ });
124
+
125
+ export const buildAuthorizePreapproval =
126
+ (signerContext: SignerContext<CantonSigner>) =>
127
+ (
128
+ deviceId: string,
129
+ derivationPath: string,
130
+ partyId: string,
131
+ ): Observable<CantonPreApprovalProgress | CantonPreApprovalResult> =>
132
+ new Observable(observer => {
133
+ async function main() {
134
+ observer.next({
135
+ status: PreApprovalStatus.PREPARE,
136
+ });
137
+
138
+ const preparedTransaction: PrepareTransactionResponse =
139
+ await preparePreApprovalTransaction(partyId);
140
+
141
+ observer.next({
142
+ status: PreApprovalStatus.SIGN,
143
+ });
144
+
145
+ const signature = await signerContext(deviceId, signer =>
146
+ signer.signTransaction(derivationPath, preparedTransaction.hash),
147
+ );
148
+
149
+ observer.next({
150
+ status: PreApprovalStatus.SUBMIT,
151
+ });
152
+
153
+ const { isApproved } = await submitPreApprovalTransaction(
154
+ partyId,
155
+ preparedTransaction,
156
+ signature,
157
+ );
158
+
159
+ observer.next({
160
+ status: PreApprovalStatus.SUCCESS,
161
+ });
162
+
163
+ observer.next({
164
+ isApproved,
165
+ });
166
+
167
+ // TODO: remove after demo
168
+ const handleTapRequest = async () => {
169
+ try {
170
+ observer.next({
171
+ status: PreApprovalStatus.PREPARE,
172
+ });
173
+
174
+ const { serialized, hash } = await prepareTapRequest({
175
+ partyId,
176
+ });
177
+
178
+ observer.next({
179
+ status: PreApprovalStatus.SIGN,
180
+ });
181
+
182
+ const signature = await signerContext(deviceId, signer =>
183
+ signer.signTransaction(derivationPath, hash),
184
+ );
185
+
186
+ observer.next({
187
+ status: PreApprovalStatus.SUBMIT,
188
+ });
189
+
190
+ await submitTapRequest({
191
+ partyId,
192
+ serialized,
193
+ signature,
194
+ });
195
+
196
+ observer.next({
197
+ status: PreApprovalStatus.SUCCESS,
198
+ });
199
+ } catch (err) {
200
+ // Tap request failure should not break the pre-approval flow
201
+ }
202
+ };
203
+ await handleTapRequest();
204
+
205
+ observer.complete();
206
+ }
207
+
208
+ main().then(
209
+ () => observer.complete(),
210
+ error => {
211
+ log("[buildAuthorizePreapproval] Error:", error);
212
+ observer.error(error);
213
+ },
214
+ );
215
+ });
216
+
217
+ const log = (message: string, ...rest: any[]) => {
218
+ // eslint-disable-next-line no-console
219
+ console.log(message, ...rest);
220
+ };
@@ -1,25 +1,16 @@
1
1
  import { AccountBridge } from "@ledgerhq/types-live";
2
2
  import { Transaction } from "../types";
3
- import { craftTransaction, estimateFees } from "../common-logic";
4
- import { getNextSequence } from "../network/node";
3
+ import { estimateFees } from "../common-logic";
5
4
  import BigNumber from "bignumber.js";
5
+ import { updateTransaction } from "./updateTransaction";
6
6
 
7
7
  export const prepareTransaction: AccountBridge<Transaction>["prepareTransaction"] = async (
8
8
  account,
9
9
  transaction,
10
10
  ) => {
11
- const seq = await getNextSequence(account.freshAddress);
12
-
13
- const craftedTransaction = await craftTransaction(
14
- { address: account.freshAddress, nextSequenceNumber: seq },
15
- { amount: transaction.amount, recipient: transaction.recipient },
16
- );
17
-
18
- const fee = await estimateFees(craftedTransaction.serializedTransaction);
19
-
20
- if (transaction.fee !== new BigNumber(fee.toString())) {
21
- return { ...transaction, fee: new BigNumber(fee.toString()) };
11
+ let fee = transaction.fee;
12
+ if (!fee || fee.eq(0)) {
13
+ fee = BigNumber((await estimateFees("")).toString());
22
14
  }
23
-
24
- return transaction;
15
+ return updateTransaction(transaction, { fee });
25
16
  };
@@ -4,7 +4,7 @@ import { AccountBridge, Operation } from "@ledgerhq/types-live";
4
4
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
5
5
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
6
6
  import { combine, craftTransaction, getNextValidSequence } from "../common-logic";
7
- import { Transaction, CantonSigner, BoilerplateNativeTransaction } from "../types";
7
+ import { Transaction, CantonSigner } from "../types";
8
8
 
9
9
  export const buildSignOperation =
10
10
  (signerContext: SignerContext<CantonSigner>): AccountBridge<Transaction>["signOperation"] =>
@@ -34,7 +34,8 @@ export const buildSignOperation =
34
34
  {
35
35
  recipient: transaction.recipient,
36
36
  amount: transaction.amount,
37
- fee: fee,
37
+ expireInSeconds: 60 * 60 * 24,
38
+ tokenId: "Amulet",
38
39
  },
39
40
  );
40
41
 
@@ -0,0 +1,180 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { AccountShapeInfo } from "@ledgerhq/coin-framework/bridge/jsHelpers";
3
+ import {
4
+ getDerivationModesForCurrency,
5
+ getDerivationScheme,
6
+ runDerivationScheme,
7
+ } from "@ledgerhq/coin-framework/derivation";
8
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
9
+ import { Account, Operation } from "@ledgerhq/types-live";
10
+ import coinConfig from "../config";
11
+ import * as gateway from "../network/gateway";
12
+ import { getAccountShape } from "./sync";
13
+
14
+ const TEST_ADDRESS =
15
+ "b6400f93ea1c74aea86be39b0ccc846fc5de01f12b2ad0d7c31848d6fb6eb6d9::1220c81315e2bf2524a9141bcc6cbf19b61c151e0dcaa95343c0ccf53aed7415c4ec";
16
+ const currency = getCryptoCurrencyById("canton_network");
17
+ const derivationMode = getDerivationModesForCurrency(currency)[0];
18
+ const derivationPath = runDerivationScheme(
19
+ getDerivationScheme({ derivationMode, currency }),
20
+ currency,
21
+ {
22
+ account: 0,
23
+ },
24
+ );
25
+ const ACCOUNT_SHAPE_INFO: AccountShapeInfo = {
26
+ address: TEST_ADDRESS,
27
+ currency,
28
+ derivationMode,
29
+ derivationPath,
30
+ index: 0,
31
+ };
32
+
33
+ describe("sync (devnet)", () => {
34
+ beforeAll(async () => {
35
+ coinConfig.setCoinConfig(() => ({
36
+ gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
37
+ useGateway: true,
38
+ networkType: "devnet",
39
+ status: {
40
+ type: "active",
41
+ },
42
+ }));
43
+ });
44
+
45
+ describe("getAccountShape", () => {
46
+ it("should fetch account shape for a valid address", async () => {
47
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
48
+
49
+ expect(result).toBeDefined();
50
+ expect(result.id).toBeDefined();
51
+ expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
52
+ expect(result.blockHeight).toBeGreaterThan(0);
53
+ expect(result.balance).toBeDefined();
54
+ expect(result.spendableBalance).toBeDefined();
55
+ expect(result.operations).toBeDefined();
56
+ expect(result.operationsCount).toBeGreaterThanOrEqual(0);
57
+
58
+ expect(result.balance).toBeInstanceOf(Object);
59
+ expect(result.balance?.toNumber).toBeDefined();
60
+ expect(result.spendableBalance).toBeInstanceOf(Object);
61
+ expect(result.spendableBalance?.toNumber).toBeDefined();
62
+
63
+ expect(result.spendableBalance?.toNumber()).toBeLessThanOrEqual(
64
+ result.balance?.toNumber() || 0,
65
+ );
66
+ });
67
+
68
+ it("should handle address with colons correctly", async () => {
69
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
70
+
71
+ expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
72
+ });
73
+
74
+ it("should merge operations correctly with initial account", async () => {
75
+ const operations: Operation[] = [
76
+ {
77
+ id: "test-op-1",
78
+ hash: "test-hash-1",
79
+ accountId: "test-account",
80
+ type: "OUT" as const,
81
+ value: new BigNumber(1000000),
82
+ fee: new BigNumber(100000),
83
+ blockHash: "block-hash-1",
84
+ blockHeight: 100,
85
+ senders: [TEST_ADDRESS],
86
+ recipients: ["recipient-1"],
87
+ date: new Date("2023-01-01"),
88
+ transactionSequenceNumber: 100,
89
+ extra: { uid: "uid-1" },
90
+ },
91
+ ];
92
+
93
+ const result = await getAccountShape(
94
+ {
95
+ ...ACCOUNT_SHAPE_INFO,
96
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
97
+ initialAccount: { operations } as Account,
98
+ },
99
+ { paginationConfig: {} },
100
+ );
101
+
102
+ expect(result.operations).toBeDefined();
103
+ expect(result.operationsCount).toBeGreaterThanOrEqual(1);
104
+ const initialOp = result.operations?.find(op => op.id === "test-op-1");
105
+ expect(initialOp).toBeDefined();
106
+ });
107
+
108
+ it("should take locked balance into account when calculating spendable balance", async () => {
109
+ const mockGetBalance = jest.spyOn(gateway, "getBalance");
110
+
111
+ mockGetBalance.mockResolvedValue([
112
+ {
113
+ instrument_id: "canton_network",
114
+ amount: 1000000,
115
+ locked: true,
116
+ },
117
+ ]);
118
+
119
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
120
+
121
+ expect(result.balance?.toNumber()).toBe(1000000);
122
+ expect(result.spendableBalance?.toNumber()).toBe(0);
123
+
124
+ mockGetBalance.mockRestore();
125
+ });
126
+
127
+ it("should call getOperations with correct cursor based with initial account", async () => {
128
+ const mockGetOperations = jest.spyOn(gateway, "getOperations");
129
+ const operation: Operation = {
130
+ id: "test-op-1",
131
+ hash: "test-hash-1",
132
+ accountId: "test-account",
133
+ type: "OUT" as const,
134
+ value: new BigNumber(1000000),
135
+ fee: new BigNumber(100000),
136
+ blockHash: "block-hash-1",
137
+ blockHeight: 100,
138
+ senders: [TEST_ADDRESS],
139
+ recipients: ["recipient-1"],
140
+ date: new Date("2023-01-01"),
141
+ transactionSequenceNumber: 100,
142
+ extra: { uid: "uid-1" },
143
+ };
144
+
145
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
146
+ const initialAccount = { operations: [operation] } as Account;
147
+
148
+ const result = await getAccountShape(
149
+ {
150
+ ...ACCOUNT_SHAPE_INFO,
151
+ initialAccount,
152
+ },
153
+ { paginationConfig: {} },
154
+ );
155
+
156
+ expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
157
+ cursor: (operation.blockHeight || 0) + 1,
158
+ limit: 100,
159
+ });
160
+ expect(result.operations).toBeDefined();
161
+ expect(result.operationsCount).toBeGreaterThan(1);
162
+ });
163
+
164
+ it("should call getOperations with cursor 0 when no initial account", async () => {
165
+ const mockGetOperations = jest.spyOn(gateway, "getOperations");
166
+
167
+ const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
168
+
169
+ expect(result.operations).toBeDefined();
170
+ expect(result.operationsCount).toBeGreaterThan(1);
171
+
172
+ expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
173
+ cursor: 0,
174
+ limit: 100,
175
+ });
176
+
177
+ mockGetOperations.mockRestore();
178
+ });
179
+ });
180
+ });
@@ -2,101 +2,109 @@ import BigNumber from "bignumber.js";
2
2
  import { Operation } from "@ledgerhq/types-live";
3
3
  import { encodeAccountId } from "@ledgerhq/coin-framework/account/index";
4
4
  import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
5
- import { getTransactions } from "../network/indexer";
6
- import { getAccountInfo, getLedgerEnd } from "../network/node";
5
+ import { getBalance, getLedgerEnd, getOperations, type OperationInfo } from "../network/gateway";
7
6
 
8
7
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
9
- import { BoilerplateOperation } from "../network/types";
10
8
  import coinConfig from "../config";
11
9
 
12
- const operationAdapter =
10
+ const txInfoToOperationAdapter =
13
11
  (accountId: string, address: string) =>
14
- ({
15
- meta: { delivered_amount },
16
- tx: { Fee, hash, inLedger, date, Account, Destination, Sequence },
17
- }: BoilerplateOperation) => {
18
- const type = Account === address ? "OUT" : "IN";
19
- let value =
20
- delivered_amount && typeof delivered_amount === "string"
21
- ? new BigNumber(delivered_amount)
22
- : new BigNumber(0);
23
- const feeValue = new BigNumber(Fee);
24
-
25
- if (type === "OUT") {
26
- if (!Number.isNaN(feeValue)) {
27
- value = value.plus(feeValue);
28
- }
29
- }
12
+ (txInfo: OperationInfo): Operation => {
13
+ const {
14
+ transaction_hash,
15
+ uid,
16
+ block: { height, hash },
17
+ senders,
18
+ recipients,
19
+ transaction_timestamp,
20
+ fee: { value: fee },
21
+ transfers: [{ value: transferValue }],
22
+ } = txInfo;
23
+
24
+ const type = senders.includes(address) ? "OUT" : "IN";
25
+ const value = new BigNumber(transferValue);
26
+ const feeValue = new BigNumber(fee);
30
27
 
31
28
  const op: Operation = {
32
- id: encodeOperationId(accountId, hash, type),
33
- hash: hash,
29
+ id: encodeOperationId(accountId, transaction_hash, type),
30
+ hash: transaction_hash,
34
31
  accountId,
35
32
  type,
36
33
  value,
37
34
  fee: feeValue,
38
- blockHash: null,
39
- blockHeight: inLedger,
40
- senders: [Account],
41
- recipients: [Destination],
42
- date: new Date(),
43
- transactionSequenceNumber: Sequence,
44
- extra: {},
35
+ blockHash: hash,
36
+ blockHeight: height,
37
+ senders,
38
+ recipients,
39
+ date: new Date(transaction_timestamp),
40
+ transactionSequenceNumber: height,
41
+ extra: {
42
+ uid,
43
+ },
45
44
  };
46
45
 
47
46
  return op;
48
47
  };
49
48
 
50
49
  const filterOperations = (
51
- transactions: BoilerplateOperation[],
50
+ transactions: OperationInfo[],
52
51
  accountId: string,
53
52
  address: string,
54
- ) => {
53
+ ): Operation[] => {
55
54
  return transactions
56
- .filter(
57
- ({ tx, meta }: BoilerplateOperation) =>
58
- tx.TransactionType === "Payment" && typeof meta.delivered_amount === "string",
59
- )
60
- .map(operationAdapter(accountId, address))
61
- .filter((op): op is Operation => Boolean(op));
55
+ .filter(tx => tx.type === "Receive" || tx.type === "Send")
56
+ .map(txInfoToOperationAdapter(accountId, address));
62
57
  };
63
58
 
64
59
  export const getAccountShape: GetAccountShape = async info => {
65
60
  const { address, initialAccount, currency, derivationMode } = info;
61
+ // TODO: we need better solution ?
62
+ const xpubOrAddress = address?.replace(/:/g, "_") || "";
66
63
 
67
64
  const accountId = encodeAccountId({
68
65
  type: "js",
69
66
  version: "2",
70
67
  currencyId: currency.id,
71
- xpubOrAddress: address,
72
- derivationMode,
68
+ xpubOrAddress,
69
+ derivationMode: "",
73
70
  });
74
71
 
75
72
  // blockheight retrieval
76
73
  const blockHeight = await getLedgerEnd();
77
74
 
78
75
  // Account info retrieval + spendable balance calculation
79
- const accountInfo = await getAccountInfo(address);
80
- const balance = new BigNumber(accountInfo.account_data.Balance);
76
+ // const accountInfo = await getAccountInfo(address);
77
+ const balances = await getBalance(address);
78
+
79
+ const balanceData = balances.find(balance => balance.instrument_id === "canton_network") || {
80
+ instrument_id: "canton_network",
81
+ amount: 0,
82
+ locked: false,
83
+ };
84
+
85
+ const balance = new BigNumber(balanceData.amount);
81
86
  const reserveMin = coinConfig.getCoinConfig().minReserve || 0;
82
- const spendableBalance = new BigNumber(accountInfo.account_data.Balance).minus(
83
- BigNumber(reserveMin),
87
+ const lockedAmount = balanceData.locked ? balance : new BigNumber(0);
88
+ const spendableBalance = BigNumber.max(
89
+ 0,
90
+ balance.minus(lockedAmount).minus(BigNumber(reserveMin)),
84
91
  );
85
92
 
86
93
  // Tx history fetching
87
94
  const oldOperations = initialAccount?.operations || [];
88
95
  const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0;
89
- const newTransactions = await getTransactions(address, {
90
- from: startAt,
91
- size: 100,
96
+ const transactionData = await getOperations(address, {
97
+ cursor: 0,
98
+ limit: 100,
92
99
  });
93
- const newOperations = filterOperations(newTransactions, accountId, address);
94
- const operations = mergeOps(oldOperations, newOperations as Operation[]);
100
+
101
+ const newOperations = filterOperations(transactionData.operations, accountId, address);
102
+ const operations = mergeOps(oldOperations, newOperations);
95
103
 
96
104
  // We return the new account shape
97
105
  const shape = {
98
106
  id: accountId,
99
- xpub: address,
107
+ xpub: xpubOrAddress,
100
108
  blockHeight,
101
109
  balance,
102
110
  spendableBalance,
@@ -8,10 +8,5 @@ import type { Transaction } from "../types";
8
8
  // NOTE: here is an example transaction updater function
9
9
  // in this case, it resets fee to null depending on the patch content
10
10
  export const updateTransaction: AccountBridge<Transaction>["updateTransaction"] = (tx, patch) => {
11
- // eslint-disable-next-line no-constant-condition
12
- if (patch.recipient === "boilerplate1" || true) {
13
- patch = { ...patch, fee: null };
14
- }
15
-
16
11
  return defaultUpdateTransaction(tx, patch);
17
12
  };