@ledgerhq/coin-canton 0.9.0-nightly.2 → 0.9.0-nightly.4
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 +28 -0
- package/lib/bridge/estimateMaxSpendable.d.ts +2 -2
- package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +2 -3
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts +5 -3
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +26 -5
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/index.d.ts +2 -2
- package/lib/bridge/index.d.ts.map +1 -1
- package/lib/bridge/index.js +3 -3
- package/lib/bridge/index.js.map +1 -1
- package/lib/bridge/onboard.d.ts.map +1 -1
- package/lib/bridge/onboard.js +2 -2
- 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.js +2 -2
- package/lib/bridge/signOperation.js.map +1 -1
- package/lib/bridge/sync.d.ts.map +1 -1
- package/lib/bridge/sync.js +13 -5
- package/lib/bridge/sync.js.map +1 -1
- package/lib/bridge/transaction.js +1 -1
- package/lib/bridge/transaction.js.map +1 -1
- package/lib/common-logic/account/getBalance.d.ts +5 -1
- package/lib/common-logic/account/getBalance.d.ts.map +1 -1
- package/lib/common-logic/account/getBalance.js +2 -0
- package/lib/common-logic/account/getBalance.js.map +1 -1
- package/lib/common-logic/transaction/craftTransaction.js +1 -1
- package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib/common-logic/transaction/sign.d.ts +2 -2
- package/lib/common-logic/transaction/sign.d.ts.map +1 -1
- package/lib/common-logic/transaction/sign.js +20 -2
- package/lib/common-logic/transaction/sign.js.map +1 -1
- package/lib/network/gateway.d.ts +5 -1
- package/lib/network/gateway.d.ts.map +1 -1
- package/lib/network/gateway.js +4 -2
- package/lib/network/gateway.js.map +1 -1
- package/lib/test/fixtures.d.ts +5 -0
- package/lib/test/fixtures.d.ts.map +1 -0
- package/lib/test/fixtures.js +57 -0
- package/lib/test/fixtures.js.map +1 -0
- package/lib/types/bridge.d.ts +14 -2
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib/types/errors.d.ts +6 -0
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/errors.js +3 -1
- package/lib/types/errors.js.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/signer.d.ts +5 -1
- package/lib/types/signer.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts +2 -2
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +2 -3
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts +5 -3
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +26 -5
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/index.d.ts +2 -2
- package/lib-es/bridge/index.d.ts.map +1 -1
- package/lib-es/bridge/index.js +3 -3
- package/lib-es/bridge/index.js.map +1 -1
- package/lib-es/bridge/onboard.d.ts.map +1 -1
- package/lib-es/bridge/onboard.js +2 -2
- 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.js +2 -2
- 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 +13 -5
- package/lib-es/bridge/sync.js.map +1 -1
- package/lib-es/bridge/transaction.js +1 -1
- package/lib-es/bridge/transaction.js.map +1 -1
- package/lib-es/common-logic/account/getBalance.d.ts +5 -1
- package/lib-es/common-logic/account/getBalance.d.ts.map +1 -1
- package/lib-es/common-logic/account/getBalance.js +2 -0
- package/lib-es/common-logic/account/getBalance.js.map +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.js +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib-es/common-logic/transaction/sign.d.ts +2 -2
- package/lib-es/common-logic/transaction/sign.d.ts.map +1 -1
- package/lib-es/common-logic/transaction/sign.js +20 -2
- package/lib-es/common-logic/transaction/sign.js.map +1 -1
- package/lib-es/network/gateway.d.ts +5 -1
- package/lib-es/network/gateway.d.ts.map +1 -1
- package/lib-es/network/gateway.js +4 -2
- package/lib-es/network/gateway.js.map +1 -1
- package/lib-es/test/fixtures.d.ts +5 -0
- package/lib-es/test/fixtures.d.ts.map +1 -0
- package/lib-es/test/fixtures.js +49 -0
- package/lib-es/test/fixtures.js.map +1 -0
- package/lib-es/types/bridge.d.ts +14 -2
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/lib-es/types/errors.d.ts +6 -0
- package/lib-es/types/errors.d.ts.map +1 -1
- package/lib-es/types/errors.js +2 -0
- package/lib-es/types/errors.js.map +1 -1
- package/lib-es/types/index.d.ts +1 -0
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +1 -0
- package/lib-es/types/index.js.map +1 -1
- package/lib-es/types/signer.d.ts +5 -1
- package/lib-es/types/signer.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/bridge/estimateMaxSpendable.ts +6 -8
- package/src/bridge/getTransactionStatus.test.ts +84 -146
- package/src/bridge/getTransactionStatus.ts +42 -7
- package/src/bridge/index.ts +6 -5
- package/src/bridge/onboard.integ.test.ts +8 -31
- package/src/bridge/onboard.ts +3 -2
- package/src/bridge/prepareTransaction.ts +1 -1
- package/src/bridge/serialization.ts +44 -0
- package/src/bridge/signOperation.test.ts +24 -15
- package/src/bridge/signOperation.ts +2 -2
- package/src/bridge/sync.test.ts +4 -0
- package/src/bridge/sync.ts +17 -6
- package/src/bridge/transaction.ts +1 -1
- package/src/common-logic/account/getBalance.ts +12 -2
- package/src/common-logic/account/getBalance.unit.test.ts +7 -1
- package/src/common-logic/transaction/craftTransaction.ts +1 -1
- package/src/common-logic/transaction/sign.test.ts +83 -11
- package/src/common-logic/transaction/sign.ts +32 -6
- package/src/network/gateway.integ.test.ts +3 -6
- package/src/network/gateway.ts +10 -2
- package/src/test/fixtures.ts +53 -0
- package/src/types/bridge.ts +15 -2
- package/src/types/errors.ts +3 -0
- package/src/types/index.ts +1 -0
- package/src/types/signer.ts +5 -1
|
@@ -1,45 +1,22 @@
|
|
|
1
|
-
import BigNumber from "bignumber.js";
|
|
2
|
-
import { firstValueFrom, toArray } from "rxjs";
|
|
3
1
|
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
2
|
+
import { firstValueFrom, toArray } from "rxjs";
|
|
3
|
+
import coinConfig from "../config";
|
|
4
|
+
import { createMockSigner, generateMockKeyPair } from "../test/cantonTestUtils";
|
|
5
|
+
import { createMockAccount, createMockCantonCurrency } from "../test/fixtures";
|
|
6
6
|
import {
|
|
7
7
|
AuthorizeStatus,
|
|
8
|
-
OnboardStatus,
|
|
9
8
|
CantonAuthorizeProgress,
|
|
10
9
|
CantonAuthorizeResult,
|
|
11
10
|
CantonOnboardProgress,
|
|
12
11
|
CantonOnboardResult,
|
|
12
|
+
OnboardStatus,
|
|
13
13
|
} from "../types/onboard";
|
|
14
|
-
import
|
|
15
|
-
import { buildOnboardAccount, isAccountOnboarded, buildAuthorizePreapproval } from "./onboard";
|
|
14
|
+
import { buildAuthorizePreapproval, buildOnboardAccount, isAccountOnboarded } from "./onboard";
|
|
16
15
|
|
|
17
16
|
describe("onboard (devnet)", () => {
|
|
18
17
|
const mockDeviceId = "test-device-id";
|
|
19
|
-
const mockCurrency =
|
|
20
|
-
|
|
21
|
-
} as unknown as CryptoCurrency;
|
|
22
|
-
const mockAccount = {
|
|
23
|
-
type: "Account" as const,
|
|
24
|
-
id: "js:2:canton_network:canton_3f5c9d9a:canton",
|
|
25
|
-
seedIdentifier: "canton_3f5c9d9a",
|
|
26
|
-
derivationMode: "canton" as const,
|
|
27
|
-
index: 0,
|
|
28
|
-
freshAddress: "canton_3f5c9d9a",
|
|
29
|
-
freshAddressPath: "44'/6767'/0'/0'/0'",
|
|
30
|
-
used: false,
|
|
31
|
-
balance: BigNumber(10000),
|
|
32
|
-
spendableBalance: BigNumber(10000),
|
|
33
|
-
creationDate: new Date(),
|
|
34
|
-
blockHeight: 1,
|
|
35
|
-
currency: mockCurrency,
|
|
36
|
-
operationsCount: 0,
|
|
37
|
-
operations: [],
|
|
38
|
-
pendingOperations: [],
|
|
39
|
-
lastSyncDate: new Date(),
|
|
40
|
-
balanceHistoryCache: emptyHistoryCache,
|
|
41
|
-
swapHistory: [],
|
|
42
|
-
};
|
|
18
|
+
const mockCurrency = createMockCantonCurrency();
|
|
19
|
+
const mockAccount = createMockAccount();
|
|
43
20
|
|
|
44
21
|
let onboardedAccount: {
|
|
45
22
|
keyPair: ReturnType<typeof generateMockKeyPair>;
|
package/src/bridge/onboard.ts
CHANGED
|
@@ -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.
|
|
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 {
|
|
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 |
|
|
30
|
-
) {
|
|
31
|
-
if ("
|
|
32
|
-
return `
|
|
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
|
|
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
|
-
|
|
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
|
|
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, `${
|
|
59
|
+
return combine(serializedTransaction, `${signature}__PARTY__${address}`);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
o.next({
|
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
|
|
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,19 +109,26 @@ 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
125
|
const spendableBalance = BigNumber.max(0, unlockedAmount.minus(reserveMin));
|
|
123
126
|
|
|
127
|
+
const instrumentUtxoCounts: Record<string, number> = {};
|
|
128
|
+
for (const [instrumentId, balance] of Object.entries(balancesData)) {
|
|
129
|
+
instrumentUtxoCounts[instrumentId] = balance.utxoCount;
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
let operations: Operation[] = [];
|
|
125
133
|
if (xpubOrAddress) {
|
|
126
134
|
const oldOperations = initialAccount?.operations || [];
|
|
@@ -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
|
});
|
|
@@ -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,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CantonPreparedTransaction,
|
|
4
|
+
CantonSigner,
|
|
5
|
+
CantonSignature,
|
|
6
|
+
CantonUntypedVersionedMessage,
|
|
7
|
+
} from "../../types/signer";
|
|
3
8
|
import { signTransaction } from "./sign";
|
|
4
9
|
|
|
5
10
|
class MockCantonSigner implements CantonSigner {
|
|
@@ -13,12 +18,22 @@ class MockCantonSigner implements CantonSigner {
|
|
|
13
18
|
|
|
14
19
|
async signTransaction(
|
|
15
20
|
path: string,
|
|
16
|
-
data: CantonPreparedTransaction |
|
|
17
|
-
) {
|
|
18
|
-
if ("
|
|
19
|
-
return `
|
|
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;
|
|
20
33
|
} else {
|
|
21
|
-
return
|
|
34
|
+
return {
|
|
35
|
+
signature: `prepared-transaction-signature-${data.damlTransaction.length}-${data.nodes.length}`,
|
|
36
|
+
};
|
|
22
37
|
}
|
|
23
38
|
}
|
|
24
39
|
}
|
|
@@ -85,7 +100,7 @@ describe("signTransaction", () => {
|
|
|
85
100
|
);
|
|
86
101
|
|
|
87
102
|
// THEN
|
|
88
|
-
expect(result).
|
|
103
|
+
expect(result).toEqual({ signature: "prepared-transaction-signature-10-1" });
|
|
89
104
|
});
|
|
90
105
|
|
|
91
106
|
it("should sign untyped versioned message", async () => {
|
|
@@ -122,7 +137,7 @@ describe("signTransaction", () => {
|
|
|
122
137
|
);
|
|
123
138
|
|
|
124
139
|
// THEN
|
|
125
|
-
expect(result).
|
|
140
|
+
expect(result).toEqual({ signature: "untyped-signature-3" });
|
|
126
141
|
});
|
|
127
142
|
|
|
128
143
|
it("should handle empty signature from signer", async () => {
|
|
@@ -201,7 +216,7 @@ describe("signTransaction", () => {
|
|
|
201
216
|
|
|
202
217
|
it("should call signer with correct parameters for prepared transaction", async () => {
|
|
203
218
|
// GIVEN
|
|
204
|
-
const mockSignerSpy = jest.fn().mockResolvedValue("test-signature");
|
|
219
|
+
const mockSignerSpy = jest.fn().mockResolvedValue({ signature: "test-signature" });
|
|
205
220
|
const mockSignerWithSpy = {
|
|
206
221
|
...mockSigner,
|
|
207
222
|
signTransaction: mockSignerSpy,
|
|
@@ -270,9 +285,9 @@ describe("signTransaction", () => {
|
|
|
270
285
|
);
|
|
271
286
|
});
|
|
272
287
|
|
|
273
|
-
it("should call signer with correct parameters for untyped versioned message", async () => {
|
|
288
|
+
it("should call signer with correct parameters for untyped versioned message without challenge", async () => {
|
|
274
289
|
// GIVEN
|
|
275
|
-
const mockSignerSpy = jest.fn().mockResolvedValue("test-signature");
|
|
290
|
+
const mockSignerSpy = jest.fn().mockResolvedValue({ signature: "test-signature" });
|
|
276
291
|
const mockSignerWithSpy = {
|
|
277
292
|
...mockSigner,
|
|
278
293
|
signTransaction: mockSignerSpy,
|
|
@@ -314,4 +329,61 @@ describe("signTransaction", () => {
|
|
|
314
329
|
],
|
|
315
330
|
});
|
|
316
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
|
+
});
|
|
317
389
|
});
|
|
@@ -1,33 +1,59 @@
|
|
|
1
1
|
import { OnboardingPrepareResponse, PrepareTransferResponse } from "../../network/gateway";
|
|
2
2
|
import { PrepareTransactionResponse } from "../../types/onboard";
|
|
3
|
-
import { CantonSigner } from "../../types/signer";
|
|
3
|
+
import { CantonSigner, CantonSignature } from "../../types/signer";
|
|
4
4
|
import { splitTransaction } from "./split";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Sign a Canton transaction - handles both prepared transactions and untyped versioned messages
|
|
8
8
|
*/
|
|
9
|
+
|
|
9
10
|
export async function signTransaction(
|
|
10
11
|
signer: CantonSigner,
|
|
11
12
|
derivationPath: string,
|
|
12
|
-
transactionData: PrepareTransferResponse |
|
|
13
|
-
): Promise<
|
|
14
|
-
let signature:
|
|
13
|
+
transactionData: PrepareTransferResponse | PrepareTransactionResponse | OnboardingPrepareResponse,
|
|
14
|
+
): Promise<CantonSignature> {
|
|
15
|
+
let signature: CantonSignature;
|
|
15
16
|
|
|
16
17
|
if ("json" in transactionData) {
|
|
17
18
|
const components = splitTransaction(transactionData.json);
|
|
18
19
|
signature = await signer.signTransaction(derivationPath, components);
|
|
19
20
|
} else {
|
|
21
|
+
const challenge = getTransactionChallenge(transactionData);
|
|
22
|
+
|
|
20
23
|
const transactions = [
|
|
21
24
|
transactionData.transactions.namespace_transaction.serialized,
|
|
22
25
|
transactionData.transactions.party_to_key_transaction.serialized,
|
|
23
26
|
transactionData.transactions.party_to_participant_transaction.serialized,
|
|
24
27
|
];
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
signature = await signer.signTransaction(derivationPath, {
|
|
30
|
+
transactions,
|
|
31
|
+
...(challenge && { challenge }),
|
|
32
|
+
});
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
if (!signature
|
|
35
|
+
if (!signature?.signature) {
|
|
29
36
|
throw new Error("Device returned empty signature");
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
return signature;
|
|
33
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
|
+
};
|
|
@@ -83,12 +83,9 @@ describe("gateway (devnet)", () => {
|
|
|
83
83
|
const signature = keyPair.sign(prepareResponse.transactions.combined_hash);
|
|
84
84
|
|
|
85
85
|
// WHEN
|
|
86
|
-
const response = await submitOnboarding(
|
|
87
|
-
mockCurrency,
|
|
88
|
-
keyPair.publicKeyHex,
|
|
89
|
-
prepareResponse,
|
|
86
|
+
const response = await submitOnboarding(mockCurrency, keyPair.publicKeyHex, prepareResponse, {
|
|
90
87
|
signature,
|
|
91
|
-
);
|
|
88
|
+
});
|
|
92
89
|
|
|
93
90
|
// Save onboarded account for next tests that need a valid party ID
|
|
94
91
|
onboardedAccount = {
|
|
@@ -118,7 +115,7 @@ describe("gateway (devnet)", () => {
|
|
|
118
115
|
mockCurrency,
|
|
119
116
|
keyPair.publicKeyHex,
|
|
120
117
|
prepareResponse!,
|
|
121
|
-
signature,
|
|
118
|
+
{ signature },
|
|
122
119
|
);
|
|
123
120
|
|
|
124
121
|
// THEN
|