@ledgerhq/coin-tester-bitcoin 1.1.0-nightly.4 → 1.1.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -43
- package/lib/src/helpers.d.ts +2 -3
- package/lib/src/helpers.d.ts.map +1 -1
- package/lib/src/helpers.js +90 -41
- package/lib/src/helpers.js.map +1 -1
- package/lib/src/scenarii/bitcoin.d.ts.map +1 -1
- package/lib/src/scenarii/bitcoin.js +0 -35
- package/lib/src/scenarii/bitcoin.js.map +1 -1
- package/lib/src/utils.d.ts +0 -2
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +0 -12
- package/lib/src/utils.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib-es/src/helpers.d.ts +2 -3
- package/lib-es/src/helpers.d.ts.map +1 -1
- package/lib-es/src/helpers.js +87 -37
- package/lib-es/src/helpers.js.map +1 -1
- package/lib-es/src/scenarii/bitcoin.d.ts.map +1 -1
- package/lib-es/src/scenarii/bitcoin.js +2 -37
- package/lib-es/src/scenarii/bitcoin.js.map +1 -1
- package/lib-es/src/utils.d.ts +0 -2
- package/lib-es/src/utils.d.ts.map +1 -1
- package/lib-es/src/utils.js +2 -12
- package/lib-es/src/utils.js.map +1 -1
- package/lib-es/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/helpers.ts +96 -48
- package/src/scenarii/bitcoin.ts +2 -54
- package/src/scenarii.test.ts +1 -1
- package/src/utils.ts +2 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/coin-tester-bitcoin",
|
|
3
|
-
"version": "1.1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Ledger BTC Coin Tester",
|
|
5
5
|
"main": "src/scenarii.test.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"bignumber.js": "^9.1.2",
|
|
49
49
|
"bitcoin-core": "^5.0.0",
|
|
50
50
|
"docker-compose": "^1.1.0",
|
|
51
|
-
"@ledgerhq/coin-bitcoin": "^0.24.0
|
|
52
|
-
"@ledgerhq/coin-framework": "^6.8.0
|
|
53
|
-
"@ledgerhq/coin-tester": "^0.11.0
|
|
54
|
-
"@ledgerhq/cryptoassets": "^13.32.0-nightly.5",
|
|
51
|
+
"@ledgerhq/coin-bitcoin": "^0.24.0",
|
|
52
|
+
"@ledgerhq/coin-framework": "^6.8.0",
|
|
53
|
+
"@ledgerhq/coin-tester": "^0.11.0",
|
|
55
54
|
"@ledgerhq/live-config": "^3.2.0",
|
|
56
|
-
"@ledgerhq/live-network": "^2.1.0
|
|
57
|
-
"@ledgerhq/
|
|
58
|
-
"@ledgerhq/types-cryptoassets": "^7.
|
|
59
|
-
"@ledgerhq/
|
|
55
|
+
"@ledgerhq/live-network": "^2.1.0",
|
|
56
|
+
"@ledgerhq/cryptoassets": "^13.32.0",
|
|
57
|
+
"@ledgerhq/types-cryptoassets": "^7.30.0",
|
|
58
|
+
"@ledgerhq/hw-app-btc": "^10.12.0",
|
|
59
|
+
"@ledgerhq/types-live": "^6.88.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/jest": "^29.5.10",
|
package/src/helpers.ts
CHANGED
|
@@ -15,6 +15,7 @@ export async function loadWallet(name: string): Promise<void> {
|
|
|
15
15
|
console.log(`✅ Wallet "${name}" created and loaded:`, res);
|
|
16
16
|
|
|
17
17
|
// Optionally verify that the wallet is accessible
|
|
18
|
+
await client.getBalance({ minconf: 0 });
|
|
18
19
|
return;
|
|
19
20
|
} catch (error: any) {
|
|
20
21
|
const message = error?.message || "Unknown error";
|
|
@@ -248,66 +249,113 @@ export const sendRaw = async (recipientAddress: string, amount: number, sequence
|
|
|
248
249
|
}
|
|
249
250
|
};
|
|
250
251
|
|
|
251
|
-
export async
|
|
252
|
-
recipientAddress
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Send an RBF-enabled transaction
|
|
257
|
-
const txid = await client.sendToAddress(recipientAddress, amount, "", "", rbf);
|
|
258
|
-
|
|
259
|
-
// Log details
|
|
260
|
-
const mempoolEntry = await client.getMempoolEntry(txid);
|
|
261
|
-
console.log("BIP125 replaceable:", mempoolEntry["bip125-replaceable"]);
|
|
262
|
-
return txid;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function replaceTransaction(
|
|
266
|
-
originalTxid: string,
|
|
267
|
-
newRecipientAddress: string,
|
|
268
|
-
feeBumpPercent: number = 0.1, // 10% bump
|
|
269
|
-
): Promise<string> {
|
|
270
|
-
const rawTx = await client.getRawTransaction(originalTxid, true);
|
|
271
|
-
const input = rawTx.vin[0];
|
|
272
|
-
const originalOutputValue = rawTx.vout[0].value;
|
|
252
|
+
export const sendToReplaceCurrentTx = async (recipientAddress: string, amount: number) => {
|
|
253
|
+
if (!recipientAddress || amount <= 0) {
|
|
254
|
+
console.error("Invalid parameters: Provide a valid address and positive amount.");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
273
257
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
258
|
+
console.log(
|
|
259
|
+
`Sending replaceable transaction to ${recipientAddress} with amount ${amount} BTC...`,
|
|
260
|
+
);
|
|
277
261
|
|
|
278
262
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
console.warn("⚠️ Could not get mempool entry, using fallback feerate.");
|
|
284
|
-
}
|
|
263
|
+
// Step 1: Get an unspent UTXO
|
|
264
|
+
const unspent = await client.listUnspent({
|
|
265
|
+
query_options: { minimumSumAmount: amount },
|
|
266
|
+
});
|
|
285
267
|
|
|
286
|
-
|
|
287
|
-
|
|
268
|
+
if (!unspent.length) {
|
|
269
|
+
console.error("No suitable UTXOs available.");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
288
272
|
|
|
289
|
-
|
|
290
|
-
const adjustedAmount = Number(originalOutputValue - estimatedFee).toFixed(8);
|
|
273
|
+
const utxo = unspent[0];
|
|
291
274
|
|
|
292
|
-
|
|
293
|
-
|
|
275
|
+
// Step 2: Create the replaceable transaction (RBF enabled)
|
|
276
|
+
const rawTx1 = await client.createRawTransaction({
|
|
277
|
+
inputs: [
|
|
278
|
+
{
|
|
279
|
+
txid: utxo.txid,
|
|
280
|
+
vout: utxo.vout,
|
|
281
|
+
sequence: 4294967293, // RBF enabled
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
outputs: {
|
|
285
|
+
[recipientAddress]: amount,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Step 3: Fund & Sign the transaction
|
|
290
|
+
const fundedTx1 = await client.fundRawTransaction({
|
|
291
|
+
hexstring: rawTx1,
|
|
292
|
+
options: { feeRate: 0.0004 }, // Increase fee rate
|
|
293
|
+
});
|
|
294
|
+
const signedTx1 = await client.signRawTransactionWithWallet({
|
|
295
|
+
hexstring: fundedTx1.hex,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Step 4: Broadcast the transaction
|
|
299
|
+
const txId1 = await client.sendRawTransaction({ hexstring: signedTx1.hex });
|
|
300
|
+
console.log(`Transaction sent (TXID: ${txId1}), waiting before replacing...`);
|
|
301
|
+
console.log(`If you need to, make a tx that sends funds to ${recipientAddress}`);
|
|
302
|
+
} catch (error: any) {
|
|
303
|
+
console.error("Error in sendReplaceableTransaction:", error.message);
|
|
294
304
|
}
|
|
305
|
+
};
|
|
295
306
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
307
|
+
export const sendReplaceableTransaction = async (recipientAddress: string, amount: number) => {
|
|
308
|
+
if (!recipientAddress || amount <= 0) {
|
|
309
|
+
console.error("Invalid parameters: Provide a valid address and positive amount.");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
299
312
|
|
|
300
|
-
|
|
313
|
+
console.log(
|
|
314
|
+
`Sending replaceable transaction to ${recipientAddress} with amount ${amount} BTC...`,
|
|
315
|
+
);
|
|
301
316
|
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
try {
|
|
318
|
+
// Step 1: Get an unspent UTXO
|
|
319
|
+
const unspent = await client.listUnspent({
|
|
320
|
+
query_options: { minimumSumAmount: amount },
|
|
321
|
+
});
|
|
304
322
|
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
if (!unspent.length) {
|
|
324
|
+
console.error("No suitable UTXOs available.");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const utxo = unspent[0];
|
|
329
|
+
|
|
330
|
+
// Step 2: Create the first replaceable transaction (RBF enabled)
|
|
331
|
+
const rawTx1 = await client.createRawTransaction({
|
|
332
|
+
inputs: [
|
|
333
|
+
{
|
|
334
|
+
txid: utxo.txid,
|
|
335
|
+
vout: utxo.vout,
|
|
336
|
+
sequence: 4294967293, // RBF enabled
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
outputs: {
|
|
340
|
+
[recipientAddress]: amount,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Step 3: Fund & Sign the first transaction
|
|
345
|
+
const fundedTx1 = await client.fundRawTransaction({
|
|
346
|
+
hexstring: rawTx1,
|
|
347
|
+
options: { feeRate: 0.0002 }, // Increase fee rate
|
|
348
|
+
});
|
|
349
|
+
const signedTx1 = await client.signRawTransactionWithWallet({
|
|
350
|
+
hexstring: fundedTx1.hex,
|
|
351
|
+
});
|
|
307
352
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
353
|
+
// Step 4: Broadcast the first transaction
|
|
354
|
+
const txId1 = await client.sendRawTransaction({ hexstring: signedTx1.hex });
|
|
355
|
+
console.log(`First transaction sent (TXID: ${txId1}), waiting before replacing...`);
|
|
356
|
+
} catch (error: any) {
|
|
357
|
+
console.error("Error in sendReplaceableTransaction:", error.message);
|
|
358
|
+
}
|
|
311
359
|
};
|
|
312
360
|
|
|
313
361
|
/*Available commands:
|
package/src/scenarii/bitcoin.ts
CHANGED
|
@@ -10,17 +10,10 @@ import { SignerContext } from "@ledgerhq/coin-bitcoin/signer";
|
|
|
10
10
|
import { BitcoinConfigInfo, setCoinConfig } from "@ledgerhq/coin-bitcoin/config";
|
|
11
11
|
import { BigNumber } from "bignumber.js";
|
|
12
12
|
import { defaultNanoApp } from "../constants";
|
|
13
|
-
import {
|
|
14
|
-
loadWallet,
|
|
15
|
-
mineToWalletAddress,
|
|
16
|
-
sendTo,
|
|
17
|
-
sendTransaction,
|
|
18
|
-
replaceTransaction,
|
|
19
|
-
getRawMempool,
|
|
20
|
-
} from "../helpers";
|
|
13
|
+
import { loadWallet, mineToWalletAddress, sendTo } from "../helpers";
|
|
21
14
|
import { makeAccount } from "../fixtures";
|
|
22
15
|
import { killAtlas, spawnAtlas } from "../atlas";
|
|
23
|
-
import { findNewUtxo, waitForExplorerSync
|
|
16
|
+
import { findNewUtxo, waitForExplorerSync } from "../utils";
|
|
24
17
|
import {
|
|
25
18
|
assertCommonTxProperties,
|
|
26
19
|
assertUtxoExcluded,
|
|
@@ -220,46 +213,6 @@ const makeScenarioTransactions = (): BitcoinScenarioTransaction[] => {
|
|
|
220
213
|
];
|
|
221
214
|
};
|
|
222
215
|
|
|
223
|
-
// TODO: RBF and CTFP scenarios are not supported on Ledger Live yet That is why they are made using regtest for now
|
|
224
|
-
// TODO: once RBF and CTFP are supported natively in Ledger Live, we can move these scenarios to use bitcoin module instead of regtest helpers
|
|
225
|
-
const makeInternalScenarioTransactions = async () => {
|
|
226
|
-
const scenarioReplaceBtcTransaction: BitcoinScenarioTransaction = {
|
|
227
|
-
name: "Send Replace BTC transaction",
|
|
228
|
-
expect: async (previousAccount, currentAccount) => {
|
|
229
|
-
const txId = await sendTransaction((currentAccount as BitcoinAccount).freshAddress, 0.003);
|
|
230
|
-
// Waiting a bit before replacing...
|
|
231
|
-
await waitForTxInMempool(txId, 10000); // waits up to 10s
|
|
232
|
-
|
|
233
|
-
const replacementTxid = await replaceTransaction(
|
|
234
|
-
txId,
|
|
235
|
-
(currentAccount as BitcoinAccount).freshAddress,
|
|
236
|
-
);
|
|
237
|
-
const mempool = await getRawMempool();
|
|
238
|
-
expect(mempool.includes(txId)).toBe(false);
|
|
239
|
-
expect(mempool.includes(replacementTxid)).toBe(true);
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
const scenarioCancelBtcTransaction: BitcoinScenarioTransaction = {
|
|
243
|
-
name: "Send Cancel BTC transaction",
|
|
244
|
-
expect: async (previousAccount, currentAccount) => {
|
|
245
|
-
await mineToWalletAddress("2");
|
|
246
|
-
const txId = await sendTransaction("bcrt1qajglhjtctn88f5l6rajzz52fy78fhxspjajjwz", 0.003);
|
|
247
|
-
// Waiting a bit before replacing...
|
|
248
|
-
await waitForTxInMempool(txId, 10000); // waits up to 10s
|
|
249
|
-
|
|
250
|
-
const replacementTxid = await replaceTransaction(
|
|
251
|
-
txId,
|
|
252
|
-
(currentAccount as BitcoinAccount).freshAddress,
|
|
253
|
-
);
|
|
254
|
-
const mempool = await getRawMempool();
|
|
255
|
-
expect(mempool.includes(txId)).toBe(false);
|
|
256
|
-
expect(mempool.includes(replacementTxid)).toBe(true);
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
return [scenarioReplaceBtcTransaction, scenarioCancelBtcTransaction];
|
|
261
|
-
};
|
|
262
|
-
|
|
263
216
|
export const scenarioBitcoin: Scenario<BtcTransaction, BitcoinAccount> = {
|
|
264
217
|
name: "Ledger Live Basic Bitcoin Transactions",
|
|
265
218
|
setup: async () => {
|
|
@@ -329,7 +282,6 @@ export const scenarioBitcoin: Scenario<BtcTransaction, BitcoinAccount> = {
|
|
|
329
282
|
};
|
|
330
283
|
},
|
|
331
284
|
getTransactions: () => makeScenarioTransactions(),
|
|
332
|
-
getInternalTransactions: () => makeInternalScenarioTransactions(),
|
|
333
285
|
beforeAll: async account => {
|
|
334
286
|
firstUtxoHash = (account as BitcoinAccount).bitcoinResources.utxos[0].hash;
|
|
335
287
|
firstUtxoOutputIndex = (account as BitcoinAccount).bitcoinResources.utxos[0].outputIndex;
|
|
@@ -339,10 +291,6 @@ export const scenarioBitcoin: Scenario<BtcTransaction, BitcoinAccount> = {
|
|
|
339
291
|
afterEach: async () => {
|
|
340
292
|
// Mine 2 blocks after each transaction to confirm it
|
|
341
293
|
await mineToWalletAddress("2");
|
|
342
|
-
await waitForExplorerSync();
|
|
343
|
-
},
|
|
344
|
-
afterAll: async account => {
|
|
345
|
-
expect(account.operations.length).toBeGreaterThanOrEqual(14);
|
|
346
294
|
},
|
|
347
295
|
beforeEach: async () => {
|
|
348
296
|
// Make sure explorer is in sync before each transaction
|
package/src/scenarii.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { scenarioBitcoin } from "./scenarii/bitcoin";
|
|
|
5
5
|
import { killAtlas } from "./atlas";
|
|
6
6
|
|
|
7
7
|
global.console = console;
|
|
8
|
-
jest.setTimeout(
|
|
8
|
+
jest.setTimeout(300_000);
|
|
9
9
|
|
|
10
10
|
describe("Bitcoin Deterministic Tester", () => {
|
|
11
11
|
it("scenario Bitcoin", async () => {
|
package/src/utils.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types";
|
|
2
|
-
import { getCurrentBlock
|
|
2
|
+
import { getCurrentBlock } from "./helpers";
|
|
3
3
|
import network from "@ledgerhq/live-network/network";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
function sleep(ms: number) {
|
|
6
6
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export async function waitForTxInMempool(txId: string, timeout = 10000) {
|
|
10
|
-
const start = Date.now();
|
|
11
|
-
while (Date.now() - start < timeout) {
|
|
12
|
-
const mempool = await getRawMempool();
|
|
13
|
-
if (mempool.includes(txId)) return true;
|
|
14
|
-
await sleep(500);
|
|
15
|
-
}
|
|
16
|
-
throw new Error(`Transaction ${txId} not found in mempool after ${timeout}ms`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
export async function waitForExplorerSync(
|
|
20
10
|
url: string = "http://localhost:9876/blockchain/v4/btc_regtest/block/current",
|
|
21
11
|
pollInterval: number = 2000,
|