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