mirror of
https://github.com/aljazceru/cyphernode.git
synced 2025-12-17 04:35:14 +01:00
First pass on watchxpub
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
# Watcher can:
|
||||
action_watch=watcher
|
||||
action_unwatch=watcher
|
||||
action_watchxpub=watcher
|
||||
action_getactivewatches=watcher
|
||||
action_getbestblockhash=watcher
|
||||
action_getbestblockinfo=watcher
|
||||
|
||||
@@ -157,5 +157,5 @@ id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(ech
|
||||
echo "GET /getbestblockinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
|
||||
echo "GET /getbalance" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
|
||||
echo "GET /ln_getinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
|
||||
docker exec -it `docker ps -q -f name=cyphernodestack_cyphernode` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
|
||||
docker exec -it `docker ps -q -f name=cyphernode_proxy` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
|
||||
```
|
||||
|
||||
@@ -40,6 +40,7 @@ services:
|
||||
- "WATCHER_BTC_NODE_PRUNED=false"
|
||||
- "OTSCLIENT_CONTAINER=otsclient:6666"
|
||||
- "OTS_FILES=/proxy/otsfiles"
|
||||
- "XPUB_DERIVATION_GAP=100"
|
||||
image: cyphernode/proxy:latest
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -23,6 +23,7 @@ const defaultAPIProperties = `
|
||||
# Watcher can:
|
||||
action_watch=watcher
|
||||
action_unwatch=watcher
|
||||
action_watchxpub=watcher
|
||||
action_getactivewatches=watcher
|
||||
action_getbestblockhash=watcher
|
||||
action_getbestblockinfo=watcher
|
||||
|
||||
@@ -30,10 +30,12 @@ services:
|
||||
# Bitcoin Mini Proxy
|
||||
environment:
|
||||
- "TRACING=1"
|
||||
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet/watching01.dat"
|
||||
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
|
||||
- "WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat"
|
||||
- "WATCHER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
|
||||
- "WATCHER_BTC_NODE_RPC_CFG=/tmp/watcher_btcnode_curlcfg.properties"
|
||||
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet/spending01.dat"
|
||||
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
|
||||
- "SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat"
|
||||
- "SPENDER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
|
||||
- "SPENDER_BTC_NODE_RPC_CFG=/tmp/spender_btcnode_curlcfg.properties"
|
||||
- "PROXY_LISTENING_PORT=8888"
|
||||
@@ -47,6 +49,7 @@ services:
|
||||
- "WATCHER_BTC_NODE_PRUNED=<%= bitcoin_prune?'true':'false' %>"
|
||||
- "OTSCLIENT_CONTAINER=otsclient:6666"
|
||||
- "OTS_FILES=/proxy/otsfiles"
|
||||
- "XPUB_DERIVATION_GAP=100"
|
||||
image: cyphernode/proxy:<%= proxy_version %>
|
||||
<% if ( devmode ) { %>
|
||||
ports:
|
||||
|
||||
@@ -42,6 +42,7 @@ OTS_CONTAINER=otsnode:6666
|
||||
DERIVATION_PUB32=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
|
||||
DERIVATION_PATH=0/n
|
||||
WATCHER_BTC_NODE_PRUNED=false
|
||||
XPUB_DERIVATION_GAP=100
|
||||
```
|
||||
|
||||
## Choose the right architecture
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE watching_by_pub32 (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
pub32 TEXT UNIQUE,
|
||||
label TEXT UNIQUE,
|
||||
derivation_path TEXT,
|
||||
callback0conf TEXT,
|
||||
callback1conf TEXT,
|
||||
last_imported_n INTEGER,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_by_pub32_pub32 ON watching_by_pub32 (pub32);
|
||||
CREATE INDEX idx_watching_by_pub32_label ON watching_by_pub32 (label);
|
||||
|
||||
CREATE TABLE watching (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
address TEXT,
|
||||
@@ -9,6 +23,8 @@ CREATE TABLE watching (
|
||||
callback1conf TEXT,
|
||||
calledback1conf INTEGER DEFAULT FALSE,
|
||||
imported INTEGER DEFAULT FALSE,
|
||||
watching_by_pub32_id INTEGER REFERENCES watching_by_pub32,
|
||||
pub32_index INTEGER,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_address ON watching (address);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Checking for OTS support in DB..."
|
||||
sqlite3 db/proxydb ".tables" | grep "stamp" > /dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
# stamp not there, we have to migrate
|
||||
echo "Migrating database from v0 to v0.1..."
|
||||
echo "Migrating database for OTS support..."
|
||||
cat sqlmigrate20181213_0-0.1.sql | sqlite3 $DB_FILE
|
||||
else
|
||||
echo "Database v0 to v0.1 migration already done, skipping!"
|
||||
echo "Database OTS support migration already done, skipping!"
|
||||
fi
|
||||
|
||||
11
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sh
Normal file
11
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Checking for watch by xpub support in DB..."
|
||||
sqlite3 db/proxydb ".tables" | grep "watching_by_pub32" > /dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
# watching_by_pub32 not there, we have to migrate
|
||||
echo "Migrating database for watch by xpub support..."
|
||||
cat sqlmigrate20190130_0.1-0.2.sql | sqlite3 $DB_FILE
|
||||
else
|
||||
echo "Database watch by xpub support migration already done, skipping!"
|
||||
fi
|
||||
18
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sql
Normal file
18
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE watching_by_pub32 (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
pub32 TEXT UNIQUE,
|
||||
label TEXT UNIQUE,
|
||||
derivation_path TEXT,
|
||||
callback0conf TEXT,
|
||||
callback1conf TEXT,
|
||||
last_imported_n INTEGER,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_by_pub32_pub32 ON watching_by_pub32 (pub32);
|
||||
CREATE INDEX idx_watching_by_pub32_label ON watching_by_pub32 (label);
|
||||
|
||||
ALTER TABLE watching ADD COLUMN watching_by_pub32_id INTEGER REFERENCES watching_by_pub32;
|
||||
ALTER TABLE watching ADD COLUMN pub32_index INTEGER;
|
||||
@@ -21,6 +21,15 @@ deriveindex()
|
||||
return $?
|
||||
}
|
||||
|
||||
derivepubpath() {
|
||||
trace "Entering derivepubpath()..."
|
||||
|
||||
# {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
|
||||
send_to_pycoin $1
|
||||
return $?
|
||||
}
|
||||
|
||||
send_to_pycoin()
|
||||
{
|
||||
trace "Entering send_to_pycoin()..."
|
||||
|
||||
@@ -11,7 +11,7 @@ do_callbacks()
|
||||
trace "Entering do_callbacks()..."
|
||||
|
||||
# Let's fetch all the watching addresses still being watched but not called back
|
||||
local callbacks=$(sql 'SELECT DISTINCT callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id WHERE NOT calledback0conf and watching_id NOT NULL and callback0conf NOT NULL and watching')
|
||||
local callbacks=$(sql 'SELECT DISTINCT w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE NOT calledback0conf AND watching_id NOT NULL AND w.callback0conf NOT NULL AND w.watching')
|
||||
trace "[do_callbacks] callbacks0conf=${callbacks}"
|
||||
|
||||
local returncode
|
||||
@@ -29,7 +29,7 @@ do_callbacks()
|
||||
fi
|
||||
done
|
||||
|
||||
callbacks=$(sql 'SELECT DISTINCT callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w, watching_tx wt, tx t WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf and confirmations>0 and callback1conf NOT NULL and watching')
|
||||
callbacks=$(sql 'SELECT DISTINCT w.callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path FROM watching w, watching_tx wt, tx t LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf AND confirmations>0 AND w.callback1conf NOT NULL AND w.watching')
|
||||
trace "[do_callbacks] callbacks1conf=${callbacks}"
|
||||
|
||||
for row in ${callbacks}
|
||||
@@ -66,6 +66,11 @@ build_callback()
|
||||
local blocktime
|
||||
local blockheight
|
||||
|
||||
local pub32_index
|
||||
local pub32
|
||||
local label
|
||||
local derivation_path
|
||||
|
||||
# callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id
|
||||
|
||||
trace "[build_callback] row=${row}"
|
||||
@@ -79,7 +84,7 @@ build_callback()
|
||||
trace "[build_callback] txid=${txid}"
|
||||
vout_n=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[build_callback] vout_n=${vout_n}"
|
||||
sent_amount=$(echo "${row}" | cut -d '|' -f5)
|
||||
sent_amount=$(echo "${row}" | cut -d '|' -f5 | awk '{ printf "%.8f", $0 }')
|
||||
trace "[build_callback] sent_amount=${sent_amount}"
|
||||
confirmations=$(echo "${row}" | cut -d '|' -f6)
|
||||
trace "[build_callback] confirmations=${confirmations}"
|
||||
@@ -106,6 +111,17 @@ build_callback()
|
||||
blocktime=$(echo "${row}" | cut -d '|' -f13)
|
||||
trace "[build_callback] blocktime=${blocktime}"
|
||||
|
||||
pub32_index=$(echo "${row}" | cut -d '|' -f16)
|
||||
trace "[build_callback] pub32_index=${pub32_index}"
|
||||
if [ -n "${pub32_index}" ]; then
|
||||
pub32=$(echo "${row}" | cut -d '|' -f17)
|
||||
trace "[build_callback] pub32=${pub32}"
|
||||
label=$(echo "${row}" | cut -d '|' -f18)
|
||||
trace "[build_callback] label=${label}"
|
||||
derivation_path=$(echo "${row}" | cut -d '|' -f19)
|
||||
trace "[build_callback] derivation_path=${derivation_path}"
|
||||
fi
|
||||
|
||||
data="{\"id\":\"${id}\","
|
||||
data="${data}\"address\":\"${address}\","
|
||||
data="${data}\"hash\":\"${txid}\","
|
||||
@@ -124,6 +140,12 @@ build_callback()
|
||||
data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\","
|
||||
data="${data}\"blockheight\":${blockheight}"
|
||||
fi
|
||||
if [ -n "${pub32_index}" ]; then
|
||||
data="${data}\"pub32\":\"${pub32}\","
|
||||
data="${data}\"pub32_label\":\"${label}\","
|
||||
derivation_path=$(echo -e $derivation_path | sed -En "s/n/${pub32_index}/p")
|
||||
data="${data}\"pub32_derivation_path\":\"${derivation_path}\""
|
||||
fi
|
||||
data="${data}}"
|
||||
trace "[build_callback] data=${data}"
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ confirmation_request()
|
||||
return $?
|
||||
}
|
||||
|
||||
confirmation()
|
||||
{
|
||||
confirmation() {
|
||||
(
|
||||
flock -x 201
|
||||
|
||||
trace "Entering confirmation()..."
|
||||
|
||||
local returncode
|
||||
@@ -56,7 +58,7 @@ confirmation()
|
||||
notfirst=true
|
||||
fi
|
||||
done
|
||||
local rows=$(sql "SELECT id, address FROM watching WHERE address IN (${addresseswhere}) AND watching")
|
||||
local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index FROM watching WHERE address IN (${addresseswhere}) AND watching")
|
||||
if [ ${#rows} -eq 0 ]; then
|
||||
trace "[confirmation] No watched address in this tx!"
|
||||
return 0
|
||||
@@ -140,7 +142,7 @@ confirmation()
|
||||
tx=$(sql "SELECT tx_id FROM watching_tx WHERE tx_id=\"${tx}\"")
|
||||
|
||||
if [ -z "${tx}" ]; then
|
||||
trace "[confirmation] For this tx, there's no watching_tx row, let's create"
|
||||
trace "[confirmation] For this tx, there's no watching_tx row, let's create it"
|
||||
local watching_id
|
||||
|
||||
# If the tx is batched and pays multiple watched addresses, we have to insert
|
||||
@@ -159,11 +161,27 @@ confirmation()
|
||||
fi
|
||||
########################################################################################################
|
||||
|
||||
########################################################################################################
|
||||
# Let's now grow the watch window in the case of a xpub watcher...
|
||||
|
||||
for row in ${rows}
|
||||
do
|
||||
watching_by_pub32_id=$(echo "${row}" | cut -d '|' -f3)
|
||||
pub32_index=$(echo "${row}" | cut -d '|' -f4)
|
||||
if [ -n "${watching_by_pub32_id}" ]; then
|
||||
extend_watchers ${watching_by_pub32_id} ${pub32_index}
|
||||
fi
|
||||
done
|
||||
|
||||
########################################################################################################
|
||||
|
||||
do_callbacks
|
||||
|
||||
echo '{"result":"confirmed"}'
|
||||
|
||||
return 0
|
||||
|
||||
) 201>./.confirmation.lock
|
||||
}
|
||||
|
||||
case "${0}" in *confirmation.sh) confirmation $@;; esac
|
||||
|
||||
@@ -3,19 +3,110 @@
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
importaddress_rpc()
|
||||
{
|
||||
trace "[Entering importaddress_rpc()]"
|
||||
importaddress_rpc() {
|
||||
trace "[Entering importaddress_rpc()]"
|
||||
|
||||
local address=${1}
|
||||
local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}"
|
||||
local result
|
||||
result=$(send_to_watcher_node ${data})
|
||||
local returncode=$?
|
||||
local address=${1}
|
||||
local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}"
|
||||
local result
|
||||
result=$(send_to_watcher_node ${data})
|
||||
local returncode=$?
|
||||
|
||||
echo "${result}"
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *importaddress.sh) importaddress_rpc $@;; esac
|
||||
importmulti_rpc() {
|
||||
trace "[Entering importmulti_rpc()]"
|
||||
|
||||
local walletname=${1}
|
||||
local addresses=$(echo "${2}" | jq ".addresses" | tr -d '\n ')
|
||||
trace "[importmulti_rpc] addresses=${addresses}"
|
||||
|
||||
# [{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}]
|
||||
# [{"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},{"scriptPubKey":{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},"timestamp":"now","watchonly":true},{"scriptPubKey":{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},"timestamp":"now","watchonly":true}]
|
||||
|
||||
# {"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},
|
||||
# {"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},
|
||||
|
||||
addresses=$(echo "${addresses}" | sed "s/\"address\"/\"scriptPubKey\":\{\"address\"/g" | sed "s/}/},\"timestamp\":\"now\",\"watchonly\":true,\"label\":\"${walletname}\"}/g")
|
||||
trace "[importmulti_rpc] addresses=${addresses}"
|
||||
|
||||
# {"method":"importmulti","params":["requests":[<req>],"options":{"rescan":false}]}
|
||||
# <req> = {"address":"<addr>","timestamp":"now","watchonly":true},...
|
||||
|
||||
local rpcstring="{\"method\":\"importmulti\",\"params\":[${addresses},{\"rescan\":false}]}"
|
||||
trace "[importmulti_rpc] rpcstring=${rpcstring}"
|
||||
|
||||
local result
|
||||
# result=$(send_to_watcher_node_wallet ${walletname} ${rpcstring})
|
||||
result=$(send_to_watcher_node ${rpcstring})
|
||||
local returncode=$?
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
|
||||
#[{"requests":
|
||||
# [
|
||||
# {"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},
|
||||
# {"scriptPubKey":{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},"timestamp":"now","watchonly":true},
|
||||
# {"scriptPubKey":{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},"timestamp":"now","watchonly":true}
|
||||
# ]},
|
||||
#{"options":
|
||||
# {
|
||||
# "rescan":false
|
||||
# }
|
||||
#}]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
# /usr/bin $ ./bitcoin-cli help importmulti
|
||||
# importmulti "requests" ( "options" )
|
||||
#
|
||||
# Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.
|
||||
#
|
||||
# Arguments:
|
||||
# 1. requests (array, required) Data to be imported
|
||||
# [ (array of json objects)
|
||||
# {
|
||||
# "scriptPubKey": "<script>" | { "address":"<address>" }, (string / json, required) Type of scriptPubKey (string for script, json for address)
|
||||
# "timestamp": timestamp | "now" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),
|
||||
# or the string "now" to substitute the current synced blockchain time. The timestamp of the oldest
|
||||
# key will determine how far back blockchain rescans need to begin for missing wallet transactions.
|
||||
# "now" can be specified to bypass scanning, for keys which are known to never have been used, and
|
||||
# 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key
|
||||
# creation time of all keys being imported by the importmulti call will be scanned.
|
||||
# "redeemscript": "<script>" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey
|
||||
# "pubkeys": ["<pubKey>", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript
|
||||
# "keys": ["<key>", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript
|
||||
# "internal": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments
|
||||
# "watchonly": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty
|
||||
# "label": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false
|
||||
# }
|
||||
# ,...
|
||||
# ]
|
||||
# 2. options (json, optional)
|
||||
# {
|
||||
# "rescan": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports
|
||||
# }
|
||||
#
|
||||
# Note: This call can take over an hour to complete if rescan is true, during that time, other rpc calls
|
||||
# may report that the imported keys, addresses or scripts exists but related transactions are still missing.
|
||||
#
|
||||
# Examples:
|
||||
# > bitcoin-cli importmulti '[{ "scriptPubKey": { "address": "<my address>" }, "timestamp":1455191478 }, { "scriptPubKey": { "address": "<my 2nd address>" }, "label": "example 2", "timestamp": 1455191480 }]'
|
||||
# > bitcoin-cli importmulti '[{ "scriptPubKey": { "address": "<my address>" }, "timestamp":1455191478 }]' '{ "rescan": false}'
|
||||
#
|
||||
# Response is an array with the same size as the input that has the execution result :
|
||||
# [{ "success": true } , { "success": false, "error": { "code": -1, "message": "Internal Server Error"} }, ... ]
|
||||
#
|
||||
|
||||
@@ -86,6 +86,16 @@ main()
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
watchxpub)
|
||||
# POST http://192.168.111.152:8080/watchxpub
|
||||
# BODY {"label":"4421","pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/n","nstart":0,"unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
|
||||
# curl -H "Content-Type: application/json" -d "{\"label\":\"2219\",\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/1/n\",\"nstart\":55,\"unconfirmedCallbackURL\":\"192.168.111.233:1111/callback0conf\",\"confirmedCallbackURL\":\"192.168.111.233:1111/callback1conf\"}" proxy:8888/watchxpub
|
||||
|
||||
response=$(watchpub32request "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getactivewatches)
|
||||
# curl (GET) 192.168.111.152:8080/getactivewatches
|
||||
|
||||
@@ -188,7 +198,8 @@ main()
|
||||
# BODY {"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/25-30"}
|
||||
# BODY {"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
|
||||
|
||||
response=$(send_to_pycoin "${line}")
|
||||
# response=$(send_to_pycoin "${line}")
|
||||
response=$(derivepubpath "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
|
||||
@@ -2,10 +2,20 @@
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
send_to_watcher_node()
|
||||
{
|
||||
send_to_watcher_node() {
|
||||
trace "Entering send_to_watcher_node()..."
|
||||
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL} ${WATCHER_NODE_RPC_CFG} $@
|
||||
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/${WATCHER_BTC_NODE_DEFAULT_WALLET} ${WATCHER_NODE_RPC_CFG} $@
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
send_to_watcher_node_wallet() {
|
||||
trace "Entering send_to_watcher_node_wallet()..."
|
||||
local walletname=$1
|
||||
shift
|
||||
trace "[send_to_watcher_node_wallet] walletname=${walletname}"
|
||||
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/$walletname ${WATCHER_NODE_RPC_CFG} $@
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
@@ -14,7 +24,7 @@ send_to_watcher_node()
|
||||
send_to_spender_node()
|
||||
{
|
||||
trace "Entering send_to_spender_node()..."
|
||||
send_to_bitcoin_node ${SPENDER_NODE_RPC_URL} ${SPENDER_NODE_RPC_CFG} $@
|
||||
send_to_bitcoin_node ${SPENDER_NODE_RPC_URL}/${SPENDER_BTC_NODE_DEFAULT_WALLET} ${SPENDER_NODE_RPC_CFG} $@
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
@@ -30,7 +40,7 @@ send_to_bitcoin_node()
|
||||
local config=${2}
|
||||
local data=${3}
|
||||
|
||||
trace "[send_to_bitcoin_node] curl -s --user ${user} -H \"Content-Type: application/json\" -d \"${data}\" ${node_url}"
|
||||
trace "[send_to_bitcoin_node] curl -s --config ${config} -H \"Content-Type: application/json\" -d \"${data}\" ${node_url}"
|
||||
result=$(curl -s --config ${config} -H "Content-Type: application/json" -d "${data}" ${node_url})
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
sql()
|
||||
{
|
||||
trace "sqlite3 ${DB_FILE} '${1}'"
|
||||
sqlite3 -cmd ".timeout 20000" ${DB_FILE} "${1}"
|
||||
sql() {
|
||||
trace "sqlite3 -cmd \".timeout 20000\" ${DB_FILE} \"${1}\""
|
||||
sqlite3 -cmd ".timeout 20000" ${DB_FILE} "${1}"
|
||||
# sqlite3 ${DB_FILE} "PRAGMA busy_timeout=20000; ${1}"
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
case "${0}" in *sql.sh) sql $@;; esac
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
spend()
|
||||
{
|
||||
spend() {
|
||||
trace "Entering spend()..."
|
||||
|
||||
local data
|
||||
@@ -26,7 +25,6 @@ spend()
|
||||
trace "[spend] txid=${txid}"
|
||||
|
||||
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
|
||||
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
|
||||
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
|
||||
trace_rc $?
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
@@ -47,8 +45,7 @@ spend()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
getbalance()
|
||||
{
|
||||
getbalance() {
|
||||
trace "Entering getbalance()..."
|
||||
|
||||
local response
|
||||
@@ -74,8 +71,7 @@ getbalance()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
getnewaddress()
|
||||
{
|
||||
getnewaddress() {
|
||||
trace "Entering getnewaddress()..."
|
||||
|
||||
local response
|
||||
@@ -101,8 +97,7 @@ getnewaddress()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
addtobatching()
|
||||
{
|
||||
addtobatching() {
|
||||
trace "Entering addtobatching()..."
|
||||
|
||||
local address=${1}
|
||||
@@ -117,8 +112,7 @@ addtobatching()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
batchspend()
|
||||
{
|
||||
batchspend() {
|
||||
trace "Entering batchspend()..."
|
||||
|
||||
local data
|
||||
@@ -165,8 +159,6 @@ batchspend()
|
||||
trace "[batchspend] txid=${txid}"
|
||||
|
||||
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
|
||||
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
|
||||
# trace_rc $?
|
||||
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
@@ -190,4 +182,20 @@ batchspend()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
#case "${0}" in *walletoperations.sh) getbalance $@;; esac
|
||||
create_wallet() {
|
||||
trace "[Entering create_wallet()]"
|
||||
|
||||
local walletname=${1}
|
||||
|
||||
local rpcstring="{\"method\":\"createwallet\",\"params\":[\"${walletname}\",true]}"
|
||||
trace "[create_wallet] rpcstring=${rpcstring}"
|
||||
|
||||
local result
|
||||
result=$(send_to_watcher_node ${rpcstring})
|
||||
local returncode=$?
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
. ./importaddress.sh
|
||||
. ./sql.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
. ./bitcoin.sh
|
||||
|
||||
watchrequest()
|
||||
{
|
||||
watchrequest() {
|
||||
trace "Entering watchrequest()..."
|
||||
|
||||
local returncode
|
||||
@@ -71,4 +71,216 @@ watchrequest()
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *watchrequest.sh) watchrequest $@;; esac
|
||||
watchpub32request() {
|
||||
trace "Entering watchpub32request()..."
|
||||
|
||||
local returncode
|
||||
local request=${1}
|
||||
local label=$(echo "${request}" | jq ".label" | tr -d '"')
|
||||
trace "[watchpub32request] label=${label}"
|
||||
local pub32=$(echo "${request}" | jq ".pub32" | tr -d '"')
|
||||
trace "[watchpub32request] pub32=${pub32}"
|
||||
local path=$(echo "${request}" | jq ".path" | tr -d '"')
|
||||
trace "[watchpub32request] path=${path}"
|
||||
local nstart=$(echo "${request}" | jq ".nstart")
|
||||
trace "[watchpub32request] nstart=${nstart}"
|
||||
local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL" | tr -d '"')
|
||||
trace "[watchpub32request] cb0conf_url=${cb0conf_url}"
|
||||
local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL" | tr -d '"')
|
||||
trace "[watchpub32request] cb1conf_url=${cb1conf_url}"
|
||||
|
||||
watchpub32 ${label} ${pub32} ${path} ${nstart} ${cb0conf_url} ${cb1conf_url}
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
watchpub32() {
|
||||
trace "Entering watchpub32()..."
|
||||
|
||||
local returncode
|
||||
local label=${1}
|
||||
trace "[watchpub32] label=${label}"
|
||||
local pub32=${2}
|
||||
trace "[watchpub32] pub32=${pub32}"
|
||||
local path=${3}
|
||||
trace "[watchpub32] path=${path}"
|
||||
local nstart=${4}
|
||||
trace "[watchpub32] nstart=${nstart}"
|
||||
local last_n=$((${nstart}+${XPUB_DERIVATION_GAP}))
|
||||
trace "[watchpub32] last_n=${last_n}"
|
||||
local cb0conf_url=${5}
|
||||
trace "[watchpub32] cb0conf_url=${cb0conf_url}"
|
||||
local cb1conf_url=${6}
|
||||
trace "[watchpub32] cb1conf_url=${cb1conf_url}"
|
||||
local upto_n=${7}
|
||||
trace "[watchpub32] upto_n=${upto_n}"
|
||||
local id_inserted
|
||||
local result
|
||||
local error_msg
|
||||
local data
|
||||
|
||||
# Derive with pycoin...
|
||||
# {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
if [ -n "${upto_n}" ]; then
|
||||
# If upto_n provided, then we create from nstart to upto_n (instead of + GAP)
|
||||
last_n=${upto_n}
|
||||
fi
|
||||
local subspath=$(echo -e $path | sed -En "s/n/${nstart}-${last_n}/p")
|
||||
trace "[watchpub32] subspath=${subspath}"
|
||||
local addresses
|
||||
addresses=$(derivepubpath "{\"pub32\":\"${pub32}\",\"path\":\"${subspath}\"}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[watchpub32] addresses=${addresses}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
# result=$(create_wallet "${pub32}")
|
||||
# returncode=$?
|
||||
# trace_rc ${returncode}
|
||||
# trace "[watchpub32request] result=${result}"
|
||||
trace "[watchpub32] Skipping create_wallet"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
# Importmulti in Bitcoin Core...
|
||||
result=$(importmulti_rpc "${pub32}" "${addresses}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[watchpub32] result=${result}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
if [ -n "${upto_n}" ]; then
|
||||
# Update existing row
|
||||
sql "UPDATE watching_by_pub32 set last_imported_n=${upto_n} WHERE pub32=\"${pub32}\""
|
||||
else
|
||||
# Insert in our DB...
|
||||
sql "INSERT OR IGNORE INTO watching_by_pub32 (pub32, label, derivation_path, watching, callback0conf, callback1conf, last_imported_n) VALUES (\"${pub32}\", \"${label}\", \"${path}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${last_n})"
|
||||
fi
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
id_inserted=$(sql "SELECT id FROM watching_by_pub32 WHERE label='${label}'")
|
||||
trace "[watchpub32] id_inserted: ${id_inserted}"
|
||||
|
||||
addresses=$(echo ${addresses} | jq ".addresses[].address")
|
||||
insert_watches "${addresses}" "${cb0conf_url}" "${cb1conf_url}" ${id_inserted} ${nstart}
|
||||
else
|
||||
error_msg="Can't insert xpub watcher in DB"
|
||||
fi
|
||||
else
|
||||
error_msg="Can't import addresses"
|
||||
fi
|
||||
else
|
||||
error_msg="Can't create wallet"
|
||||
fi
|
||||
else
|
||||
error_msg="Can't derive addresses"
|
||||
fi
|
||||
|
||||
if [ -z "${error_msg}" ]; then
|
||||
data="{\"id\":\"${id_inserted}\",
|
||||
\"event\":\"watchxpub\",
|
||||
\"pub32\":\"${pub32}\",
|
||||
\"label\":\"${label}\",
|
||||
\"path\":\"${path}\",
|
||||
\"nstart\":\"${nstart}\",
|
||||
\"unconfirmedCallbackURL\":\"${cb0conf_url}\",
|
||||
\"confirmedCallbackURL\":\"${cb1conf_url}\"}"
|
||||
|
||||
returncode=0
|
||||
else
|
||||
data="{\"error\":\"${error_msg}\",
|
||||
\"event\":\"watchxpub\",
|
||||
\"pub32\":\"${pub32}\",
|
||||
\"label\":\"${label}\",
|
||||
\"path\":\"${path}\",
|
||||
\"nstart\":\"${nstart}\",
|
||||
\"unconfirmedCallbackURL\":\"${cb0conf_url}\",
|
||||
\"confirmedCallbackURL\":\"${cb1conf_url}\"}"
|
||||
|
||||
returncode=1
|
||||
fi
|
||||
trace "[watchpub32] responding=${data}"
|
||||
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
insert_watches() {
|
||||
trace "Entering insert_watches()..."
|
||||
|
||||
local addresses=${1}
|
||||
local callback0conf=${2}
|
||||
local callback1conf=${3}
|
||||
local xpub_id=${4}
|
||||
local nstart=${5}
|
||||
local inserted_values=""
|
||||
|
||||
local IFS=$'\n'
|
||||
for address in ${addresses}
|
||||
do
|
||||
# (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id)
|
||||
if [ -n "${inserted_values}" ]; then
|
||||
inserted_values="${inserted_values},"
|
||||
fi
|
||||
inserted_values="${inserted_values}(${address}, 1, \"${callback0conf}\", \"${callback1conf}\", 1"
|
||||
if [ -n "${xpub_id}" ]; then
|
||||
inserted_values="${inserted_values}, ${xpub_id}, ${nstart}"
|
||||
nstart=$((${nstart} + 1))
|
||||
fi
|
||||
inserted_values="${inserted_values})"
|
||||
done
|
||||
trace "[insert_watches] inserted_values=${inserted_values}"
|
||||
|
||||
sql "INSERT OR IGNORE INTO watching (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id, pub32_index) VALUES ${inserted_values}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
extend_watchers() {
|
||||
trace "Entering extend_watchers()..."
|
||||
|
||||
local watching_by_pub32_id=${1}
|
||||
trace "[extend_watchers] watching_by_pub32_id=${watching_by_pub32_id}"
|
||||
local pub32_index=${2}
|
||||
trace "[extend_watchers] pub32_index=${pub32_index}"
|
||||
local upgrade_to_n=$((${pub32_index} + ${XPUB_DERIVATION_GAP}))
|
||||
trace "[extend_watchers] upgrade_to_n=${upgrade_to_n}"
|
||||
|
||||
local last_imported_n
|
||||
local row
|
||||
row=$(sql "SELECT pub32, label, derivation_path, callback0conf, callback1conf, last_imported_n FROM watching_by_pub32 WHERE id=${watching_by_pub32_id}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
trace "[extend_watchers] row=${row}"
|
||||
local pub32=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[extend_watchers] pub32=${pub32}"
|
||||
local label=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[extend_watchers] label=${label}"
|
||||
local derivation_path=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[extend_watchers] derivation_path=${derivation_path}"
|
||||
local callback0conf=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[extend_watchers] callback0conf=${callback0conf}"
|
||||
local callback1conf=$(echo "${row}" | cut -d '|' -f5)
|
||||
trace "[extend_watchers] callback1conf=${callback1conf}"
|
||||
local last_imported_n=$(echo "${row}" | cut -d '|' -f6)
|
||||
trace "[extend_watchers] last_imported_n=${last_imported_n}"
|
||||
|
||||
if [ "${last_imported_n}" -lt "${upgrade_to_n}" ]; then
|
||||
# We want to keep our gap between last tx and last n watched...
|
||||
# For example, if the last imported n is 155 and we just got a tx with pub32 index of 66,
|
||||
# we want to extend the watched addresses to 166 if our gap is 100 (default).
|
||||
|
||||
watchpub32 ${label} ${pub32} ${derivation_path} $((${last_imported_n} + 1)) ${callback0conf} ${callback1conf} ${upgrade_to_n} > /dev/null
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
fi
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
TRACING=1
|
||||
WATCHER_BTC_NODE_RPC_URL=bitcoin:18332/wallet/watching01.dat
|
||||
WATCHER_BTC_NODE_RPC_URL=bitcoin:18332/wallet
|
||||
WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat
|
||||
WATCHER_BTC_NODE_RPC_USER=rpc_username:rpc_password
|
||||
WATCHER_BTC_NODE_RPC_CFG=/proxy/watcher_btcnode_curlcfg.properties
|
||||
SPENDER_BTC_NODE_RPC_URL=bitcoin:18332/wallet/spending01.dat
|
||||
SPENDER_BTC_NODE_RPC_URL=bitcoin:18332/wallet
|
||||
SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat
|
||||
SPENDER_BTC_NODE_RPC_USER=rpc_username:rpc_password
|
||||
SPENDER_BTC_NODE_RPC_CFG=/proxy/spender_btcnode_curlcfg.properties
|
||||
PROXY_LISTENING_PORT=8888
|
||||
@@ -18,3 +20,4 @@ OTS_FILES=/otsfiles
|
||||
DERIVATION_PUB32=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
|
||||
DERIVATION_PATH=0/n
|
||||
WATCHER_BTC_NODE_PRUNED=false
|
||||
XPUB_DERIVATION_GAP=100
|
||||
|
||||
Reference in New Issue
Block a user