@ledgerhq/coin-canton 0.8.1-nightly.0 → 0.9.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 (104) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +75 -0
  4. package/lib/api/index.d.ts.map +1 -1
  5. package/lib/api/index.js +3 -0
  6. package/lib/api/index.js.map +1 -1
  7. package/lib/bridge/deviceTransactionConfig.d.ts +1 -1
  8. package/lib/bridge/deviceTransactionConfig.d.ts.map +1 -1
  9. package/lib/bridge/deviceTransactionConfig.js +1 -1
  10. package/lib/bridge/deviceTransactionConfig.js.map +1 -1
  11. package/lib/bridge/onboard.d.ts.map +1 -1
  12. package/lib/bridge/onboard.js +48 -22
  13. package/lib/bridge/onboard.js.map +1 -1
  14. package/lib/bridge/signOperation.d.ts.map +1 -1
  15. package/lib/bridge/signOperation.js +10 -3
  16. package/lib/bridge/signOperation.js.map +1 -1
  17. package/lib/common-logic/index.d.ts +1 -0
  18. package/lib/common-logic/index.d.ts.map +1 -1
  19. package/lib/common-logic/index.js +3 -1
  20. package/lib/common-logic/index.js.map +1 -1
  21. package/lib/common-logic/transaction/sign.d.ts +8 -0
  22. package/lib/common-logic/transaction/sign.d.ts.map +1 -0
  23. package/lib/common-logic/transaction/sign.js +27 -0
  24. package/lib/common-logic/transaction/sign.js.map +1 -0
  25. package/lib/common-logic/transaction/split.d.ts +9 -0
  26. package/lib/common-logic/transaction/split.d.ts.map +1 -0
  27. package/lib/common-logic/transaction/split.js +119 -0
  28. package/lib/common-logic/transaction/split.js.map +1 -0
  29. package/lib/common-logic/utils.js +2 -2
  30. package/lib/common-logic/utils.js.map +1 -1
  31. package/lib/network/gateway.d.ts +1 -0
  32. package/lib/network/gateway.d.ts.map +1 -1
  33. package/lib/network/gateway.js +2 -7
  34. package/lib/network/gateway.js.map +1 -1
  35. package/lib/test/cantonTestUtils.d.ts +4 -9
  36. package/lib/test/cantonTestUtils.d.ts.map +1 -1
  37. package/lib/test/cantonTestUtils.js +736 -27
  38. package/lib/test/cantonTestUtils.js.map +1 -1
  39. package/lib/types/signer.d.ts +10 -1
  40. package/lib/types/signer.d.ts.map +1 -1
  41. package/lib/types/transaction-proto.json +1238 -0
  42. package/lib-es/api/index.d.ts.map +1 -1
  43. package/lib-es/api/index.js +3 -0
  44. package/lib-es/api/index.js.map +1 -1
  45. package/lib-es/bridge/deviceTransactionConfig.d.ts +1 -1
  46. package/lib-es/bridge/deviceTransactionConfig.d.ts.map +1 -1
  47. package/lib-es/bridge/deviceTransactionConfig.js +1 -1
  48. package/lib-es/bridge/deviceTransactionConfig.js.map +1 -1
  49. package/lib-es/bridge/onboard.d.ts.map +1 -1
  50. package/lib-es/bridge/onboard.js +49 -23
  51. package/lib-es/bridge/onboard.js.map +1 -1
  52. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  53. package/lib-es/bridge/signOperation.js +10 -3
  54. package/lib-es/bridge/signOperation.js.map +1 -1
  55. package/lib-es/common-logic/index.d.ts +1 -0
  56. package/lib-es/common-logic/index.d.ts.map +1 -1
  57. package/lib-es/common-logic/index.js +1 -0
  58. package/lib-es/common-logic/index.js.map +1 -1
  59. package/lib-es/common-logic/transaction/sign.d.ts +8 -0
  60. package/lib-es/common-logic/transaction/sign.d.ts.map +1 -0
  61. package/lib-es/common-logic/transaction/sign.js +24 -0
  62. package/lib-es/common-logic/transaction/sign.js.map +1 -0
  63. package/lib-es/common-logic/transaction/split.d.ts +9 -0
  64. package/lib-es/common-logic/transaction/split.d.ts.map +1 -0
  65. package/lib-es/common-logic/transaction/split.js +83 -0
  66. package/lib-es/common-logic/transaction/split.js.map +1 -0
  67. package/lib-es/common-logic/utils.js +2 -2
  68. package/lib-es/common-logic/utils.js.map +1 -1
  69. package/lib-es/network/gateway.d.ts +1 -0
  70. package/lib-es/network/gateway.d.ts.map +1 -1
  71. package/lib-es/network/gateway.js +1 -8
  72. package/lib-es/network/gateway.js.map +1 -1
  73. package/lib-es/test/cantonTestUtils.d.ts +4 -9
  74. package/lib-es/test/cantonTestUtils.d.ts.map +1 -1
  75. package/lib-es/test/cantonTestUtils.js +697 -21
  76. package/lib-es/test/cantonTestUtils.js.map +1 -1
  77. package/lib-es/types/signer.d.ts +10 -1
  78. package/lib-es/types/signer.d.ts.map +1 -1
  79. package/lib-es/types/transaction-proto.json +1238 -0
  80. package/package.json +10 -7
  81. package/scripts/generate.js +261 -0
  82. package/src/api/index.ts +4 -0
  83. package/src/bridge/deviceTransactionConfig.test.ts +14 -10
  84. package/src/bridge/deviceTransactionConfig.ts +2 -2
  85. package/src/bridge/getTransactionStatus.test.ts +19 -19
  86. package/src/bridge/onboard.integ.test.ts +0 -20
  87. package/src/bridge/onboard.ts +57 -33
  88. package/src/bridge/signOperation.test.ts +114 -0
  89. package/src/bridge/signOperation.ts +12 -5
  90. package/src/bridge/sync.integ.test.ts +157 -132
  91. package/src/common-logic/index.ts +1 -0
  92. package/src/common-logic/transaction/sign.test.ts +317 -0
  93. package/src/common-logic/transaction/sign.ts +33 -0
  94. package/src/common-logic/transaction/split.test.ts +50 -0
  95. package/src/common-logic/transaction/split.ts +101 -0
  96. package/src/common-logic/utils.test.ts +22 -30
  97. package/src/common-logic/utils.ts +2 -2
  98. package/src/network/gateway.integ.test.ts +2 -0
  99. package/src/network/gateway.ts +3 -8
  100. package/src/test/cantonTestUtils.ts +789 -24
  101. package/src/test/prepare-transfer-serialized.json +26 -0
  102. package/src/test/prepare-transfer.json +3298 -0
  103. package/src/types/signer.ts +17 -3
  104. 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,
