@ledgerhq/coin-canton 0.9.0-nightly.2 → 0.9.0-nightly.20251031023756

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 (151) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +2 -1
  3. package/CHANGELOG.md +18 -17
  4. package/lib/bridge/estimateMaxSpendable.d.ts +2 -2
  5. package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
  6. package/lib/bridge/estimateMaxSpendable.js +2 -3
  7. package/lib/bridge/estimateMaxSpendable.js.map +1 -1
  8. package/lib/bridge/getTransactionStatus.d.ts +5 -3
  9. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  10. package/lib/bridge/getTransactionStatus.js +27 -10
  11. package/lib/bridge/getTransactionStatus.js.map +1 -1
  12. package/lib/bridge/index.d.ts +2 -2
  13. package/lib/bridge/index.d.ts.map +1 -1
  14. package/lib/bridge/index.js +3 -3
  15. package/lib/bridge/index.js.map +1 -1
  16. package/lib/bridge/onboard.d.ts.map +1 -1
  17. package/lib/bridge/onboard.js +2 -2
  18. package/lib/bridge/onboard.js.map +1 -1
  19. package/lib/bridge/prepareTransaction.js +1 -1
  20. package/lib/bridge/prepareTransaction.js.map +1 -1
  21. package/lib/bridge/serialization.d.ts +4 -0
  22. package/lib/bridge/serialization.d.ts.map +1 -0
  23. package/lib/bridge/serialization.js +36 -0
  24. package/lib/bridge/serialization.js.map +1 -0
  25. package/lib/bridge/signOperation.js +2 -2
  26. package/lib/bridge/signOperation.js.map +1 -1
  27. package/lib/bridge/sync.d.ts.map +1 -1
  28. package/lib/bridge/sync.js +14 -6
  29. package/lib/bridge/sync.js.map +1 -1
  30. package/lib/bridge/transaction.js +1 -1
  31. package/lib/bridge/transaction.js.map +1 -1
  32. package/lib/common-logic/account/getBalance.d.ts +5 -1
  33. package/lib/common-logic/account/getBalance.d.ts.map +1 -1
  34. package/lib/common-logic/account/getBalance.js +2 -0
  35. package/lib/common-logic/account/getBalance.js.map +1 -1
  36. package/lib/common-logic/transaction/craftTransaction.js +1 -1
  37. package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
  38. package/lib/common-logic/transaction/estimateFees.js +1 -1
  39. package/lib/common-logic/transaction/estimateFees.js.map +1 -1
  40. package/lib/common-logic/transaction/sign.d.ts +2 -2
  41. package/lib/common-logic/transaction/sign.d.ts.map +1 -1
  42. package/lib/common-logic/transaction/sign.js +20 -2
  43. package/lib/common-logic/transaction/sign.js.map +1 -1
  44. package/lib/network/gateway.d.ts +5 -1
  45. package/lib/network/gateway.d.ts.map +1 -1
  46. package/lib/network/gateway.js +4 -2
  47. package/lib/network/gateway.js.map +1 -1
  48. package/lib/test/fixtures.d.ts +5 -0
  49. package/lib/test/fixtures.d.ts.map +1 -0
  50. package/lib/test/fixtures.js +57 -0
  51. package/lib/test/fixtures.js.map +1 -0
  52. package/lib/types/bridge.d.ts +14 -2
  53. package/lib/types/bridge.d.ts.map +1 -1
  54. package/lib/types/errors.d.ts +6 -0
  55. package/lib/types/errors.d.ts.map +1 -1
  56. package/lib/types/errors.js +3 -1
  57. package/lib/types/errors.js.map +1 -1
  58. package/lib/types/index.d.ts +1 -0
  59. package/lib/types/index.d.ts.map +1 -1
  60. package/lib/types/index.js +1 -0
  61. package/lib/types/index.js.map +1 -1
  62. package/lib/types/signer.d.ts +5 -1
  63. package/lib/types/signer.d.ts.map +1 -1
  64. package/lib-es/bridge/estimateMaxSpendable.d.ts +2 -2
  65. package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
  66. package/lib-es/bridge/estimateMaxSpendable.js +2 -3
  67. package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
  68. package/lib-es/bridge/getTransactionStatus.d.ts +5 -3
  69. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  70. package/lib-es/bridge/getTransactionStatus.js +27 -10
  71. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  72. package/lib-es/bridge/index.d.ts +2 -2
  73. package/lib-es/bridge/index.d.ts.map +1 -1
  74. package/lib-es/bridge/index.js +3 -3
  75. package/lib-es/bridge/index.js.map +1 -1
  76. package/lib-es/bridge/onboard.d.ts.map +1 -1
  77. package/lib-es/bridge/onboard.js +2 -2
  78. package/lib-es/bridge/onboard.js.map +1 -1
  79. package/lib-es/bridge/prepareTransaction.js +1 -1
  80. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  81. package/lib-es/bridge/serialization.d.ts +4 -0
  82. package/lib-es/bridge/serialization.d.ts.map +1 -0
  83. package/lib-es/bridge/serialization.js +32 -0
  84. package/lib-es/bridge/serialization.js.map +1 -0
  85. package/lib-es/bridge/signOperation.js +2 -2
  86. package/lib-es/bridge/signOperation.js.map +1 -1
  87. package/lib-es/bridge/sync.d.ts.map +1 -1
  88. package/lib-es/bridge/sync.js +14 -6
  89. package/lib-es/bridge/sync.js.map +1 -1
  90. package/lib-es/bridge/transaction.js +1 -1
  91. package/lib-es/bridge/transaction.js.map +1 -1
  92. package/lib-es/common-logic/account/getBalance.d.ts +5 -1
  93. package/lib-es/common-logic/account/getBalance.d.ts.map +1 -1
  94. package/lib-es/common-logic/account/getBalance.js +2 -0
  95. package/lib-es/common-logic/account/getBalance.js.map +1 -1
  96. package/lib-es/common-logic/transaction/craftTransaction.js +1 -1
  97. package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
  98. package/lib-es/common-logic/transaction/estimateFees.js +1 -1
  99. package/lib-es/common-logic/transaction/estimateFees.js.map +1 -1
  100. package/lib-es/common-logic/transaction/sign.d.ts +2 -2
  101. package/lib-es/common-logic/transaction/sign.d.ts.map +1 -1
  102. package/lib-es/common-logic/transaction/sign.js +20 -2
  103. package/lib-es/common-logic/transaction/sign.js.map +1 -1
  104. package/lib-es/network/gateway.d.ts +5 -1
  105. package/lib-es/network/gateway.d.ts.map +1 -1
  106. package/lib-es/network/gateway.js +4 -2
  107. package/lib-es/network/gateway.js.map +1 -1
  108. package/lib-es/test/fixtures.d.ts +5 -0
  109. package/lib-es/test/fixtures.d.ts.map +1 -0
  110. package/lib-es/test/fixtures.js +49 -0
  111. package/lib-es/test/fixtures.js.map +1 -0
  112. package/lib-es/types/bridge.d.ts +14 -2
  113. package/lib-es/types/bridge.d.ts.map +1 -1
  114. package/lib-es/types/errors.d.ts +6 -0
  115. package/lib-es/types/errors.d.ts.map +1 -1
  116. package/lib-es/types/errors.js +2 -0
  117. package/lib-es/types/errors.js.map +1 -1
  118. package/lib-es/types/index.d.ts +1 -0
  119. package/lib-es/types/index.d.ts.map +1 -1
  120. package/lib-es/types/index.js +1 -0
  121. package/lib-es/types/index.js.map +1 -1
  122. package/lib-es/types/signer.d.ts +5 -1
  123. package/lib-es/types/signer.d.ts.map +1 -1
  124. package/package.json +10 -10
  125. package/src/bridge/estimateMaxSpendable.ts +6 -8
  126. package/src/bridge/getTransactionStatus.test.ts +88 -150
  127. package/src/bridge/getTransactionStatus.ts +43 -11
  128. package/src/bridge/index.ts +6 -5
  129. package/src/bridge/onboard.integ.test.ts +8 -31
  130. package/src/bridge/onboard.ts +3 -2
  131. package/src/bridge/prepareTransaction.ts +1 -1
  132. package/src/bridge/serialization.ts +44 -0
  133. package/src/bridge/signOperation.test.ts +24 -15
  134. package/src/bridge/signOperation.ts +2 -2
  135. package/src/bridge/sync.test.ts +5 -1
  136. package/src/bridge/sync.ts +18 -7
  137. package/src/bridge/transaction.ts +1 -1
  138. package/src/common-logic/account/getBalance.ts +12 -2
  139. package/src/common-logic/account/getBalance.unit.test.ts +7 -1
  140. package/src/common-logic/transaction/craftTransaction.ts +1 -1
  141. package/src/common-logic/transaction/estimateFees.test.ts +10 -0
  142. package/src/common-logic/transaction/estimateFees.ts +1 -1
  143. package/src/common-logic/transaction/sign.test.ts +83 -11
  144. package/src/common-logic/transaction/sign.ts +32 -6
  145. package/src/network/gateway.integ.test.ts +3 -6
  146. package/src/network/gateway.ts +10 -2
  147. package/src/test/fixtures.ts +53 -0
  148. package/src/types/bridge.ts +15 -2
  149. package/src/types/errors.ts +3 -0
  150. package/src/types/index.ts +1 -0
  151. package/src/types/signer.ts +5 -1
