@ledgerhq/coin-vechain 3.0.0 → 4.0.0
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/.unimportedrc.json +2 -1
- package/CHANGELOG.md +42 -2699
- package/lib/api/index.d.ts +2 -2
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +25 -22
- package/lib/api/index.js.map +1 -1
- package/lib/bridge/broadcast.d.ts.map +1 -1
- package/lib/bridge/broadcast.js +2 -1
- package/lib/bridge/broadcast.js.map +1 -1
- package/lib/bridge/createTransaction.js +1 -1
- package/lib/bridge/createTransaction.js.map +1 -1
- package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +2 -1
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +2 -1
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib/bridge/prepareTransaction.js +4 -2
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/synchronisation.d.ts.map +1 -1
- package/lib/bridge/synchronisation.js +6 -4
- package/lib/bridge/synchronisation.js.map +1 -1
- package/lib/common-logic/calculateGasFees.d.ts +2 -1
- package/lib/common-logic/calculateGasFees.d.ts.map +1 -1
- package/lib/common-logic/calculateGasFees.js +3 -3
- package/lib/common-logic/calculateGasFees.js.map +1 -1
- package/lib/common-logic/calculateTransactionInfo.d.ts +2 -1
- package/lib/common-logic/calculateTransactionInfo.d.ts.map +1 -1
- package/lib/common-logic/calculateTransactionInfo.js +2 -2
- package/lib/common-logic/calculateTransactionInfo.js.map +1 -1
- package/lib/common-logic/estimateGas.d.ts +2 -1
- package/lib/common-logic/estimateGas.d.ts.map +1 -1
- package/lib/common-logic/estimateGas.js +2 -2
- package/lib/common-logic/estimateGas.js.map +1 -1
- package/lib/common-logic/getThorClient.d.ts +2 -1
- package/lib/common-logic/getThorClient.d.ts.map +1 -1
- package/lib/common-logic/getThorClient.js +2 -2
- package/lib/common-logic/getThorClient.js.map +1 -1
- package/lib/common-logic/mapTransfersToOperations.d.ts +3 -2
- package/lib/common-logic/mapTransfersToOperations.d.ts.map +1 -1
- package/lib/common-logic/mapTransfersToOperations.js +4 -4
- package/lib/common-logic/mapTransfersToOperations.js.map +1 -1
- package/lib/config.d.ts +10 -4
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +9 -5
- package/lib/config.js.map +1 -1
- package/lib/logic/account/getBalance.d.ts +2 -1
- package/lib/logic/account/getBalance.d.ts.map +1 -1
- package/lib/logic/account/getBalance.js +3 -2
- package/lib/logic/account/getBalance.js.map +1 -1
- package/lib/logic/history/getBlock.d.ts +2 -1
- package/lib/logic/history/getBlock.d.ts.map +1 -1
- package/lib/logic/history/getBlock.js +3 -2
- package/lib/logic/history/getBlock.js.map +1 -1
- package/lib/logic/history/getBlockInfo.d.ts +2 -1
- package/lib/logic/history/getBlockInfo.d.ts.map +1 -1
- package/lib/logic/history/getBlockInfo.js +3 -2
- package/lib/logic/history/getBlockInfo.js.map +1 -1
- package/lib/logic/history/lastBlock.d.ts +2 -1
- package/lib/logic/history/lastBlock.d.ts.map +1 -1
- package/lib/logic/history/lastBlock.js +4 -3
- package/lib/logic/history/lastBlock.js.map +1 -1
- package/lib/logic/history/listOperations.d.ts +2 -1
- package/lib/logic/history/listOperations.d.ts.map +1 -1
- package/lib/logic/history/listOperations.js +5 -4
- package/lib/logic/history/listOperations.js.map +1 -1
- package/lib/logic/transaction/broadcast.d.ts +2 -1
- package/lib/logic/transaction/broadcast.d.ts.map +1 -1
- package/lib/logic/transaction/broadcast.js +3 -2
- package/lib/logic/transaction/broadcast.js.map +1 -1
- package/lib/logic/transaction/combine.d.ts +1 -1
- package/lib/logic/transaction/combine.d.ts.map +1 -1
- package/lib/logic/transaction/combine.js +4 -1
- package/lib/logic/transaction/combine.js.map +1 -1
- package/lib/logic/transaction/craftTransaction.d.ts +2 -1
- package/lib/logic/transaction/craftTransaction.d.ts.map +1 -1
- package/lib/logic/transaction/craftTransaction.js +11 -10
- package/lib/logic/transaction/craftTransaction.js.map +1 -1
- package/lib/logic/transaction/estimateFees.d.ts +2 -1
- package/lib/logic/transaction/estimateFees.d.ts.map +1 -1
- package/lib/logic/transaction/estimateFees.js +2 -2
- package/lib/logic/transaction/estimateFees.js.map +1 -1
- package/lib/network/getBlock.d.ts +2 -1
- package/lib/network/getBlock.d.ts.map +1 -1
- package/lib/network/getBlock.js +2 -2
- package/lib/network/getBlock.js.map +1 -1
- package/lib/network/getFees.d.ts +2 -1
- package/lib/network/getFees.d.ts.map +1 -1
- package/lib/network/getFees.js +2 -2
- package/lib/network/getFees.js.map +1 -1
- package/lib/network/sdk.d.ts +7 -6
- package/lib/network/sdk.d.ts.map +1 -1
- package/lib/network/sdk.js +18 -18
- package/lib/network/sdk.js.map +1 -1
- package/lib/test/context.d.ts +6 -0
- package/lib/test/context.d.ts.map +1 -0
- package/lib/test/context.js +15 -0
- package/lib/test/context.js.map +1 -0
- package/lib-es/api/index.d.ts +2 -2
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +25 -22
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/bridge/broadcast.d.ts.map +1 -1
- package/lib-es/bridge/broadcast.js +2 -1
- package/lib-es/bridge/broadcast.js.map +1 -1
- package/lib-es/bridge/createTransaction.js +2 -2
- package/lib-es/bridge/createTransaction.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +2 -1
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +2 -1
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +4 -2
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/synchronisation.d.ts.map +1 -1
- package/lib-es/bridge/synchronisation.js +6 -4
- package/lib-es/bridge/synchronisation.js.map +1 -1
- package/lib-es/common-logic/calculateGasFees.d.ts +2 -1
- package/lib-es/common-logic/calculateGasFees.d.ts.map +1 -1
- package/lib-es/common-logic/calculateGasFees.js +3 -3
- package/lib-es/common-logic/calculateGasFees.js.map +1 -1
- package/lib-es/common-logic/calculateTransactionInfo.d.ts +2 -1
- package/lib-es/common-logic/calculateTransactionInfo.d.ts.map +1 -1
- package/lib-es/common-logic/calculateTransactionInfo.js +2 -2
- package/lib-es/common-logic/calculateTransactionInfo.js.map +1 -1
- package/lib-es/common-logic/estimateGas.d.ts +2 -1
- package/lib-es/common-logic/estimateGas.d.ts.map +1 -1
- package/lib-es/common-logic/estimateGas.js +2 -2
- package/lib-es/common-logic/estimateGas.js.map +1 -1
- package/lib-es/common-logic/getThorClient.d.ts +2 -1
- package/lib-es/common-logic/getThorClient.d.ts.map +1 -1
- package/lib-es/common-logic/getThorClient.js +2 -2
- package/lib-es/common-logic/getThorClient.js.map +1 -1
- package/lib-es/common-logic/mapTransfersToOperations.d.ts +3 -2
- package/lib-es/common-logic/mapTransfersToOperations.d.ts.map +1 -1
- package/lib-es/common-logic/mapTransfersToOperations.js +4 -4
- package/lib-es/common-logic/mapTransfersToOperations.js.map +1 -1
- package/lib-es/config.d.ts +10 -4
- package/lib-es/config.d.ts.map +1 -1
- package/lib-es/config.js +9 -5
- package/lib-es/config.js.map +1 -1
- package/lib-es/logic/account/getBalance.d.ts +2 -1
- package/lib-es/logic/account/getBalance.d.ts.map +1 -1
- package/lib-es/logic/account/getBalance.js +3 -2
- package/lib-es/logic/account/getBalance.js.map +1 -1
- package/lib-es/logic/history/getBlock.d.ts +2 -1
- package/lib-es/logic/history/getBlock.d.ts.map +1 -1
- package/lib-es/logic/history/getBlock.js +3 -2
- package/lib-es/logic/history/getBlock.js.map +1 -1
- package/lib-es/logic/history/getBlockInfo.d.ts +2 -1
- package/lib-es/logic/history/getBlockInfo.d.ts.map +1 -1
- package/lib-es/logic/history/getBlockInfo.js +3 -2
- package/lib-es/logic/history/getBlockInfo.js.map +1 -1
- package/lib-es/logic/history/lastBlock.d.ts +2 -1
- package/lib-es/logic/history/lastBlock.d.ts.map +1 -1
- package/lib-es/logic/history/lastBlock.js +4 -3
- package/lib-es/logic/history/lastBlock.js.map +1 -1
- package/lib-es/logic/history/listOperations.d.ts +2 -1
- package/lib-es/logic/history/listOperations.d.ts.map +1 -1
- package/lib-es/logic/history/listOperations.js +5 -4
- package/lib-es/logic/history/listOperations.js.map +1 -1
- package/lib-es/logic/transaction/broadcast.d.ts +2 -1
- package/lib-es/logic/transaction/broadcast.d.ts.map +1 -1
- package/lib-es/logic/transaction/broadcast.js +3 -2
- package/lib-es/logic/transaction/broadcast.js.map +1 -1
- package/lib-es/logic/transaction/combine.d.ts +1 -1
- package/lib-es/logic/transaction/combine.d.ts.map +1 -1
- package/lib-es/logic/transaction/combine.js +4 -1
- package/lib-es/logic/transaction/combine.js.map +1 -1
- package/lib-es/logic/transaction/craftTransaction.d.ts +2 -1
- package/lib-es/logic/transaction/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/transaction/craftTransaction.js +11 -10
- package/lib-es/logic/transaction/craftTransaction.js.map +1 -1
- package/lib-es/logic/transaction/estimateFees.d.ts +2 -1
- package/lib-es/logic/transaction/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/transaction/estimateFees.js +2 -2
- package/lib-es/logic/transaction/estimateFees.js.map +1 -1
- package/lib-es/network/getBlock.d.ts +2 -1
- package/lib-es/network/getBlock.d.ts.map +1 -1
- package/lib-es/network/getBlock.js +2 -2
- package/lib-es/network/getBlock.js.map +1 -1
- package/lib-es/network/getFees.d.ts +2 -1
- package/lib-es/network/getFees.d.ts.map +1 -1
- package/lib-es/network/getFees.js +2 -2
- package/lib-es/network/getFees.js.map +1 -1
- package/lib-es/network/sdk.d.ts +7 -6
- package/lib-es/network/sdk.d.ts.map +1 -1
- package/lib-es/network/sdk.js +18 -18
- package/lib-es/network/sdk.js.map +1 -1
- package/lib-es/test/context.d.ts +6 -0
- package/lib-es/test/context.d.ts.map +1 -0
- package/lib-es/test/context.js +11 -0
- package/lib-es/test/context.js.map +1 -0
- package/package.json +6 -6
- package/src/api/index.integ.test.ts +21 -24
- package/src/api/index.ts +74 -27
- package/src/api/index.unit.test.ts +68 -48
- package/src/bridge/broadcast.integ.test.ts +2 -1
- package/src/bridge/broadcast.test.ts +1 -1
- package/src/bridge/broadcast.ts +2 -1
- package/src/bridge/createTransaction.ts +2 -2
- package/src/bridge/estimateMaxSpendable.test.ts +19 -3
- package/src/bridge/estimateMaxSpendable.ts +2 -0
- package/src/bridge/getTransactionStatus.test.ts +11 -6
- package/src/bridge/getTransactionStatus.ts +2 -0
- package/src/bridge/prepareTransaction.test.ts +5 -1
- package/src/bridge/prepareTransaction.ts +4 -2
- package/src/bridge/synchronisation.ts +7 -3
- package/src/common-logic/calculateGasFees.test.ts +101 -27
- package/src/common-logic/calculateGasFees.ts +4 -2
- package/src/common-logic/calculateTransactionInfo.test.ts +71 -16
- package/src/common-logic/calculateTransactionInfo.ts +3 -1
- package/src/common-logic/estimateGas.test.ts +13 -10
- package/src/common-logic/estimateGas.ts +3 -1
- package/src/common-logic/getThorClient.test.ts +7 -7
- package/src/common-logic/getThorClient.ts +3 -3
- package/src/common-logic/mapTransfersToOperations.test.ts +48 -13
- package/src/common-logic/mapTransfersToOperations.ts +5 -2
- package/src/config.test.ts +3 -3
- package/src/config.ts +13 -6
- package/src/logic/account/getBalance.ts +4 -2
- package/src/logic/account/getBalance.unit.test.ts +6 -4
- package/src/logic/history/getBlock.ts +4 -2
- package/src/logic/history/getBlock.unit.test.ts +8 -5
- package/src/logic/history/getBlockInfo.ts +4 -2
- package/src/logic/history/getBlockInfo.unit.test.ts +6 -3
- package/src/logic/history/lastBlock.ts +5 -3
- package/src/logic/history/lastBlock.unit.test.ts +6 -3
- package/src/logic/history/listOperations.ts +6 -3
- package/src/logic/history/listOperations.unit.test.ts +10 -6
- package/src/logic/transaction/broadcast.msw.test.ts +7 -2
- package/src/logic/transaction/broadcast.ts +8 -2
- package/src/logic/transaction/broadcast.unit.test.ts +6 -3
- package/src/logic/transaction/combine.ts +6 -2
- package/src/logic/transaction/combine.unit.test.ts +3 -3
- package/src/logic/transaction/craftTransaction.ts +16 -9
- package/src/logic/transaction/craftTransaction.unit.test.ts +29 -28
- package/src/logic/transaction/estimateFees.ts +3 -0
- package/src/logic/transaction/estimateFees.unit.test.ts +15 -12
- package/src/network/getBlock.ts +3 -2
- package/src/network/getBlock.unit.test.ts +11 -6
- package/src/network/getFees.ts +6 -3
- package/src/network/sdk.integ.test.ts +8 -1
- package/src/network/sdk.test.ts +18 -7
- package/src/network/sdk.ts +25 -15
- package/src/test/context.ts +15 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import BigNumber from "bignumber.js";
|
|
2
2
|
import type { Operation as LegacyOperation } from "@ledgerhq/types-live";
|
|
3
3
|
import { getLastBlockHeight, getOperations, getTokenOperations } from "../../network";
|
|
4
|
+
import { createMockVechainContext } from "../../test/context";
|
|
4
5
|
import { listOperations } from "./listOperations";
|
|
5
6
|
|
|
6
7
|
jest.mock("../../network", () => ({
|
|
@@ -10,6 +11,7 @@ jest.mock("../../network", () => ({
|
|
|
10
11
|
}));
|
|
11
12
|
|
|
12
13
|
const ADDRESS = "0x0fe6688548f0C303932bB197B0A96034f1d74dba";
|
|
14
|
+
const context = createMockVechainContext();
|
|
13
15
|
|
|
14
16
|
function makeLegacyOp(overrides: Partial<LegacyOperation> = {}): LegacyOperation {
|
|
15
17
|
return {
|
|
@@ -41,7 +43,7 @@ describe("listOperations", () => {
|
|
|
41
43
|
.mocked(getTokenOperations)
|
|
42
44
|
.mockResolvedValueOnce([makeLegacyOp({ id: "vtho-op", type: "IN" })]);
|
|
43
45
|
|
|
44
|
-
const page = await listOperations(ADDRESS, { minHeight: 0 });
|
|
46
|
+
const page = await listOperations(context, ADDRESS, { minHeight: 0 });
|
|
45
47
|
|
|
46
48
|
expect(page.items).toHaveLength(2);
|
|
47
49
|
expect(page.items.find(op => op.id === "vet-op")?.asset).toEqual({
|
|
@@ -67,7 +69,7 @@ describe("listOperations", () => {
|
|
|
67
69
|
makeLegacyOp({ id: "vtho-1", date: new Date("2024-01-02T00:00:00Z") }),
|
|
68
70
|
]);
|
|
69
71
|
|
|
70
|
-
const page = await listOperations(ADDRESS, { minHeight: 0 });
|
|
72
|
+
const page = await listOperations(context, ADDRESS, { minHeight: 0 });
|
|
71
73
|
|
|
72
74
|
const ids = page.items.map(op => op.id);
|
|
73
75
|
expect(ids).toHaveLength(3);
|
|
@@ -87,7 +89,7 @@ describe("listOperations", () => {
|
|
|
87
89
|
jest.mocked(getOperations).mockResolvedValueOnce([outOp]);
|
|
88
90
|
jest.mocked(getTokenOperations).mockResolvedValueOnce([]);
|
|
89
91
|
|
|
90
|
-
const page = await listOperations(ADDRESS, { minHeight: 0 });
|
|
92
|
+
const page = await listOperations(context, ADDRESS, { minHeight: 0 });
|
|
91
93
|
|
|
92
94
|
expect(page.items[0].value).toBe(BigInt("1000000000000000000"));
|
|
93
95
|
expect(page.items[0].tx.fees).toBe(0n);
|
|
@@ -103,7 +105,7 @@ describe("listOperations", () => {
|
|
|
103
105
|
jest.mocked(getOperations).mockResolvedValueOnce([]);
|
|
104
106
|
jest.mocked(getTokenOperations).mockResolvedValueOnce([vthoOut]);
|
|
105
107
|
|
|
106
|
-
const page = await listOperations(ADDRESS, { minHeight: 0 });
|
|
108
|
+
const page = await listOperations(context, ADDRESS, { minHeight: 0 });
|
|
107
109
|
|
|
108
110
|
expect(page.items[0].value).toBe(BigInt("5000000000000000000"));
|
|
109
111
|
expect(page.items[0].tx.fees).toBe(BigInt("21000000000000000"));
|
|
@@ -112,7 +114,7 @@ describe("listOperations", () => {
|
|
|
112
114
|
it("returns an empty page (with a resumable cursor) when the range is already exhausted", async () => {
|
|
113
115
|
jest.mocked(getLastBlockHeight).mockResolvedValueOnce(50);
|
|
114
116
|
|
|
115
|
-
const page = await listOperations(ADDRESS, { minHeight: 0, cursor: "100" });
|
|
117
|
+
const page = await listOperations(context, ADDRESS, { minHeight: 0, cursor: "100" });
|
|
116
118
|
|
|
117
119
|
expect(page.items).toEqual([]);
|
|
118
120
|
expect(getOperations).not.toHaveBeenCalled();
|
|
@@ -124,6 +126,8 @@ describe("listOperations", () => {
|
|
|
124
126
|
jest.mocked(getOperations).mockRejectedValueOnce(new Error("network down"));
|
|
125
127
|
jest.mocked(getTokenOperations).mockResolvedValueOnce([]);
|
|
126
128
|
|
|
127
|
-
await expect(listOperations(ADDRESS, { minHeight: 0 })).rejects.toThrow(
|
|
129
|
+
await expect(listOperations(context, ADDRESS, { minHeight: 0 })).rejects.toThrow(
|
|
130
|
+
"network down",
|
|
131
|
+
);
|
|
128
132
|
});
|
|
129
133
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { http, HttpResponse } from "msw";
|
|
2
2
|
import { server, TEST_VECHAIN_ENDPOINT } from "../../test/msw.mock";
|
|
3
|
+
import { createMockVechainContext } from "../../test/context";
|
|
3
4
|
import { broadcast } from "./broadcast";
|
|
4
5
|
|
|
5
6
|
const SUBMIT_URL = `${TEST_VECHAIN_ENDPOINT}/transactions`;
|
|
7
|
+
const context = createMockVechainContext({
|
|
8
|
+
status: { type: "active" },
|
|
9
|
+
node: { url: TEST_VECHAIN_ENDPOINT },
|
|
10
|
+
});
|
|
6
11
|
|
|
7
12
|
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
8
13
|
afterEach(() => server.resetHandlers());
|
|
@@ -12,12 +17,12 @@ describe("broadcast via MSW", () => {
|
|
|
12
17
|
it("submits the signed raw transaction and returns the transaction id", async () => {
|
|
13
18
|
server.use(http.post(SUBMIT_URL, () => HttpResponse.json({ id: "0xtxid" })));
|
|
14
19
|
|
|
15
|
-
expect(await broadcast("0xaabbcc")).toBe("0xtxid");
|
|
20
|
+
expect(await broadcast(context, "0xaabbcc")).toBe("0xtxid");
|
|
16
21
|
});
|
|
17
22
|
|
|
18
23
|
it("throws on an HTTP 200 whose body carries no transaction id (never swallows an error body)", async () => {
|
|
19
24
|
server.use(http.post(SUBMIT_URL, () => HttpResponse.json({})));
|
|
20
25
|
|
|
21
|
-
await expect(broadcast("0xaabbcc")).rejects.toThrow("Expected an ID to be returned");
|
|
26
|
+
await expect(broadcast(context, "0xaabbcc")).rejects.toThrow("Expected an ID to be returned");
|
|
22
27
|
});
|
|
23
28
|
});
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { BroadcastConfig } from "@ledgerhq/coin-module-framework/api/types";
|
|
2
|
+
import type { VechainContext } from "../../config";
|
|
2
3
|
import { submit } from "../../network";
|
|
3
4
|
import type { VechainSDKTransaction } from "../../types";
|
|
4
5
|
|
|
5
6
|
// Submit a signed tx (hex from combine) via Thor POST /transactions.
|
|
6
|
-
export async function broadcast(
|
|
7
|
+
export async function broadcast(
|
|
8
|
+
context: VechainContext,
|
|
9
|
+
tx: string,
|
|
10
|
+
_broadcastConfig?: BroadcastConfig,
|
|
11
|
+
): Promise<string> {
|
|
12
|
+
const config = await context.config();
|
|
7
13
|
const encoded = Uint8Array.from(Buffer.from(tx.replace(/^0x/, ""), "hex"));
|
|
8
14
|
|
|
9
|
-
return submit({ encoded } as VechainSDKTransaction);
|
|
15
|
+
return submit(config, { encoded } as VechainSDKTransaction);
|
|
10
16
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { submit } from "../../network";
|
|
2
|
+
import { createMockVechainContext } from "../../test/context";
|
|
2
3
|
import { broadcast } from "./broadcast";
|
|
3
4
|
|
|
4
5
|
jest.mock("../../network", () => ({ submit: jest.fn() }));
|
|
5
6
|
|
|
7
|
+
const context = createMockVechainContext();
|
|
8
|
+
|
|
6
9
|
describe("broadcast", () => {
|
|
7
10
|
afterEach(() => {
|
|
8
11
|
jest.clearAllMocks();
|
|
@@ -11,16 +14,16 @@ describe("broadcast", () => {
|
|
|
11
14
|
it("decodes the hex raw transaction and delegates to submit", async () => {
|
|
12
15
|
jest.mocked(submit).mockResolvedValueOnce("0xtxid");
|
|
13
16
|
|
|
14
|
-
const id = await broadcast("0xaabbcc");
|
|
17
|
+
const id = await broadcast(context, "0xaabbcc");
|
|
15
18
|
|
|
16
19
|
expect(id).toBe("0xtxid");
|
|
17
|
-
const [passed] = jest.mocked(submit).mock.calls[0];
|
|
20
|
+
const [, passed] = jest.mocked(submit).mock.calls[0];
|
|
18
21
|
expect(Array.from(passed.encoded as Uint8Array)).toEqual([0xaa, 0xbb, 0xcc]);
|
|
19
22
|
});
|
|
20
23
|
|
|
21
24
|
it("propagates an error from submit", async () => {
|
|
22
25
|
jest.mocked(submit).mockRejectedValueOnce(new Error("vechain: broadcast rejected"));
|
|
23
26
|
|
|
24
|
-
await expect(broadcast("0xaabbcc")).rejects.toThrow("vechain: broadcast rejected");
|
|
27
|
+
await expect(broadcast(context, "0xaabbcc")).rejects.toThrow("vechain: broadcast rejected");
|
|
25
28
|
});
|
|
26
29
|
});
|
|
@@ -2,9 +2,13 @@ import type { VechainSDKTransactionBody } from "../../types";
|
|
|
2
2
|
import { VechainSDKTransaction } from "../../types";
|
|
3
3
|
|
|
4
4
|
// Attach the signature to the crafted body and return the signed, hex-encoded tx for broadcast.
|
|
5
|
-
export function combine(tx: string, signature: string): string {
|
|
5
|
+
export function combine(tx: string, signature: string[]): string {
|
|
6
|
+
if (signature.length !== 1) {
|
|
7
|
+
throw new Error(`VeChain combine expects exactly one signature, got ${signature.length}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const body = JSON.parse(tx) as VechainSDKTransactionBody;
|
|
7
|
-
const signed = VechainSDKTransaction.of(body, Buffer.from(signature, "hex"));
|
|
11
|
+
const signed = VechainSDKTransaction.of(body, Buffer.from(signature[0], "hex"));
|
|
8
12
|
|
|
9
13
|
return `0x${Buffer.from(signed.encoded).toString("hex")}`;
|
|
10
14
|
}
|
|
@@ -17,7 +17,7 @@ describe("combine", () => {
|
|
|
17
17
|
it("returns a hex-encoded signed transaction", () => {
|
|
18
18
|
const signature = "aa".repeat(65);
|
|
19
19
|
|
|
20
|
-
const signed = combine(JSON.stringify(BODY), signature);
|
|
20
|
+
const signed = combine(JSON.stringify(BODY), [signature]);
|
|
21
21
|
|
|
22
22
|
expect(signed).toMatch(/^0x[0-9a-f]+$/);
|
|
23
23
|
});
|
|
@@ -25,13 +25,13 @@ describe("combine", () => {
|
|
|
25
25
|
it("produces a different (longer) payload than the unsigned body JSON, reflecting the signature", () => {
|
|
26
26
|
const signature = "bb".repeat(65);
|
|
27
27
|
|
|
28
|
-
const signed = combine(JSON.stringify(BODY), signature);
|
|
28
|
+
const signed = combine(JSON.stringify(BODY), [signature]);
|
|
29
29
|
|
|
30
30
|
expect(signed.length).toBeGreaterThan(0);
|
|
31
31
|
expect(signed).not.toBe(JSON.stringify(BODY));
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it("throws on a malformed transaction payload", () => {
|
|
35
|
-
expect(() => combine("not-json", "aa".repeat(65))).toThrow();
|
|
35
|
+
expect(() => combine("not-json", ["aa".repeat(65)])).toThrow();
|
|
36
36
|
});
|
|
37
37
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../../common-logic";
|
|
13
13
|
import { estimateGas } from "../../common-logic/estimateGas";
|
|
14
14
|
import { getThorClient } from "../../common-logic/getThorClient";
|
|
15
|
-
import { getChainTag } from "../../config";
|
|
15
|
+
import { getChainTag, type VechainContext, type VechainCurrencyConfig } from "../../config";
|
|
16
16
|
import { getAccount, getBlockRef } from "../../network";
|
|
17
17
|
import type { VechainSDKTransactionBody, VechainSDKTransactionClause } from "../../types";
|
|
18
18
|
|
|
@@ -28,16 +28,21 @@ function isTokenAsset(intent: TransactionIntent): boolean {
|
|
|
28
28
|
return intent.asset.type !== "native";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function resolveAmount(
|
|
31
|
+
async function resolveAmount(
|
|
32
|
+
config: VechainCurrencyConfig,
|
|
33
|
+
intent: TransactionIntent,
|
|
34
|
+
isToken: boolean,
|
|
35
|
+
): Promise<BigNumber> {
|
|
32
36
|
if (!intent.useAllAmount) {
|
|
33
37
|
return new BigNumber(intent.amount.toString());
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
const { balance, energy } = await getAccount(intent.sender);
|
|
40
|
+
const { balance, energy } = await getAccount(config, intent.sender);
|
|
37
41
|
return new BigNumber((isToken ? energy : balance) || "0");
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
async function resolveGasFees(
|
|
45
|
+
config: VechainCurrencyConfig,
|
|
41
46
|
clauses: VechainSDKTransactionClause[],
|
|
42
47
|
sender: string,
|
|
43
48
|
customFees?: FeeEstimation,
|
|
@@ -57,8 +62,8 @@ async function resolveGasFees(
|
|
|
57
62
|
};
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
const gasResult = await estimateGas(clauses, sender);
|
|
61
|
-
const thorClient = getThorClient();
|
|
65
|
+
const gasResult = await estimateGas(config, clauses, sender);
|
|
66
|
+
const thorClient = getThorClient(config);
|
|
62
67
|
const body = await thorClient.transactions.buildTransactionBody(clauses, gasResult.totalGas, {});
|
|
63
68
|
|
|
64
69
|
// Partial override: apply each provided field individually over the estimate, so e.g. a
|
|
@@ -75,6 +80,7 @@ async function resolveGasFees(
|
|
|
75
80
|
// Craft an unsigned single-clause Thor tx (VET transfer, or VIP-180 transfer for VTHO). The returned
|
|
76
81
|
// transaction is the JSON VechainSDKTransactionBody that combine() parses back — keep them in sync.
|
|
77
82
|
export async function craftTransaction(
|
|
83
|
+
context: VechainContext,
|
|
78
84
|
intent: TransactionIntent,
|
|
79
85
|
customFees?: FeeEstimation,
|
|
80
86
|
): Promise<CraftedTransaction> {
|
|
@@ -87,8 +93,9 @@ export async function craftTransaction(
|
|
|
87
93
|
throw new Error("vechain: invalid sender address");
|
|
88
94
|
}
|
|
89
95
|
|
|
96
|
+
const config = await context.config();
|
|
90
97
|
const isToken = isTokenAsset(intent);
|
|
91
|
-
let amount = await resolveAmount(intent, isToken);
|
|
98
|
+
let amount = await resolveAmount(config, intent, isToken);
|
|
92
99
|
if (amount.lte(0)) {
|
|
93
100
|
throw new Error("vechain: transaction amount must be positive");
|
|
94
101
|
}
|
|
@@ -98,8 +105,8 @@ export async function craftTransaction(
|
|
|
98
105
|
: await calculateClausesVet(intent.recipient, amount);
|
|
99
106
|
|
|
100
107
|
const [{ gas, maxFeePerGas, maxPriorityFeePerGas }, blockRef] = await Promise.all([
|
|
101
|
-
resolveGasFees(clauses, intent.sender, customFees),
|
|
102
|
-
getBlockRef(),
|
|
108
|
+
resolveGasFees(config, clauses, intent.sender, customFees),
|
|
109
|
+
getBlockRef(config),
|
|
103
110
|
]);
|
|
104
111
|
|
|
105
112
|
const fee = new BigNumber(maxFeePerGas).times(gas);
|
|
@@ -113,7 +120,7 @@ export async function craftTransaction(
|
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
const body: VechainSDKTransactionBody = {
|
|
116
|
-
chainTag: getChainTag(),
|
|
123
|
+
chainTag: getChainTag(config),
|
|
117
124
|
blockRef,
|
|
118
125
|
expiration: EXPIRATION,
|
|
119
126
|
clauses,
|
|
@@ -3,7 +3,8 @@ import type { TransactionIntent } from "@ledgerhq/coin-module-framework/api/inde
|
|
|
3
3
|
import { calculateClausesVet, calculateClausesVtho, parseAddress } from "../../common-logic";
|
|
4
4
|
import { estimateGas } from "../../common-logic/estimateGas";
|
|
5
5
|
import { getThorClient } from "../../common-logic/getThorClient";
|
|
6
|
-
import {
|
|
6
|
+
import type { VechainCurrencyConfig } from "../../config";
|
|
7
|
+
import { createMockVechainContext } from "../../test/context";
|
|
7
8
|
import { getAccount, getBlockRef } from "../../network";
|
|
8
9
|
import { craftTransaction } from "./craftTransaction";
|
|
9
10
|
|
|
@@ -44,7 +45,14 @@ function mockBuildTransactionBody() {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const DEFAULT_CONFIG: VechainCurrencyConfig = {
|
|
49
|
+
status: { type: "active" },
|
|
50
|
+
node: { url: "https://vechain.coin.ledger.com" },
|
|
51
|
+
};
|
|
52
|
+
|
|
47
53
|
describe("craftTransaction", () => {
|
|
54
|
+
let context = createMockVechainContext(DEFAULT_CONFIG);
|
|
55
|
+
|
|
48
56
|
beforeEach(() => {
|
|
49
57
|
jest
|
|
50
58
|
.mocked(calculateClausesVet)
|
|
@@ -55,10 +63,7 @@ describe("craftTransaction", () => {
|
|
|
55
63
|
jest.mocked(estimateGas).mockResolvedValue({ totalGas: 21000 } as never);
|
|
56
64
|
jest.mocked(getThorClient).mockReturnValue(mockBuildTransactionBody() as never);
|
|
57
65
|
jest.mocked(getBlockRef).mockResolvedValue("0xblockref12345678");
|
|
58
|
-
|
|
59
|
-
status: { type: "active" },
|
|
60
|
-
node: { url: "https://vechain.coin.ledger.com" },
|
|
61
|
-
}));
|
|
66
|
+
context = createMockVechainContext(DEFAULT_CONFIG);
|
|
62
67
|
});
|
|
63
68
|
|
|
64
69
|
afterEach(() => {
|
|
@@ -66,7 +71,7 @@ describe("craftTransaction", () => {
|
|
|
66
71
|
});
|
|
67
72
|
|
|
68
73
|
it("crafts a native VET transaction using calculateClausesVet", async () => {
|
|
69
|
-
const crafted = await craftTransaction(NATIVE_INTENT);
|
|
74
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT);
|
|
70
75
|
|
|
71
76
|
expect(calculateClausesVet).toHaveBeenCalled();
|
|
72
77
|
expect(calculateClausesVtho).not.toHaveBeenCalled();
|
|
@@ -78,27 +83,23 @@ describe("craftTransaction", () => {
|
|
|
78
83
|
});
|
|
79
84
|
|
|
80
85
|
it("defaults chainTag to mainnet (74) when the config has no chainTag", async () => {
|
|
81
|
-
const crafted = await craftTransaction(NATIVE_INTENT);
|
|
86
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT);
|
|
82
87
|
|
|
83
88
|
const body = JSON.parse(crafted.transaction);
|
|
84
89
|
expect(body.chainTag).toBe(74);
|
|
85
90
|
});
|
|
86
91
|
|
|
87
92
|
it("uses the config-provided chainTag when set", async () => {
|
|
88
|
-
|
|
89
|
-
status: { type: "active" },
|
|
90
|
-
node: { url: "https://vechain.coin.ledger.com" },
|
|
91
|
-
chainTag: 39,
|
|
92
|
-
}));
|
|
93
|
+
context = createMockVechainContext({ ...DEFAULT_CONFIG, chainTag: 39 });
|
|
93
94
|
|
|
94
|
-
const crafted = await craftTransaction(NATIVE_INTENT);
|
|
95
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT);
|
|
95
96
|
|
|
96
97
|
const body = JSON.parse(crafted.transaction);
|
|
97
98
|
expect(body.chainTag).toBe(39);
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
it("crafts a VTHO transaction using calculateClausesVtho", async () => {
|
|
101
|
-
const crafted = await craftTransaction(TOKEN_INTENT);
|
|
102
|
+
const crafted = await craftTransaction(context, TOKEN_INTENT);
|
|
102
103
|
|
|
103
104
|
expect(calculateClausesVtho).toHaveBeenCalled();
|
|
104
105
|
expect(calculateClausesVet).not.toHaveBeenCalled();
|
|
@@ -106,7 +107,7 @@ describe("craftTransaction", () => {
|
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
it("computes the fee as maxFeePerGas * gas", async () => {
|
|
109
|
-
const crafted = await craftTransaction(NATIVE_INTENT);
|
|
110
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT);
|
|
110
111
|
|
|
111
112
|
expect(crafted.details?.fee).toBe((1000 * 21000).toString());
|
|
112
113
|
});
|
|
@@ -118,14 +119,14 @@ describe("craftTransaction", () => {
|
|
|
118
119
|
hasCode: false,
|
|
119
120
|
});
|
|
120
121
|
|
|
121
|
-
await craftTransaction({ ...NATIVE_INTENT, useAllAmount: true, amount: 0n });
|
|
122
|
+
await craftTransaction(context, { ...NATIVE_INTENT, useAllAmount: true, amount: 0n });
|
|
122
123
|
|
|
123
|
-
expect(getAccount).toHaveBeenCalledWith(SENDER);
|
|
124
|
+
expect(getAccount).toHaveBeenCalledWith(DEFAULT_CONFIG, SENDER);
|
|
124
125
|
expect(calculateClausesVet).toHaveBeenCalledWith(RECIPIENT, expect.any(Object));
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it("uses custom fee parameters instead of re-estimating gas when provided", async () => {
|
|
128
|
-
await craftTransaction(NATIVE_INTENT, {
|
|
129
|
+
await craftTransaction(context, NATIVE_INTENT, {
|
|
129
130
|
value: 5000n,
|
|
130
131
|
parameters: { gas: 30000, maxFeePerGas: 50, maxPriorityFeePerGas: 5 },
|
|
131
132
|
});
|
|
@@ -134,7 +135,7 @@ describe("craftTransaction", () => {
|
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
it("applies a partial fee override (gas only) over the estimate instead of discarding it", async () => {
|
|
137
|
-
const crafted = await craftTransaction(NATIVE_INTENT, {
|
|
138
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT, {
|
|
138
139
|
value: 0n,
|
|
139
140
|
parameters: { gas: 30000 },
|
|
140
141
|
});
|
|
@@ -149,7 +150,7 @@ describe("craftTransaction", () => {
|
|
|
149
150
|
it("coerces bigint fee parameters to numbers so the body stays JSON-serializable", async () => {
|
|
150
151
|
// The generic coin-framework hands fee parameters back as bigint; JSON.stringify cannot
|
|
151
152
|
// serialize a bigint, so craftTransaction must coerce them to numbers before building the body.
|
|
152
|
-
const crafted = await craftTransaction(NATIVE_INTENT, {
|
|
153
|
+
const crafted = await craftTransaction(context, NATIVE_INTENT, {
|
|
153
154
|
value: 5000n,
|
|
154
155
|
parameters: { gas: 30000n, maxFeePerGas: 50n, maxPriorityFeePerGas: 5n },
|
|
155
156
|
} as never);
|
|
@@ -162,7 +163,7 @@ describe("craftTransaction", () => {
|
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
it("throws when the recipient is missing", async () => {
|
|
165
|
-
await expect(craftTransaction({ ...NATIVE_INTENT, recipient: "" })).rejects.toThrow(
|
|
166
|
+
await expect(craftTransaction(context, { ...NATIVE_INTENT, recipient: "" })).rejects.toThrow(
|
|
166
167
|
"vechain: recipient is required",
|
|
167
168
|
);
|
|
168
169
|
});
|
|
@@ -170,13 +171,13 @@ describe("craftTransaction", () => {
|
|
|
170
171
|
it("throws when the sender is not a valid address", async () => {
|
|
171
172
|
jest.mocked(parseAddress).mockReturnValueOnce(false);
|
|
172
173
|
|
|
173
|
-
await expect(craftTransaction({ ...NATIVE_INTENT, sender: "0xbad" })).rejects.toThrow(
|
|
174
|
+
await expect(craftTransaction(context, { ...NATIVE_INTENT, sender: "0xbad" })).rejects.toThrow(
|
|
174
175
|
"vechain: invalid sender address",
|
|
175
176
|
);
|
|
176
177
|
});
|
|
177
178
|
|
|
178
179
|
it("throws when the resolved amount is not positive", async () => {
|
|
179
|
-
await expect(craftTransaction({ ...NATIVE_INTENT, amount: 0n })).rejects.toThrow(
|
|
180
|
+
await expect(craftTransaction(context, { ...NATIVE_INTENT, amount: 0n })).rejects.toThrow(
|
|
180
181
|
"vechain: transaction amount must be positive",
|
|
181
182
|
);
|
|
182
183
|
});
|
|
@@ -186,7 +187,7 @@ describe("craftTransaction", () => {
|
|
|
186
187
|
.mocked(getAccount)
|
|
187
188
|
.mockResolvedValueOnce({ balance: "0x0", energy: "0x5F5E100", hasCode: false });
|
|
188
189
|
|
|
189
|
-
await craftTransaction({ ...TOKEN_INTENT, useAllAmount: true, amount: 0n });
|
|
190
|
+
await craftTransaction(context, { ...TOKEN_INTENT, useAllAmount: true, amount: 0n });
|
|
190
191
|
|
|
191
192
|
const calls = jest.mocked(calculateClausesVtho).mock.calls;
|
|
192
193
|
expect(calls).toHaveLength(2);
|
|
@@ -199,12 +200,12 @@ describe("craftTransaction", () => {
|
|
|
199
200
|
.mockResolvedValueOnce({ balance: "0x0", energy: "0x3E8", hasCode: false });
|
|
200
201
|
|
|
201
202
|
await expect(
|
|
202
|
-
craftTransaction({ ...TOKEN_INTENT, useAllAmount: true, amount: 0n }),
|
|
203
|
+
craftTransaction(context, { ...TOKEN_INTENT, useAllAmount: true, amount: 0n }),
|
|
203
204
|
).rejects.toThrow("vechain: VTHO balance too low to cover the transaction gas fee");
|
|
204
205
|
});
|
|
205
206
|
|
|
206
207
|
it("crafts a dust native amount (1 wei)", async () => {
|
|
207
|
-
const crafted = await craftTransaction({ ...NATIVE_INTENT, amount: 1n });
|
|
208
|
+
const crafted = await craftTransaction(context, { ...NATIVE_INTENT, amount: 1n });
|
|
208
209
|
|
|
209
210
|
expect(calculateClausesVet).toHaveBeenCalledWith(RECIPIENT, expect.any(Object));
|
|
210
211
|
expect(JSON.parse(crafted.transaction).gas).toBe(21000);
|
|
@@ -215,7 +216,7 @@ describe("craftTransaction", () => {
|
|
|
215
216
|
.mocked(getAccount)
|
|
216
217
|
.mockResolvedValueOnce({ balance: "0x0", energy: "21000001", hasCode: false });
|
|
217
218
|
|
|
218
|
-
await craftTransaction({ ...TOKEN_INTENT, useAllAmount: true, amount: 0n });
|
|
219
|
+
await craftTransaction(context, { ...TOKEN_INTENT, useAllAmount: true, amount: 0n });
|
|
219
220
|
|
|
220
221
|
const calls = jest.mocked(calculateClausesVtho).mock.calls;
|
|
221
222
|
expect(calls[calls.length - 1][1].toString()).toBe("1");
|
|
@@ -227,7 +228,7 @@ describe("craftTransaction", () => {
|
|
|
227
228
|
.mockResolvedValueOnce({ balance: "0x0", energy: "21000000", hasCode: false });
|
|
228
229
|
|
|
229
230
|
await expect(
|
|
230
|
-
craftTransaction({ ...TOKEN_INTENT, useAllAmount: true, amount: 0n }),
|
|
231
|
+
craftTransaction(context, { ...TOKEN_INTENT, useAllAmount: true, amount: 0n }),
|
|
231
232
|
).rejects.toThrow("vechain: VTHO balance too low to cover the transaction gas fee");
|
|
232
233
|
});
|
|
233
234
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FeeEstimation, TransactionIntent } from "@ledgerhq/coin-module-framework/api/index";
|
|
2
2
|
import { parseAddress } from "../../common-logic";
|
|
3
|
+
import type { VechainContext } from "../../config";
|
|
3
4
|
import { craftTransaction } from "./craftTransaction";
|
|
4
5
|
|
|
5
6
|
// Estimate VTHO gas by crafting the unsigned tx and reading back its settled fee/gas parameters.
|
|
6
7
|
export async function estimateFees(
|
|
8
|
+
context: VechainContext,
|
|
7
9
|
intent: TransactionIntent,
|
|
8
10
|
customFeesParameters?: FeeEstimation["parameters"],
|
|
9
11
|
): Promise<FeeEstimation> {
|
|
@@ -15,6 +17,7 @@ export async function estimateFees(
|
|
|
15
17
|
!intent.useAllAmount && intent.amount <= 0n ? { ...intent, amount: 1n } : intent;
|
|
16
18
|
|
|
17
19
|
const { details } = await craftTransaction(
|
|
20
|
+
context,
|
|
18
21
|
estimationIntent,
|
|
19
22
|
customFeesParameters ? { value: 0n, parameters: customFeesParameters } : undefined,
|
|
20
23
|
);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { TransactionIntent } from "@ledgerhq/coin-module-framework/api/index";
|
|
2
|
+
import { createMockVechainContext } from "../../test/context";
|
|
2
3
|
import { craftTransaction } from "./craftTransaction";
|
|
3
4
|
import { estimateFees } from "./estimateFees";
|
|
4
5
|
|
|
5
6
|
jest.mock("./craftTransaction", () => ({ craftTransaction: jest.fn() }));
|
|
6
7
|
|
|
8
|
+
const context = createMockVechainContext();
|
|
9
|
+
|
|
7
10
|
const INTENT: TransactionIntent = {
|
|
8
11
|
intentType: "transaction",
|
|
9
12
|
type: "send",
|
|
@@ -24,9 +27,9 @@ describe("estimateFees", () => {
|
|
|
24
27
|
details: { fee: "21000000", gas: 21000, maxFeePerGas: 1000, maxPriorityFeePerGas: 100 },
|
|
25
28
|
});
|
|
26
29
|
|
|
27
|
-
const fees = await estimateFees(INTENT);
|
|
30
|
+
const fees = await estimateFees(context, INTENT);
|
|
28
31
|
|
|
29
|
-
expect(craftTransaction).toHaveBeenCalledWith(INTENT, undefined);
|
|
32
|
+
expect(craftTransaction).toHaveBeenCalledWith(context, INTENT, undefined);
|
|
30
33
|
expect(fees).toEqual({
|
|
31
34
|
value: 21000000n,
|
|
32
35
|
parameters: { gas: 21000, maxFeePerGas: 1000, maxPriorityFeePerGas: 100 },
|
|
@@ -39,9 +42,9 @@ describe("estimateFees", () => {
|
|
|
39
42
|
details: { fee: "1", gas: 1, maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
|
|
40
43
|
});
|
|
41
44
|
|
|
42
|
-
await estimateFees(INTENT, { gas: 30000 });
|
|
45
|
+
await estimateFees(context, INTENT, { gas: 30000 });
|
|
43
46
|
|
|
44
|
-
expect(craftTransaction).toHaveBeenCalledWith(INTENT, {
|
|
47
|
+
expect(craftTransaction).toHaveBeenCalledWith(context, INTENT, {
|
|
45
48
|
value: 0n,
|
|
46
49
|
parameters: { gas: 30000 },
|
|
47
50
|
});
|
|
@@ -50,7 +53,7 @@ describe("estimateFees", () => {
|
|
|
50
53
|
it("defaults the fee to 0 when craftTransaction returns no details", async () => {
|
|
51
54
|
jest.mocked(craftTransaction).mockResolvedValueOnce({ transaction: "{}" });
|
|
52
55
|
|
|
53
|
-
const fees = await estimateFees(INTENT);
|
|
56
|
+
const fees = await estimateFees(context, INTENT);
|
|
54
57
|
|
|
55
58
|
expect(fees.value).toBe(0n);
|
|
56
59
|
});
|
|
@@ -60,7 +63,7 @@ describe("estimateFees", () => {
|
|
|
60
63
|
.mocked(craftTransaction)
|
|
61
64
|
.mockRejectedValueOnce(new Error("vechain: recipient is required"));
|
|
62
65
|
|
|
63
|
-
await expect(estimateFees(INTENT)).rejects.toThrow("vechain: recipient is required");
|
|
66
|
+
await expect(estimateFees(context, INTENT)).rejects.toThrow("vechain: recipient is required");
|
|
64
67
|
});
|
|
65
68
|
|
|
66
69
|
it("substitutes a 1-wei placeholder amount for a zero-amount pre-estimation", async () => {
|
|
@@ -69,9 +72,9 @@ describe("estimateFees", () => {
|
|
|
69
72
|
details: { fee: "21000000", gas: 21000, maxFeePerGas: 1000, maxPriorityFeePerGas: 100 },
|
|
70
73
|
});
|
|
71
74
|
|
|
72
|
-
await estimateFees({ ...INTENT, amount: 0n });
|
|
75
|
+
await estimateFees(context, { ...INTENT, amount: 0n });
|
|
73
76
|
|
|
74
|
-
expect(craftTransaction).toHaveBeenCalledWith({ ...INTENT, amount: 1n }, undefined);
|
|
77
|
+
expect(craftTransaction).toHaveBeenCalledWith(context, { ...INTENT, amount: 1n }, undefined);
|
|
75
78
|
});
|
|
76
79
|
|
|
77
80
|
it("does not substitute a placeholder when useAllAmount is set (craftTransaction resolves the balance)", async () => {
|
|
@@ -81,20 +84,20 @@ describe("estimateFees", () => {
|
|
|
81
84
|
});
|
|
82
85
|
|
|
83
86
|
const maxIntent = { ...INTENT, amount: 0n, useAllAmount: true };
|
|
84
|
-
await estimateFees(maxIntent);
|
|
87
|
+
await estimateFees(context, maxIntent);
|
|
85
88
|
|
|
86
|
-
expect(craftTransaction).toHaveBeenCalledWith(maxIntent, undefined);
|
|
89
|
+
expect(craftTransaction).toHaveBeenCalledWith(context, maxIntent, undefined);
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
it("returns a zero estimate without crafting when the recipient is missing", async () => {
|
|
90
|
-
const fees = await estimateFees({ ...INTENT, recipient: "" });
|
|
93
|
+
const fees = await estimateFees(context, { ...INTENT, recipient: "" });
|
|
91
94
|
|
|
92
95
|
expect(fees).toEqual({ value: 0n, parameters: {} });
|
|
93
96
|
expect(craftTransaction).not.toHaveBeenCalled();
|
|
94
97
|
});
|
|
95
98
|
|
|
96
99
|
it("returns a zero estimate without crafting when the recipient is not a valid address", async () => {
|
|
97
|
-
const fees = await estimateFees({ ...INTENT, recipient: "0xnot-an-address" });
|
|
100
|
+
const fees = await estimateFees(context, { ...INTENT, recipient: "0xnot-an-address" });
|
|
98
101
|
|
|
99
102
|
expect(fees).toEqual({ value: 0n, parameters: {} });
|
|
100
103
|
expect(craftTransaction).not.toHaveBeenCalled();
|
package/src/network/getBlock.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import network from "@ledgerhq/live-network";
|
|
2
|
-
import { getNodeUrl } from "../config";
|
|
2
|
+
import { getNodeUrl, type VechainCurrencyConfig } from "../config";
|
|
3
3
|
import type { ApiResponseBlock } from "../types";
|
|
4
4
|
|
|
5
5
|
// Thor GET /blocks/{revision}. `expanded` returns full clause/output detail (getBlock) vs. tx
|
|
6
6
|
// hashes only (getBlockInfo/lastBlock).
|
|
7
7
|
export const getBlock = async (
|
|
8
|
+
config: VechainCurrencyConfig,
|
|
8
9
|
revision: number,
|
|
9
10
|
expanded = false,
|
|
10
11
|
): Promise<ApiResponseBlock | null> => {
|
|
@@ -16,7 +17,7 @@ export const getBlock = async (
|
|
|
16
17
|
|
|
17
18
|
const { data } = await network<ApiResponseBlock | null>({
|
|
18
19
|
method: "GET",
|
|
19
|
-
url: `${getNodeUrl()}/blocks/${revision}?${query}`,
|
|
20
|
+
url: `${getNodeUrl(config)}/blocks/${revision}?${query}`,
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
return data;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import network from "@ledgerhq/live-network";
|
|
2
2
|
import { getBlock } from "./getBlock";
|
|
3
|
+
import { mockVechainConfig } from "../test/context";
|
|
3
4
|
|
|
4
5
|
jest.mock("@ledgerhq/live-network", () => jest.fn());
|
|
5
6
|
|
|
@@ -12,7 +13,7 @@ describe("getBlock", () => {
|
|
|
12
13
|
const block = { id: "0xabc", number: 42, timestamp: 1_700_000_000, transactions: [] };
|
|
13
14
|
jest.mocked(network).mockResolvedValueOnce({ data: block, status: 200 });
|
|
14
15
|
|
|
15
|
-
const result = await getBlock(42);
|
|
16
|
+
const result = await getBlock(mockVechainConfig, 42, undefined);
|
|
16
17
|
|
|
17
18
|
expect(result).toEqual(block);
|
|
18
19
|
});
|
|
@@ -20,7 +21,7 @@ describe("getBlock", () => {
|
|
|
20
21
|
it("requests expanded=false by default", async () => {
|
|
21
22
|
jest.mocked(network).mockResolvedValueOnce({ data: null, status: 200 });
|
|
22
23
|
|
|
23
|
-
await getBlock(42);
|
|
24
|
+
await getBlock(mockVechainConfig, 42, undefined);
|
|
24
25
|
|
|
25
26
|
expect(network).toHaveBeenCalledWith(
|
|
26
27
|
expect.objectContaining({
|
|
@@ -33,7 +34,7 @@ describe("getBlock", () => {
|
|
|
33
34
|
it("requests expanded=true when asked", async () => {
|
|
34
35
|
jest.mocked(network).mockResolvedValueOnce({ data: null, status: 200 });
|
|
35
36
|
|
|
36
|
-
await getBlock(42, true);
|
|
37
|
+
await getBlock(mockVechainConfig, 42, true);
|
|
37
38
|
|
|
38
39
|
expect(network).toHaveBeenCalledWith(
|
|
39
40
|
expect.objectContaining({ url: expect.stringContaining("expanded=true") }),
|
|
@@ -41,18 +42,22 @@ describe("getBlock", () => {
|
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
it("rejects a negative revision without making a network call", async () => {
|
|
44
|
-
await expect(getBlock(-1)).rejects.toThrow(
|
|
45
|
+
await expect(getBlock(mockVechainConfig, -1, undefined)).rejects.toThrow(
|
|
46
|
+
"vechain: getBlock: invalid revision -1",
|
|
47
|
+
);
|
|
45
48
|
expect(network).not.toHaveBeenCalled();
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
it("rejects a non-integer revision without making a network call", async () => {
|
|
49
|
-
await expect(getBlock(1.5)).rejects.toThrow(
|
|
52
|
+
await expect(getBlock(mockVechainConfig, 1.5, undefined)).rejects.toThrow(
|
|
53
|
+
"vechain: getBlock: invalid revision 1.5",
|
|
54
|
+
);
|
|
50
55
|
expect(network).not.toHaveBeenCalled();
|
|
51
56
|
});
|
|
52
57
|
|
|
53
58
|
it("propagates a network error", async () => {
|
|
54
59
|
jest.mocked(network).mockRejectedValueOnce(new Error("status 500"));
|
|
55
60
|
|
|
56
|
-
await expect(getBlock(42)).rejects.toThrow("status 500");
|
|
61
|
+
await expect(getBlock(mockVechainConfig, 42, undefined)).rejects.toThrow("status 500");
|
|
57
62
|
});
|
|
58
63
|
});
|