@@ -96,12 +100,9 @@ export const buildOnboardAccount =
96
100
 
97
101
  o.next({ status: OnboardStatus.SIGN });
98
102
 
99
- const signature = await signerContext(deviceId, signer =>
100
- signer.signTransaction(
101
- account.freshAddressPath,
102
- preparedTransaction.transactions.combined_hash,
103
- ),
104
- );
103
+ const signature = await signerContext(deviceId, async signer => {
104
+ return await signTransaction(signer, account.freshAddressPath, preparedTransaction);
105
+ });
105
106
 
106
107
  o.next({ status: OnboardStatus.SUBMIT });
107
108
 
@@ -115,7 +116,9 @@ export const buildOnboardAccount =
115
116
  () => o.complete(),
116
117
  error => {
117
118
  log("[canton:onboard] onboardAccount failed:", error);
118
- o.error(error);
119
+
120
+ const handledError = handleDeviceErrors(error);
121
+ o.error(handledError || error);
119
122
  },
120
123
  );
121
124
  });
@@ -141,10 +144,9 @@ export const buildAuthorizePreapproval =
141
144
 
142
145
  o.next({ status: AuthorizeStatus.SIGN });
143
146
 
144
- const signature = await signerContext(deviceId, signer =>
145
- signer.signTransaction(account.freshAddressPath, preparedTransaction.hash),
146
- );
147
-
147
+ const signature = await signerContext(deviceId, async signer => {
148
+ return await signTransaction(signer, account.freshAddressPath, preparedTransaction);
149
+ });
148
150
  o.next({ status: AuthorizeStatus.SUBMIT });
