@ledgerhq/coin-tester-bitcoin 1.1.0-nightly.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/.env.example +7 -0
- package/.eslintrc.js +23 -0
- package/.turbo/turbo-build.log +4 -0
- package/.unimportedrc.json +8 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE.txt +21 -0
- package/README.md +30 -0
- package/jest.config.ts +19 -0
- package/lib/src/assert.d.ts +6 -0
- package/lib/src/assert.d.ts.map +1 -0
- package/lib/src/assert.js +29 -0
- package/lib/src/assert.js.map +1 -0
- package/lib/src/atlas.d.ts +3 -0
- package/lib/src/atlas.d.ts.map +1 -0
- package/lib/src/atlas.js +84 -0
- package/lib/src/atlas.js.map +1 -0
- package/lib/src/constants.d.ts +5 -0
- package/lib/src/constants.d.ts.map +1 -0
- package/lib/src/constants.js +5 -0
- package/lib/src/constants.js.map +1 -0
- package/lib/src/fixtures.d.ts +5 -0
- package/lib/src/fixtures.d.ts.map +1 -0
- package/lib/src/fixtures.js +61 -0
- package/lib/src/fixtures.js.map +1 -0
- package/lib/src/helpers.d.ts +11 -0
- package/lib/src/helpers.d.ts.map +1 -0
- package/lib/src/helpers.js +339 -0
- package/lib/src/helpers.js.map +1 -0
- package/lib/src/scenarii/bitcoin.d.ts +4 -0
- package/lib/src/scenarii/bitcoin.d.ts.map +1 -0
- package/lib/src/scenarii/bitcoin.js +258 -0
- package/lib/src/scenarii/bitcoin.js.map +1 -0
- package/lib/src/utils.d.ts +5 -0
- package/lib/src/utils.d.ts.map +1 -0
- package/lib/src/utils.js +48 -0
- package/lib/src/utils.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib-es/src/assert.d.ts +6 -0
- package/lib-es/src/assert.d.ts.map +1 -0
- package/lib-es/src/assert.js +23 -0
- package/lib-es/src/assert.js.map +1 -0
- package/lib-es/src/atlas.d.ts +3 -0
- package/lib-es/src/atlas.d.ts.map +1 -0
- package/lib-es/src/atlas.js +43 -0
- package/lib-es/src/atlas.js.map +1 -0
- package/lib-es/src/constants.d.ts +5 -0
- package/lib-es/src/constants.d.ts.map +1 -0
- package/lib-es/src/constants.js +2 -0
- package/lib-es/src/constants.js.map +1 -0
- package/lib-es/src/fixtures.d.ts +5 -0
- package/lib-es/src/fixtures.d.ts.map +1 -0
- package/lib-es/src/fixtures.js +54 -0
- package/lib-es/src/fixtures.js.map +1 -0
- package/lib-es/src/helpers.d.ts +11 -0
- package/lib-es/src/helpers.d.ts.map +1 -0
- package/lib-es/src/helpers.js +323 -0
- package/lib-es/src/helpers.js.map +1 -0
- package/lib-es/src/scenarii/bitcoin.d.ts +4 -0
- package/lib-es/src/scenarii/bitcoin.d.ts.map +1 -0
- package/lib-es/src/scenarii/bitcoin.js +252 -0
- package/lib-es/src/scenarii/bitcoin.js.map +1 -0
- package/lib-es/src/utils.d.ts +5 -0
- package/lib-es/src/utils.d.ts.map +1 -0
- package/lib-es/src/utils.js +40 -0
- package/lib-es/src/utils.js.map +1 -0
- package/lib-es/tsconfig.tsbuildinfo +1 -0
- package/package.json +83 -0
- package/src/assert.ts +34 -0
- package/src/atlas.ts +52 -0
- package/src/constants.ts +1 -0
- package/src/docker/atlas/bitcoin.conf +56 -0
- package/src/docker/atlas/pending.conf +36 -0
- package/src/docker/docker-compose.yml +76 -0
- package/src/docker/nginx/default.conf +31 -0
- package/src/docker/postgres/create-db.sh +9 -0
- package/src/fixtures.ts +66 -0
- package/src/helpers.ts +372 -0
- package/src/scenarii/bitcoin.ts +306 -0
- package/src/scenarii.test.ts +27 -0
- package/src/utils.ts +52 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { Scenario, ScenarioTransaction } from "@ledgerhq/coin-tester/main";
|
|
2
|
+
import { killSpeculos, spawnSpeculos } from "@ledgerhq/coin-tester/signers/speculos";
|
|
3
|
+
import Btc from "@ledgerhq/hw-app-btc";
|
|
4
|
+
import { BitcoinAccount, Transaction as BtcTransaction } from "@ledgerhq/coin-bitcoin/types";
|
|
5
|
+
import { createBridges } from "@ledgerhq/coin-bitcoin/bridge/js";
|
|
6
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
7
|
+
import resolver from "@ledgerhq/coin-bitcoin/hw-getAddress";
|
|
8
|
+
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
|
|
9
|
+
import { SignerContext } from "@ledgerhq/coin-bitcoin/signer";
|
|
10
|
+
import { BitcoinConfigInfo, setCoinConfig } from "@ledgerhq/coin-bitcoin/config";
|
|
11
|
+
import { BigNumber } from "bignumber.js";
|
|
12
|
+
import { defaultNanoApp } from "../constants";
|
|
13
|
+
import { loadWallet, mineToWalletAddress, sendTo } from "../helpers";
|
|
14
|
+
import { makeAccount } from "../fixtures";
|
|
15
|
+
import { killAtlas, spawnAtlas } from "../atlas";
|
|
16
|
+
import { findNewUtxo, waitForExplorerSync } from "../utils";
|
|
17
|
+
import {
|
|
18
|
+
assertCommonTxProperties,
|
|
19
|
+
assertUtxoExcluded,
|
|
20
|
+
assertUtxoSpent,
|
|
21
|
+
getNewChangeUtxos,
|
|
22
|
+
} from "../assert";
|
|
23
|
+
|
|
24
|
+
type BitcoinCoinConfig = {
|
|
25
|
+
info: BitcoinConfigInfo;
|
|
26
|
+
};
|
|
27
|
+
type BitcoinScenarioTransaction = ScenarioTransaction<BtcTransaction, BitcoinAccount>;
|
|
28
|
+
|
|
29
|
+
let firstUtxoHash = "";
|
|
30
|
+
let firstUtxoOutputIndex = 0;
|
|
31
|
+
let secondUtxoHash = "";
|
|
32
|
+
let secondUtxoOutputIndex = 0;
|
|
33
|
+
|
|
34
|
+
const makeScenarioTransactions = (): BitcoinScenarioTransaction[] => {
|
|
35
|
+
const scenarioExcludeTwoUtxos: BitcoinScenarioTransaction = {
|
|
36
|
+
name: "Send BTC excluding two UTXOs",
|
|
37
|
+
rbf: true,
|
|
38
|
+
utxoStrategy: {
|
|
39
|
+
strategy: 1, // Optimize size, for exemple
|
|
40
|
+
excludeUTXOs: [
|
|
41
|
+
{ hash: firstUtxoHash, outputIndex: firstUtxoOutputIndex },
|
|
42
|
+
{ hash: secondUtxoHash, outputIndex: secondUtxoOutputIndex },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
amount: new BigNumber(1e8),
|
|
46
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
47
|
+
expect: (previousAccount, currentAccount) => {
|
|
48
|
+
assertCommonTxProperties(previousAccount, currentAccount);
|
|
49
|
+
|
|
50
|
+
assertUtxoExcluded(currentAccount, firstUtxoHash, firstUtxoOutputIndex);
|
|
51
|
+
assertUtxoExcluded(currentAccount, secondUtxoHash, secondUtxoOutputIndex);
|
|
52
|
+
|
|
53
|
+
const newChangeUtxo = findNewUtxo(previousAccount, currentAccount);
|
|
54
|
+
expect(newChangeUtxo).toBeDefined();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const scenarioExcludeOneUtxo: BitcoinScenarioTransaction = {
|
|
58
|
+
name: "Send BTC excluding second UTXO",
|
|
59
|
+
rbf: true,
|
|
60
|
+
utxoStrategy: {
|
|
61
|
+
strategy: 1,
|
|
62
|
+
excludeUTXOs: [{ hash: secondUtxoHash, outputIndex: secondUtxoOutputIndex }],
|
|
63
|
+
},
|
|
64
|
+
useAllAmount: true,
|
|
65
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
66
|
+
expect: (previousAccount, currentAccount) => {
|
|
67
|
+
const latestOperation = assertCommonTxProperties(previousAccount, currentAccount);
|
|
68
|
+
|
|
69
|
+
// Excluded UTXO must still be there
|
|
70
|
+
assertUtxoExcluded(currentAccount, secondUtxoHash, secondUtxoOutputIndex);
|
|
71
|
+
|
|
72
|
+
// First UTXO must be spent
|
|
73
|
+
assertUtxoSpent(previousAccount, currentAccount, firstUtxoHash, firstUtxoOutputIndex);
|
|
74
|
+
|
|
75
|
+
// No change expected when using all amount
|
|
76
|
+
const changeUtxos = getNewChangeUtxos(previousAccount, currentAccount);
|
|
77
|
+
expect(changeUtxos.length).toBe(0);
|
|
78
|
+
|
|
79
|
+
const spentUtxos = previousAccount.bitcoinResources.utxos.filter(
|
|
80
|
+
utxo => !(utxo.hash === secondUtxoHash && utxo.outputIndex === secondUtxoOutputIndex),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const totalSpent = spentUtxos.reduce((sum, utxo) => sum.plus(utxo.value), new BigNumber(0));
|
|
84
|
+
const totalChange = changeUtxos.reduce((sum, utxo) => sum.plus(utxo.value), new BigNumber(0));
|
|
85
|
+
const expectedChange = totalSpent.minus(latestOperation.value);
|
|
86
|
+
expect(totalChange.toFixed()).toBe(expectedChange.toFixed());
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const scenarioSendBtcTransaction: BitcoinScenarioTransaction = {
|
|
90
|
+
name: "Send 1 BTC",
|
|
91
|
+
rbf: false,
|
|
92
|
+
amount: new BigNumber(1e8),
|
|
93
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
94
|
+
expect: (previousAccount, currentAccount) => {
|
|
95
|
+
const [latestOperation] = currentAccount.operations;
|
|
96
|
+
|
|
97
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
98
|
+
expect(latestOperation.type).toBe("OUT");
|
|
99
|
+
expect(latestOperation.value.toFixed()).toBe(latestOperation.fee.plus(1e8).toFixed());
|
|
100
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
101
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const scenarioSendFastFeesStrategyBtcTransaction: BitcoinScenarioTransaction = {
|
|
106
|
+
name: "Send Fast Fees Strategy BTC",
|
|
107
|
+
rbf: false,
|
|
108
|
+
feesStrategy: "fast",
|
|
109
|
+
amount: new BigNumber(1e6),
|
|
110
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
111
|
+
expect: (previousAccount, currentAccount) => {
|
|
112
|
+
const [latestOperation] = currentAccount.operations;
|
|
113
|
+
|
|
114
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
115
|
+
expect(latestOperation.type).toBe("OUT");
|
|
116
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
117
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
const scenarioSendSlowFeesStrategyBtcTransaction: BitcoinScenarioTransaction = {
|
|
122
|
+
name: "Send Slow Fees Strategy BTC",
|
|
123
|
+
rbf: false,
|
|
124
|
+
feesStrategy: "slow",
|
|
125
|
+
amount: new BigNumber(1e6),
|
|
126
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
127
|
+
expect: (previousAccount, currentAccount) => {
|
|
128
|
+
const [latestOperation] = currentAccount.operations;
|
|
129
|
+
|
|
130
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
131
|
+
expect(latestOperation.type).toBe("OUT");
|
|
132
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
133
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const scenarioSendFIFOBtcTransaction: BitcoinScenarioTransaction = {
|
|
138
|
+
name: "Send FIFO BTC",
|
|
139
|
+
rbf: true,
|
|
140
|
+
utxoStrategy: { strategy: 0, excludeUTXOs: [] },
|
|
141
|
+
amount: new BigNumber(1e6),
|
|
142
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
143
|
+
expect: (previousAccount, currentAccount) => {
|
|
144
|
+
const [latestOperation] = currentAccount.operations;
|
|
145
|
+
|
|
146
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
147
|
+
expect(latestOperation.type).toBe("OUT");
|
|
148
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
149
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const scenarioSendOptimizeSizeBtcTransaction: BitcoinScenarioTransaction = {
|
|
154
|
+
name: "Send Optimize Size BTC",
|
|
155
|
+
rbf: true,
|
|
156
|
+
utxoStrategy: {
|
|
157
|
+
strategy: 1,
|
|
158
|
+
excludeUTXOs: [],
|
|
159
|
+
},
|
|
160
|
+
amount: new BigNumber(1e6),
|
|
161
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
162
|
+
expect: (previousAccount, currentAccount) => {
|
|
163
|
+
const [latestOperation] = currentAccount.operations;
|
|
164
|
+
|
|
165
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
166
|
+
expect(latestOperation.type).toBe("OUT");
|
|
167
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
168
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
169
|
+
);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
const scenarioSendMergeCoinsBtcTransaction: BitcoinScenarioTransaction = {
|
|
173
|
+
name: "Send Merge Coins BTC",
|
|
174
|
+
rbf: true,
|
|
175
|
+
utxoStrategy: { strategy: 2, excludeUTXOs: [] },
|
|
176
|
+
amount: new BigNumber(1e6),
|
|
177
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
178
|
+
expect: (previousAccount, currentAccount) => {
|
|
179
|
+
const [latestOperation] = currentAccount.operations;
|
|
180
|
+
|
|
181
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
182
|
+
expect(latestOperation.type).toBe("OUT");
|
|
183
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
184
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const scenarioSendMaxBtcTransaction: BitcoinScenarioTransaction = {
|
|
189
|
+
name: "Send Max BTC",
|
|
190
|
+
rbf: false,
|
|
191
|
+
useAllAmount: true,
|
|
192
|
+
recipient: "bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz",
|
|
193
|
+
expect: (previousAccount, currentAccount) => {
|
|
194
|
+
const [latestOperation] = currentAccount.operations;
|
|
195
|
+
|
|
196
|
+
expect(currentAccount.operations.length - previousAccount.operations.length).toBe(1);
|
|
197
|
+
expect(latestOperation.type).toBe("OUT");
|
|
198
|
+
expect(currentAccount.balance.toFixed()).toBe(
|
|
199
|
+
previousAccount.balance.minus(latestOperation.value).toFixed(),
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
return [
|
|
204
|
+
scenarioExcludeTwoUtxos,
|
|
205
|
+
scenarioExcludeOneUtxo,
|
|
206
|
+
scenarioSendBtcTransaction,
|
|
207
|
+
scenarioSendFIFOBtcTransaction,
|
|
208
|
+
scenarioSendOptimizeSizeBtcTransaction,
|
|
209
|
+
scenarioSendMergeCoinsBtcTransaction,
|
|
210
|
+
scenarioSendFastFeesStrategyBtcTransaction,
|
|
211
|
+
scenarioSendSlowFeesStrategyBtcTransaction,
|
|
212
|
+
scenarioSendMaxBtcTransaction,
|
|
213
|
+
];
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const scenarioBitcoin: Scenario<BtcTransaction, BitcoinAccount> = {
|
|
217
|
+
name: "Ledger Live Basic Bitcoin Transactions",
|
|
218
|
+
setup: async () => {
|
|
219
|
+
const [{ getOnSpeculosConfirmation, transport }] = await Promise.all([
|
|
220
|
+
spawnSpeculos(`/${defaultNanoApp.firmware}/BitcoinTest/app_${defaultNanoApp.version}.elf`),
|
|
221
|
+
spawnAtlas(),
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
const signerContext: SignerContext = (_, crypto, fn) =>
|
|
225
|
+
fn(new Btc({ transport, currency: BITCOIN.id }));
|
|
226
|
+
|
|
227
|
+
const coinConfig: BitcoinCoinConfig = {
|
|
228
|
+
info: {
|
|
229
|
+
status: {
|
|
230
|
+
type: "active",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
setCoinConfig(() => ({ ...coinConfig }));
|
|
235
|
+
LiveConfig.setConfig({
|
|
236
|
+
config_currency_bitcoin_regtest: {
|
|
237
|
+
type: "object",
|
|
238
|
+
default: {
|
|
239
|
+
status: {
|
|
240
|
+
type: "active",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const onSignerConfirmation = getOnSpeculosConfirmation("Sign transaction");
|
|
247
|
+
const { accountBridge, currencyBridge } = createBridges(signerContext, () => coinConfig);
|
|
248
|
+
await currencyBridge.preload();
|
|
249
|
+
const BITCOIN = getCryptoCurrencyById("bitcoin_regtest");
|
|
250
|
+
const getAddress = resolver(signerContext);
|
|
251
|
+
// Can also test LEGACY here
|
|
252
|
+
const { address, publicKey } = await getAddress("", {
|
|
253
|
+
path: "49'/1'/0'/0/0",
|
|
254
|
+
currency: BITCOIN,
|
|
255
|
+
derivationMode: "segwit",
|
|
256
|
+
});
|
|
257
|
+
const { bitcoinLikeInfo } = BITCOIN;
|
|
258
|
+
const { XPUBVersion: xpubVersion } = bitcoinLikeInfo as {
|
|
259
|
+
XPUBVersion: number;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const xpub = await signerContext("", BITCOIN, signer =>
|
|
263
|
+
signer.getWalletXpub({
|
|
264
|
+
path: "49'/1'/0'",
|
|
265
|
+
xpubVersion,
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
const scenarioAccount = makeAccount(xpub, publicKey, address, BITCOIN, "segwit");
|
|
269
|
+
await loadWallet("coinTester");
|
|
270
|
+
// Need to wait 100 blocks to be able to spend coinbase UTXOs
|
|
271
|
+
await mineToWalletAddress("101");
|
|
272
|
+
// Fund it with 7 BTC in three send to have 3 UTXOs
|
|
273
|
+
await sendTo(address, 2);
|
|
274
|
+
await sendTo(address, 3);
|
|
275
|
+
await sendTo(address, 2);
|
|
276
|
+
return {
|
|
277
|
+
accountBridge,
|
|
278
|
+
currencyBridge,
|
|
279
|
+
account: scenarioAccount,
|
|
280
|
+
onSignerConfirmation,
|
|
281
|
+
retryLimit: 0,
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
getTransactions: () => makeScenarioTransactions(),
|
|
285
|
+
beforeAll: async account => {
|
|
286
|
+
firstUtxoHash = (account as BitcoinAccount).bitcoinResources.utxos[0].hash;
|
|
287
|
+
firstUtxoOutputIndex = (account as BitcoinAccount).bitcoinResources.utxos[0].outputIndex;
|
|
288
|
+
secondUtxoHash = (account as BitcoinAccount).bitcoinResources.utxos[1].hash;
|
|
289
|
+
secondUtxoOutputIndex = (account as BitcoinAccount).bitcoinResources.utxos[1].outputIndex;
|
|
290
|
+
},
|
|
291
|
+
afterEach: async () => {
|
|
292
|
+
// Mine 2 blocks after each transaction to confirm it
|
|
293
|
+
await mineToWalletAddress("2");
|
|
294
|
+
},
|
|
295
|
+
beforeEach: async () => {
|
|
296
|
+
// Make sure explorer is in sync before each transaction
|
|
297
|
+
await waitForExplorerSync();
|
|
298
|
+
},
|
|
299
|
+
beforeSync: async () => {
|
|
300
|
+
// Make sure explorer is in sync before sync
|
|
301
|
+
await waitForExplorerSync();
|
|
302
|
+
},
|
|
303
|
+
teardown: async () => {
|
|
304
|
+
await Promise.all([killSpeculos(), killAtlas()]);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import console from "console";
|
|
2
|
+
import { executeScenario } from "@ledgerhq/coin-tester/main";
|
|
3
|
+
import { killSpeculos } from "@ledgerhq/coin-tester/signers/speculos";
|
|
4
|
+
import { scenarioBitcoin } from "./scenarii/bitcoin";
|
|
5
|
+
import { killAtlas } from "./atlas";
|
|
6
|
+
|
|
7
|
+
global.console = console;
|
|
8
|
+
jest.setTimeout(300_000);
|
|
9
|
+
|
|
10
|
+
describe("Bitcoin Deterministic Tester", () => {
|
|
11
|
+
it("scenario Bitcoin", async () => {
|
|
12
|
+
try {
|
|
13
|
+
await executeScenario(scenarioBitcoin);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
if (e != "done") {
|
|
16
|
+
await Promise.all([killSpeculos(), killAtlas()]);
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
["exit", "SIGINT", "SIGQUIT", "SIGTERM", "SIGUSR1", "SIGUSR2", "uncaughtException"].map(e =>
|
|
24
|
+
process.on(e, async () => {
|
|
25
|
+
await Promise.all([killSpeculos(), killAtlas()]);
|
|
26
|
+
}),
|
|
27
|
+
);
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types";
|
|
2
|
+
import { getCurrentBlock } from "./helpers";
|
|
3
|
+
import network from "@ledgerhq/live-network/network";
|
|
4
|
+
|
|
5
|
+
function sleep(ms: number) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function waitForExplorerSync(
|
|
10
|
+
url: string = "http://localhost:9876/blockchain/v4/btc_regtest/block/current",
|
|
11
|
+
pollInterval: number = 2000,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
let explorerBlock = 0;
|
|
14
|
+
let nodeBlock = await getCurrentBlock();
|
|
15
|
+
let i = 0;
|
|
16
|
+
|
|
17
|
+
console.log(`🕓 FIRST Waiting for explorer to sync... ${i} ${explorerBlock}/${nodeBlock}`);
|
|
18
|
+
|
|
19
|
+
while (explorerBlock < nodeBlock) {
|
|
20
|
+
try {
|
|
21
|
+
const { data } = await network({
|
|
22
|
+
method: "GET",
|
|
23
|
+
url,
|
|
24
|
+
});
|
|
25
|
+
explorerBlock = data?.height ?? 0;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("❌ Error fetching explorer block:", error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
nodeBlock = await getCurrentBlock();
|
|
31
|
+
console.log(`🔁 Waiting for explorer to sync... ${i} ${explorerBlock}/${nodeBlock}`);
|
|
32
|
+
await sleep(pollInterval);
|
|
33
|
+
if (i > 100) {
|
|
34
|
+
throw new Error("Explorer sync timeout");
|
|
35
|
+
}
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`✅ Explorer is synced at block ${explorerBlock}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function findUtxo(account: BitcoinAccount, hash: string, index: number) {
|
|
43
|
+
return account.bitcoinResources.utxos.find(
|
|
44
|
+
utxo => utxo.hash === hash && utxo.outputIndex === index,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Utility to find UTXO that didn’t exist before (i.e., new change output)
|
|
49
|
+
export function findNewUtxo(previous: BitcoinAccount, current: BitcoinAccount) {
|
|
50
|
+
const prevSet = new Set(previous.bitcoinResources.utxos.map(u => `${u.hash}:${u.outputIndex}`));
|
|
51
|
+
return current.bitcoinResources.utxos.find(u => !prevSet.has(`${u.hash}:${u.outputIndex}`));
|
|
52
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"lib": ["es2020", "dom"],
|
|
5
|
+
"outDir": "lib",
|
|
6
|
+
"composite": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"downlevelIteration": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"resolveJsonModule": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*", "src/**/*.json"]
|
|
14
|
+
}
|