@ledgerhq/coin-vechain 2.27.0 → 2.28.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 +3 -21
- package/CHANGELOG.md +34 -0
- package/jest.config.js +39 -13
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.d.ts.map +1 -0
- package/lib/api/index.js +45 -0
- package/lib/api/index.js.map +1 -0
- package/lib/bridge/getTransactionStatus.js +1 -1
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/logic/account/getBalance.d.ts +5 -0
- package/lib/logic/account/getBalance.d.ts.map +1 -0
- package/lib/logic/account/getBalance.js +25 -0
- package/lib/logic/account/getBalance.js.map +1 -0
- package/lib/logic/history/getBlock.d.ts +3 -0
- package/lib/logic/history/getBlock.d.ts.map +1 -0
- package/lib/logic/history/getBlock.js +35 -0
- package/lib/logic/history/getBlock.js.map +1 -0
- package/lib/logic/history/getBlockInfo.d.ts +6 -0
- package/lib/logic/history/getBlockInfo.d.ts.map +1 -0
- package/lib/logic/history/getBlockInfo.js +23 -0
- package/lib/logic/history/getBlockInfo.js.map +1 -0
- package/lib/logic/history/lastBlock.d.ts +3 -0
- package/lib/logic/history/lastBlock.d.ts.map +1 -0
- package/lib/logic/history/lastBlock.js +11 -0
- package/lib/logic/history/lastBlock.js.map +1 -0
- package/lib/logic/history/listOperations.d.ts +3 -0
- package/lib/logic/history/listOperations.d.ts.map +1 -0
- package/lib/logic/history/listOperations.js +50 -0
- package/lib/logic/history/listOperations.js.map +1 -0
- package/lib/logic/index.d.ts +12 -0
- package/lib/logic/index.d.ts.map +1 -0
- package/lib/logic/index.js +28 -0
- package/lib/logic/index.js.map +1 -0
- package/lib/logic/transaction/broadcast.d.ts +3 -0
- package/lib/logic/transaction/broadcast.d.ts.map +1 -0
- package/lib/logic/transaction/broadcast.js +10 -0
- package/lib/logic/transaction/broadcast.js.map +1 -0
- package/lib/logic/transaction/combine.d.ts +2 -0
- package/lib/logic/transaction/combine.d.ts.map +1 -0
- package/lib/logic/transaction/combine.js +11 -0
- package/lib/logic/transaction/combine.js.map +1 -0
- package/lib/logic/transaction/craftTransaction.d.ts +3 -0
- package/lib/logic/transaction/craftTransaction.d.ts.map +1 -0
- package/lib/logic/transaction/craftTransaction.js +99 -0
- package/lib/logic/transaction/craftTransaction.js.map +1 -0
- package/lib/logic/transaction/estimateFees.d.ts +3 -0
- package/lib/logic/transaction/estimateFees.d.ts.map +1 -0
- package/lib/logic/transaction/estimateFees.js +23 -0
- package/lib/logic/transaction/estimateFees.js.map +1 -0
- package/lib/logic/validateAddress.d.ts +4 -0
- package/lib/logic/validateAddress.d.ts.map +1 -0
- package/lib/logic/validateAddress.js +14 -0
- package/lib/logic/validateAddress.js.map +1 -0
- package/lib/logic/validateIntent.d.ts +3 -0
- package/lib/logic/validateIntent.d.ts.map +1 -0
- package/lib/logic/validateIntent.js +65 -0
- package/lib/logic/validateIntent.js.map +1 -0
- package/lib/network/getBlock.d.ts +3 -0
- package/lib/network/getBlock.d.ts.map +1 -0
- package/lib/network/getBlock.js +23 -0
- package/lib/network/getBlock.js.map +1 -0
- package/lib/network/getFees.d.ts.map +1 -1
- package/lib/network/getFees.js +2 -3
- package/lib/network/getFees.js.map +1 -1
- package/lib/network/index.d.ts +1 -0
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +1 -0
- package/lib/network/index.js.map +1 -1
- package/lib/network/sdk.d.ts.map +1 -1
- package/lib/network/sdk.js +14 -8
- package/lib/network/sdk.js.map +1 -1
- package/lib/test/bridgeDatasetTest.js +1 -1
- package/lib/test/bridgeDatasetTest.js.map +1 -1
- package/lib/test/msw-setup.d.ts +2 -0
- package/lib/test/msw-setup.d.ts.map +1 -0
- package/lib/test/msw-setup.js +9 -0
- package/lib/test/msw-setup.js.map +1 -0
- package/lib/test/msw.mock.d.ts +3 -0
- package/lib/test/msw.mock.d.ts.map +1 -0
- package/lib/test/msw.mock.js +9 -0
- package/lib/test/msw.mock.js.map +1 -0
- package/lib/types/network.d.ts +30 -0
- package/lib/types/network.d.ts.map +1 -1
- package/lib-es/api/index.d.ts +4 -0
- package/lib-es/api/index.d.ts.map +1 -0
- package/lib-es/api/index.js +42 -0
- package/lib-es/api/index.js.map +1 -0
- package/lib-es/bridge/getTransactionStatus.js +1 -1
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/logic/account/getBalance.d.ts +5 -0
- package/lib-es/logic/account/getBalance.d.ts.map +1 -0
- package/lib-es/logic/account/getBalance.js +20 -0
- package/lib-es/logic/account/getBalance.js.map +1 -0
- package/lib-es/logic/history/getBlock.d.ts +3 -0
- package/lib-es/logic/history/getBlock.d.ts.map +1 -0
- package/lib-es/logic/history/getBlock.js +32 -0
- package/lib-es/logic/history/getBlock.js.map +1 -0
- package/lib-es/logic/history/getBlockInfo.d.ts +6 -0
- package/lib-es/logic/history/getBlockInfo.d.ts.map +1 -0
- package/lib-es/logic/history/getBlockInfo.js +19 -0
- package/lib-es/logic/history/getBlockInfo.js.map +1 -0
- package/lib-es/logic/history/lastBlock.d.ts +3 -0
- package/lib-es/logic/history/lastBlock.d.ts.map +1 -0
- package/lib-es/logic/history/lastBlock.js +8 -0
- package/lib-es/logic/history/lastBlock.js.map +1 -0
- package/lib-es/logic/history/listOperations.d.ts +3 -0
- package/lib-es/logic/history/listOperations.d.ts.map +1 -0
- package/lib-es/logic/history/listOperations.js +47 -0
- package/lib-es/logic/history/listOperations.js.map +1 -0
- package/lib-es/logic/index.d.ts +12 -0
- package/lib-es/logic/index.d.ts.map +1 -0
- package/lib-es/logic/index.js +12 -0
- package/lib-es/logic/index.js.map +1 -0
- package/lib-es/logic/transaction/broadcast.d.ts +3 -0
- package/lib-es/logic/transaction/broadcast.d.ts.map +1 -0
- package/lib-es/logic/transaction/broadcast.js +7 -0
- package/lib-es/logic/transaction/broadcast.js.map +1 -0
- package/lib-es/logic/transaction/combine.d.ts +2 -0
- package/lib-es/logic/transaction/combine.d.ts.map +1 -0
- package/lib-es/logic/transaction/combine.js +8 -0
- package/lib-es/logic/transaction/combine.js.map +1 -0
- package/lib-es/logic/transaction/craftTransaction.d.ts +3 -0
- package/lib-es/logic/transaction/craftTransaction.d.ts.map +1 -0
- package/lib-es/logic/transaction/craftTransaction.js +96 -0
- package/lib-es/logic/transaction/craftTransaction.js.map +1 -0
- package/lib-es/logic/transaction/estimateFees.d.ts +3 -0
- package/lib-es/logic/transaction/estimateFees.d.ts.map +1 -0
- package/lib-es/logic/transaction/estimateFees.js +20 -0
- package/lib-es/logic/transaction/estimateFees.js.map +1 -0
- package/lib-es/logic/validateAddress.d.ts +4 -0
- package/lib-es/logic/validateAddress.d.ts.map +1 -0
- package/lib-es/logic/validateAddress.js +10 -0
- package/lib-es/logic/validateAddress.js.map +1 -0
- package/lib-es/logic/validateIntent.d.ts +3 -0
- package/lib-es/logic/validateIntent.d.ts.map +1 -0
- package/lib-es/logic/validateIntent.js +62 -0
- package/lib-es/logic/validateIntent.js.map +1 -0
- package/lib-es/network/getBlock.d.ts +3 -0
- package/lib-es/network/getBlock.d.ts.map +1 -0
- package/lib-es/network/getBlock.js +16 -0
- package/lib-es/network/getBlock.js.map +1 -0
- package/lib-es/network/getFees.d.ts.map +1 -1
- package/lib-es/network/getFees.js +1 -2
- package/lib-es/network/getFees.js.map +1 -1
- package/lib-es/network/index.d.ts +1 -0
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +1 -0
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/sdk.d.ts.map +1 -1
- package/lib-es/network/sdk.js +8 -2
- package/lib-es/network/sdk.js.map +1 -1
- package/lib-es/test/bridgeDatasetTest.js +1 -1
- package/lib-es/test/bridgeDatasetTest.js.map +1 -1
- package/lib-es/test/msw-setup.d.ts +2 -0
- package/lib-es/test/msw-setup.d.ts.map +1 -0
- package/lib-es/test/msw-setup.js +7 -0
- package/lib-es/test/msw-setup.js.map +1 -0
- package/lib-es/test/msw.mock.d.ts +3 -0
- package/lib-es/test/msw.mock.d.ts.map +1 -0
- package/lib-es/test/msw.mock.js +6 -0
- package/lib-es/test/msw.mock.js.map +1 -0
- package/lib-es/types/network.d.ts +30 -0
- package/lib-es/types/network.d.ts.map +1 -1
- package/package.json +7 -8
- package/src/api/index.integ.test.ts +123 -0
- package/src/api/index.ts +89 -0
- package/src/api/index.unit.test.ts +234 -0
- package/src/bridge/getTransactionStatus.test.ts +1 -1
- package/src/bridge/getTransactionStatus.ts +1 -1
- package/src/bridge/synchronisation.test.ts +7 -3
- package/src/logic/account/getBalance.ts +24 -0
- package/src/logic/account/getBalance.unit.test.ts +50 -0
- package/src/logic/history/getBlock.ts +47 -0
- package/src/logic/history/getBlock.unit.test.ts +106 -0
- package/src/logic/history/getBlockInfo.ts +24 -0
- package/src/logic/history/getBlockInfo.unit.test.ts +30 -0
- package/src/logic/history/lastBlock.ts +9 -0
- package/src/logic/history/lastBlock.unit.test.ts +30 -0
- package/src/logic/history/listOperations.ts +64 -0
- package/src/logic/history/listOperations.unit.test.ts +129 -0
- package/src/logic/index.ts +11 -0
- package/src/logic/transaction/broadcast.msw.test.ts +23 -0
- package/src/logic/transaction/broadcast.ts +10 -0
- package/src/logic/transaction/broadcast.unit.test.ts +26 -0
- package/src/logic/transaction/combine.ts +10 -0
- package/src/logic/transaction/combine.unit.test.ts +37 -0
- package/src/logic/transaction/craftTransaction.ts +137 -0
- package/src/logic/transaction/craftTransaction.unit.test.ts +217 -0
- package/src/logic/transaction/estimateFees.ts +32 -0
- package/src/logic/transaction/estimateFees.unit.test.ts +102 -0
- package/src/logic/validateAddress.ts +15 -0
- package/src/logic/validateAddress.unit.test.ts +15 -0
- package/src/logic/validateIntent.ts +96 -0
- package/src/logic/validateIntent.unit.test.ts +156 -0
- package/src/network/getBlock.ts +23 -0
- package/src/network/getBlock.unit.test.ts +58 -0
- package/src/network/getFees.ts +1 -3
- package/src/network/index.ts +1 -0
- package/src/network/sdk.test.ts +32 -1
- package/src/network/sdk.ts +9 -2
- package/src/test/bridgeDatasetTest.ts +1 -1
- package/src/test/msw-setup.ts +7 -0
- package/src/test/msw.mock.ts +6 -0
- package/src/types/network.ts +34 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the createApi factory — verifies the coin config is set, every CoinModuleApi
|
|
3
|
+
* method is wired and delegates to its logic function with the right arguments, and unsupported
|
|
4
|
+
* methods throw. Network and logic layers are mocked; no network access.
|
|
5
|
+
*/
|
|
6
|
+
import type {
|
|
7
|
+
Balance,
|
|
8
|
+
CoinModuleApi,
|
|
9
|
+
CraftedTransaction,
|
|
10
|
+
FeeEstimation,
|
|
11
|
+
Operation,
|
|
12
|
+
Page,
|
|
13
|
+
TransactionIntent,
|
|
14
|
+
} from "@ledgerhq/coin-module-framework/api/index";
|
|
15
|
+
import { createApi } from "./index";
|
|
16
|
+
import { getCoinConfig } from "../config";
|
|
17
|
+
import { broadcast } from "../logic/transaction/broadcast";
|
|
18
|
+
import { combine } from "../logic/transaction/combine";
|
|
19
|
+
import { craftTransaction } from "../logic/transaction/craftTransaction";
|
|
20
|
+
import { estimateFees } from "../logic/transaction/estimateFees";
|
|
21
|
+
import { getBalance } from "../logic/account/getBalance";
|
|
22
|
+
import { getBlock } from "../logic/history/getBlock";
|
|
23
|
+
import { getBlockInfo } from "../logic/history/getBlockInfo";
|
|
24
|
+
import { lastBlock } from "../logic/history/lastBlock";
|
|
25
|
+
import { listOperations } from "../logic/history/listOperations";
|
|
26
|
+
import { validateIntent } from "../logic/validateIntent";
|
|
27
|
+
|
|
28
|
+
jest.mock("../logic/transaction/broadcast", () => ({ broadcast: jest.fn() }));
|
|
29
|
+
jest.mock("../logic/transaction/combine", () => ({ combine: jest.fn() }));
|
|
30
|
+
jest.mock("../logic/transaction/craftTransaction", () => ({ craftTransaction: jest.fn() }));
|
|
31
|
+
jest.mock("../logic/transaction/estimateFees", () => ({ estimateFees: jest.fn() }));
|
|
32
|
+
jest.mock("../logic/account/getBalance", () => ({ getBalance: jest.fn() }));
|
|
33
|
+
jest.mock("../logic/history/getBlock", () => ({ getBlock: jest.fn() }));
|
|
34
|
+
jest.mock("../logic/history/getBlockInfo", () => ({ getBlockInfo: jest.fn() }));
|
|
35
|
+
jest.mock("../logic/history/lastBlock", () => ({ lastBlock: jest.fn() }));
|
|
36
|
+
jest.mock("../logic/history/listOperations", () => ({ listOperations: jest.fn() }));
|
|
37
|
+
jest.mock("../logic/validateIntent", () => ({ validateIntent: jest.fn() }));
|
|
38
|
+
|
|
39
|
+
const config = () => ({ status: { type: "active" as const } });
|
|
40
|
+
|
|
41
|
+
const SENDER = "0x0fe6688548f0C303932bB197B0A96034f1d74dba";
|
|
42
|
+
const RECIPIENT = "0x02961B92B8D20A4ea12f1f1CeFA74Dd7B4355A86";
|
|
43
|
+
|
|
44
|
+
const nativeIntent: TransactionIntent = {
|
|
45
|
+
intentType: "transaction",
|
|
46
|
+
type: "send",
|
|
47
|
+
sender: SENDER,
|
|
48
|
+
recipient: RECIPIENT,
|
|
49
|
+
amount: 100_000_000n,
|
|
50
|
+
asset: { type: "native" },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe("createApi", () => {
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
jest.clearAllMocks();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("sets the coin config, forcing status active", () => {
|
|
59
|
+
createApi(config, "vechain");
|
|
60
|
+
|
|
61
|
+
expect(getCoinConfig()).toEqual({ status: { type: "active" } });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns an object with every CoinModuleApi method", () => {
|
|
65
|
+
const api = createApi(config, "vechain");
|
|
66
|
+
|
|
67
|
+
expect(api).toEqual(
|
|
68
|
+
expect.objectContaining({
|
|
69
|
+
broadcast: expect.any(Function),
|
|
70
|
+
combine: expect.any(Function),
|
|
71
|
+
craftTransaction: expect.any(Function),
|
|
72
|
+
craftRawTransaction: expect.any(Function),
|
|
73
|
+
craftTransactionData: expect.any(Function),
|
|
74
|
+
estimateFees: expect.any(Function),
|
|
75
|
+
getBalance: expect.any(Function),
|
|
76
|
+
lastBlock: expect.any(Function),
|
|
77
|
+
listOperations: expect.any(Function),
|
|
78
|
+
getBlock: expect.any(Function),
|
|
79
|
+
getBlockInfo: expect.any(Function),
|
|
80
|
+
getRewards: expect.any(Function),
|
|
81
|
+
getStakes: expect.any(Function),
|
|
82
|
+
getValidators: expect.any(Function),
|
|
83
|
+
validateIntent: expect.any(Function),
|
|
84
|
+
getNextSequence: expect.any(Function),
|
|
85
|
+
validateAddress: expect.any(Function),
|
|
86
|
+
call: expect.any(Function),
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("delegates broadcast to the logic function", async () => {
|
|
92
|
+
jest.mocked(broadcast).mockResolvedValueOnce("txHash");
|
|
93
|
+
const api = createApi(config, "vechain");
|
|
94
|
+
|
|
95
|
+
const result = await api.broadcast("signedTx");
|
|
96
|
+
|
|
97
|
+
expect(broadcast).toHaveBeenCalledWith("signedTx");
|
|
98
|
+
expect(result).toBe("txHash");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("delegates combine to the logic function", () => {
|
|
102
|
+
jest.mocked(combine).mockReturnValueOnce("0xsigned");
|
|
103
|
+
const api = createApi(config, "vechain");
|
|
104
|
+
|
|
105
|
+
const result = api.combine("tx", "sig");
|
|
106
|
+
|
|
107
|
+
expect(combine).toHaveBeenCalledWith("tx", "sig");
|
|
108
|
+
expect(result).toBe("0xsigned");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("delegates craftTransaction to the logic function (intent, customFees)", async () => {
|
|
112
|
+
const crafted: CraftedTransaction = { transaction: "{}", details: { fee: "1000" } };
|
|
113
|
+
jest.mocked(craftTransaction).mockResolvedValueOnce(crafted);
|
|
114
|
+
const api = createApi(config, "vechain");
|
|
115
|
+
const customFees: FeeEstimation = { value: 5000n };
|
|
116
|
+
|
|
117
|
+
const result = await api.craftTransaction(nativeIntent, customFees);
|
|
118
|
+
|
|
119
|
+
expect(craftTransaction).toHaveBeenCalledWith(nativeIntent, customFees);
|
|
120
|
+
expect(result).toEqual(crafted);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("delegates estimateFees to the logic function (intent, params)", async () => {
|
|
124
|
+
const fees: FeeEstimation = { value: 1000n };
|
|
125
|
+
jest.mocked(estimateFees).mockResolvedValueOnce(fees);
|
|
126
|
+
const api = createApi(config, "vechain");
|
|
127
|
+
|
|
128
|
+
const result = await api.estimateFees(nativeIntent);
|
|
129
|
+
|
|
130
|
+
expect(estimateFees).toHaveBeenCalledWith(nativeIntent, undefined);
|
|
131
|
+
expect(result).toEqual(fees);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("delegates getBalance to the logic function (address)", async () => {
|
|
135
|
+
const balances: Balance[] = [{ value: 1000n, asset: { type: "native" } }];
|
|
136
|
+
jest.mocked(getBalance).mockResolvedValueOnce(balances);
|
|
137
|
+
const api = createApi(config, "vechain");
|
|
138
|
+
|
|
139
|
+
const result = await api.getBalance(SENDER);
|
|
140
|
+
|
|
141
|
+
expect(getBalance).toHaveBeenCalledWith(SENDER);
|
|
142
|
+
expect(result).toEqual(balances);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("rejects getBalance when options are provided (not supported)", async () => {
|
|
146
|
+
const api = createApi(config, "vechain");
|
|
147
|
+
|
|
148
|
+
await expect(
|
|
149
|
+
api.getBalance(SENDER, {} as Parameters<CoinModuleApi["getBalance"]>[1]),
|
|
150
|
+
).rejects.toThrow();
|
|
151
|
+
expect(getBalance).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("delegates lastBlock to the logic function", async () => {
|
|
155
|
+
const block = { height: 100, hash: "abc", time: new Date() };
|
|
156
|
+
jest.mocked(lastBlock).mockResolvedValueOnce(block);
|
|
157
|
+
const api = createApi(config, "vechain");
|
|
158
|
+
|
|
159
|
+
const result = await api.lastBlock();
|
|
160
|
+
|
|
161
|
+
expect(lastBlock).toHaveBeenCalled();
|
|
162
|
+
expect(result).toEqual(block);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("delegates getBlockInfo to the logic function (height)", async () => {
|
|
166
|
+
const info = { height: 42, hash: "abc", time: new Date() };
|
|
167
|
+
jest.mocked(getBlockInfo).mockResolvedValueOnce(info);
|
|
168
|
+
const api = createApi(config, "vechain");
|
|
169
|
+
|
|
170
|
+
const result = await api.getBlockInfo(42);
|
|
171
|
+
|
|
172
|
+
expect(getBlockInfo).toHaveBeenCalledWith(42);
|
|
173
|
+
expect(result).toEqual(info);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("delegates getBlock to the logic function (height)", async () => {
|
|
177
|
+
const block = { info: { height: 42, hash: "abc", time: new Date() }, transactions: [] };
|
|
178
|
+
jest.mocked(getBlock).mockResolvedValueOnce(block);
|
|
179
|
+
const api = createApi(config, "vechain");
|
|
180
|
+
|
|
181
|
+
const result = await api.getBlock(42);
|
|
182
|
+
|
|
183
|
+
expect(getBlock).toHaveBeenCalledWith(42);
|
|
184
|
+
expect(result).toEqual(block);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("delegates listOperations to the logic function (address, options)", async () => {
|
|
188
|
+
const page: Page<Operation> = { items: [], next: undefined };
|
|
189
|
+
jest.mocked(listOperations).mockResolvedValueOnce(page);
|
|
190
|
+
const api = createApi(config, "vechain");
|
|
191
|
+
|
|
192
|
+
const result = await api.listOperations(SENDER, { minHeight: 0 });
|
|
193
|
+
|
|
194
|
+
expect(listOperations).toHaveBeenCalledWith(SENDER, { minHeight: 0 });
|
|
195
|
+
expect(result).toEqual(page);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("delegates validateIntent to the logic function (intent, balances, customFees)", async () => {
|
|
199
|
+
const validation = { errors: {}, warnings: {}, estimatedFees: 0n, amount: 0n, totalSpent: 0n };
|
|
200
|
+
jest.mocked(validateIntent).mockResolvedValueOnce(validation);
|
|
201
|
+
const api = createApi(config, "vechain");
|
|
202
|
+
|
|
203
|
+
const result = await api.validateIntent(nativeIntent, [], undefined);
|
|
204
|
+
|
|
205
|
+
expect(validateIntent).toHaveBeenCalledWith(nativeIntent, [], undefined);
|
|
206
|
+
expect(result).toEqual(validation);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("throws synchronously for methods not applicable/supported for Vechain", () => {
|
|
210
|
+
const api = createApi(config, "vechain");
|
|
211
|
+
|
|
212
|
+
expect(() => api.getNextSequence(SENDER)).toThrow(
|
|
213
|
+
"getNextSequence is not applicable for Vechain",
|
|
214
|
+
);
|
|
215
|
+
expect(() => api.craftRawTransaction("raw", SENDER, "pubkey", 0n)).toThrow(
|
|
216
|
+
"craftRawTransaction is not supported",
|
|
217
|
+
);
|
|
218
|
+
expect(() => api.getStakes(SENDER)).toThrow("getStakes is not supported");
|
|
219
|
+
expect(() => api.getRewards(SENDER)).toThrow("getRewards is not supported");
|
|
220
|
+
expect(() => api.getValidators()).toThrow("getValidators is not supported");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("throws for call (not supported)", () => {
|
|
224
|
+
const api = createApi(config, "vechain");
|
|
225
|
+
expect(() => api.call({})).toThrow("call is not supported");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("validates addresses via parseAddress", async () => {
|
|
229
|
+
const api = createApi(config, "vechain");
|
|
230
|
+
await expect(api.validateAddress(SENDER, {})).resolves.toBe(true);
|
|
231
|
+
await expect(api.validateAddress("0xnot-an-address", {})).resolves.toBe(false);
|
|
232
|
+
await expect(api.validateAddress("", {})).resolves.toBe(false);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
InvalidAddressBecauseDestinationIsAlsoSource,
|
|
7
7
|
NotEnoughBalance,
|
|
8
8
|
RecipientRequired,
|
|
9
|
-
} from "@ledgerhq/errors";
|
|
9
|
+
} from "@ledgerhq/ledger-wallet-framework/errors";
|
|
10
10
|
import { AccountBridge } from "@ledgerhq/types-live";
|
|
11
11
|
import { calculateTransactionInfo, parseAddress } from "../common-logic";
|
|
12
12
|
import type { Transaction } from "../types";
|
|
@@ -3,7 +3,11 @@ import { makeScanAccounts } from "@ledgerhq/ledger-wallet-framework/bridge/jsHel
|
|
|
3
3
|
import { getCryptoCurrencyById } from "@ledgerhq/ledger-wallet-framework/currencies";
|
|
4
4
|
import { getCryptoAssetsStore } from "@ledgerhq/ledger-wallet-framework/cryptoAssetsStore";
|
|
5
5
|
import { setCryptoAssetsStore } from "@ledgerhq/ledger-wallet-framework/cryptoAssetsStore";
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
CryptoCurrencyIdSchema,
|
|
8
|
+
TokenCurrencyIdSchema,
|
|
9
|
+
type TokenCurrency,
|
|
10
|
+
} from "@ledgerhq/ledger-wallet-framework/types";
|
|
7
11
|
import BigNumber from "bignumber.js";
|
|
8
12
|
import { setupServer } from "msw/node";
|
|
9
13
|
import { firstValueFrom } from "rxjs";
|
|
@@ -34,9 +38,9 @@ describe("scanAccounts", () => {
|
|
|
34
38
|
beforeAll(() => {
|
|
35
39
|
const vthoToken: TokenCurrency = {
|
|
36
40
|
type: "TokenCurrency",
|
|
37
|
-
id: "vechain/vip180/vtho",
|
|
41
|
+
id: TokenCurrencyIdSchema.parse("vechain/vip180/vtho"),
|
|
38
42
|
contractAddress: "0x0000000000000000000000000000456E65726779",
|
|
39
|
-
parentCurrencyId: "vechain",
|
|
43
|
+
parentCurrencyId: CryptoCurrencyIdSchema.parse("vechain"),
|
|
40
44
|
tokenType: "vip180",
|
|
41
45
|
name: "VeThor",
|
|
42
46
|
ticker: "VTHO",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AssetInfo, Balance } from "@ledgerhq/coin-module-framework/api/index";
|
|
2
|
+
import { VTHO_ADDRESS } from "@vechain/sdk-core";
|
|
3
|
+
import { getAccount } from "../../network";
|
|
4
|
+
|
|
5
|
+
export const NATIVE_ASSET: AssetInfo = { type: "native", name: "VET" };
|
|
6
|
+
|
|
7
|
+
// `assetOwner` lets the generic coin-framework adapter attach the VTHO balance to its sub-account
|
|
8
|
+
// (buildSubAccounts filters by `extra.assetReference` + `extra.assetOwner`). Mirrors coin-tron.
|
|
9
|
+
export const vthoAsset = (address: string): AssetInfo => ({
|
|
10
|
+
type: "token",
|
|
11
|
+
assetReference: VTHO_ADDRESS,
|
|
12
|
+
assetOwner: address,
|
|
13
|
+
name: "VTHO",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// VET + VTHO balances from Thor's /accounts/{address} (both returned in one call); 0 if absent.
|
|
17
|
+
export async function getBalance(address: string): Promise<Balance[]> {
|
|
18
|
+
const { balance, energy } = await getAccount(address);
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
{ value: BigInt(balance || "0"), asset: NATIVE_ASSET },
|
|
22
|
+
{ value: BigInt(energy || "0"), asset: vthoAsset(address) },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getAccount } from "../../network";
|
|
2
|
+
import { getBalance, NATIVE_ASSET, vthoAsset } from "./getBalance";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../network", () => ({ getAccount: jest.fn() }));
|
|
5
|
+
|
|
6
|
+
const ADDRESS = "0x0fe6688548f0C303932bB197B0A96034f1d74dba";
|
|
7
|
+
|
|
8
|
+
describe("getBalance", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns VET and VTHO balances parsed from the account response", async () => {
|
|
14
|
+
jest.mocked(getAccount).mockResolvedValueOnce({
|
|
15
|
+
balance: "0x1bc16d674ec80000",
|
|
16
|
+
energy: "0xde0b6b3a7640000",
|
|
17
|
+
hasCode: false,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const balances = await getBalance(ADDRESS);
|
|
21
|
+
|
|
22
|
+
expect(getAccount).toHaveBeenCalledWith(ADDRESS);
|
|
23
|
+
expect(balances).toEqual([
|
|
24
|
+
{ value: BigInt("0x1bc16d674ec80000"), asset: NATIVE_ASSET },
|
|
25
|
+
{ value: BigInt("0xde0b6b3a7640000"), asset: vthoAsset(ADDRESS) },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("defaults both balances to 0 for a pristine account", async () => {
|
|
30
|
+
jest.mocked(getAccount).mockResolvedValueOnce({ balance: "", energy: "", hasCode: false });
|
|
31
|
+
|
|
32
|
+
const balances = await getBalance(ADDRESS);
|
|
33
|
+
|
|
34
|
+
expect(balances).toEqual([
|
|
35
|
+
{ value: 0n, asset: NATIVE_ASSET },
|
|
36
|
+
{ value: 0n, asset: vthoAsset(ADDRESS) },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns a token asset for VTHO distinguished from the native asset", async () => {
|
|
41
|
+
jest
|
|
42
|
+
.mocked(getAccount)
|
|
43
|
+
.mockResolvedValueOnce({ balance: "0x1", energy: "0x2", hasCode: false });
|
|
44
|
+
|
|
45
|
+
const [nativeBalance, tokenBalance] = await getBalance(ADDRESS);
|
|
46
|
+
|
|
47
|
+
expect(nativeBalance.asset.type).toBe("native");
|
|
48
|
+
expect(tokenBalance.asset.type).not.toBe("native");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Block,
|
|
3
|
+
BlockOperation,
|
|
4
|
+
BlockTransaction,
|
|
5
|
+
} from "@ledgerhq/coin-module-framework/api/index";
|
|
6
|
+
import { getBlock as getBlockFromNetwork } from "../../network";
|
|
7
|
+
import type { ApiResponseBlockTransaction } from "../../types";
|
|
8
|
+
import { toBlockInfo } from "./getBlockInfo";
|
|
9
|
+
|
|
10
|
+
// Full expanded block; block info is derived from it (no separate fetch). Only native VET transfers
|
|
11
|
+
// map to operations; VTHO (VIP-180) movements are ABI-encoded events, not decoded here.
|
|
12
|
+
export async function getBlock(height: number): Promise<Block> {
|
|
13
|
+
const block = await getBlockFromNetwork(height, true);
|
|
14
|
+
|
|
15
|
+
if (!block) {
|
|
16
|
+
throw new Error(`vechain: no block at height ${height}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const transactions = block.transactions as ApiResponseBlockTransaction[];
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
info: toBlockInfo(block),
|
|
23
|
+
transactions: transactions.map(toBlockTransaction),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toBlockTransaction(tx: ApiResponseBlockTransaction): BlockTransaction {
|
|
28
|
+
const operations: BlockOperation[] = tx.outputs.flatMap(output =>
|
|
29
|
+
output.transfers.map(
|
|
30
|
+
(transfer): BlockOperation => ({
|
|
31
|
+
type: "transfer",
|
|
32
|
+
address: transfer.recipient,
|
|
33
|
+
peer: transfer.sender,
|
|
34
|
+
asset: { type: "native" },
|
|
35
|
+
amount: BigInt(transfer.amount || "0"),
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
hash: tx.id,
|
|
42
|
+
failed: tx.reverted,
|
|
43
|
+
operations,
|
|
44
|
+
fees: BigInt(tx.paid || "0"),
|
|
45
|
+
feesPayer: tx.gasPayer ?? tx.origin,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getBlock as getBlockFromNetwork } from "../../network";
|
|
2
|
+
import { getBlock } from "./getBlock";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../network", () => ({ getBlock: jest.fn() }));
|
|
5
|
+
|
|
6
|
+
describe("getBlock", () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("maps VET transfers within an expanded block to transfer operations", async () => {
|
|
12
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce({
|
|
13
|
+
id: "0xabc",
|
|
14
|
+
number: 10,
|
|
15
|
+
timestamp: 1_700_000_000,
|
|
16
|
+
transactions: [
|
|
17
|
+
{
|
|
18
|
+
id: "0xtx1",
|
|
19
|
+
origin: "0xsender",
|
|
20
|
+
gasUsed: 21000,
|
|
21
|
+
paid: "0x100",
|
|
22
|
+
reverted: false,
|
|
23
|
+
outputs: [
|
|
24
|
+
{
|
|
25
|
+
contractAddress: null,
|
|
26
|
+
events: [],
|
|
27
|
+
transfers: [{ sender: "0xsender", recipient: "0xrecipient", amount: "0x64" }],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const block = await getBlock(10);
|
|
35
|
+
|
|
36
|
+
expect(getBlockFromNetwork).toHaveBeenCalledTimes(1);
|
|
37
|
+
expect(getBlockFromNetwork).toHaveBeenCalledWith(10, true);
|
|
38
|
+
expect(block.info).toEqual({
|
|
39
|
+
height: 10,
|
|
40
|
+
hash: "0xabc",
|
|
41
|
+
time: new Date(1_700_000_000 * 1000),
|
|
42
|
+
});
|
|
43
|
+
expect(block.transactions).toEqual([
|
|
44
|
+
{
|
|
45
|
+
hash: "0xtx1",
|
|
46
|
+
failed: false,
|
|
47
|
+
fees: BigInt("0x100"),
|
|
48
|
+
feesPayer: "0xsender",
|
|
49
|
+
operations: [
|
|
50
|
+
{
|
|
51
|
+
type: "transfer",
|
|
52
|
+
address: "0xrecipient",
|
|
53
|
+
peer: "0xsender",
|
|
54
|
+
asset: { type: "native" },
|
|
55
|
+
amount: BigInt("0x64"),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("marks a reverted transaction as failed", async () => {
|
|
63
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce({
|
|
64
|
+
id: "0xabc",
|
|
65
|
+
number: 10,
|
|
66
|
+
timestamp: 1_700_000_000,
|
|
67
|
+
transactions: [
|
|
68
|
+
{ id: "0xtx2", origin: "0xsender", gasUsed: 0, paid: "0x0", reverted: true, outputs: [] },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const block = await getBlock(10);
|
|
73
|
+
|
|
74
|
+
expect(block.transactions[0].failed).toBe(true);
|
|
75
|
+
expect(block.transactions[0].operations).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("throws when there is no block at the given height", async () => {
|
|
79
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce(null);
|
|
80
|
+
|
|
81
|
+
await expect(getBlock(1)).rejects.toThrow("vechain: no block at height 1");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("uses the VIP-191 gasPayer as feesPayer when a tx is fee-delegated", async () => {
|
|
85
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce({
|
|
86
|
+
id: "0xabc",
|
|
87
|
+
number: 10,
|
|
88
|
+
timestamp: 1_700_000_000,
|
|
89
|
+
transactions: [
|
|
90
|
+
{
|
|
91
|
+
id: "0xtx3",
|
|
92
|
+
origin: "0xsender",
|
|
93
|
+
gasPayer: "0xdelegate",
|
|
94
|
+
gasUsed: 21000,
|
|
95
|
+
paid: "0x100",
|
|
96
|
+
reverted: false,
|
|
97
|
+
outputs: [],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const block = await getBlock(10);
|
|
103
|
+
|
|
104
|
+
expect(block.transactions[0].feesPayer).toBe("0xdelegate");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BlockInfo } from "@ledgerhq/coin-module-framework/api/index";
|
|
2
|
+
import { getBlock as getBlockFromNetwork } from "../../network";
|
|
3
|
+
import type { ApiResponseBlock } from "../../types";
|
|
4
|
+
|
|
5
|
+
// Map a Thor block to BlockInfo (id/number/timestamp are present regardless of `expanded`).
|
|
6
|
+
export function toBlockInfo(block: ApiResponseBlock): BlockInfo {
|
|
7
|
+
return {
|
|
8
|
+
height: block.number,
|
|
9
|
+
hash: block.id,
|
|
10
|
+
// Thor block timestamps are unix seconds.
|
|
11
|
+
time: new Date(block.timestamp * 1000),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Block metadata (height/hash/time) at a given block height, via Thor `GET /blocks/{height}`. */
|
|
16
|
+
export async function getBlockInfo(height: number): Promise<BlockInfo> {
|
|
17
|
+
const block = await getBlockFromNetwork(height);
|
|
18
|
+
|
|
19
|
+
if (!block) {
|
|
20
|
+
throw new Error(`vechain: no block at height ${height}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return toBlockInfo(block);
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getBlock as getBlockFromNetwork } from "../../network";
|
|
2
|
+
import { getBlockInfo } from "./getBlockInfo";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../network", () => ({ getBlock: jest.fn() }));
|
|
5
|
+
|
|
6
|
+
describe("getBlockInfo", () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("maps the network block to BlockInfo", async () => {
|
|
12
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce({
|
|
13
|
+
id: "0xabc",
|
|
14
|
+
number: 42,
|
|
15
|
+
timestamp: 1_700_000_000,
|
|
16
|
+
transactions: [],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const info = await getBlockInfo(42);
|
|
20
|
+
|
|
21
|
+
expect(getBlockFromNetwork).toHaveBeenCalledWith(42);
|
|
22
|
+
expect(info).toEqual({ height: 42, hash: "0xabc", time: new Date(1_700_000_000 * 1000) });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("throws when there is no block at the given height", async () => {
|
|
26
|
+
jest.mocked(getBlockFromNetwork).mockResolvedValueOnce(null);
|
|
27
|
+
|
|
28
|
+
await expect(getBlockInfo(999)).rejects.toThrow("vechain: no block at height 999");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockInfo } from "@ledgerhq/coin-module-framework/api/index";
|
|
2
|
+
import { getLastBlockHeight } from "../../network";
|
|
3
|
+
import { getBlockInfo } from "./getBlockInfo";
|
|
4
|
+
|
|
5
|
+
// Latest block; /blocks/best exposes only the height, so hash/time come from getBlockInfo.
|
|
6
|
+
export async function lastBlock(): Promise<BlockInfo> {
|
|
7
|
+
const height = await getLastBlockHeight();
|
|
8
|
+
return getBlockInfo(height);
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getLastBlockHeight } from "../../network";
|
|
2
|
+
import { getBlockInfo } from "./getBlockInfo";
|
|
3
|
+
import { lastBlock } from "./lastBlock";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../network", () => ({ getLastBlockHeight: jest.fn() }));
|
|
6
|
+
jest.mock("./getBlockInfo", () => ({ getBlockInfo: jest.fn() }));
|
|
7
|
+
|
|
8
|
+
describe("lastBlock", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("resolves the current height then delegates to getBlockInfo", async () => {
|
|
14
|
+
jest.mocked(getLastBlockHeight).mockResolvedValueOnce(123);
|
|
15
|
+
const info = { height: 123, hash: "0xabc", time: new Date() };
|
|
16
|
+
jest.mocked(getBlockInfo).mockResolvedValueOnce(info);
|
|
17
|
+
|
|
18
|
+
const result = await lastBlock();
|
|
19
|
+
|
|
20
|
+
expect(getBlockInfo).toHaveBeenCalledWith(123);
|
|
21
|
+
expect(result).toBe(info);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("propagates an error from the height lookup", async () => {
|
|
25
|
+
jest.mocked(getLastBlockHeight).mockRejectedValueOnce(new Error("network down"));
|
|
26
|
+
|
|
27
|
+
await expect(lastBlock()).rejects.toThrow("network down");
|
|
28
|
+
expect(getBlockInfo).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssetInfo,
|
|
3
|
+
ListOperationsOptions,
|
|
4
|
+
MemoNotSupported,
|
|
5
|
+
Operation,
|
|
6
|
+
Page,
|
|
7
|
+
} from "@ledgerhq/coin-module-framework/api/index";
|
|
8
|
+
import type { Operation as LegacyOperation } from "@ledgerhq/types-live";
|
|
9
|
+
import { VTHO_ADDRESS } from "@vechain/sdk-core";
|
|
10
|
+
import { getLastBlockHeight, getOperations, getTokenOperations } from "../../network";
|
|
11
|
+
import { NATIVE_ASSET, vthoAsset } from "../account/getBalance";
|
|
12
|
+
|
|
13
|
+
function toFrameworkOperation(op: LegacyOperation, asset: AssetInfo): Operation<MemoNotSupported> {
|
|
14
|
+
return {
|
|
15
|
+
id: op.id,
|
|
16
|
+
type: op.type,
|
|
17
|
+
senders: op.senders,
|
|
18
|
+
recipients: op.recipients,
|
|
19
|
+
value: BigInt(op.value.toFixed(0)),
|
|
20
|
+
asset,
|
|
21
|
+
details: { ledgerOpType: op.type },
|
|
22
|
+
tx: {
|
|
23
|
+
hash: op.hash,
|
|
24
|
+
block: { height: op.blockHeight ?? 0, hash: op.blockHash ?? "", time: op.date },
|
|
25
|
+
fees: asset.type === "native" ? 0n : BigInt((op.fee ?? 0).toFixed(0)),
|
|
26
|
+
date: op.date,
|
|
27
|
+
failed: op.hasFailed ?? false,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseCursor(options: ListOperationsOptions): number {
|
|
33
|
+
const parsed = options.cursor ? Number.parseInt(options.cursor, 10) : Number.NaN;
|
|
34
|
+
const fromCursor = !Number.isNaN(parsed) && parsed >= 1 ? parsed : 1;
|
|
35
|
+
return Math.max(fromCursor, options.minHeight || 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Merged VET + VTHO operations; `next` is one block past the current head for incremental resume.
|
|
39
|
+
export async function listOperations(
|
|
40
|
+
address: string,
|
|
41
|
+
options: ListOperationsOptions,
|
|
42
|
+
): Promise<Page<Operation<MemoNotSupported>>> {
|
|
43
|
+
const startAt = parseCursor(options);
|
|
44
|
+
const stopAt = await getLastBlockHeight();
|
|
45
|
+
|
|
46
|
+
if (startAt > stopAt) {
|
|
47
|
+
return { items: [], next: String(startAt) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const [vetOps, vthoOps] = await Promise.all([
|
|
51
|
+
getOperations(address, address, startAt, stopAt),
|
|
52
|
+
getTokenOperations(address, address, VTHO_ADDRESS, startAt, stopAt),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const items = [
|
|
56
|
+
...vetOps.map(op => toFrameworkOperation(op, NATIVE_ASSET)),
|
|
57
|
+
...vthoOps.map(op => toFrameworkOperation(op, vthoAsset(address))),
|
|
58
|
+
].sort((a, b) => b.tx.date.getTime() - a.tx.date.getTime());
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
items,
|
|
62
|
+
next: String(stopAt + 1),
|
|
63
|
+
};
|
|
64
|
+
}
|