149
151
 
150
152
  await submitPreApprovalTransaction(currency, partyId, preparedTransaction, signature);
@@ -152,37 +154,59 @@ export const buildAuthorizePreapproval =
152
154
 
153
155
  o.next({ isApproved: true }); // success
154
156
 
155
- const handleTapRequest = async () => {
156
- try {
157
- const { serialized, hash } = await prepareTapRequest(currency, { partyId });
157
+ if (getNetworkType(currency) !== "mainnet") {
158
+ const handleTapRequest = async () => {
159
+ try {
160
+ const { serialized, hash } = await prepareTapRequest(currency, { partyId });
158
161
 
159
- if (serialized && hash) {
160
- o.next({ status: AuthorizeStatus.SIGN });
162
+ if (serialized && hash) {
163
+ o.next({ status: AuthorizeStatus.SIGN });
161
164
 
162
- const signature = await signerContext(deviceId, signer =>
163
- signer.signTransaction(account.freshAddressPath, hash),
164
- );
165
+ const signature = await signerContext(deviceId, signer =>
166
+ signer.signTransaction(account.freshAddressPath, hash),
167
+ );
165
168
 
166
- o.next({ status: AuthorizeStatus.SUBMIT });
169
+ o.next({ status: AuthorizeStatus.SUBMIT });
167
170
 
168
- await submitTapRequest(currency, {
169
- partyId,
170
- serialized,
171
- signature,
172
- });
171
+ await submitTapRequest(currency, {
172
+ partyId,
173
+ serialized,
174
+ signature,
175
+ });
176
+ }
177
+ } catch (err) {
178
+ // Tap request failure should not break the pre-approval flow
173
179
  }
174
- } catch (err) {
175
- // Tap request failure should not break the pre-approval flow
176
- }
177
- };
178
- await handleTapRequest();
180
+ };
181
+ await handleTapRequest();
182
+ }
179
183
  }
180
184
 
181
185
  main().then(
182
186
  () => o.complete(),
183
187
  error => {
184
188
  log("[canton:onboard] authorizePreapproval failed:", error);
185
- o.error(error);
189
+
190
+ const handledError = handleDeviceErrors(error);
191
+ o.error(handledError || error);
186
192
  },
187
193
  );
188
194
  });
