@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/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
|
|
|
@@ -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(
|
|
135
|
+
expect(shape.spendableBalance).toEqual(BigNumber(1010));
|
|
132
136
|
});
|
|
133
137
|
|
|
134
138
|
it("should handle empty balances correctly", async () => {
|
package/src/bridge/sync.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
112
|
-
acc[
|
|
112
|
+
(acc, balance) => {
|
|
113
|
+
acc[balance.instrumentId] = balance;
|
|
113
114
|
return acc;
|
|
114
115
|
},
|
|
115
|
-
{} as Record<string,
|
|
116
|
+
{} as Record<string, CantonBalance>,
|
|
116
117
|
);
|
|
117
118
|
|
|
118
|
-
const unlockedAmount = new BigNumber(balancesData[nativeInstrumentId]?.
|
|
119
|
-
const lockedAmount = new BigNumber(
|
|
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,
|
|
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;
|
|
@@ -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):
|
|
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(
|
|
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
|
|
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
|
});
|
|
@@ -2,6 +2,7 @@ export { broadcast } from "./transaction/broadcast";
|
|
|
2
2
|
export { combine } from "./transaction/combine";
|
|
3
3
|
export { craftTransaction } from "./transaction/craftTransaction";
|
|
4
4
|
export { estimateFees } from "./transaction/estimateFees";
|
|
5
|
+
export { signTransaction } from "./transaction/sign";
|
|
5
6
|
export { getBalance } from "./account/getBalance";
|
|
6
7
|
export { lastBlock } from "./history/lastBlock";
|
|
7
8
|
export { listOperations } from "./history/listOperations";
|
|
@@ -27,7 +27,7 @@ export async function craftTransaction(
|
|
|
27
27
|
}> {
|
|
28
28
|
const params: PrepareTransferRequest = {
|
|
29
29
|
recipient: transaction.recipient || "",
|
|
30
|
-
amount: transaction.amount.
|
|
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(
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
|
|
2
|
+
import {
|
|
3
|
+
CantonPreparedTransaction,
|
|
4
|
+
CantonSigner,
|
|
5
|
+
CantonSignature,
|
|
6
|
+
CantonUntypedVersionedMessage,
|
|
7
|
+
} from "../../types/signer";
|
|
8
|
+
import { signTransaction } from "./sign";
|
|
9
|
+
|
|
10
|
+
class MockCantonSigner implements CantonSigner {
|
|
11
|
+
async getAddress(path: string, display?: boolean) {
|
|
12
|
+
return {
|
|
13
|
+
publicKey: "mock-public-key",
|
|
14
|
+
address: "mock-address",
|
|
15
|
+
path,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async signTransaction(
|
|
20
|
+
path: string,
|
|
21
|
+
data: CantonPreparedTransaction | CantonUntypedVersionedMessage | string,
|
|
22
|
+
): Promise<CantonSignature> {
|
|
23
|
+
if (typeof data === "string") {
|
|
24
|
+
return { signature: `txhash-signature-${data}` };
|
|
25
|
+
} else if ("transactions" in data) {
|
|
26
|
+
const result: CantonSignature = {
|
|
27
|
+
signature: `untyped-signature-${data.transactions.length}`,
|
|
28
|
+
};
|
|
29
|
+
if (data.challenge) {
|
|
30
|
+
result.applicationSignature = `challenge-signature-${data.challenge}`;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
} else {
|
|
34
|
+
return {
|
|
35
|
+
signature: `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("signTransaction", () => {
|
|
42
|
+
const mockSigner = new MockCantonSigner();
|
|
43
|
+
const mockDerivationPath = "44'/6767'/0'/0'/0'";
|
|
44
|
+
|
|
45
|
+
it("should sign prepared transaction", async () => {
|
|
46
|
+
// GIVEN
|
|
47
|
+
const mockPrepareTransferResponse: PrepareTransferResponse = {
|
|
48
|
+
json: {
|
|
49
|
+
transaction: {
|
|
50
|
+
version: "2.1",
|
|
51
|
+
roots: ["0"],
|
|
52
|
+
nodes: [
|
|
53
|
+
{
|
|
54
|
+
nodeId: "0",
|
|
55
|
+
v1: {
|
|
56
|
+
create: {
|
|
57
|
+
lfVersion: "2.1",
|
|
58
|
+
contractId: "test-contract-id",
|
|
59
|
+
packageName: "test-package",
|
|
60
|
+
templateId: {
|
|
61
|
+
packageId: "test-package-id",
|
|
62
|
+
moduleName: "TestModule",
|
|
63
|
+
entityName: "TestEntity",
|
|
64
|
+
},
|
|
65
|
+
argument: {
|
|
66
|
+
record: {
|
|
67
|
+
recordId: {
|
|
68
|
+
packageId: "test-package-id",
|
|
69
|
+
moduleName: "TestModule",
|
|
70
|
+
entityName: "TestEntity",
|
|
71
|
+
},
|
|
72
|
+
fields: [],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
metadata: {
|
|
81
|
+
submitterInfo: {
|
|
82
|
+
actAs: ["test::party"],
|
|
83
|
+
commandId: "test-command-id",
|
|
84
|
+
},
|
|
85
|
+
synchronizerId: "test-synchronizer-id",
|
|
86
|
+
transactionUuid: "test-transaction-uuid",
|
|
87
|
+
submissionTime: "1234567890",
|
|
88
|
+
inputContracts: [],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
serialized: "serialized-transaction",
|
|
92
|
+
hash: "test-hash",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// WHEN
|
|
96
|
+
const result = await signTransaction(
|
|
97
|
+
mockSigner,
|
|
98
|
+
mockDerivationPath,
|
|
99
|
+
mockPrepareTransferResponse,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// THEN
|
|
103
|
+
expect(result).toEqual({ signature: "prepared-transaction-signature-10-1" });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should sign untyped versioned message", async () => {
|
|
107
|
+
// GIVEN
|
|
108
|
+
const mockOnboardingPrepareResponse: OnboardingPrepareResponse = {
|
|
109
|
+
party_id: "test-party-id",
|
|
110
|
+
party_name: "test-party-name",
|
|
111
|
+
public_key_fingerprint: "test-fingerprint",
|
|
112
|
+
transactions: {
|
|
113
|
+
namespace_transaction: {
|
|
114
|
+
serialized: "namespace-transaction-data",
|
|
115
|
+
json: {},
|
|
116
|
+
hash: "namespace-hash",
|
|
117
|
+
},
|
|
118
|
+
party_to_key_transaction: {
|
|
119
|
+
serialized: "party-to-key-transaction-data",
|
|
120
|
+
json: {},
|
|
121
|
+
hash: "party-to-key-hash",
|
|
122
|
+
},
|
|
123
|
+
party_to_participant_transaction: {
|
|
124
|
+
serialized: "party-to-participant-transaction-data",
|
|
125
|
+
json: {},
|
|
126
|
+
hash: "party-to-participant-hash",
|
|
127
|
+
},
|
|
128
|
+
combined_hash: "combined-hash",
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// WHEN
|
|
133
|
+
const result = await signTransaction(
|
|
134
|
+
mockSigner,
|
|
135
|
+
mockDerivationPath,
|
|
136
|
+
mockOnboardingPrepareResponse,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// THEN
|
|
140
|
+
expect(result).toEqual({ signature: "untyped-signature-3" });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should handle empty signature from signer", async () => {
|
|
144
|
+
// GIVEN
|
|
145
|
+
const mockSignerWithEmptySignature = {
|
|
146
|
+
...mockSigner,
|
|
147
|
+
signTransaction: jest.fn().mockResolvedValue(""),
|
|
148
|
+
} as unknown as CantonSigner;
|
|
149
|
+
|
|
150
|
+
const mockPrepareTransferResponse: PrepareTransferResponse = {
|
|
151
|
+
json: {
|
|
152
|
+
transaction: {
|
|
153
|
+
version: "2.1",
|
|
154
|
+
roots: ["0"],
|
|
155
|
+
nodes: [],
|
|
156
|
+
},
|
|
157
|
+
metadata: {
|
|
158
|
+
submitterInfo: {
|
|
159
|
+
actAs: ["test::party"],
|
|
160
|
+
commandId: "test-command-id",
|
|
161
|
+
},
|
|
162
|
+
synchronizerId: "test-synchronizer-id",
|
|
163
|
+
transactionUuid: "test-transaction-uuid",
|
|
164
|
+
submissionTime: "1234567890",
|
|
165
|
+
inputContracts: [],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
serialized: "serialized-transaction",
|
|
169
|
+
hash: "test-hash",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// WHEN & THEN
|
|
173
|
+
await expect(
|
|
174
|
+
signTransaction(
|
|
175
|
+
mockSignerWithEmptySignature,
|
|
176
|
+
mockDerivationPath,
|
|
177
|
+
mockPrepareTransferResponse,
|
|
178
|
+
),
|
|
179
|
+
).rejects.toThrow("Device returned empty signature");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should handle signer errors", async () => {
|
|
183
|
+
// GIVEN
|
|
184
|
+
const mockSignerWithError = {
|
|
185
|
+
...mockSigner,
|
|
186
|
+
signTransaction: jest.fn().mockRejectedValue(new Error("Signer error")),
|
|
187
|
+
} as unknown as CantonSigner;
|
|
188
|
+
|
|
189
|
+
const mockPrepareTransferResponse: PrepareTransferResponse = {
|
|
190
|
+
json: {
|
|
191
|
+
transaction: {
|
|
192
|
+
version: "2.1",
|
|
193
|
+
roots: ["0"],
|
|
194
|
+
nodes: [],
|
|
195
|
+
},
|
|
196
|
+
metadata: {
|
|
197
|
+
submitterInfo: {
|
|
198
|
+
actAs: ["test::party"],
|
|
199
|
+
commandId: "test-command-id",
|
|
200
|
+
},
|
|
201
|
+
synchronizerId: "test-synchronizer-id",
|
|
202
|
+
transactionUuid: "test-transaction-uuid",
|
|
203
|
+
submissionTime: "1234567890",
|
|
204
|
+
inputContracts: [],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
serialized: "serialized-transaction",
|
|
208
|
+
hash: "test-hash",
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// WHEN & THEN
|
|
212
|
+
await expect(
|
|
213
|
+
signTransaction(mockSignerWithError, mockDerivationPath, mockPrepareTransferResponse),
|
|
214
|
+
).rejects.toThrow("Signer error");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should call signer with correct parameters for prepared transaction", async () => {
|
|
218
|
+
// GIVEN
|
|
219
|
+
const mockSignerSpy = jest.fn().mockResolvedValue({ signature: "test-signature" });
|
|
220
|
+
const mockSignerWithSpy = {
|
|
221
|
+
...mockSigner,
|
|
222
|
+
signTransaction: mockSignerSpy,
|
|
223
|
+
} as unknown as CantonSigner;
|
|
224
|
+
|
|
225
|
+
const mockPrepareTransferResponse: PrepareTransferResponse = {
|
|
226
|
+
json: {
|
|
227
|
+
transaction: {
|
|
228
|
+
version: "2.1",
|
|
229
|
+
roots: ["0"],
|
|
230
|
+
nodes: [
|
|
231
|
+
{
|
|
232
|
+
nodeId: "0",
|
|
233
|
+
v1: {
|
|
234
|
+
create: {
|
|
235
|
+
lfVersion: "2.1",
|
|
236
|
+
contractId: "test-contract-id",
|
|
237
|
+
packageName: "test-package",
|
|
238
|
+
templateId: {
|
|
239
|
+
packageId: "test-package-id",
|
|
240
|
+
moduleName: "TestModule",
|
|
241
|
+
entityName: "TestEntity",
|
|
242
|
+
},
|
|
243
|
+
argument: {
|
|
244
|
+
record: {
|
|
245
|
+
recordId: {
|
|
246
|
+
packageId: "test-package-id",
|
|
247
|
+
moduleName: "TestModule",
|
|
248
|
+
entityName: "TestEntity",
|
|
249
|
+
},
|
|
250
|
+
fields: [],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
metadata: {
|
|
259
|
+
submitterInfo: {
|
|
260
|
+
actAs: ["test::party"],
|
|
261
|
+
commandId: "test-command-id",
|
|
262
|
+
},
|
|
263
|
+
synchronizerId: "test-synchronizer-id",
|
|
264
|
+
transactionUuid: "test-transaction-uuid",
|
|
265
|
+
submissionTime: "1234567890",
|
|
266
|
+
inputContracts: [],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
serialized: "serialized-transaction",
|
|
270
|
+
hash: "test-hash",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// WHEN
|
|
274
|
+
await signTransaction(mockSignerWithSpy, mockDerivationPath, mockPrepareTransferResponse);
|
|
275
|
+
|
|
276
|
+
// THEN
|
|
277
|
+
expect(mockSignerSpy).toHaveBeenCalledWith(
|
|
278
|
+
mockDerivationPath,
|
|
279
|
+
expect.objectContaining({
|
|
280
|
+
damlTransaction: expect.any(Uint8Array),
|
|
281
|
+
nodes: expect.any(Array),
|
|
282
|
+
metadata: expect.any(Uint8Array),
|
|
283
|
+
inputContracts: expect.any(Array),
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should call signer with correct parameters for untyped versioned message without challenge", async () => {
|
|
289
|
+
// GIVEN
|
|
290
|
+
const mockSignerSpy = jest.fn().mockResolvedValue({ signature: "test-signature" });
|
|
291
|
+
const mockSignerWithSpy = {
|
|
292
|
+
...mockSigner,
|
|
293
|
+
signTransaction: mockSignerSpy,
|
|
294
|
+
} as unknown as CantonSigner;
|
|
295
|
+
|
|
296
|
+
const mockOnboardingPrepareResponse: OnboardingPrepareResponse = {
|
|
297
|
+
party_id: "test-party-id",
|
|
298
|
+
party_name: "test-party-name",
|
|
299
|
+
public_key_fingerprint: "test-fingerprint",
|
|
300
|
+
transactions: {
|
|
301
|
+
namespace_transaction: {
|
|
302
|
+
serialized: "namespace-transaction-data",
|
|
303
|
+
json: {},
|
|
304
|
+
hash: "namespace-hash",
|
|
305
|
+
},
|
|
306
|
+
party_to_key_transaction: {
|
|
307
|
+
serialized: "party-to-key-transaction-data",
|
|
308
|
+
json: {},
|
|
309
|
+
hash: "party-to-key-hash",
|
|
310
|
+
},
|
|
311
|
+
party_to_participant_transaction: {
|
|
312
|
+
serialized: "party-to-participant-transaction-data",
|
|
313
|
+
json: {},
|
|
314
|
+
hash: "party-to-participant-hash",
|
|
315
|
+
},
|
|
316
|
+
combined_hash: "combined-hash",
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// WHEN
|
|
321
|
+
await signTransaction(mockSignerWithSpy, mockDerivationPath, mockOnboardingPrepareResponse);
|
|
322
|
+
|
|
323
|
+
// THEN
|
|
324
|
+
expect(mockSignerSpy).toHaveBeenCalledWith(mockDerivationPath, {
|
|
325
|
+
transactions: [
|
|
326
|
+
"namespace-transaction-data",
|
|
327
|
+
"party-to-key-transaction-data",
|
|
328
|
+
"party-to-participant-transaction-data",
|
|
329
|
+
],
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should call signer with correct parameters for untyped versioned message with challenge", async () => {
|
|
334
|
+
const mockSignerSpy = jest.fn().mockResolvedValue({
|
|
335
|
+
signature: "main-signature",
|
|
336
|
+
applicationSignature: "challenge-signature",
|
|
337
|
+
});
|
|
338
|
+
const mockSignerWithSpy = {
|
|
339
|
+
...mockSigner,
|
|
340
|
+
signTransaction: mockSignerSpy,
|
|
341
|
+
} as unknown as CantonSigner;
|
|
342
|
+
|
|
343
|
+
const mockOnboardingPrepareResponse: OnboardingPrepareResponse = {
|
|
344
|
+
party_id: "test-party-id",
|
|
345
|
+
party_name: "test-party-name",
|
|
346
|
+
public_key_fingerprint: "test-fingerprint",
|
|
347
|
+
challenge_nonce: "1234567890abcdef",
|
|
348
|
+
challenge_deadline: 1735689599,
|
|
349
|
+
transactions: {
|
|
350
|
+
namespace_transaction: {
|
|
351
|
+
serialized: "namespace-transaction-data",
|
|
352
|
+
json: {},
|
|
353
|
+
hash: "namespace-hash",
|
|
354
|
+
},
|
|
355
|
+
party_to_key_transaction: {
|
|
356
|
+
serialized: "party-to-key-transaction-data",
|
|
357
|
+
json: {},
|
|
358
|
+
hash: "party-to-key-hash",
|
|
359
|
+
},
|
|
360
|
+
party_to_participant_transaction: {
|
|
361
|
+
serialized: "party-to-participant-transaction-data",
|
|
362
|
+
json: {},
|
|
363
|
+
hash: "party-to-participant-hash",
|
|
364
|
+
},
|
|
365
|
+
combined_hash: "combined-hash",
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const result = await signTransaction(
|
|
370
|
+
mockSignerWithSpy,
|
|
371
|
+
mockDerivationPath,
|
|
372
|
+
mockOnboardingPrepareResponse,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(result).toEqual({
|
|
376
|
+
signature: "main-signature",
|
|
377
|
+
applicationSignature: "challenge-signature",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(mockSignerSpy).toHaveBeenCalledWith(mockDerivationPath, {
|
|
381
|
+
transactions: [
|
|
382
|
+
"namespace-transaction-data",
|
|
383
|
+
"party-to-key-transaction-data",
|
|
384
|
+
"party-to-participant-transaction-data",
|
|
385
|
+
],
|
|
386
|
+
challenge: "181234567890abcdef000000006774857f",
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
|
|
2
|
+
import { PrepareTransactionResponse } from "../../types/onboard";
|
|
3
|
+
import { CantonSigner, CantonSignature } from "../../types/signer";
|
|
4
|
+
import { splitTransaction } from "./split";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sign a Canton transaction - handles both prepared transactions and untyped versioned messages
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export async function signTransaction(
|
|
11
|
+
signer: CantonSigner,
|
|
12
|
+
derivationPath: string,
|
|
13
|
+
transactionData: PrepareTransferResponse | PrepareTransactionResponse | OnboardingPrepareResponse,
|
|
14
|
+
): Promise<CantonSignature> {
|
|
15
|
+
let signature: CantonSignature;
|
|
16
|
+
|
|
17
|
+
if ("json" in transactionData) {
|
|
18
|
+
const components = splitTransaction(transactionData.json);
|
|
19
|
+
signature = await signer.signTransaction(derivationPath, components);
|
|
20
|
+
} else {
|
|
21
|
+
const challenge = getTransactionChallenge(transactionData);
|
|
22
|
+
|
|
23
|
+
const transactions = [
|
|
24
|
+
transactionData.transactions.namespace_transaction.serialized,
|
|
25
|
+
transactionData.transactions.party_to_key_transaction.serialized,
|
|
26
|
+
transactionData.transactions.party_to_participant_transaction.serialized,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
signature = await signer.signTransaction(derivationPath, {
|
|
30
|
+
transactions,
|
|
31
|
+
...(challenge && { challenge }),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!signature?.signature) {
|
|
36
|
+
throw new Error("Device returned empty signature");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return signature;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getTransactionChallenge = ({
|
|
43
|
+
challenge_nonce,
|
|
44
|
+
challenge_deadline,
|
|
45
|
+
}: OnboardingPrepareResponse) => {
|
|
46
|
+
if (!challenge_nonce || !challenge_deadline) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const deadlineHex = Buffer.alloc(8);
|
|
52
|
+
deadlineHex.writeBigInt64BE(BigInt(challenge_deadline), 0);
|
|
53
|
+
|
|
54
|
+
// Format: [length (1B) = 0x18][challenge (16 bytes)][deadline (8 bytes)]
|
|
55
|
+
return "18" + challenge_nonce.toLowerCase() + deadlineHex.toString("hex");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
};
|