@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,76 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:15
|
|
4
|
+
cpus: 1
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_USER: username
|
|
7
|
+
POSTGRES_PASSWORD: password
|
|
8
|
+
volumes:
|
|
9
|
+
- "./postgres:/docker-entrypoint-initdb.d"
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD-SHELL", "pg_isready -U username"]
|
|
12
|
+
interval: 10s
|
|
13
|
+
timeout: 5s
|
|
14
|
+
retries: 5
|
|
15
|
+
btcliked:
|
|
16
|
+
image: jfrog.ledgerlabs.net/bbs-oci-prod-green/bitcoin-node:28.0
|
|
17
|
+
platform: linux/amd64
|
|
18
|
+
command: /app/bitcoin/bin/bitcoind -datadir=/config -regtest -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 -rpcuser=user -rpcpassword=pass -fallbackfee=0.001 -deprecatedrpc=addresses -txindex -zmqpubsequence=tcp://0.0.0.0:19999
|
|
19
|
+
ports:
|
|
20
|
+
- "18443:18443" # exposes RPC
|
|
21
|
+
atlas-pending:
|
|
22
|
+
image: jfrog.ledgerlabs.net/bbs-oci-prod-green/atlas-bitcoin-pending-v2:rolling
|
|
23
|
+
platform: linux/amd64
|
|
24
|
+
cpus: 1
|
|
25
|
+
pull_policy: always
|
|
26
|
+
command: /app/bin/atlas /config/pending.conf
|
|
27
|
+
volumes:
|
|
28
|
+
- "./atlas:/config"
|
|
29
|
+
depends_on:
|
|
30
|
+
rabbitmq:
|
|
31
|
+
condition: service_healthy
|
|
32
|
+
btcliked:
|
|
33
|
+
condition: service_started
|
|
34
|
+
restart: on-failure
|
|
35
|
+
healthcheck:
|
|
36
|
+
test: curl --fail http://localhost:9877/_health || exit 1
|
|
37
|
+
interval: 10s
|
|
38
|
+
retries: 5
|
|
39
|
+
timeout: 5s
|
|
40
|
+
atlas:
|
|
41
|
+
image: jfrog.ledgerlabs.net/bbs-oci-prod-green/atlas-bitcoin-v2:rolling
|
|
42
|
+
platform: linux/amd64
|
|
43
|
+
cpus: 1
|
|
44
|
+
pull_policy: always
|
|
45
|
+
command: /app/bin/atlas /config/bitcoin.conf
|
|
46
|
+
volumes:
|
|
47
|
+
- "./atlas:/config"
|
|
48
|
+
depends_on:
|
|
49
|
+
postgres:
|
|
50
|
+
condition: service_healthy
|
|
51
|
+
atlas-pending:
|
|
52
|
+
condition: service_started
|
|
53
|
+
btcliked:
|
|
54
|
+
condition: service_started
|
|
55
|
+
restart: on-failure
|
|
56
|
+
healthcheck:
|
|
57
|
+
test: curl --fail http://localhost:9877/_health || exit 1
|
|
58
|
+
interval: 10s
|
|
59
|
+
retries: 5
|
|
60
|
+
timeout: 5s
|
|
61
|
+
rabbitmq:
|
|
62
|
+
cpus: 1
|
|
63
|
+
image: rabbitmq:3
|
|
64
|
+
healthcheck:
|
|
65
|
+
test: rabbitmq-diagnostics -q ping
|
|
66
|
+
interval: 5s
|
|
67
|
+
timeout: 30s
|
|
68
|
+
retries: 10
|
|
69
|
+
atlas-nginx:
|
|
70
|
+
image: nginx:latest
|
|
71
|
+
volumes:
|
|
72
|
+
- "./nginx:/etc/nginx/conf.d/"
|
|
73
|
+
depends_on:
|
|
74
|
+
- atlas
|
|
75
|
+
ports:
|
|
76
|
+
- "9876:9876"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
upstream atlas {
|
|
2
|
+
server atlas:9876;
|
|
3
|
+
keepalive 20;
|
|
4
|
+
}
|
|
5
|
+
upstream atlas-pending {
|
|
6
|
+
server atlas-pending:9876;
|
|
7
|
+
keepalive 20;
|
|
8
|
+
}
|
|
9
|
+
server {
|
|
10
|
+
listen 9876;
|
|
11
|
+
listen [::]:9876;
|
|
12
|
+
server_name localhost;
|
|
13
|
+
|
|
14
|
+
location / {
|
|
15
|
+
proxy_http_version 1.1;
|
|
16
|
+
proxy_set_header Host $host;
|
|
17
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
18
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
19
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
20
|
+
proxy_set_header Connection "";
|
|
21
|
+
proxy_read_timeout 300;
|
|
22
|
+
proxy_buffering off;
|
|
23
|
+
|
|
24
|
+
proxy_pass http://atlas; # NOTE: no trailing slash -> preserves the full incoming path
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
error_page 500 502 503 504 /50x.html;
|
|
28
|
+
location = /50x.html {
|
|
29
|
+
root /usr/share/nginx/html;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/fixtures.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* instanbul ignore file: don't test fixtures */
|
|
2
|
+
|
|
3
|
+
import BigNumber from "bignumber.js";
|
|
4
|
+
import { getDerivationScheme, runDerivationScheme } from "@ledgerhq/coin-framework/derivation";
|
|
5
|
+
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
6
|
+
import { DerivationMode } from "@ledgerhq/types-live";
|
|
7
|
+
import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/types";
|
|
8
|
+
|
|
9
|
+
export const makeAccount = (
|
|
10
|
+
xpub: string,
|
|
11
|
+
publicKey: string,
|
|
12
|
+
address: string,
|
|
13
|
+
currency: CryptoCurrency,
|
|
14
|
+
derivationMode: DerivationMode,
|
|
15
|
+
): BitcoinAccount => {
|
|
16
|
+
const id = `js:2:${currency.id}:${xpub}:${derivationMode}`;
|
|
17
|
+
const scheme = getDerivationScheme({
|
|
18
|
+
derivationMode: derivationMode as DerivationMode,
|
|
19
|
+
currency,
|
|
20
|
+
});
|
|
21
|
+
const index = 0;
|
|
22
|
+
const freshAddressPath = runDerivationScheme(scheme, currency, {
|
|
23
|
+
account: index,
|
|
24
|
+
node: 0,
|
|
25
|
+
address: 0,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
type: "Account",
|
|
30
|
+
id: id,
|
|
31
|
+
seedIdentifier: publicKey,
|
|
32
|
+
derivationMode: derivationMode,
|
|
33
|
+
index: 0,
|
|
34
|
+
freshAddress: address,
|
|
35
|
+
freshAddressPath: freshAddressPath,
|
|
36
|
+
used: true,
|
|
37
|
+
balance: new BigNumber(0),
|
|
38
|
+
spendableBalance: new BigNumber(0),
|
|
39
|
+
creationDate: new Date(),
|
|
40
|
+
blockHeight: 0,
|
|
41
|
+
currency,
|
|
42
|
+
operationsCount: 0,
|
|
43
|
+
operations: [],
|
|
44
|
+
pendingOperations: [],
|
|
45
|
+
lastSyncDate: new Date(),
|
|
46
|
+
balanceHistoryCache: {
|
|
47
|
+
HOUR: {
|
|
48
|
+
latestDate: null,
|
|
49
|
+
balances: [],
|
|
50
|
+
},
|
|
51
|
+
DAY: {
|
|
52
|
+
latestDate: null,
|
|
53
|
+
balances: [],
|
|
54
|
+
},
|
|
55
|
+
WEEK: {
|
|
56
|
+
latestDate: null,
|
|
57
|
+
balances: [],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
swapHistory: [],
|
|
61
|
+
|
|
62
|
+
bitcoinResources: {
|
|
63
|
+
utxos: [],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import Client from "bitcoin-core";
|
|
2
|
+
|
|
3
|
+
// fetched from bitcoin.conf
|
|
4
|
+
const client: any = new Client({
|
|
5
|
+
version: "0.24.1",
|
|
6
|
+
username: "user",
|
|
7
|
+
password: "pass",
|
|
8
|
+
host: "http://localhost:18443",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function loadWallet(name: string): Promise<void> {
|
|
12
|
+
try {
|
|
13
|
+
// Try creating and loading the wallet
|
|
14
|
+
const res = await client.command("createwallet", name);
|
|
15
|
+
console.log(`✅ Wallet "${name}" created and loaded:`, res);
|
|
16
|
+
|
|
17
|
+
// Optionally verify that the wallet is accessible
|
|
18
|
+
await client.getBalance({ minconf: 0 });
|
|
19
|
+
return;
|
|
20
|
+
} catch (error: any) {
|
|
21
|
+
const message = error?.message || "Unknown error";
|
|
22
|
+
|
|
23
|
+
// Wallet already exists → try loading it
|
|
24
|
+
if (message.includes("already exists")) {
|
|
25
|
+
console.log(`ℹ️ Wallet "${name}" already exists. Attempting to load...`);
|
|
26
|
+
return loadExistingWallet(name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.error(`❌ Failed to create wallet "${name}":`, message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Attempt to load an existing wallet, handling "already loaded" gracefully.
|
|
35
|
+
*/
|
|
36
|
+
async function loadExistingWallet(name: string): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
await client.command("loadwallet", name);
|
|
39
|
+
console.log(`✅ Wallet "${name}" loaded successfully.`);
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
const message = error?.message || "Unknown error";
|
|
42
|
+
|
|
43
|
+
if (message.includes("already loaded")) {
|
|
44
|
+
console.log(`ℹ️ Wallet "${name}" is already loaded.`);
|
|
45
|
+
} else {
|
|
46
|
+
console.error(`❌ Failed to load existing wallet "${name}":`, message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function mineToWalletAddress(param: string) {
|
|
52
|
+
const address = await client.getNewAddress();
|
|
53
|
+
const nbBlocks = parseInt(param);
|
|
54
|
+
await client.generateToAddress({
|
|
55
|
+
nblocks: nbBlocks,
|
|
56
|
+
address,
|
|
57
|
+
});
|
|
58
|
+
console.log(`Mined ${nbBlocks} blocks to: ${address}`);
|
|
59
|
+
}
|
|
60
|
+
const FEES = 0.0001;
|
|
61
|
+
|
|
62
|
+
export const mineBlock = async (address: string) => {
|
|
63
|
+
await client.generateToAddress({
|
|
64
|
+
nblocks: 1,
|
|
65
|
+
address,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const getCurrentBlock = async () => {
|
|
70
|
+
return await client.command("getblockcount");
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Note that it sets the input sequence to 4294967293, <=0xFFFFFFFD — Replace By Fee (RBF).
|
|
75
|
+
*/
|
|
76
|
+
export const sendTo = async (recipientAddress: string, amount: number) => {
|
|
77
|
+
await client.getBalance({ minconf: 0 });
|
|
78
|
+
if (!recipientAddress || amount <= 0) {
|
|
79
|
+
console.error("Invalid parameters: Provide a valid recipient address and positive amount.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(`Sending ${amount} BTC to ${recipientAddress}...`);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Step 1: Send the specified amount to the recipient address
|
|
87
|
+
const txSendToAddress = await client
|
|
88
|
+
.sendToAddress({
|
|
89
|
+
address: recipientAddress,
|
|
90
|
+
amount: amount,
|
|
91
|
+
})
|
|
92
|
+
.then((txid: string) => txid);
|
|
93
|
+
|
|
94
|
+
// Step 2: Fetch transaction details
|
|
95
|
+
await client.getTransaction({
|
|
96
|
+
txid: txSendToAddress,
|
|
97
|
+
verbose: true,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Step 4: Fetch updated transaction details after confirmation
|
|
101
|
+
await client.getTransaction({
|
|
102
|
+
txid: txSendToAddress,
|
|
103
|
+
verbose: true,
|
|
104
|
+
});
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
console.error("Error in sendToAddress:", error.message);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
export const sendToMany = async (recipientAddress: string, amount: number, times: number) => {
|
|
110
|
+
// calls sendTo times times
|
|
111
|
+
for (let i = 0; i < times; i++) {
|
|
112
|
+
await sendTo(recipientAddress, amount);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const sendAutomatedRaw = async (destinationAddress: string, amount: number) => {
|
|
117
|
+
if (!destinationAddress || amount <= 0) {
|
|
118
|
+
console.error("Invalid parameters: Provide a valid address and positive amount.");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Step 1: Create an unfinished raw transaction (no inputs, outputs only)
|
|
124
|
+
const unfinishedTx = await client.createRawTransaction({
|
|
125
|
+
inputs: [],
|
|
126
|
+
outputs: {
|
|
127
|
+
[destinationAddress]: amount,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Step 2: Fund the transaction (Bitcoin Core automatically selects UTXOs)
|
|
132
|
+
const fundedTx = await client.fundRawTransaction({
|
|
133
|
+
hexstring: unfinishedTx,
|
|
134
|
+
options: { replaceable: true }, // Set to true if RBF is needed
|
|
135
|
+
// if replaceable: false, sequence of vin set to 4294967294 (0xFFFFFFFE) // Locktime BUT non-rbf
|
|
136
|
+
// else, sets to: 4294967293 // Locktime & RBF
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Step 3: Decode the transaction for debugging
|
|
140
|
+
await client.decodeRawTransaction({
|
|
141
|
+
hexstring: fundedTx.hex,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Step 4: Sign the transaction
|
|
145
|
+
const signedTxHex = await client.signRawTransactionWithWallet({
|
|
146
|
+
hexstring: fundedTx.hex,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Step 5: Broadcast the transaction to the network
|
|
150
|
+
const transactionId = await client.sendRawTransaction({
|
|
151
|
+
hexstring: signedTxHex.hex,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
console.log(`Transaction Broadcasted! TXID: ${transactionId}`);
|
|
155
|
+
} catch (error: any) {
|
|
156
|
+
console.error("Error sending transaction:", error.message);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* https://learnmeabitcoin.com/technical/transaction/input/sequence/
|
|
162
|
+
* NOTE: You only need to set one of the sequence fields to enable locktime or RBF
|
|
163
|
+
* (even if you have multiple inputs and sequence fields in one transaction)
|
|
164
|
+
* However, relative locktime settings are specific to each input.
|
|
165
|
+
*
|
|
166
|
+
* If set to 0xFFFFFFFE (4294967294) → Locktime / Non-RBF
|
|
167
|
+
* If set to a number ≤ 0x0000FFFF (65535) → Blocks-based timelock.
|
|
168
|
+
* Sequence Effect
|
|
169
|
+
* 0xFFFFFFFE (4294967294) Default (Non-RBF): Cannot be replaced
|
|
170
|
+
* 0xFFFFFFFD (4294967293) Opt-in RBF: Can be replaced by a higher fee transaction
|
|
171
|
+
*
|
|
172
|
+
*
|
|
173
|
+
* Called like this as you've got more control over all the inputs
|
|
174
|
+
*/
|
|
175
|
+
export const sendRaw = async (recipientAddress: string, amount: number, sequence = 4294967294) => {
|
|
176
|
+
if (!recipientAddress || amount <= 0) {
|
|
177
|
+
console.error("Invalid parameters: Provide a valid address and positive amount.");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(
|
|
182
|
+
`Creating raw transaction: Sending ${amount} BTC to ${recipientAddress} with sequence=${sequence}...`,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Step 2: Get new addresses
|
|
187
|
+
const newAddress = await client.getNewAddress({ address_type: "legacy" });
|
|
188
|
+
const changeAddress = await client.getRawChangeAddress({
|
|
189
|
+
address_type: "legacy",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Step 3: Get UTXO for spending
|
|
193
|
+
const unspents = await client.listUnspent({});
|
|
194
|
+
let id = 0;
|
|
195
|
+
let unspent = unspents[id];
|
|
196
|
+
|
|
197
|
+
while (unspent.amount < amount) {
|
|
198
|
+
console.warn("Insufficient funds. Picking another UTXO.");
|
|
199
|
+
id++;
|
|
200
|
+
unspent = unspents[id];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Step 4: Calculate change
|
|
204
|
+
const changeAmountStr = Number(unspent.amount - amount - FEES).toFixed(6);
|
|
205
|
+
const changeAmount = Number(changeAmountStr);
|
|
206
|
+
|
|
207
|
+
if (changeAmount < 0) {
|
|
208
|
+
console.warn("Insufficient funds after fees.");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Step 5: Create raw transaction
|
|
213
|
+
const rawTxHex = await client.createRawTransaction({
|
|
214
|
+
inputs: [
|
|
215
|
+
{
|
|
216
|
+
txid: unspent.txid,
|
|
217
|
+
vout: unspent.vout,
|
|
218
|
+
sequence: sequence,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
outputs: {
|
|
222
|
+
[recipientAddress]: amount,
|
|
223
|
+
[changeAddress]: changeAmount,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await client.decodeRawTransaction({
|
|
228
|
+
hexstring: rawTxHex,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Step 6: Sign the transaction
|
|
232
|
+
const signedTxHex = await client.signRawTransactionWithWallet({
|
|
233
|
+
hexstring: rawTxHex,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Step 7: Broadcast the transaction
|
|
237
|
+
const txId = await client.sendRawTransaction({
|
|
238
|
+
hexstring: signedTxHex.hex,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Step 8: Fetch transaction details
|
|
242
|
+
await client.getTransaction({ txid: txId, verbose: true });
|
|
243
|
+
|
|
244
|
+
// Step 9: Mine a block to confirm the transaction
|
|
245
|
+
await mineBlock(newAddress);
|
|
246
|
+
} catch (error: any) {
|
|
247
|
+
console.error("Error in sendRaw:", error.message);
|
|
248
|
+
console.error({ error });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
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
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log(
|
|
259
|
+
`Sending replaceable transaction to ${recipientAddress} with amount ${amount} BTC...`,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// Step 1: Get an unspent UTXO
|
|
264
|
+
const unspent = await client.listUnspent({
|
|
265
|
+
query_options: { minimumSumAmount: amount },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!unspent.length) {
|
|
269
|
+
console.error("No suitable UTXOs available.");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const utxo = unspent[0];
|
|
274
|
+
|
|
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);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
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
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(
|
|
314
|
+
`Sending replaceable transaction to ${recipientAddress} with amount ${amount} BTC...`,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
// Step 1: Get an unspent UTXO
|
|
319
|
+
const unspent = await client.listUnspent({
|
|
320
|
+
query_options: { minimumSumAmount: amount },
|
|
321
|
+
});
|
|
322
|
+
|
|
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
|
+
});
|
|
352
|
+
|
|
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
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/*Available commands:
|
|
362
|
+
mineBlock <address> - Mine a block to the address
|
|
363
|
+
sendTo <address> <amount> - Send a transaction to an address
|
|
364
|
+
sendToMany <address> <amount> <times> - Send a transaction to an address
|
|
365
|
+
replaceTx <address> <amount> - Send a transaction and replace it using RBF
|
|
366
|
+
sendAutomatedRaw <address> <amount> - Send a raw transaction, automatically funded
|
|
367
|
+
sendRaw <address> <amount> [sequence] - Send a raw transaction with a custom sequence
|
|
368
|
+
sendRawTwoOutputs <address1> <address2> <amount> [sequence] - Send a raw transaction with a custom sequence to 2 addresses
|
|
369
|
+
# doubleSpend <address> - Attempt a double-spend attack
|
|
370
|
+
# dustTransaction <address> - Create a dust transaction
|
|
371
|
+
# multisig <address> - Test a multisig transaction`;
|
|
372
|
+
*/
|