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