@ledgerhq/coin-canton 0.9.0-nightly.3 → 0.9.0-nightly.5
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.
- package/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +2 -1
- package/CHANGELOG.md +29 -0
- package/lib/bridge/estimateMaxSpendable.d.ts +2 -2
- package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +2 -3
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts +5 -3
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +26 -5
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/index.d.ts +2 -2
- package/lib/bridge/index.d.ts.map +1 -1
- package/lib/bridge/index.js +3 -3
- package/lib/bridge/index.js.map +1 -1
- package/lib/bridge/prepareTransaction.js +1 -1
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/serialization.d.ts +4 -0
- package/lib/bridge/serialization.d.ts.map +1 -0
- package/lib/bridge/serialization.js +36 -0
- package/lib/bridge/serialization.js.map +1 -0
- package/lib/bridge/sync.d.ts.map +1 -1
- package/lib/bridge/sync.js +13 -5
- package/lib/bridge/sync.js.map +1 -1
- package/lib/bridge/transaction.js +1 -1
- package/lib/bridge/transaction.js.map +1 -1
- package/lib/common-logic/account/getBalance.d.ts +5 -1
- package/lib/common-logic/account/getBalance.d.ts.map +1 -1
- package/lib/common-logic/account/getBalance.js +2 -0
- package/lib/common-logic/account/getBalance.js.map +1 -1
- package/lib/common-logic/transaction/craftTransaction.js +1 -1
- package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib/network/gateway.d.ts +1 -0
- package/lib/network/gateway.d.ts.map +1 -1
- package/lib/network/gateway.js.map +1 -1
- package/lib/test/fixtures.d.ts +5 -0
- package/lib/test/fixtures.d.ts.map +1 -0
- package/lib/test/fixtures.js +57 -0
- package/lib/test/fixtures.js.map +1 -0
- package/lib/types/bridge.d.ts +14 -2
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib/types/errors.d.ts +6 -0
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/errors.js +3 -1
- package/lib/types/errors.js.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/index.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts +2 -2
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +2 -3
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts +5 -3
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +26 -5
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/index.d.ts +2 -2
- package/lib-es/bridge/index.d.ts.map +1 -1
- package/lib-es/bridge/index.js +3 -3
- package/lib-es/bridge/index.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +1 -1
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/serialization.d.ts +4 -0
- package/lib-es/bridge/serialization.d.ts.map +1 -0
- package/lib-es/bridge/serialization.js +32 -0
- package/lib-es/bridge/serialization.js.map +1 -0
- package/lib-es/bridge/sync.d.ts.map +1 -1
- package/lib-es/bridge/sync.js +13 -5
- package/lib-es/bridge/sync.js.map +1 -1
- package/lib-es/bridge/transaction.js +1 -1
- package/lib-es/bridge/transaction.js.map +1 -1
- package/lib-es/common-logic/account/getBalance.d.ts +5 -1
- package/lib-es/common-logic/account/getBalance.d.ts.map +1 -1
- package/lib-es/common-logic/account/getBalance.js +2 -0
- package/lib-es/common-logic/account/getBalance.js.map +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.js +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib-es/network/gateway.d.ts +1 -0
- package/lib-es/network/gateway.d.ts.map +1 -1
- package/lib-es/network/gateway.js.map +1 -1
- package/lib-es/test/fixtures.d.ts +5 -0
- package/lib-es/test/fixtures.d.ts.map +1 -0
- package/lib-es/test/fixtures.js +49 -0
- package/lib-es/test/fixtures.js.map +1 -0
- package/lib-es/types/bridge.d.ts +14 -2
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/lib-es/types/errors.d.ts +6 -0
- package/lib-es/types/errors.d.ts.map +1 -1
- package/lib-es/types/errors.js +2 -0
- package/lib-es/types/errors.js.map +1 -1
- package/lib-es/types/index.d.ts +1 -0
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +1 -0
- package/lib-es/types/index.js.map +1 -1
- package/package.json +7 -7
- package/src/bridge/estimateMaxSpendable.ts +6 -8
- package/src/bridge/getTransactionStatus.test.ts +84 -146
- package/src/bridge/getTransactionStatus.ts +42 -7
- package/src/bridge/index.ts +6 -5
- package/src/bridge/onboard.integ.test.ts +8 -31
- package/src/bridge/prepareTransaction.ts +1 -1
- package/src/bridge/serialization.ts +44 -0
- package/src/bridge/signOperation.test.ts +3 -9
- package/src/bridge/sync.test.ts +4 -0
- package/src/bridge/sync.ts +17 -6
- package/src/bridge/transaction.ts +1 -1
- package/src/common-logic/account/getBalance.ts +12 -2
- package/src/common-logic/account/getBalance.unit.test.ts +7 -1
- package/src/common-logic/transaction/craftTransaction.ts +1 -1
- package/src/network/gateway.ts +1 -0
- package/src/test/fixtures.ts +53 -0
- package/src/types/bridge.ts +15 -2
- package/src/types/errors.ts +3 -0
- package/src/types/index.ts +1 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import BigNumber from "bignumber.js";
|
|
2
|
-
import { Account } from "@ledgerhq/types-live";
|
|
3
|
-
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
4
1
|
import {
|
|
5
2
|
AmountRequired,
|
|
6
3
|
FeeNotLoaded,
|
|
@@ -12,76 +9,37 @@ import {
|
|
|
12
9
|
NotEnoughSpendableBalance,
|
|
13
10
|
RecipientRequired,
|
|
14
11
|
} from "@ledgerhq/errors";
|
|
15
|
-
import
|
|
16
|
-
import { Transaction } from "../types";
|
|
12
|
+
import BigNumber from "bignumber.js";
|
|
17
13
|
import coinConfig from "../config";
|
|
14
|
+
import { createMockAccount } from "../test/fixtures";
|
|
15
|
+
import { CantonAccount, TooManyUtxosCritical, TooManyUtxosWarning, Transaction } from "../types";
|
|
16
|
+
import {
|
|
17
|
+
getTransactionStatus,
|
|
18
|
+
TO_MANY_UTXOS_CRITICAL_COUNT,
|
|
19
|
+
TO_MANY_UTXOS_WARNING_COUNT,
|
|
20
|
+
} from "./getTransactionStatus";
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
jest.mock("../config", () => ({
|
|
21
|
-
getCoinConfig: jest.fn(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
22
|
+
jest.mock("../config", () => ({ getCoinConfig: jest.fn() }));
|
|
24
23
|
const mockCoinConfig = jest.mocked(coinConfig);
|
|
25
24
|
|
|
26
25
|
describe("getTransactionStatus", () => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const mockAccount: CantonAccount = {
|
|
27
|
+
...createMockAccount({
|
|
28
|
+
balance: new BigNumber(1000),
|
|
29
|
+
spendableBalance: new BigNumber(1000),
|
|
30
|
+
freshAddress: "test::33333333333333333333333333333333333333333333333333333333333333333333",
|
|
31
|
+
}),
|
|
32
|
+
cantonResources: {
|
|
33
|
+
instrumentUtxoCounts: {
|
|
34
|
+
Amulet: 5,
|
|
36
35
|
},
|
|
37
|
-
],
|
|
38
|
-
ticker: "CANTON",
|
|
39
|
-
scheme: "canton",
|
|
40
|
-
color: "#000000",
|
|
41
|
-
type: "CryptoCurrency",
|
|
42
|
-
managerAppName: "Canton",
|
|
43
|
-
coinType: 0,
|
|
44
|
-
disableCountervalue: false,
|
|
45
|
-
delisted: false,
|
|
46
|
-
keywords: ["canton"],
|
|
47
|
-
explorerViews: [],
|
|
48
|
-
terminated: {
|
|
49
|
-
link: "",
|
|
50
36
|
},
|
|
51
37
|
};
|
|
52
38
|
|
|
53
|
-
const mockAccount: Account = {
|
|
54
|
-
id: "test-account-id",
|
|
55
|
-
seedIdentifier: "test-seed-identifier",
|
|
56
|
-
currency: mockCurrency,
|
|
57
|
-
balance: new BigNumber(1000), // 1000 units
|
|
58
|
-
spendableBalance: new BigNumber(1000),
|
|
59
|
-
freshAddress: "test::33333333333333333333333333333333333333333333333333333333333333333333",
|
|
60
|
-
freshAddressPath: "44'/60'/0'/0/0",
|
|
61
|
-
index: 0,
|
|
62
|
-
derivationMode: "canton",
|
|
63
|
-
used: true,
|
|
64
|
-
operations: [],
|
|
65
|
-
pendingOperations: [],
|
|
66
|
-
lastSyncDate: new Date(),
|
|
67
|
-
creationDate: new Date(),
|
|
68
|
-
operationsCount: 0,
|
|
69
|
-
blockHeight: 100,
|
|
70
|
-
balanceHistoryCache: {
|
|
71
|
-
HOUR: { latestDate: null, balances: [] },
|
|
72
|
-
DAY: { latestDate: null, balances: [] },
|
|
73
|
-
WEEK: { latestDate: null, balances: [] },
|
|
74
|
-
},
|
|
75
|
-
swapHistory: [],
|
|
76
|
-
nfts: [],
|
|
77
|
-
subAccounts: [],
|
|
78
|
-
type: "Account",
|
|
79
|
-
};
|
|
80
|
-
|
|
81
39
|
beforeEach(() => {
|
|
82
40
|
jest.clearAllMocks();
|
|
83
41
|
mockCoinConfig.getCoinConfig.mockReturnValue({
|
|
84
|
-
minReserve: 100,
|
|
42
|
+
minReserve: 100,
|
|
85
43
|
networkType: "mainnet",
|
|
86
44
|
status: { type: "active" },
|
|
87
45
|
nativeInstrumentId: "Amulet",
|
|
@@ -122,9 +80,9 @@ describe("getTransactionStatus", () => {
|
|
|
122
80
|
it("should add FeeTooHigh warning when fee is more than 10 times the amount", async () => {
|
|
123
81
|
const transaction: Transaction = {
|
|
124
82
|
family: "canton",
|
|
125
|
-
amount: new BigNumber(100),
|
|
83
|
+
amount: new BigNumber(100),
|
|
126
84
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
127
|
-
fee: new BigNumber(1500),
|
|
85
|
+
fee: new BigNumber(1500),
|
|
128
86
|
tokenId: "",
|
|
129
87
|
};
|
|
130
88
|
|
|
@@ -139,7 +97,7 @@ describe("getTransactionStatus", () => {
|
|
|
139
97
|
family: "canton",
|
|
140
98
|
amount: new BigNumber(100),
|
|
141
99
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
142
|
-
fee: new BigNumber(10),
|
|
100
|
+
fee: new BigNumber(10),
|
|
143
101
|
tokenId: "",
|
|
144
102
|
};
|
|
145
103
|
|
|
@@ -154,7 +112,9 @@ describe("getTransactionStatus", () => {
|
|
|
154
112
|
it("should return NotEnoughSpendableBalance error when total spent exceeds balance minus reserve", async () => {
|
|
155
113
|
const transaction: Transaction = {
|
|
156
114
|
family: "canton",
|
|
157
|
-
amount:
|
|
115
|
+
amount: mockAccount.balance
|
|
116
|
+
.minus(new BigNumber(mockCoinConfig.getCoinConfig().minReserve))
|
|
117
|
+
.plus(1),
|
|
158
118
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
159
119
|
fee: new BigNumber(10),
|
|
160
120
|
tokenId: "",
|
|
@@ -168,7 +128,7 @@ describe("getTransactionStatus", () => {
|
|
|
168
128
|
it("should return NotEnoughBalanceBecauseDestinationNotCreated error when amount is below reserve", async () => {
|
|
169
129
|
const transaction: Transaction = {
|
|
170
130
|
family: "canton",
|
|
171
|
-
amount: new BigNumber(
|
|
131
|
+
amount: new BigNumber(mockCoinConfig.getCoinConfig().minReserve).minus(1),
|
|
172
132
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
173
133
|
fee: new BigNumber(10),
|
|
174
134
|
tokenId: "",
|
|
@@ -182,7 +142,7 @@ describe("getTransactionStatus", () => {
|
|
|
182
142
|
it("should pass balance validation when transaction is within limits", async () => {
|
|
183
143
|
const transaction: Transaction = {
|
|
184
144
|
family: "canton",
|
|
185
|
-
amount:
|
|
145
|
+
amount: mockAccount.balance.multipliedBy(0.5),
|
|
186
146
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
187
147
|
fee: new BigNumber(10),
|
|
188
148
|
tokenId: "",
|
|
@@ -209,18 +169,18 @@ describe("getTransactionStatus", () => {
|
|
|
209
169
|
expect(result.errors.recipient).toBeInstanceOf(RecipientRequired);
|
|
210
170
|
});
|
|
211
171
|
|
|
212
|
-
it("should return
|
|
172
|
+
it("should not return error when sending to self", async () => {
|
|
213
173
|
const transaction: Transaction = {
|
|
214
174
|
family: "canton",
|
|
215
175
|
amount: new BigNumber(100),
|
|
216
|
-
recipient:
|
|
176
|
+
recipient: mockAccount.freshAddress,
|
|
217
177
|
fee: new BigNumber(10),
|
|
218
178
|
tokenId: "",
|
|
219
179
|
};
|
|
220
180
|
|
|
221
181
|
const result = await getTransactionStatus(mockAccount, transaction);
|
|
222
182
|
|
|
223
|
-
expect(result.errors.recipient).
|
|
183
|
+
expect(result.errors.recipient).toBeUndefined();
|
|
224
184
|
});
|
|
225
185
|
|
|
226
186
|
it("should return InvalidAddress error when recipient is invalid", async () => {
|
|
@@ -254,16 +214,8 @@ describe("getTransactionStatus", () => {
|
|
|
254
214
|
|
|
255
215
|
describe("amount validation", () => {
|
|
256
216
|
it("should return AmountRequired error when amount is zero", async () => {
|
|
257
|
-
// Create a scenario where there are no other amount errors
|
|
258
|
-
// Use a high balance and amount above reserve to avoid other amount errors
|
|
259
|
-
const accountWithHighBalance = {
|
|
260
|
-
...mockAccount,
|
|
261
|
-
balance: new BigNumber(10000), // High balance to avoid balance errors
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// Set a high reserve to avoid the NotEnoughBalanceBecauseDestinationNotCreated error
|
|
265
217
|
mockCoinConfig.getCoinConfig.mockReturnValue({
|
|
266
|
-
minReserve: 0,
|
|
218
|
+
minReserve: 0,
|
|
267
219
|
networkType: "mainnet",
|
|
268
220
|
status: { type: "active" },
|
|
269
221
|
nativeInstrumentId: "Amulet",
|
|
@@ -277,7 +229,7 @@ describe("getTransactionStatus", () => {
|
|
|
277
229
|
tokenId: "",
|
|
278
230
|
};
|
|
279
231
|
|
|
280
|
-
const result = await getTransactionStatus(
|
|
232
|
+
const result = await getTransactionStatus(mockAccount, transaction);
|
|
281
233
|
|
|
282
234
|
expect(result.errors.amount).toBeInstanceOf(AmountRequired);
|
|
283
235
|
});
|
|
@@ -330,11 +282,15 @@ describe("getTransactionStatus", () => {
|
|
|
330
282
|
});
|
|
331
283
|
});
|
|
332
284
|
|
|
333
|
-
describe("
|
|
334
|
-
it("should
|
|
335
|
-
const
|
|
285
|
+
describe("UTXO count validation", () => {
|
|
286
|
+
it("should show critical warning when UTXO count exceeds TO_MANY_UTXOS_CRITICAL_COUNT", async () => {
|
|
287
|
+
const accountWithTooManyUtxos = {
|
|
336
288
|
...mockAccount,
|
|
337
|
-
|
|
289
|
+
cantonResources: {
|
|
290
|
+
instrumentUtxoCounts: {
|
|
291
|
+
Amulet: TO_MANY_UTXOS_CRITICAL_COUNT + 1,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
338
294
|
};
|
|
339
295
|
|
|
340
296
|
const transaction: Transaction = {
|
|
@@ -342,18 +298,23 @@ describe("getTransactionStatus", () => {
|
|
|
342
298
|
amount: new BigNumber(50),
|
|
343
299
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
344
300
|
fee: new BigNumber(10),
|
|
345
|
-
tokenId: "",
|
|
301
|
+
tokenId: "Amulet",
|
|
346
302
|
};
|
|
347
303
|
|
|
348
|
-
const result = await getTransactionStatus(
|
|
304
|
+
const result = await getTransactionStatus(accountWithTooManyUtxos, transaction);
|
|
349
305
|
|
|
350
|
-
expect(result.
|
|
306
|
+
expect(result.warnings.tooManyUtxos).toBeDefined();
|
|
307
|
+
expect(result.warnings.tooManyUtxos).toBeInstanceOf(TooManyUtxosCritical);
|
|
351
308
|
});
|
|
352
309
|
|
|
353
|
-
it("should
|
|
354
|
-
const
|
|
310
|
+
it("should show warning when UTXO count exceeds TO_MANY_UTXOS_WARNING_COUNT", async () => {
|
|
311
|
+
const accountWithManyUtxos = {
|
|
355
312
|
...mockAccount,
|
|
356
|
-
|
|
313
|
+
cantonResources: {
|
|
314
|
+
instrumentUtxoCounts: {
|
|
315
|
+
Amulet: TO_MANY_UTXOS_WARNING_COUNT + 1,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
357
318
|
};
|
|
358
319
|
|
|
359
320
|
const transaction: Transaction = {
|
|
@@ -361,86 +322,63 @@ describe("getTransactionStatus", () => {
|
|
|
361
322
|
amount: new BigNumber(50),
|
|
362
323
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
363
324
|
fee: new BigNumber(10),
|
|
364
|
-
tokenId: "",
|
|
325
|
+
tokenId: "Amulet",
|
|
365
326
|
};
|
|
366
327
|
|
|
367
|
-
const result = await getTransactionStatus(
|
|
328
|
+
const result = await getTransactionStatus(accountWithManyUtxos, transaction);
|
|
368
329
|
|
|
369
|
-
expect(result.
|
|
330
|
+
expect(result.warnings.tooManyUtxos).toBeDefined();
|
|
331
|
+
expect(result.warnings.tooManyUtxos).toBeInstanceOf(TooManyUtxosWarning);
|
|
332
|
+
expect(result.warnings.tooManyUtxos?.message).toContain(
|
|
333
|
+
"families.canton.tooManyUtxos.warning",
|
|
334
|
+
);
|
|
370
335
|
});
|
|
371
336
|
|
|
372
|
-
it("should
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const transaction: Transaction = {
|
|
381
|
-
family: "canton",
|
|
382
|
-
amount: new BigNumber(50),
|
|
383
|
-
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
384
|
-
fee: new BigNumber(10),
|
|
385
|
-
tokenId: "",
|
|
337
|
+
it("should not show warning or error when UTXO count is less than TO_MANY_UTXOS_WARNING_COUNT", async () => {
|
|
338
|
+
const accountWithFewUtxos = {
|
|
339
|
+
...mockAccount,
|
|
340
|
+
cantonResources: {
|
|
341
|
+
instrumentUtxoCounts: {
|
|
342
|
+
Amulet: TO_MANY_UTXOS_WARNING_COUNT - 1,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
386
345
|
};
|
|
387
346
|
|
|
388
|
-
const result = await getTransactionStatus(mockAccount, transaction);
|
|
389
|
-
|
|
390
|
-
expect(result.errors.amount).toBeUndefined();
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("should handle undefined reserve amount", async () => {
|
|
394
|
-
mockCoinConfig.getCoinConfig.mockReturnValue({
|
|
395
|
-
networkType: "mainnet",
|
|
396
|
-
status: { type: "active" },
|
|
397
|
-
nativeInstrumentId: "Amulet",
|
|
398
|
-
});
|
|
399
|
-
|
|
400
347
|
const transaction: Transaction = {
|
|
401
348
|
family: "canton",
|
|
402
349
|
amount: new BigNumber(50),
|
|
403
350
|
recipient: "valid::11111111111111111111111111111111111111111111111111111111111111111111",
|
|
404
351
|
fee: new BigNumber(10),
|
|
405
|
-
tokenId: "",
|
|
352
|
+
tokenId: "Amulet", // Use the same tokenId as in cantonResources
|
|
406
353
|
};
|
|
407
354
|
|
|
408
|
-
const result = await getTransactionStatus(
|
|
355
|
+
const result = await getTransactionStatus(accountWithFewUtxos, transaction);
|
|
409
356
|
|
|
410
|
-
expect(result.
|
|
357
|
+
expect(result.warnings.tooManyUtxos).toBeUndefined();
|
|
411
358
|
});
|
|
412
|
-
});
|
|
413
359
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
360
|
+
it("should not show warning or error for abandon seed address transactions", async () => {
|
|
361
|
+
const accountWithManyUtxos = {
|
|
362
|
+
...mockAccount,
|
|
363
|
+
cantonResources: {
|
|
364
|
+
instrumentUtxoCounts: {
|
|
365
|
+
Amulet: 25,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
422
368
|
};
|
|
423
369
|
|
|
424
|
-
const result = await getTransactionStatus(mockAccount, transaction);
|
|
425
|
-
|
|
426
|
-
expect(result.errors.amount).toBeInstanceOf(AmountRequired);
|
|
427
|
-
expect(result.errors.recipient).toBeInstanceOf(RecipientRequired);
|
|
428
|
-
expect(result.errors.fee).toBeInstanceOf(FeeNotLoaded);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it("should return both errors and warnings", async () => {
|
|
432
370
|
const transaction: Transaction = {
|
|
433
371
|
family: "canton",
|
|
434
|
-
amount: new BigNumber(
|
|
435
|
-
recipient: "
|
|
436
|
-
fee: new BigNumber(
|
|
437
|
-
tokenId: "",
|
|
372
|
+
amount: new BigNumber(50),
|
|
373
|
+
recipient: "abandon::ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
374
|
+
fee: new BigNumber(10),
|
|
375
|
+
tokenId: "Amulet",
|
|
438
376
|
};
|
|
439
377
|
|
|
440
|
-
const result = await getTransactionStatus(
|
|
378
|
+
const result = await getTransactionStatus(accountWithManyUtxos, transaction);
|
|
441
379
|
|
|
442
|
-
expect(result.warnings.
|
|
443
|
-
expect(result.errors.
|
|
380
|
+
expect(result.warnings.tooManyUtxos).toBeUndefined();
|
|
381
|
+
expect(result.errors.utxoCount).toBeUndefined();
|
|
444
382
|
});
|
|
445
383
|
});
|
|
446
384
|
});
|
|
@@ -4,21 +4,30 @@ import {
|
|
|
4
4
|
FeeRequired,
|
|
5
5
|
FeeTooHigh,
|
|
6
6
|
InvalidAddress,
|
|
7
|
-
InvalidAddressBecauseDestinationIsAlsoSource,
|
|
8
7
|
NotEnoughBalanceBecauseDestinationNotCreated,
|
|
9
8
|
NotEnoughSpendableBalance,
|
|
10
9
|
RecipientRequired,
|
|
11
10
|
} from "@ledgerhq/errors";
|
|
12
11
|
import BigNumber from "bignumber.js";
|
|
13
|
-
import {
|
|
12
|
+
import { AccountBridge } from "@ledgerhq/types-live";
|
|
14
13
|
import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
|
|
15
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Transaction,
|
|
16
|
+
TransactionStatus,
|
|
17
|
+
CantonAccount,
|
|
18
|
+
TooManyUtxosCritical,
|
|
19
|
+
TooManyUtxosWarning,
|
|
20
|
+
} from "../types";
|
|
16
21
|
import { isRecipientValid } from "../common-logic/utils";
|
|
17
22
|
import coinConfig from "../config";
|
|
23
|
+
import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets/abandonseed";
|
|
24
|
+
|
|
25
|
+
export const TO_MANY_UTXOS_CRITICAL_COUNT = 24;
|
|
26
|
+
export const TO_MANY_UTXOS_WARNING_COUNT = 10;
|
|
18
27
|
|
|
19
28
|
export const getTransactionStatus: AccountBridge<
|
|
20
29
|
Transaction,
|
|
21
|
-
|
|
30
|
+
CantonAccount,
|
|
22
31
|
TransactionStatus
|
|
23
32
|
>["getTransactionStatus"] = async (account, transaction) => {
|
|
24
33
|
const errors: Record<string, Error> = {};
|
|
@@ -63,9 +72,6 @@ export const getTransactionStatus: AccountBridge<
|
|
|
63
72
|
|
|
64
73
|
if (!transaction.recipient) {
|
|
65
74
|
errors.recipient = new RecipientRequired("");
|
|
66
|
-
} else if (account.freshAddress === transaction.recipient) {
|
|
67
|
-
// we want to prevent user from sending to themselves (even if it's technically feasible)
|
|
68
|
-
errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
|
|
69
75
|
} else if (!isRecipientValid(transaction.recipient)) {
|
|
70
76
|
// We want to prevent user from sending to an invalid address
|
|
71
77
|
errors.recipient = new InvalidAddress("", {
|
|
@@ -78,6 +84,8 @@ export const getTransactionStatus: AccountBridge<
|
|
|
78
84
|
errors.amount = new AmountRequired();
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
validateUtxoCount(account, transaction, warnings);
|
|
88
|
+
|
|
81
89
|
return {
|
|
82
90
|
errors,
|
|
83
91
|
warnings,
|
|
@@ -86,3 +94,30 @@ export const getTransactionStatus: AccountBridge<
|
|
|
86
94
|
totalSpent,
|
|
87
95
|
};
|
|
88
96
|
};
|
|
97
|
+
|
|
98
|
+
function validateUtxoCount(
|
|
99
|
+
account: CantonAccount,
|
|
100
|
+
transaction: Transaction,
|
|
101
|
+
warnings: Record<string, Error>,
|
|
102
|
+
): void {
|
|
103
|
+
const abandonSeedAddress = getAbandonSeedAddress(account.currency.id);
|
|
104
|
+
const isAbandonSeedAddress = transaction.recipient?.includes(abandonSeedAddress);
|
|
105
|
+
|
|
106
|
+
// UTXO count validation - only validate if recipient is valid and not equal to sender
|
|
107
|
+
// Skip validation for abandon seed addresses
|
|
108
|
+
if (
|
|
109
|
+
account?.cantonResources?.instrumentUtxoCounts &&
|
|
110
|
+
transaction.recipient &&
|
|
111
|
+
isRecipientValid(transaction.recipient) &&
|
|
112
|
+
account.xpub !== transaction.recipient &&
|
|
113
|
+
!isAbandonSeedAddress
|
|
114
|
+
) {
|
|
115
|
+
const { instrumentUtxoCounts } = account.cantonResources;
|
|
116
|
+
const instrumentUtxoCount = instrumentUtxoCounts[transaction.tokenId] || 0;
|
|
117
|
+
if (instrumentUtxoCount > TO_MANY_UTXOS_CRITICAL_COUNT) {
|
|
118
|
+
warnings.tooManyUtxos = new TooManyUtxosCritical();
|
|
119
|
+
} else if (instrumentUtxoCount > TO_MANY_UTXOS_WARNING_COUNT) {
|
|
120
|
+
warnings.tooManyUtxos = new TooManyUtxosWarning("families.canton.tooManyUtxos.warning");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/bridge/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
|
10
10
|
import type { AccountBridge } from "@ledgerhq/types-live";
|
|
11
11
|
import cantonCoinConfig, { type CantonCoinConfig } from "../config";
|
|
12
12
|
import resolver from "../signer";
|
|
13
|
-
import { CantonCurrencyBridge, CantonSigner } from "../types";
|
|
13
|
+
import { CantonCurrencyBridge, CantonSigner, CantonAccount } from "../types";
|
|
14
14
|
import type { Transaction } from "../types";
|
|
15
15
|
import { broadcast } from "./broadcast";
|
|
16
16
|
import { createTransaction } from "./createTransaction";
|
|
@@ -21,6 +21,7 @@ import { buildSignOperation } from "./signOperation";
|
|
|
21
21
|
import { makeGetAccountShape } from "./sync";
|
|
22
22
|
import { updateTransaction } from "./updateTransaction";
|
|
23
23
|
import { buildOnboardAccount, buildAuthorizePreapproval } from "./onboard";
|
|
24
|
+
import { assignToAccountRaw, assignFromAccountRaw } from "./serialization";
|
|
24
25
|
|
|
25
26
|
export function createBridges(
|
|
26
27
|
signerContext: SignerContext<CantonSigner>,
|
|
@@ -49,13 +50,11 @@ export function createBridges(
|
|
|
49
50
|
|
|
50
51
|
const signOperation = buildSignOperation(signerContext);
|
|
51
52
|
const sync = makeSync({ getAccountShape: makeGetAccountShape(signerContext) });
|
|
52
|
-
|
|
53
|
-
const accountBridge: AccountBridge<Transaction> = {
|
|
53
|
+
|
|
54
|
+
const accountBridge: AccountBridge<Transaction, CantonAccount> = {
|
|
54
55
|
broadcast,
|
|
55
56
|
createTransaction,
|
|
56
57
|
updateTransaction,
|
|
57
|
-
// NOTE: use updateTransaction: defaultUpdateTransaction<Transaction>,
|
|
58
|
-
// if you don't need to update the transaction patch object
|
|
59
58
|
prepareTransaction,
|
|
60
59
|
getTransactionStatus,
|
|
61
60
|
estimateMaxSpendable,
|
|
@@ -65,6 +64,8 @@ export function createBridges(
|
|
|
65
64
|
signRawOperation: () => {
|
|
66
65
|
throw new Error("signRawOperation is not supported");
|
|
67
66
|
},
|
|
67
|
+
assignToAccountRaw,
|
|
68
|
+
assignFromAccountRaw,
|
|
68
69
|
getSerializedAddressParameters,
|
|
69
70
|
};
|
|
70
71
|
|
|
@@ -1,45 +1,22 @@
|
|
|
1
|
-
import BigNumber from "bignumber.js";
|
|
2
|
-
import { firstValueFrom, toArray } from "rxjs";
|
|
3
1
|
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
2
|
+
import { firstValueFrom, toArray } from "rxjs";
|
|
3
|
+
import coinConfig from "../config";
|
|
4
|
+
import { createMockSigner, generateMockKeyPair } from "../test/cantonTestUtils";
|
|
5
|
+
import { createMockAccount, createMockCantonCurrency } from "../test/fixtures";
|
|
6
6
|
import {
|
|
7
7
|
AuthorizeStatus,
|
|
8
|
-
OnboardStatus,
|
|
9
8
|
CantonAuthorizeProgress,
|
|
10
9
|
CantonAuthorizeResult,
|
|
11
10
|
CantonOnboardProgress,
|
|
12
11
|
CantonOnboardResult,
|
|
12
|
+
OnboardStatus,
|
|
13
13
|
} from "../types/onboard";
|
|
14
|
-
import
|
|
15
|
-
import { buildOnboardAccount, isAccountOnboarded, buildAuthorizePreapproval } from "./onboard";
|
|
14
|
+
import { buildAuthorizePreapproval, buildOnboardAccount, isAccountOnboarded } from "./onboard";
|
|
16
15
|
|
|
17
16
|
describe("onboard (devnet)", () => {
|
|
18
17
|
const mockDeviceId = "test-device-id";
|
|
19
|
-
const mockCurrency =
|
|
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
|
-
};
|
|
18
|
+
const mockCurrency = createMockCantonCurrency();
|
|
19
|
+
const mockAccount = createMockAccount();
|
|
43
20
|
|
|
44
21
|
let onboardedAccount: {
|
|
45
22
|
keyPair: ReturnType<typeof generateMockKeyPair>;
|
|
@@ -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.
|
|
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
|
+
}
|
|
@@ -9,6 +9,7 @@ import { Transaction } from "../types";
|
|
|
9
9
|
import { craftTransaction } from "../common-logic";
|
|
10
10
|
import prepareTransferMock from "../test/prepare-transfer.json";
|
|
11
11
|
import { buildSignOperation } from "./signOperation";
|
|
12
|
+
import { createMockAccount } from "../test/fixtures";
|
|
12
13
|
|
|
13
14
|
jest.mock("../common-logic", () => {
|
|
14
15
|
const actual = jest.requireActual("../common-logic");
|
|
@@ -54,25 +55,18 @@ class MockCantonSigner implements CantonSigner {
|
|
|
54
55
|
describe("buildSignOperation", () => {
|
|
55
56
|
const mockDeviceId = "test-device-id";
|
|
56
57
|
const mockDerivationPath = "44'/6767'/0'/0'/0'";
|
|
57
|
-
const mockPartyId = "alice::1220d466a5d96a3509736c821e25fe81fc8a73f226d92e57e94a65170e58b07fc08e";
|
|
58
58
|
|
|
59
59
|
beforeEach(() => {
|
|
60
60
|
jest.clearAllMocks();
|
|
61
61
|
mockCraftTransaction.mockReset();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
const mockAccount = {
|
|
64
|
+
const mockAccount = createMockAccount({
|
|
65
65
|
id: "js:2:canton_network:test-party-id:",
|
|
66
66
|
freshAddress: "test-address",
|
|
67
67
|
freshAddressPath: mockDerivationPath,
|
|
68
68
|
xpub: "test-party-id",
|
|
69
|
-
|
|
70
|
-
id: "canton_network",
|
|
71
|
-
},
|
|
72
|
-
cantonResources: {
|
|
73
|
-
partyId: mockPartyId,
|
|
74
|
-
},
|
|
75
|
-
} as any;
|
|
69
|
+
});
|
|
76
70
|
|
|
77
71
|
const mockTransaction: Transaction = {
|
|
78
72
|
family: "canton",
|
package/src/bridge/sync.test.ts
CHANGED
|
@@ -52,6 +52,7 @@ describe("makeGetAccountShape", () => {
|
|
|
52
52
|
mockedCoinConfig.mockReturnValue({
|
|
53
53
|
nativeInstrumentId: "Native",
|
|
54
54
|
minReserve: "0",
|
|
55
|
+
useGateway: true,
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
mockedIsAuthorized.mockResolvedValue(true);
|
|
@@ -64,6 +65,7 @@ describe("makeGetAccountShape", () => {
|
|
|
64
65
|
instrument_id: "Native",
|
|
65
66
|
amount: "1000",
|
|
66
67
|
locked: false,
|
|
68
|
+
utxo_count: 1,
|
|
67
69
|
},
|
|
68
70
|
]);
|
|
69
71
|
mockedGetOperations.mockResolvedValue({
|
|
@@ -113,11 +115,13 @@ describe("makeGetAccountShape", () => {
|
|
|
113
115
|
instrument_id: "LockedNative",
|
|
114
116
|
amount: "1000",
|
|
115
117
|
locked: true,
|
|
118
|
+
utxo_count: 1,
|
|
116
119
|
},
|
|
117
120
|
{
|
|
118
121
|
instrument_id: "Native",
|
|
119
122
|
amount: "10",
|
|
120
123
|
locked: false,
|
|
124
|
+
utxo_count: 1,
|
|
121
125
|
},
|
|
122
126
|
]);
|
|
123
127
|
|