195
+
196
+ /**
197
+ * Check if an error is a LockedDeviceError or UserRefusedOnDevice and create user-friendly error messages
198
+ */
199
+ const handleDeviceErrors = (error: Error): Error | null => {
200
+ if (error instanceof TransportStatusError) {
201
+ if (error.statusCode === 0x6985) {
202
+ const userRefusedError = new UserRefusedOnDevice("errors.UserRefusedOnDevice.description");
203
+ return userRefusedError;
204
+ }
205
+ if (error.statusCode === 0x5515) {
206
+ const lockedDeviceError = new LockedDeviceError("errors.LockedDeviceError.description");
207
+ return lockedDeviceError;
208
+ }
209
+ }
210
+
211
+ return null;
212
+ };
@@ -0,0 +1,114 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { CantonSigner, CantonPreparedTransaction } from "../types";
3
+ import { Transaction } from "../types";
4
+ import { craftTransaction } from "../common-logic";
5
+ import prepareTransferMock from "../test/prepare-transfer.json";
6
+ import { buildSignOperation } from "./signOperation";
7
+
8
+ jest.mock("../common-logic", () => {
9
+ const actual = jest.requireActual("../common-logic");
10
+ return {
11
+ ...actual,
12
+ craftTransaction: jest.fn(),
13
+ };
14
+ });
15
+
16
+ const mockCraftTransaction = craftTransaction as jest.MockedFunction<typeof craftTransaction>;
17
+
18
+ class MockCantonSigner implements CantonSigner {
19
+ async getAddress(path: string, display?: boolean) {
20
+ return {
21
+ publicKey: "mock-public-key",
22
+ address: "mock-address",
23
+ path,
24
+ };
25
+ }
26
+
27
+ async signTransaction(
28
+ path: string,
29
+ data: CantonPreparedTransaction | { transactions: string[] },
30
+ ) {
31
+ if ("transactions" in data) {
32
+ return `untyped-signature-${data.transactions.length}`;
33
+ } else {
34
+ return `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`;
35
+ }
36
+ }
37
+ }
38
+
39
+ describe("buildSignOperation", () => {
40
+ const mockDeviceId = "test-device-id";
41
+ const mockDerivationPath = "44'/6767'/0'/0'/0'";
42
+ const mockPartyId = "alice::1220d466a5d96a3509736c821e25fe81fc8a73f226d92e57e94a65170e58b07fc08e";
43
+
44
+ beforeEach(() => {
45
+ jest.clearAllMocks();
46
+ mockCraftTransaction.mockReset();
47
+ });
48
+
49
+ const mockAccount = {
50
+ id: "js:2:canton_network:test-party-id:",
51
+ freshAddress: "test-address",
52
+ freshAddressPath: mockDerivationPath,
53
+ xpub: "test-party-id",
54
+ currency: {
55
+ id: "canton_network",
56
+ },
57
+ cantonResources: {
58
+ partyId: mockPartyId,
59
+ },
60
+ } as any;
61
+
62
+ const mockTransaction: Transaction = {
63
+ family: "canton",
64
+ recipient: "bob::44444444444444444444444444444444444444444444444444444444444444444444",
65
+ amount: new BigNumber("1000000"),
66
+ tokenId: "Amulet",
67
+ fee: new BigNumber("1000"),
68
+ memo: "Test transaction",
69
+ };
70
+
71
+ it("should handle prepared transaction signing", async () => {
72
+ // GIVEN
73
+ const mockSigner = new MockCantonSigner();
74
+ const mockSignerContext = jest.fn().mockImplementation(async (deviceId, callback) => {
75
+ return await callback(mockSigner);
76
+ });
77
+
78
+ mockCraftTransaction.mockResolvedValue({
79
+ nativeTransaction: {
80
+ // @ts-expect-error fix types
81
+ transaction: prepareTransferMock.transaction,
82
+ metadata: prepareTransferMock.metadata,
83
+ },
84
+ serializedTransaction: "serialized-transaction",
85
+ hash: "mock-hash",
86
+ });
87
+
88
+ const signOperation = buildSignOperation(mockSignerContext);
89
+
90
+ // WHEN
91
+ const result = await new Promise((resolve, reject) => {
92
+ signOperation({
93
+ account: mockAccount,
94
+ deviceId: mockDeviceId,
95
+ transaction: mockTransaction,
96
+ }).subscribe({
97
+ next: value => {
98
+ if (value.type === "signed") {
99
+ resolve(value);
100
+ }
101
+ },
102
+ error: reject,
103
+ });
104
+ });
105
+
106
+ // THEN
107
+ expect(mockCraftTransaction).toHaveBeenCalled();
108
+ expect(result).toMatchObject({
109
+ signedOperation: {
110
+ signature: expect.stringContaining("prepared-transaction-signature-"),
111
+ },
112
+ });
113
+ });
114
+ });
@@ -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,14 +42,19 @@ 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
+
53
+ const transactionSignature = await signTransaction(signer, derivationPath, {
54
+ json: nativeTransaction,
55
+ serialized: serializedTransaction,
56
+ hash: hash,
57
+ });
52
58
 
53
59
  return combine(serializedTransaction, `${transactionSignature}__PARTY__${address}`);
54
60
  });
@@ -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
  });
@@ -2,6 +2,7 @@ export { broadcast } from "./transaction/broadcast";
2
2
  export { combine } from "./transaction/combine";
3
3
  export { craftTransaction } from "./transaction/craftTransaction";
4
4
  export { estimateFees } from "./transaction/estimateFees";
5
+ export { signTransaction } from "./transaction/sign";
5
6
  export { getBalance } from "./account/getBalance";
6
7
  export { lastBlock } from "./history/lastBlock";
7
8
  export { listOperations } from "./history/listOperations";