@@ -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 { Account, AccountBridge } from "@ledgerhq/types-live";
12
+ import { AccountBridge } from "@ledgerhq/types-live";
14
13
  import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
15
- import { Transaction, TransactionStatus } from "../types";
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
- Account,
30
+ CantonAccount,
22
31
  TransactionStatus
23
32
  >["getTransactionStatus"] = async (account, transaction) => {
24
33
  const errors: Record<string, Error> = {};
@@ -35,12 +44,9 @@ export const getTransactionStatus: AccountBridge<
35
44
  warnings.feeTooHigh = new FeeTooHigh();
36
45
  }
37
46
 
38
- if (!transaction.fee) {
47
+ if (transaction.fee === null || transaction.fee === undefined) {
39
48
  // if the fee is not loaded, we can't do much
40
49
  errors.fee = new FeeNotLoaded();
41
- } else if (transaction.fee.eq(0)) {
42
- // On some chains, 0 fee could still work so this is optional
43
- errors.fee = new FeeRequired();
44
50
  } else if (totalSpent.gt(account.balance.minus(reserveAmount))) {
45
51
  // if the total spent is greater than the balance minus the reserve amount, tx is invalid
46
52
  errors.amount = new NotEnoughSpendableBalance("", {
@@ -63,9 +69,6 @@ export const getTransactionStatus: AccountBridge<
63
69
 
64
70
  if (!transaction.recipient) {
65
71
  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
72
  } else if (!isRecipientValid(transaction.recipient)) {
70
73
  // We want to prevent user from sending to an invalid address
71
74
  errors.recipient = new InvalidAddress("", {
@@ -78,6 +81,8 @@ export const getTransactionStatus: AccountBridge<
78
81
  errors.amount = new AmountRequired();
79
82
  }
80
83
 
84
+ validateUtxoCount(account, transaction, warnings);
85
+
81
86
  return {
82
87
  errors,
83
88
  warnings,
@@ -86,3 +91,30 @@ export const getTransactionStatus: AccountBridge<
86
91
  totalSpent,
87
92
  };
88
93
  };
94
+
95
+ function validateUtxoCount(
96
+ account: CantonAccount,
97
+ transaction: Transaction,
98
+ warnings: Record<string, Error>,
99
+ ): void {
100
+ const abandonSeedAddress = getAbandonSeedAddress(account.currency.id);
101
+ const isAbandonSeedAddress = transaction.recipient?.includes(abandonSeedAddress);
102
+
103
+ // UTXO count validation - only validate if recipient is valid and not equal to sender
104
+ // Skip validation for abandon seed addresses
105
+ if (
106
+ account?.cantonResources?.instrumentUtxoCounts &&
107
+ transaction.recipient &&
108
+ isRecipientValid(transaction.recipient) &&
109
+ account.xpub !== transaction.recipient &&
110
+ !isAbandonSeedAddress
111
+ ) {
112
+ const { instrumentUtxoCounts } = account.cantonResources;
113
+ const instrumentUtxoCount = instrumentUtxoCounts[transaction.tokenId] || 0;
114
+ if (instrumentUtxoCount > TO_MANY_UTXOS_CRITICAL_COUNT) {
115
+ warnings.tooManyUtxos = new TooManyUtxosCritical();
116
+ } else if (instrumentUtxoCount > TO_MANY_UTXOS_WARNING_COUNT) {
117
+ warnings.tooManyUtxos = new TooManyUtxosWarning("families.canton.tooManyUtxos.warning");
118
+ }
119
+ }
120
+ }
@@ -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
- // we want one method per file
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 { emptyHistoryCache } from "@ledgerhq/coin-framework/account/index";
5
- import { generateMockKeyPair, createMockSigner } from "../test/cantonTestUtils";
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 coinConfig from "../config";
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
- id: "canton_network",
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>;
@@ -89,6 +89,7 @@ export const buildOnboardAccount =
89
89
  o.next({ status: OnboardStatus.PREPARE });
90
90
 
91
91
  let { partyId } = await isAccountOnboarded(currency, publicKey);
92
+
92
93
  if (partyId) {
93
94
  const onboardedAccount = createOnboardedAccount(account, partyId, currency);
94
95
  o.next({ partyId, account: onboardedAccount }); // success
@@ -144,7 +145,7 @@ export const buildAuthorizePreapproval =
144
145
 
145
146
  o.next({ status: AuthorizeStatus.SIGN });
146
147
 
147
- const signature = await signerContext(deviceId, async signer => {
148
+ const { signature } = await signerContext(deviceId, async signer => {
148
149
  return await signTransaction(signer, account.freshAddressPath, preparedTransaction);
149
150
  });
150
151
  o.next({ status: AuthorizeStatus.SUBMIT });
@@ -162,7 +163,7 @@ export const buildAuthorizePreapproval =
162
163
  if (serialized && hash) {
163
164
  o.next({ status: AuthorizeStatus.SIGN });
164
165
 
165
- const signature = await signerContext(deviceId, signer =>
166
+ const { signature } = await signerContext(deviceId, signer =>
166
167
  signer.signTransaction(account.freshAddressPath, hash),
167
168
  );
168
169
 
@@ -11,7 +11,7 @@ export const prepareTransaction: AccountBridge<Transaction>["prepareTransaction"
11
11
  ) => {
12
12
  const amount = transaction.amount || BigNumber(0);
13
13
  const fee = BigNumber(
14
- (await estimateFees(account.currency, BigInt(amount.toString()))).toString(),
14
+ (await estimateFees(account.currency, BigInt(amount.toFixed()))).toString(),
15
15
  );
16
16
 
17
17
  if (!transaction.tokenId) {
@@ -0,0 +1,44 @@
1
+ import type { Account, AccountRaw } from "@ledgerhq/types-live";
2
+ import {
3
+ type CantonAccount,
4
+ type CantonAccountRaw,
5
+ type CantonResources,
6
+ type CantonResourcesRaw,
7
+ } from "../types";
8
+
9
+ function isCantonAccount(account: Account): account is CantonAccount {
10
+ return "cantonResources" in account;
11
+ }
12
+
13
+ function isCantonAccountRaw(accountRaw: AccountRaw): accountRaw is CantonAccountRaw {
14
+ return "cantonResources" in accountRaw;
15
+ }
16
+
17
+ function toResourcesRaw(r: CantonResources): CantonResourcesRaw {
18
+ const { instrumentUtxoCounts } = r;
19
+ return {
20
+ instrumentUtxoCounts,
21
+ };
22
+ }
23
+
24
+ function fromResourcesRaw(r: CantonResourcesRaw): CantonResources {
25
+ return {
26
+ instrumentUtxoCounts: r.instrumentUtxoCounts,
27
+ };
28
+ }
29
+
30
+ export function assignToAccountRaw(account: Account, accountRaw: AccountRaw): void {
31
+ if (isCantonAccount(account) && isCantonAccountRaw(accountRaw)) {
32
+ if (account.cantonResources) {
33
+ accountRaw.cantonResources = toResourcesRaw(account.cantonResources);
34
+ }
35
+ }
36
+ }
37
+
38
+ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account): void {
39
+ if (isCantonAccountRaw(accountRaw) && isCantonAccount(account)) {
40
+ if (accountRaw.cantonResources) {
41
+ account.cantonResources = fromResourcesRaw(accountRaw.cantonResources);
42
+ }
43
+ }
44
+ }
@@ -1,9 +1,15 @@
1
1
  import { BigNumber } from "bignumber.js";
2
- import { CantonSigner, CantonPreparedTransaction } from "../types";
2
+ import {
3
+ CantonSigner,
4
+ CantonPreparedTransaction,
5
+ CantonSignature,
6
+ CantonUntypedVersionedMessage,
7
+ } from "../types";
3
8
  import { Transaction } from "../types";
4
9
  import { craftTransaction } from "../common-logic";
5
10
  import prepareTransferMock from "../test/prepare-transfer.json";
6
11
  import { buildSignOperation } from "./signOperation";
12
+ import { createMockAccount } from "../test/fixtures";
7
13
 
8
14
  jest.mock("../common-logic", () => {
9
15
  const actual = jest.requireActual("../common-logic");
@@ -26,12 +32,22 @@ class MockCantonSigner implements CantonSigner {
26
32
 
27
33
  async signTransaction(
28
34
  path: string,
29
- data: CantonPreparedTransaction | { transactions: string[] },
30
- ) {
31
- if ("transactions" in data) {
32
- return `untyped-signature-${data.transactions.length}`;
35
+ data: CantonPreparedTransaction | CantonUntypedVersionedMessage | string,
36
+ ): Promise<CantonSignature> {
37
+ if (typeof data === "string") {
38
+ return { signature: `txhash-signature-${data}` };
39
+ } else if ("transactions" in data) {
40
+ const result: CantonSignature = {
41
+ signature: `untyped-signature-${data.transactions.length}`,
42
+ };
43
+ if (data.challenge) {
44
+ result.applicationSignature = `challenge-signature-${data.challenge}`;
45
+ }
46
+ return result;
33
47
  } else {
34
- return `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`;
48
+ return {
49
+ signature: `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`,
50
+ };
35
51
  }
36
52
  }
37
53
  }
@@ -39,25 +55,18 @@ class MockCantonSigner implements CantonSigner {
39
55
  describe("buildSignOperation", () => {
40
56
  const mockDeviceId = "test-device-id";
41
57
  const mockDerivationPath = "44'/6767'/0'/0'/0'";
42
- const mockPartyId = "alice::1220d466a5d96a3509736c821e25fe81fc8a73f226d92e57e94a65170e58b07fc08e";
43
58
 
44
59
  beforeEach(() => {
45
60
  jest.clearAllMocks();
46
61
  mockCraftTransaction.mockReset();
47
62
  });
48
63
 
49
- const mockAccount = {
64
+ const mockAccount = createMockAccount({
50
65
  id: "js:2:canton_network:test-party-id:",
51
66
  freshAddress: "test-address",
52
67
  freshAddressPath: mockDerivationPath,
53
68
  xpub: "test-party-id",
54
- currency: {
55
- id: "canton_network",
56
- },
57
- cantonResources: {
58
- partyId: mockPartyId,
59
- },
60
- } as any;
69
+ });
61
70
 
62
71
  const mockTransaction: Transaction = {
63
72
  family: "canton",
@@ -50,13 +50,13 @@ export const buildSignOperation =
50
50
  params,
51
51
  );
52
52
 
53
- const transactionSignature = await signTransaction(signer, derivationPath, {
53
+ const { signature } = await signTransaction(signer, derivationPath, {
54
54
  json: nativeTransaction,
55
55
  serialized: serializedTransaction,
56
56
  hash: hash,
57
57
  });
58
58
 
59
- return combine(serializedTransaction, `${transactionSignature}__PARTY__${address}`);
59
+ return combine(serializedTransaction, `${signature}__PARTY__${address}`);
60
60
  });
61
61
 
62
62
  o.next({
@@ -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
 
@@ -128,7 +132,7 @@ describe("makeGetAccountShape", () => {
128
132
 
129
133
  expect(shape).toBeDefined();
130
134
  expect(shape.balance).toEqual(BigNumber(1010));
131
- expect(shape.spendableBalance).toEqual(BigNumber(10));
135
+ expect(shape.spendableBalance).toEqual(BigNumber(1010));
132
136
  });
133
137
 
134
138
  it("should handle empty balances correctly", async () => {
@@ -4,7 +4,8 @@ import { encodeAccountId } from "@ledgerhq/coin-framework/account/index";
4
4
  import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
5
5
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
6
6
  import { SignerContext } from "@ledgerhq/coin-framework/signer";
7
- import { getBalance, getLedgerEnd, getOperations, type OperationInfo } from "../network/gateway";
7
+ import { getLedgerEnd, getOperations, type OperationInfo } from "../network/gateway";
8
+ import { getBalance, type CantonBalance } from "../common-logic/account/getBalance";
8
9
  import coinConfig from "../config";
9
10
  import resolver from "../signer";
10
11
  import { CantonAccount, CantonSigner } from "../types";
@@ -108,18 +109,25 @@ export function makeGetAccountShape(
108
109
  const balances = xpubOrAddress ? await getBalance(currency, xpubOrAddress) : [];
109
110
 
110
111
  const balancesData = (balances || []).reduce(
111
- (acc, { amount, instrument_id, locked }) => {
112
- acc[instrument_id] = { amount, locked };
112
+ (acc, balance) => {
113
+ acc[balance.instrumentId] = balance;
113
114
  return acc;
114
115
  },
115
- {} as Record<string, { amount: string; locked: boolean }>,
116
+ {} as Record<string, CantonBalance>,
116
117
  );
117
118
 
118
- const unlockedAmount = new BigNumber(balancesData[nativeInstrumentId]?.amount || "0");
119
- const lockedAmount = new BigNumber(balancesData[`Locked${nativeInstrumentId}`]?.amount || "0");
119
+ const unlockedAmount = new BigNumber(balancesData[nativeInstrumentId]?.value.toString() || "0");
120
+ const lockedAmount = new BigNumber(
121
+ balancesData[`Locked${nativeInstrumentId}`]?.value.toString() || "0",
122
+ );
120
123
  const totalBalance = unlockedAmount.plus(lockedAmount);
121
124
  const reserveMin = new BigNumber(coinConfig.getCoinConfig(currency).minReserve || 0);
122
- const spendableBalance = BigNumber.max(0, unlockedAmount.minus(reserveMin));
125
+ const spendableBalance = BigNumber.max(0, totalBalance.minus(reserveMin));
126
+
127
+ const instrumentUtxoCounts: Record<string, number> = {};
128
+ for (const [instrumentId, balance] of Object.entries(balancesData)) {
129
+ instrumentUtxoCounts[instrumentId] = balance.utxoCount;
130
+ }
123
131
 
124
132
  let operations: Operation[] = [];
125
133
  if (xpubOrAddress) {
@@ -159,6 +167,9 @@ export function makeGetAccountShape(
159
167
  spendableBalance,
160
168
  xpub: xpubOrAddress,
161
169
  used,
170
+ cantonResources: {
171
+ instrumentUtxoCounts,
172
+ },
162
173
  };
163
174
 
164
175
  return shape;
@@ -48,7 +48,7 @@ export const toTransactionRaw = (t: Transaction): TransactionRaw => {
48
48
  return {
49
49
  ...common,
50
50
  family: t.family,
51
- fee: t.fee ? t.fee.toString() : null,
51
+ fee: t.fee ? t.fee.toFixed() : null,
52
52
  tokenId: t.tokenId,
53
53
  };
54
54
  };
@@ -3,12 +3,17 @@ import { getBalance as gatewayGetBalance, type InstrumentBalance } from "../../n
3
3
  import coinConfig from "../../config";
4
4
  import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
5
5
 
6
+ export type CantonBalance = Balance & {
7
+ utxoCount: number;
8
+ instrumentId: string;
9
+ };
10
+
6
11
  const useGateway = (currency: CryptoCurrency) =>
7
12
  coinConfig.getCoinConfig(currency).useGateway === true;
8
13
  const getNativeId = (currency: CryptoCurrency) =>
9
14
  coinConfig.getCoinConfig(currency).nativeInstrumentId;
10
15
 
11
- function adaptInstrument(currency: CryptoCurrency, instrument: InstrumentBalance): Balance {
16
+ function adaptInstrument(currency: CryptoCurrency, instrument: InstrumentBalance): CantonBalance {
12
17
  return {
13
18
  value: BigInt(instrument.amount),
14
19
  locked: instrument.locked === true ? BigInt(instrument.amount) : BigInt(0),
@@ -16,10 +21,15 @@ function adaptInstrument(currency: CryptoCurrency, instrument: InstrumentBalance
16
21
  getNativeId(currency) === instrument.instrument_id
17
22
  ? { type: "native" }
18
23
  : { type: "token", assetReference: instrument.instrument_id },
24
+ utxoCount: instrument.utxo_count,
25
+ instrumentId: instrument.instrument_id,
19
26
  };
20
27
  }
21
28
 
22
- export async function getBalance(currency: CryptoCurrency, partyId: string): Promise<Balance[]> {
29
+ export async function getBalance(
30
+ currency: CryptoCurrency,
31
+ partyId: string,
32
+ ): Promise<CantonBalance[]> {
23
33
  if (useGateway(currency))
24
34
  return (await gatewayGetBalance(currency, partyId)).map(instrument =>
25
35
  adaptInstrument(currency, instrument),
@@ -29,11 +29,13 @@ describe("getBalance", () => {
29
29
  {
30
30
  instrument_id: "native-id",
31
31
  amount: "1000",
32
+ utxo_count: 1,
32
33
  locked: false,
33
34
  },
34
35
  {
35
36
  instrument_id: "token-123",
36
37
  amount: "5000",
38
+ utxo_count: 1,
37
39
  locked: true,
38
40
  },
39
41
  ];
@@ -43,16 +45,20 @@ describe("getBalance", () => {
43
45
  const result = await getBalance(mockCurrency, "party-id");
44
46
 
45
47
  expect(getBalanceFromNetwork).toHaveBeenCalledWith(mockCurrency, "party-id");
46
- expect(result).toEqual<Balance[]>([
48
+ expect(result).toEqual([
47
49
  {
48
50
  value: BigInt(1000),
49
51
  locked: BigInt(0),
50
52
  asset: { type: "native" },
53
+ utxoCount: 1,
54
+ instrumentId: "native-id",
51
55
  },
52
56
  {
53
57
  value: BigInt(5000),
54
58
  locked: BigInt(5000),
55
59
  asset: { type: "token", assetReference: "token-123" },
60
+ utxoCount: 1,
61
+ instrumentId: "token-123",
56
62
  },
57
63
  ]);
58
64
  });
@@ -27,7 +27,7 @@ export async function craftTransaction(
27
27
  }> {
28
28
  const params: PrepareTransferRequest = {
29
29
  recipient: transaction.recipient || "",
30
- amount: transaction.amount.toString(),
30
+ amount: transaction.amount.toFixed(),
31
31
  type: "token-transfer-request" as const,
32
32
  execute_before_secs: transaction.expireInSeconds,
33
33
  instrument_id: transaction.tokenId,
@@ -47,4 +47,14 @@ describe("estimateFees", () => {
47
47
  // 3 CC
48
48
  expect(result).toEqual(3n * magnitude);
49
49
  });
50
+
51
+ it("returns forced fees when 0 is setup in config", async () => {
52
+ mockGetCoinConfig.mockReturnValue({
53
+ fee: 0,
54
+ } as unknown as coinConfigModule.CantonCoinConfig);
55
+
56
+ const result = await estimateFees(mockCurrency, 1000n * magnitude);
57
+
58
+ expect(result).toEqual(0n);
59
+ });
50
60
  });
@@ -21,7 +21,7 @@ const feeValue = (currency: CryptoCurrency) => coinConfig.getCoinConfig(currency
21
21
 
22
22
  export async function estimateFees(currency: CryptoCurrency, amount: bigint): Promise<bigint> {
23
23
  const forcedValue = feeValue(currency);
24
- if (forcedValue) {
24
+ if (forcedValue !== undefined) {
25
25
  return Promise.resolve(BigInt(forcedValue) * magnitude);
26
26
  } else {
27
27
  return Promise.resolve(