Merge pull request #4 from SatoshiPortal/dev

Trying something?
This commit is contained in:
Francis Pouliot
2020-01-17 14:58:12 -05:00
committed by GitHub
25 changed files with 757 additions and 125 deletions

View File

@@ -70,7 +70,9 @@ The core component of cyphernode is a request handler which exposes HTTP endpoin
## Documentation ## Documentation
* Read the API docs here: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.md * Read the API docs here:
* API v0 (Current): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.v0.md
* API v1 (RESTful): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.v1.md
* Installation documentation: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL.md * Installation documentation: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL.md
* Step-by-step manual install (deprecated): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL-MANUAL-STEPS.md * Step-by-step manual install (deprecated): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL-MANUAL-STEPS.md

View File

@@ -18,6 +18,8 @@ action_getactivewatchesbylabel=watcher
action_getactivexpubwatches=watcher action_getactivexpubwatches=watcher
action_watchtxid=watcher action_watchtxid=watcher
action_getactivewatches=watcher action_getactivewatches=watcher
action_get_txns_by_watchlabel=watcher
action_get_unused_addresses_by_watchlabel=watcher
action_getbestblockhash=watcher action_getbestblockhash=watcher
action_getbestblockinfo=watcher action_getbestblockinfo=watcher
action_getblockinfo=watcher action_getblockinfo=watcher
@@ -28,6 +30,7 @@ action_ln_getconnectionstring=watcher
action_ln_decodebolt11=watcher action_ln_decodebolt11=watcher
# Spender can do what the watcher can do, plus: # Spender can do what the watcher can do, plus:
action_getxnslist=spender
action_getbalance=spender action_getbalance=spender
action_getbalances=spender action_getbalances=spender
action_getbalancebyxpub=spender action_getbalancebyxpub=spender

View File

@@ -35,7 +35,7 @@
"lightning_nodecolor": "LN nodes have colors. Choose the <font underline='true'>color you want</font> for yours in RGB format (RRGGBB). For example, pure red would be <font color='#ff0000'>ff0000</font>.", "lightning_nodecolor": "LN nodes have colors. Choose the <font underline='true'>color you want</font> for yours in RGB format (RRGGBB). For example, pure red would be <font color='#ff0000'>ff0000</font>.",
"lightning_datapath": "<font underline='true'>Path name</font> to where LN's data files are stored. This directory will be mounted into the LN node's container. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>", "lightning_datapath": "<font underline='true'>Path name</font> to where LN's data files are stored. This directory will be mounted into the LN node's container. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"lightning_datapath_custom": " ", "lightning_datapath_custom": " ",
"lightning_expose": "By default, LN node port will be <font underline='true'>published</font> outside of Docker. Do you want to hide it so that your node can't be accessed from outside of the Docker network?", "lightning_expose": "By default, LN node port will be <font underline='true'>published</font> outside of Docker. Do you want to expose it so that your node can be accessed from outside of the Docker network?",
"otsclient_datapath": "<font underline='true'>Full path</font> where the OTS files will be stored. This path will be mounted into the otsclient container which will create the OTS files when <font color='#00ff00'>stamping</font> and update them when <font color='#00ff00'>upgrading</font> stamps. It will also be mounted to the proxy container so that it can serve the <font color='#00ff00'>ots_getfile</font> and send the OTS files to clients. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>", "otsclient_datapath": "<font underline='true'>Full path</font> where the OTS files will be stored. This path will be mounted into the otsclient container which will create the OTS files when <font color='#00ff00'>stamping</font> and update them when <font color='#00ff00'>upgrading</font> stamps. It will also be mounted to the proxy container so that it can serve the <font color='#00ff00'>ots_getfile</font> and send the OTS files to clients. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"otsclient_datapath_custom": " ", "otsclient_datapath_custom": " ",
"installer_mode": "Only one <font underline='true'>installation mode</font> is supported, right now: <font color='#0000ff'>local docker (self-hosted)</font>. Choose wisely ;-)", "installer_mode": "Only one <font underline='true'>installation mode</font> is supported, right now: <font color='#0000ff'>local docker (self-hosted)</font>. Choose wisely ;-)",

View File

@@ -149,6 +149,11 @@ module.exports = class App {
} ); } );
if( !fs.existsSync(this.destinationPath(configArchiveFileName)) ) { if( !fs.existsSync(this.destinationPath(configArchiveFileName)) ) {
if( this.sessionData.noWizard ) {
console.log(chalk.bold.red('Unable to run in no wizard mode without a config.7z')+'\n');
process.exit();
return;
}
let r = {}; let r = {};
process.stdout.write(ansi.clear+ansi.reset); process.stdout.write(ansi.clear+ansi.reset);
while( !r.password0 || !r.password1 || r.password0 !== r.password1 ) { while( !r.password0 || !r.password1 || r.password0 !== r.password1 ) {
@@ -449,7 +454,7 @@ module.exports = class App {
name: 'MQ broker', name: 'MQ broker',
label: 'broker', label: 'broker',
host: 'broker', host: 'broker',
networks: ['cyphernodenet'], networks: ['cyphernodenet', 'cyphernodeappsnet'],
docker: 'eclipse-mosquitto:'+this.config.docker_versions['eclipse-mosquitto'] docker: 'eclipse-mosquitto:'+this.config.docker_versions['eclipse-mosquitto']
} }

View File

@@ -2451,9 +2451,9 @@
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.1.2", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
"dev": true, "dev": true,
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
@@ -3721,9 +3721,9 @@
"dev": true "dev": true
}, },
"mixin-deep": { "mixin-deep": {
"version": "1.3.1", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true, "dev": true,
"requires": { "requires": {
"for-in": "^1.0.2", "for-in": "^1.0.2",

View File

@@ -8,6 +8,7 @@ action_helloworld=stats
action_getblockchaininfo=stats action_getblockchaininfo=stats
action_installation_info=stats action_installation_info=stats
action_getmempoolinfo=stats action_getmempoolinfo=stats
action_getblockhash=stats
# Watcher can: # Watcher can:
action_watch=watcher action_watch=watcher
@@ -18,6 +19,8 @@ action_unwatchxpubbylabel=watcher
action_getactivewatchesbyxpub=watcher action_getactivewatchesbyxpub=watcher
action_getactivewatchesbylabel=watcher action_getactivewatchesbylabel=watcher
action_getactivexpubwatches=watcher action_getactivexpubwatches=watcher
action_get_txns_by_watchlabel=watcher
action_get_unused_addresses_by_watchlabel=watcher
action_watchtxid=watcher action_watchtxid=watcher
action_getactivewatches=watcher action_getactivewatches=watcher
action_getbestblockhash=watcher action_getbestblockhash=watcher
@@ -32,6 +35,7 @@ action_ln_getconnectionstring=watcher
action_ln_decodebolt11=watcher action_ln_decodebolt11=watcher
# Spender can do what the watcher can do, plus: # Spender can do what the watcher can do, plus:
action_get_txns_spending=spender
action_getbalance=spender action_getbalance=spender
action_getbalances=spender action_getbalances=spender
action_getbalancebyxpub=spender action_getbalancebyxpub=spender

View File

@@ -106,6 +106,7 @@ services:
image: eclipse-mosquitto:1.6 image: eclipse-mosquitto:1.6
networks: networks:
- cyphernodenet - cyphernodenet
- cyphernodeappsnet
restart: always restart: always
# deploy: # deploy:
# placement: # placement:
@@ -165,7 +166,7 @@ services:
<% } %> <% } %>
volumes: volumes:
- "<%= otsclient_datapath %>:/otsfiles" - "<%= otsclient_datapath %>:/otsfiles"
- "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf" - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf:ro"
command: $USER /script/startotsclient.sh command: $USER /script/startotsclient.sh
networks: networks:
- cyphernodenet - cyphernodenet
@@ -190,8 +191,8 @@ services:
- "<%= gatekeeper_port %>:<%= gatekeeper_port %>" - "<%= gatekeeper_port %>:<%= gatekeeper_port %>"
<% } %> <% } %>
volumes: volumes:
- "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs" - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs:ro"
- "<%= gatekeeper_datapath %>/private:/etc/ssl/private" - "<%= gatekeeper_datapath %>/private:/etc/ssl/private:ro"
- "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties" - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties"
- "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties" - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties"
- "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf" - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf"
@@ -220,9 +221,9 @@ services:
- 443:443 - 443:443
volumes: volumes:
- "/var/run/docker.sock:/var/run/docker.sock" - "/var/run/docker.sock:/var/run/docker.sock"
- "<%= traefik_datapath%>/traefik.toml:/traefik.toml" - "<%= traefik_datapath%>/traefik.toml:/traefik.toml:ro"
- "<%= traefik_datapath%>/acme.json:/acme.json" - "<%= traefik_datapath%>/acme.json:/acme.json"
- "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd" - "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd:ro"
networks: networks:
- cyphernodeappsnet - cyphernodeappsnet
restart: always restart: always
@@ -246,7 +247,7 @@ services:
<% } %> <% } %>
volumes: volumes:
- "<%= lightning_datapath %>:/.lightning" - "<%= lightning_datapath %>:/.lightning"
- "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf" - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf:ro"
- bitcoin_monitor:/bitcoin_monitor:ro - bitcoin_monitor:/bitcoin_monitor:ro
networks: networks:
- cyphernodenet - cyphernodenet

View File

@@ -78,7 +78,7 @@ EXIT_STATUS=$(($? | ${EXIT_STATUS}))
printf "\r\n\e[1;32mTests finished.\e[0m\n" printf "\r\n\e[1;32mTests finished.\e[0m\n"
if [ "$EXIT_STATUS" -ne "0" ]; then if [ "$EXIT_STATUS" -ne "0" ]; then
printf "\r\n\033[1;31mThere was an error during cyphernode installation. Please see Docker's logs for more information. Run ./testdeployment.sh to rerun the tests. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m" printf "\r\n\033[1;31mThere was an error during cyphernode installation. full logs: docker ps -q | xargs -L 1 docker logs , Containers logs: docker logs <containerid> , list containers: docker ps .Please see Docker's logs for more information. Run ./testdeployment.sh to rerun the tests. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m"
exit 1 exit 1
fi fi

8
dist/setup.sh vendored
View File

@@ -437,12 +437,6 @@ install_docker() {
esac esac
done done
fi fi
elif [[ $cmpStatus == 'incompatible' ]]; then
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf.cyphernode 0 $SUDO_REQUIRED
echo " Blockchain data is not compatible, due to misconfigured nets."
echo " Your cyphernode installation is most likely broken."
echo " Please check bitcoin.conf.cyphernode on how to repair it manually."
else else
if [[ $cmpStatus == 'reindex' ]]; then if [[ $cmpStatus == 'reindex' ]]; then
echo " Warning Reindexing will take some time." echo " Warning Reindexing will take some time."
@@ -698,9 +692,11 @@ sanity_checks_pre_install() {
install_apps() { install_apps() {
if [ ! -d "$current_path/apps" ]; then if [ ! -d "$current_path/apps" ]; then
local user=$(id -u $RUN_AS_USER):$(id -g $RUN_AS_USER)
local apps_repo="https://github.com/SatoshiPortal/cypherapps.git" local apps_repo="https://github.com/SatoshiPortal/cypherapps.git"
echo " clone $apps_repo into apps" echo " clone $apps_repo into apps"
docker run --rm -v "$current_path":/git --entrypoint git cyphernode/cyphernodeconf:$CONF_VERSION clone --single-branch -b ${CYPHERAPPS_VERSION} "$apps_repo" /git/apps > /dev/null 2>&1 docker run --rm -v "$current_path":/git --entrypoint git cyphernode/cyphernodeconf:$CONF_VERSION clone --single-branch -b ${CYPHERAPPS_VERSION} "$apps_repo" /git/apps > /dev/null 2>&1
sudo_if_required chown -R $user $current_path/apps
fi fi
} }

View File

@@ -4,12 +4,12 @@
### Watch a Bitcoin Address (called by application) ### Watch a Bitcoin Address (called by application)
Inserts the address and callbacks in the DB and imports the address to the Watching wallet. Inserts the address and callbacks in the DB and imports the address to the Watching wallet. The callback URLs and event message are optional. If eventMessage is not supplied, tx_confirmation for that watch will not be published. Event message should be in base64 format to avoid dealing with escaping special characters.
```http ```http
POST http://cyphernode:8888/watch POST http://cyphernode:8888/watch
with body... with body...
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"} {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"}
``` ```
Proxy response: Proxy response:
@@ -26,7 +26,8 @@ Proxy response:
"estimatesmartfee2blocks": "0.000010", "estimatesmartfee2blocks": "0.000010",
"estimatesmartfee6blocks": "0.000010", "estimatesmartfee6blocks": "0.000010",
"estimatesmartfee36blocks": "0.000010", "estimatesmartfee36blocks": "0.000010",
"estimatesmartfee144blocks": "0.000010" "estimatesmartfee144blocks": "0.000010",
"eventMessage": "eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"
} }
``` ```
@@ -66,7 +67,8 @@ Proxy response:
"imported":"1", "imported":"1",
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf", "unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf", "confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
"watching_since":"2018-09-06 21:14:03"} "watching_since":"2018-09-06 21:14:03",
"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"}
] ]
} }
``` ```
@@ -619,12 +621,14 @@ Proxy response:
### Spend coins from spending wallet (called by application) ### Spend coins from spending wallet (called by application)
Calls sendtoaddress RPC on the spending wallet with supplied info. Calls sendtoaddress RPC on the spending wallet with supplied info. Can supply an eventMessage to be published on successful spending. eventMessage should be base64 encoded to avoid dealing with escaping special characters.
```http ```http
POST http://cyphernode:8888/spend POST http://cyphernode:8888/spend
with body... with body...
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233} {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
or
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJ3aGF0ZXZlciI6MTIzfQo="}
``` ```
Proxy response: Proxy response:
@@ -782,12 +786,14 @@ Proxy response:
### Create a Lightning Network invoice (called by application) ### Create a Lightning Network invoice (called by application)
Returns a LN invoice. Label must be unique. Description will be used by your user for payment. Expiry is in seconds. Returns a LN invoice. Label must be unique. Description will be used by your user for payment. Expiry is in seconds and optional. If msatoshi is not supplied, will use "any" (ie donation invoice). callbackUrl is optional.
```http ```http
POST http://cyphernode:8888/ln_create_invoice POST http://cyphernode:8888/ln_create_invoice
with body... with body...
{"msatoshi":10000,"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900} {"msatoshi":10000,"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900,"callbackUrl":"https://thesite/lnwebhook/9d8sa98yd"}
or
{"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900}
``` ```
Proxy response: Proxy response:
@@ -802,7 +808,7 @@ Proxy response:
### Pay a Lightning Network invoice (called by application) ### Pay a Lightning Network invoice (called by application)
Make a LN payment. expected_msatoshi and expected_description are respectively the amount and description you gave your user for her to create the invoice; they must match the given bolt11 invoice supplied by your user. Make a LN payment. expected_msatoshi and expected_description are respectively the amount and description you gave your user for her to create the invoice; they must match the given bolt11 invoice supplied by your user. If the bolt11 invoice doesn't contain an amount, then the expected_msatoshi supplied here will be used as the paid amount.
```http ```http
POST http://cyphernode:8888/ln_pay POST http://cyphernode:8888/ln_pay

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -50,8 +50,6 @@ paths:
type: "object" type: "object"
required: required:
- "address" - "address"
- "confirmedCallbackURL"
- "unconfirmedCallbackURL"
properties: properties:
address: address:
$ref: '#/components/schemas/TypeAddressString' $ref: '#/components/schemas/TypeAddressString'
@@ -61,6 +59,9 @@ paths:
confirmedCallbackURL: confirmedCallbackURL:
type: "string" type: "string"
format: "url" format: "url"
eventMessage:
description: "Will be part of the published message on confirmations"
type: "string"
responses: responses:
'200': '200':
description: "successfully created" description: "successfully created"
@@ -350,6 +351,276 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable' $ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_txns_by_watchlabel/{label}:
get:
parameters:
- in: "path"
name: "label"
description: "Xpub label"
required: true
schema:
type: "string"
tags:
- "transactions"
- "core features"
summary: "Get list of upto 10 transactions observed for addreses belonging to this label"
description: "Get list of transactions observed for addreses belonging to this label"
operationId: "get_txns_by_watchlabel"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "label_txns"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/WatchXPubTxn'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_txns_by_watchlabel/{label}/{count}:
get:
parameters:
- in: "path"
name: "label"
description: "Xpub label"
required: true
schema:
type: "string"
- in: "path"
name: "count"
description: "Number of transactions to return"
required: true
schema:
type: "number"
tags:
- "transactions"
- "core features"
summary: "Get list of transactions observed for addreses belonging to this label"
description: "Get list of upto count number of transactions observed for addreses belonging to this label"
operationId: "get_txns_by_watchlabel_count"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "label_txns"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/WatchXPubTxn'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_txns_spending:
get:
tags:
- "transactions"
- "core features"
summary: "Get list of upto 10 spending wallet transactions"
description: "Get list of upto 10 spending wallet transactions"
operationId: "get_txns_spending"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "txns"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/TransactionsSpending'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_txns_spending/{count}/:
get:
parameters:
- in: "path"
name: "count"
description: "Number of txns to return"
required: true
schema:
type: "integer"
tags:
- "transactions"
- "core features"
summary: "Get list of upto count spending wallet transactions"
description: "Get list of upto count wallets transactions"
operationId: "get_txns_spending_count"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "txns"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/TransactionsSpending'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_txns_spending/{count}/{skip}:
get:
parameters:
- in: "path"
name: "count"
description: "Number of txns to return"
required: true
schema:
type: "integer"
- in: "path"
name: "skip"
description: "Number of txns to skip (start from)"
required: true
schema:
type: "integer"
tags:
- "transactions"
- "core features"
summary: "Get list of upto count spending wallet transactions, skipping over the first {skip} number of transactions"
description: "Get list of upto count spending wallet transactions, skipping over the first {skip} number of transactions"
operationId: "get_txns_spending_count_skip"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "txns"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/TransactionsSpending'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_unused_addresses_by_watchlabel/{label}/:
get:
parameters:
- in: "path"
name: "label"
description: "Xpub label"
required: true
schema:
type: "string"
tags:
- "watching addresses"
- "core features"
summary: "Get list of upto 10 unused derived addreses belonging to this label"
description: "Gets an unused subset of addreses from the set of derived addresses belonging to this label"
operationId: "get_unused_addresses_by_watchlabel"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "label_unused_addresses"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/UnusedWatchXPubAddress'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/get_unused_addresses_by_watchlabel/{label}/{count}:
get:
parameters:
- in: "path"
name: "label"
description: "Xpub label"
required: true
schema:
type: "string"
- in: "path"
name: "count"
description: "Number of addresses to return"
required: true
schema:
type: "integer"
tags:
- "watching addresses"
- "core features"
summary: "Get list of unused upto to {count} derived addreses belonging to this label"
description: "Get list of unused upto to {count} derived addreses belonging to this label"
operationId: "get_unused_addresses_by_watchlabel_count"
responses:
'200':
description: "successful operation"
content:
application/json:
schema:
type: "object"
required:
- "label_unused_addresses"
properties:
watches:
type: "array"
items:
$ref: '#/components/schemas/UnusedWatchXPubAddress'
'403':
$ref: '#/components/schemas/ApiResponseNotAllowed'
'503':
description: "Resource temporarily unavailable"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/getactivexpubwatches: /getactivexpubwatches:
get: get:
tags: tags:
@@ -796,6 +1067,9 @@ paths:
$ref: '#/components/schemas/TypeAddressString' $ref: '#/components/schemas/TypeAddressString'
amount: amount:
type: "number" type: "number"
eventMessage:
description: "Will be part of the published message on spend"
type: "string"
responses: responses:
'200': '200':
description: "operation successful" description: "operation successful"
@@ -1113,10 +1387,8 @@ paths:
schema: schema:
type: "object" type: "object"
required: required:
- "msatoshi"
- "label" - "label"
- "description" - "description"
- "expiry"
properties: properties:
msatoshi: msatoshi:
type: "integer" type: "integer"
@@ -1126,6 +1398,9 @@ paths:
type: "string" type: "string"
expiry: expiry:
type: "integer" type: "integer"
callbackUrl:
type: "string"
format: "url"
responses: responses:
'200': '200':
description: "successful operation" description: "successful operation"
@@ -1166,7 +1441,6 @@ paths:
required: required:
- "bolt11" - "bolt11"
- "expected_msatoshi" - "expected_msatoshi"
- "expected_description"
properties: properties:
bolt11: bolt11:
type: "string" type: "string"
@@ -1543,6 +1817,7 @@ components:
- "address" - "address"
- "unconfirmedCallbackURL" - "unconfirmedCallbackURL"
- "confirmedCallbackURL" - "confirmedCallbackURL"
- "eventMessage"
properties: properties:
id: id:
type: "string" type: "string"
@@ -1574,6 +1849,8 @@ components:
type: "string" type: "string"
watching_since: watching_since:
type: "string" type: "string"
eventMessage:
type: "string"
WatchedByXpubAddress: WatchedByXpubAddress:
type: "object" type: "object"
required: required:
@@ -1607,6 +1884,109 @@ components:
type: "string" type: "string"
pub32_index: pub32_index:
type: "string" type: "string"
UnusedWatchXPubAddress:
type: "object"
required:
- "pub32_watch_id"
- "pub32_label"
- "pub32"
- "address_pub32_index"
- "address"
properties:
pub32_watch_id:
type: "string"
address:
$ref: '#/components/schemas/TypeAddressString'
pub32:
$ref: '#/components/schemas/TypeXpubString'
pub32_label:
type: "string"
address_pub32_index:
type: "string"
TransactionsSpending:
type: "object"
required:
- "address"
- "category"
- "amount"
- "label"
- "vout"
- "fee"
- "confirmations"
- "blockhash"
- "blockindex"
- "blocktime"
- "txid"
- "time"
- "timereceived"
- "comment"
- "bip125-replaceable"
- "abandoned"
properties:
address:
$ref: '#/components/schemas/TypeAddressString'
category:
$ref: 'string'
amount:
type: "number"
label:
type: "string"
vout:
type: "integer"
fee:
type: "fee"
confirmations:
type: "integer"
blockhash:
$ref: '#/components/schemas/TypeHashString'
blockindex:
$ref: 'number'
blocktime:
type: "number"
txid:
$ref: '#/components/schemas/TypeHashString'
time:
type: "number"
timereceived:
type: "number"
comment:
type: "string"
bip125-replaceable:
type: "string"
abandoned:
type: "boolean"
WatchXPubTxn:
type: "object"
required:
- "label"
- "address"
- "txid"
- "confirmations"
- "blockheight"
- "v_out"
- "amount"
- "blockhash"
- "blocktime"
- "timereceived"
properties:
label:
type: "string"
address:
$ref: '#/components/schemas/TypeAddressString'
txid:
$ref: '#/components/schemas/TypeHashString'
confirmations:
type: "integer"
v_out:
type: "integer"
amount:
type: "number"
blockhash:
$ref: '#/components/schemas/TypeHashString'
blocktime:
type: "number"
timereceived:
type: "number"
WatchedXpub: WatchedXpub:
type: "object" type: "object"
required: required:

View File

@@ -23,6 +23,7 @@ CREATE TABLE watching (
imported INTEGER DEFAULT FALSE, imported INTEGER DEFAULT FALSE,
watching_by_pub32_id INTEGER REFERENCES watching_by_pub32, watching_by_pub32_id INTEGER REFERENCES watching_by_pub32,
pub32_index INTEGER, pub32_index INTEGER,
event_message TEXT,
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
); );

View File

@@ -0,0 +1,14 @@
#!/bin/sh
echo "Checking for watching event support in DB..."
count=$(sqlite3 $DB_FILE "select count(*) from pragma_table_info('watching') where name='event_message'")
if [ "${count}" -eq "0" ]; then
# event_message not there, we have to migrate
echo "Migrating database for event triggered on watch notif..."
echo "Backing up current DB..."
cp $DB_FILE $DB_FILE-sqlmigrate20191127_0.2.4-0.3.0
echo "Altering DB..."
cat sqlmigrate20191127_0.2.4-0.3.0.sql | sqlite3 $DB_FILE
else
echo "Database watching event support migration already done, skipping!"
fi

View File

@@ -0,0 +1 @@
ALTER TABLE watching ADD COLUMN event_message TEXT;

View File

@@ -20,6 +20,11 @@ ln_create_invoice() {
trace "[ln_create_invoice] expiry=${expiry}" trace "[ln_create_invoice] expiry=${expiry}"
local callback_url=$(echo "${request}" | jq -r ".callbackUrl") local callback_url=$(echo "${request}" | jq -r ".callbackUrl")
trace "[ln_create_invoice] callback_url=${callback_url}" trace "[ln_create_invoice] callback_url=${callback_url}"
if [ "${callback_url}" != "null" ]; then
# If not null, let's add double-quotes so we don't need to add the double-quotes in the sql insert,
# so if it's null, it will insert the actual sql NULL value.
callback_url="\"${callback_url}\""
fi
#/proxy $ ./lightning-cli invoice 10000 "t1" "t1d" 60 #/proxy $ ./lightning-cli invoice 10000 "t1" "t1d" 60
#{ #{
@@ -28,8 +33,13 @@ ln_create_invoice() {
# "bolt11": "lnbc100n1pwzllqgpp55a8xen9sdcntehwr93pkwnuu8nmtqx9yew0flalcxhx9nvy34crqdq9wsckgxqzpucqp2rzjqt04ll5ft3mcuy8hws4xcku2pnhma9r9mavtjtadawyrw5kgzp7g7zr745qq3mcqqyqqqqlgqqqqqzsqpcr85k33shzaxscpj29fadmjmfej6y2p380x9w4kxydqpxq87l6lshy69fry9q2yrtu037nt44x77uhzkdyn8043n5yj8tqgluvmcl69cquaxr68" # "bolt11": "lnbc100n1pwzllqgpp55a8xen9sdcntehwr93pkwnuu8nmtqx9yew0flalcxhx9nvy34crqdq9wsckgxqzpucqp2rzjqt04ll5ft3mcuy8hws4xcku2pnhma9r9mavtjtadawyrw5kgzp7g7zr745qq3mcqqyqqqqlgqqqqqzsqpcr85k33shzaxscpj29fadmjmfej6y2p380x9w4kxydqpxq87l6lshy69fry9q2yrtu037nt44x77uhzkdyn8043n5yj8tqgluvmcl69cquaxr68"
#} #}
trace "[ln_create_invoice] ./lightning-cli invoice ${msatoshi} \"${label}\" \"${description}\" ${expiry}" if [ "${msatoshi}" = "null" ]; then
result=$(./lightning-cli invoice ${msatoshi} "${label}" "${description}" ${expiry}) trace "[ln_create_invoice] ./lightning-cli invoice \"any\" \"${label}\" \"${description}\" ${expiry}"
result=$(./lightning-cli invoice "any" "${label}" "${description}" ${expiry})
else
trace "[ln_create_invoice] ./lightning-cli invoice ${msatoshi} \"${label}\" \"${description}\" ${expiry}"
result=$(./lightning-cli invoice ${msatoshi} "${label}" "${description}" ${expiry})
fi
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
trace "[ln_create_invoice] result=${result}" trace "[ln_create_invoice] result=${result}"
@@ -47,7 +57,11 @@ ln_create_invoice() {
# Let's get the connect string if provided in configuration # Let's get the connect string if provided in configuration
local connectstring=$(get_connection_string) local connectstring=$(get_connection_string)
sql "INSERT OR IGNORE INTO ln_invoice (label, bolt11, callback_url, payment_hash, expires_at, msatoshi, description, status) VALUES (\"${label}\", \"${bolt11}\", \"${callback_url}\", \"${payment_hash}\", ${expires_at}, ${msatoshi}, \"${description}\", \"unpaid\")" if [ "${msatoshi}" = "null" ]; then
sql "INSERT OR IGNORE INTO ln_invoice (label, bolt11, callback_url, payment_hash, expires_at, description, status) VALUES (\"${label}\", \"${bolt11}\", ${callback_url}, \"${payment_hash}\", ${expires_at}, \"${description}\", \"unpaid\")"
else
sql "INSERT OR IGNORE INTO ln_invoice (label, bolt11, callback_url, payment_hash, expires_at, msatoshi, description, status) VALUES (\"${label}\", \"${bolt11}\", ${callback_url}, \"${payment_hash}\", ${expires_at}, ${msatoshi}, \"${description}\", \"unpaid\")"
fi
trace_rc $? trace_rc $?
id=$(sql "SELECT id FROM ln_invoice WHERE bolt11=\"${bolt11}\"") id=$(sql "SELECT id FROM ln_invoice WHERE bolt11=\"${bolt11}\"")
trace_rc $? trace_rc $?
@@ -58,9 +72,13 @@ ln_create_invoice() {
if [ -n "${connectstring}" ]; then if [ -n "${connectstring}" ]; then
data="${data}\"connectstring\":\"${connectstring}\"," data="${data}\"connectstring\":\"${connectstring}\","
fi fi
data="${data}\"callback_url\":\"${callback_url}\"," if [ "${callback_url}" != "null" ]; then
data="${data}\"callbackUrl\":${callback_url},"
fi
data="${data}\"payment_hash\":\"${payment_hash}\"," data="${data}\"payment_hash\":\"${payment_hash}\","
data="${data}\"msatoshi\":${msatoshi}," if [ "${msatoshi}" != "null" ]; then
data="${data}\"msatoshi\":${msatoshi},"
fi
data="${data}\"status\":\"unpaid\"," data="${data}\"status\":\"unpaid\","
data="${data}\"description\":\"${description}\"," data="${data}\"description\":\"${description}\","
data="${data}\"expires_at\":${expires_at}}" data="${data}\"expires_at\":${expires_at}}"
@@ -306,21 +324,31 @@ ln_pay() {
local invoice_description=$(echo "${result}" | jq ".description") local invoice_description=$(echo "${result}" | jq ".description")
trace "[ln_pay] invoice_description=${invoice_description}" trace "[ln_pay] invoice_description=${invoice_description}"
# The amount must match # The amount must match if not "any"
if [ "${expected_msatoshi}" != "${invoice_msatoshi}" ]; then # If the amount is not in the invoice and not supplied as expected_msatoshi, then both will be null, that's ok!
# Same thing goes for the description.
if [ "${expected_msatoshi}" != "${invoice_msatoshi}" ] && [ "${invoice_msatoshi}" != "null" ]; then
# If invoice_msatoshi is null, that means "any" was supplied, so the amounts don't have to match!
result="{\"result\":\"error\",\"expected_msatoshi\":${expected_msatoshi},\"invoice_msatoshi\":${invoice_msatoshi}}" result="{\"result\":\"error\",\"expected_msatoshi\":${expected_msatoshi},\"invoice_msatoshi\":${invoice_msatoshi}}"
returncode=1 returncode=1
elif [ "${expected_description}" != '""' ] && [ "${expected_description}" != "${invoice_description}" ]; then elif [ -n "${expected_description}" ] && [ "${expected_description}" != "null" ] && [ "${expected_description}" != "${invoice_description}" ]; then
# If expected description is empty, we accept any description on the invoice. Amount is the important thing. # If expected description is not empty but doesn't correspond to invoice_description, there'a problem.
# (we don't care about the description if expected description is empty. Amount is the most important thing)
result="{\"result\":\"error\",\"expected_description\":${expected_description},\"invoice_description\":${invoice_description}}" result="{\"result\":\"error\",\"expected_description\":${expected_description},\"invoice_description\":${invoice_description}}"
returncode=1 returncode=1
else else
# Amount and description is as expected, let's pay! # Amount and description are as expected (or empty description), let's pay!
trace "[ln_pay] Amount and description are as expected, let's try to pay!" trace "[ln_pay] Amount and description are as expected, let's try to pay!"
trace "[ln_pay] ./lightning-cli pay -k bolt11=${bolt11} retry_for=15" if [ "${invoice_msatoshi}" = "null" ]; then
result=$(./lightning-cli pay -k bolt11=${bolt11} retry_for=15) # "any" amount on the invoice, we force paying the expected_msatoshi provided to ln_pay by the user
trace "[ln_pay] ./lightning-cli pay -k bolt11=${bolt11} msatoshi=${expected_msatoshi} retry_for=15"
result=$(./lightning-cli pay -k bolt11=${bolt11} msatoshi=${expected_msatoshi} retry_for=15)
else
trace "[ln_pay] ./lightning-cli pay -k bolt11=${bolt11} retry_for=15"
result=$(./lightning-cli pay -k bolt11=${bolt11} retry_for=15)
fi
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
trace "[ln_pay] result=${result}" trace "[ln_pay] result=${result}"

View File

@@ -11,7 +11,7 @@ do_callbacks() {
trace "Entering do_callbacks()..." trace "Entering do_callbacks()..."
# Let's fetch all the watching addresses still being watched but not called back # Let's fetch all the watching addresses still being watched but not called back
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') 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, event_message 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}" trace "[do_callbacks] callbacks0conf=${callbacks}"
local returncode local returncode
@@ -30,7 +30,7 @@ do_callbacks() {
fi fi
done done
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') 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, event_message 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}" trace "[do_callbacks] callbacks1conf=${callbacks}"
for row in ${callbacks} for row in ${callbacks}
@@ -64,12 +64,21 @@ ln_manage_callback() {
local id=$(echo "${row}" | cut -d '|' -f1) local id=$(echo "${row}" | cut -d '|' -f1)
trace "[ln_manage_callback] id=${id}" trace "[ln_manage_callback] id=${id}"
local callback_url=$(echo "${row}" | cut -d '|' -f4)
trace "[ln_manage_callback] callback_url=${callback_url}"
if [ -z "${callback_url}" ]; then
# No callback url provided for that invoice
trace "[ln_manage_callback] No callback url provided for that invoice"
sql "UPDATE ln_invoice SET calledback=1 WHERE id=\"${id}\""
trace_rc $?
return
fi
local label=$(echo "${row}" | cut -d '|' -f2) local label=$(echo "${row}" | cut -d '|' -f2)
trace "[ln_manage_callback] label=${label}" trace "[ln_manage_callback] label=${label}"
local bolt11=$(echo "${row}" | cut -d '|' -f3) local bolt11=$(echo "${row}" | cut -d '|' -f3)
trace "[ln_manage_callback] bolt11=${bolt11}" trace "[ln_manage_callback] bolt11=${bolt11}"
local callback_url=$(echo "${row}" | cut -d '|' -f4)
trace "[ln_manage_callback] callback_url=${callback_url}"
local payment_hash=$(echo "${row}" | cut -d '|' -f5) local payment_hash=$(echo "${row}" | cut -d '|' -f5)
trace "[ln_manage_callback] payment_hash=${payment_hash}" trace "[ln_manage_callback] payment_hash=${payment_hash}"
local msatoshi=$(echo "${row}" | cut -d '|' -f6) local msatoshi=$(echo "${row}" | cut -d '|' -f6)
@@ -88,13 +97,6 @@ ln_manage_callback() {
trace "[ln_manage_callback] expires_at=${expires_at}" trace "[ln_manage_callback] expires_at=${expires_at}"
local returncode local returncode
if [ -z "${callback_url}" ]; then
# No callback url provided for that invoice
sql "UPDATE ln_invoice SET calledback=1 WHERE id=\"${id}\""
trace_rc $?
return
fi
data="{\"id\":\"${id}\"," data="{\"id\":\"${id}\","
data="${data}\"label\":\"${label}\"," data="${data}\"label\":\"${label}\","
data="${data}\"bolt11\":\"${bolt11}\"," data="${data}\"bolt11\":\"${bolt11}\","
@@ -149,13 +151,22 @@ build_callback() {
local label local label
local derivation_path local derivation_path
# callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id local event_message
# w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime,
# w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message
url=$(echo "${row}" | cut -d '|' -f1)
trace "[build_callback] url=${url}"
if [ -z "${url}" ]; then
# No callback url provided for that watch
trace "[build_callback] No callback url provided for that watch, skipping webhook call"
return
fi
trace "[build_callback] row=${row}" trace "[build_callback] row=${row}"
id=$(echo "${row}" | cut -d '|' -f14) id=$(echo "${row}" | cut -d '|' -f14)
trace "[build_callback] id=${id}" trace "[build_callback] id=${id}"
url=$(echo "${row}" | cut -d '|' -f1)
trace "[build_callback] url=${url}"
address=$(echo "${row}" | cut -d '|' -f2) address=$(echo "${row}" | cut -d '|' -f2)
trace "[build_callback] address=${address}" trace "[build_callback] address=${address}"
txid=$(echo "${row}" | cut -d '|' -f3) txid=$(echo "${row}" | cut -d '|' -f3)
@@ -199,6 +210,8 @@ build_callback() {
derivation_path=$(echo "${row}" | cut -d '|' -f19) derivation_path=$(echo "${row}" | cut -d '|' -f19)
trace "[build_callback] derivation_path=${derivation_path}" trace "[build_callback] derivation_path=${derivation_path}"
fi fi
event_message=$(echo "${row}" | cut -d '|' -f20)
trace "[build_callback] event_message=${event_message}"
data="{\"id\":\"${id}\"," data="{\"id\":\"${id}\","
data="${data}\"address\":\"${address}\"," data="${data}\"address\":\"${address}\","
@@ -212,19 +225,19 @@ build_callback() {
if [ -n "${fee}" ]; then if [ -n "${fee}" ]; then
data="${data}\"fees\":${fee}," data="${data}\"fees\":${fee},"
fi fi
data="${data}\"is_replaceable\":${is_replaceable}" data="${data}\"is_replaceable\":${is_replaceable},"
if [ -n "${blocktime}" ]; then if [ -n "${blocktime}" ]; then
data="${data},\"blockhash\":\"${blockhash}\"," data="${data}\"blockhash\":\"${blockhash}\","
data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\"," data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\","
data="${data}\"blockheight\":${blockheight}" data="${data}\"blockheight\":${blockheight},"
fi fi
if [ -n "${pub32_index}" ]; then if [ -n "${pub32_index}" ]; then
data="${data}\"pub32\":\"${pub32}\"," data="${data}\"pub32\":\"${pub32}\","
data="${data}\"pub32_label\":\"${label}\"," data="${data}\"pub32_label\":\"${label}\","
derivation_path=$(echo -e $derivation_path | sed -En "s/n/${pub32_index}/p") derivation_path=$(echo -e $derivation_path | sed -En "s/n/${pub32_index}/p")
data="${data}\"pub32_derivation_path\":\"${derivation_path}\"" data="${data}\"pub32_derivation_path\":\"${derivation_path}\","
fi fi
data="${data}}" data="${data}\"eventMessage\":\"${event_message}\"}"
trace "[build_callback] data=${data}" trace "[build_callback] data=${data}"
curl_callback "${url}" "${data}" curl_callback "${url}" "${data}"

View File

@@ -30,7 +30,7 @@ confirmation() {
local returncode local returncode
local txid=${1} local txid=${1}
local tx_details local tx_details
tx_details=$(get_transaction ${txid}) tx_details="$(get_transaction ${txid})"
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
trace "[confirmation] tx_details=${tx_details}" trace "[confirmation] tx_details=${tx_details}"
@@ -38,7 +38,6 @@ confirmation() {
trace "[confirmation] Transaction not in watcher, exiting." trace "[confirmation] Transaction not in watcher, exiting."
return 0 return 0
fi fi
######################################################################################################## ########################################################################################################
# First of all, let's make sure we're working on watched addresses... # First of all, let's make sure we're working on watched addresses...
local address local address
@@ -58,7 +57,7 @@ confirmation() {
notfirst=true notfirst=true
fi fi
done done
local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index FROM watching WHERE address IN (${addresseswhere}) AND watching") local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index, event_message FROM watching WHERE address IN (${addresseswhere}) AND watching")
if [ ${#rows} -eq 0 ]; then if [ ${#rows} -eq 0 ]; then
trace "[confirmation] No watched address in this tx!" trace "[confirmation] No watched address in this tx!"
return 0 return 0
@@ -68,7 +67,7 @@ confirmation() {
local tx=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"") local tx=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
local id_inserted local id_inserted
local tx_raw_details=$(get_rawtransaction ${txid}) local tx_raw_details=$(get_rawtransaction ${txid})
local tx_nb_conf=$(echo "${tx_details}" | jq '.result.confirmations') local tx_nb_conf=$(echo "${tx_details}" | jq -r '.result.confirmations // 0')
# Sometimes raw tx are too long to be passed as paramater, so let's write # Sometimes raw tx are too long to be passed as paramater, so let's write
# it to a temp file for it to be read by sqlite3 and then delete the file # it to a temp file for it to be read by sqlite3 and then delete the file
@@ -137,45 +136,62 @@ confirmation() {
rm rawtx-${txid}.blob rm rawtx-${txid}.blob
######################################################################################################## ########################################################################################################
# Let's now insert in the join table if not already done
local event_message
local watching_id
# Let's see if we need to insert tx in the join table
tx=$(sql "SELECT tx_id FROM watching_tx WHERE tx_id=\"${tx}\"") 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 it"
local watching_id
# If the tx is batched and pays multiple watched addresses, we have to insert
# those additional addresses in watching_tx!
for row in ${rows}
do
watching_id=$(echo "${row}" | cut -d '|' -f1)
address=$(echo "${row}" | cut -d '|' -f2)
# In the case of us spending to a watched address, the address appears twice in the details,
# once on the spend side (negative amount) and once on the receiving side (positive amount)
tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout")
tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }')
sql "INSERT OR IGNORE INTO watching_tx (watching_id, tx_id, vout, amount) VALUES (${watching_id}, ${id_inserted}, ${tx_vout_n}, ${tx_vout_amount})"
trace_rc $?
done
else
trace "[confirmation] For this tx, there's already watching_tx rows"
fi
########################################################################################################
########################################################################################################
# Let's now grow the watch window in the case of a xpub watcher...
trace "[confirmation] Let's now grow the watch window in the case of a xpub watcher"
for row in ${rows} for row in ${rows}
do do
address=$(echo "${row}" | cut -d '|' -f2)
tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }')
# In the case of us spending to a watched address, the address appears twice in the details,
# once on the spend side (negative amount) and once on the receiving side (positive amount)
tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout")
########################################################################################################
# Let's now insert in the join table if not already done
if [ -z "${tx}" ]; then
trace "[confirmation] For this tx, there's no watching_tx row, let's create it"
# If the tx is batched and pays multiple watched addresses, we have to insert
# those additional addresses in watching_tx!
watching_id=$(echo "${row}" | cut -d '|' -f1)
sql "INSERT OR IGNORE INTO watching_tx (watching_id, tx_id, vout, amount) VALUES (${watching_id}, ${id_inserted}, ${tx_vout_n}, ${tx_vout_amount})"
trace_rc $?
else
trace "[confirmation] For this tx, there's already watching_tx rows"
fi
########################################################################################################
########################################################################################################
# Let's now grow the watch window in the case of a xpub watcher...
watching_by_pub32_id=$(echo "${row}" | cut -d '|' -f3) watching_by_pub32_id=$(echo "${row}" | cut -d '|' -f3)
pub32_index=$(echo "${row}" | cut -d '|' -f4)
if [ -n "${watching_by_pub32_id}" ]; then if [ -n "${watching_by_pub32_id}" ]; then
trace "[confirmation] Let's now grow the watch window in the case of a xpub watcher"
pub32_index=$(echo "${row}" | cut -d '|' -f4)
extend_watchers ${watching_by_pub32_id} ${pub32_index} extend_watchers ${watching_by_pub32_id} ${pub32_index}
fi fi
done ########################################################################################################
######################################################################################################## ########################################################################################################
# Let's publish the event if needed
event_message=$(echo "${row}" | cut -d '|' -f5)
if [ -n "${event_message}" ]; then
# There's an event message, let's publish it!
trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}\""
response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}")
returncode=$?
trace_rc ${returncode}
fi
########################################################################################################
done
) 201>./.confirmation.lock ) 201>./.confirmation.lock

View File

@@ -3,6 +3,64 @@
. ./trace.sh . ./trace.sh
. ./sql.sh . ./sql.sh
get_txns_by_watchlabel(){
trace "Entering get_txns_by_watchlabel() for label ${1}..."
local label_txns
query=$(cat <<-HERE
SELECT w32.label, w.address, tx.txid, tx.confirmations,tx.blockheight, wtxn.vout, wtxn.amount, tx.blockhash, tx.blocktime, tx.timereceived
FROM watching_by_pub32 as w32
INNER JOIN watching AS w ON w32.id = w.watching_by_pub32_id
INNER JOIN watching_tx AS wtxn ON w.id = wtxn.watching_id
INNER JOIN tx AS tx ON wtxn.tx_id = tx.id
WHERE w32.label="$1"
LIMIT 0,${2-10}
HERE
)
label_txns=$(sql "$query")
returncode=$?
trace_rc ${returncode}
label_txns_json=$(echo "$label_txns" | jq -Rcsn '
{"label_txns":
[inputs
| . / "\n"
| (.[] | select(length > 0) | . / "|") as $input
| {"label": $input[0], "address": $input[1], "txid": $input[2], "confirmations": $input[3], "blockheight": $input[4], "v_out": $input[5], "amount": $input[6], "blockhash": $input[7], "blocktime": $input[8], "timereceived": $input[9]}
]
}
')
echo "$label_txns_json"
return ${returncode}
}
get_unused_addresses_by_watchlabel(){
trace "Entering get_unused_addresses_by_watchlabel() for label ${1}..."
local label_unused_addrs
query=$(cat <<-HERE
SELECT w32.id, w32.label, w32.pub32, w.pub32_index, w.address
FROM watching as w
INNER JOIN watching_by_pub32 AS w32 ON w.watching_by_pub32_id = w32.id
WHERE w32.label="$1"
AND NOT EXISTS (
SELECT 1 FROM watching_tx WHERE watching_id = w.id
)
ORDER BY w.pub32_index ASC
LIMIT 0,${2-10}
HERE
)
label_unused_addrs=$(sql "$query")
returncode=$?
trace_rc ${returncode}
label_unused_addrs_json=$(echo "$label_unused_addrs" | jq -Rcsn '
{"label_unused_addresses":
[inputs
| . / "\n"
| (.[] | select(length > 0) | . / "|") as $input
| {"pub32_watch_id": $input[0], "pub32_label": $input[1], "pub32" : $input[2], "address_pub32_index": $input[3], "address": $input[4]}
]
}
')
echo "$label_unused_addrs_json"
return ${returncode}
}
getactivewatches() { getactivewatches() {
trace "Entering getactivewatches()..." trace "Entering getactivewatches()..."

View File

@@ -2,6 +2,7 @@
. ./trace.sh . ./trace.sh
. ./callbacks_txid.sh . ./callbacks_txid.sh
. ./blockchainrpc.sh
newblock() { newblock() {
trace "Entering newblock()..." trace "Entering newblock()..."
@@ -9,5 +10,16 @@ newblock() {
local request=${1} local request=${1}
local blockhash=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3) local blockhash=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3)
local blockinfo
blockinfo=$(get_block_info ${blockhash})
local blockheight
blockheight=$(echo ${blockinfo} | jq -r ".result.height")
trace "[newblock] mosquitto_pub -h broker -t newblock -m \"{\"blockhash\":\"${blockhash}\",\"blockheight\":\"${blockheight}\"}\""
response=$(mosquitto_pub -h broker -t newblock -m "{\"blockhash\":\"${blockhash}\",\"blockheight\":\"${blockheight}\"}")
returncode=$?
trace_rc ${returncode}
do_callbacks_txid do_callbacks_txid
} }

View File

@@ -89,6 +89,7 @@ main() {
watch) watch)
# POST http://192.168.111.152:8080/watch # POST http://192.168.111.152:8080/watch
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"} # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"}
response=$(watchrequest "${line}") response=$(watchrequest "${line}")
response_to_client "${response}" ${?} response_to_client "${response}" ${?}
@@ -161,6 +162,18 @@ main() {
response_to_client "${response}" ${?} response_to_client "${response}" ${?}
break break
;; ;;
get_txns_by_watchlabel)
# curl (GET) 192.168.111.152:8080/get_txns_by_watchlabel/<label>/<count>
response=$(get_txns_by_watchlabel $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3) $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f4))
response_to_client "${response}" ${?}
break
;;
get_unused_addresses_by_watchlabel)
# curl (GET) 192.168.111.152:8080/get_unused_addresses_by_watchlabel/<label>/<count>
response=$(get_unused_addresses_by_watchlabel $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3) $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f4))
response_to_client "${response}" ${?}
break
;;
conf) conf)
# curl (GET) 192.168.111.152:8080/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387 # curl (GET) 192.168.111.152:8080/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387
@@ -225,6 +238,13 @@ main() {
response_to_client "${response}" ${?} response_to_client "${response}" ${?}
break break
;; ;;
get_txns_spending)
# curl (GET) http://192.168.111.152:8080/get_txns_spending/20/10
response=$(get_txns_spending $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3) $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f4))
response_to_client "${response}" ${?}
break
;;
getbalance) getbalance)
# curl (GET) http://192.168.111.152:8080/getbalance # curl (GET) http://192.168.111.152:8080/getbalance
@@ -263,7 +283,7 @@ main() {
;; ;;
spend) spend)
# POST http://192.168.111.152:8080/spend # POST http://192.168.111.152:8080/spend
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233} # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJ3aGF0ZXZlciI6MTIzfQo="}
response=$(spend "${line}") response=$(spend "${line}")
response_to_client "${response}" ${?} response_to_client "${response}" ${?}

View File

@@ -4,18 +4,18 @@
send_to_watcher_node() { send_to_watcher_node() {
trace "Entering send_to_watcher_node()..." trace "Entering send_to_watcher_node()..."
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/${WATCHER_BTC_NODE_DEFAULT_WALLET} ${WATCHER_NODE_RPC_CFG} $@ local node_payload
node_payload="$(send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/${WATCHER_BTC_NODE_DEFAULT_WALLET} ${WATCHER_NODE_RPC_CFG} $@)"
local returncode=$? local returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
if [ "${returncode}" -ne 0 ]; then if [ "${returncode}" -ne 0 ]; then
# Ok, since we now have multiple watching wallets, we need to try them all if it fails # Ok, since we now have multiple watching wallets, we need to try them all if it fails
# We have 2 right now: watching and watching-for-xpubs # We have 2 right now: watching and watching-for-xpubs
send_to_watcher_node_wallet ${WATCHER_BTC_NODE_XPUB_WALLET} $@ node_payload="$(send_to_watcher_node_wallet ${WATCHER_BTC_NODE_XPUB_WALLET} $@)"
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
fi fi
echo "$node_payload"
return ${returncode} return ${returncode}
} }

View File

@@ -43,7 +43,7 @@ ln_waitanyinvoice() {
paid_at=$(echo "${result}" | jq -r ".paid_at") paid_at=$(echo "${result}" | jq -r ".paid_at")
sql "UPDATE ln_invoice SET status=\"${status}\", pay_index=${pay_index}, msatoshi_received=${msatoshi_received}, paid_at=${paid_at} WHERE bolt11=\"${bolt11}\"" sql "UPDATE ln_invoice SET status=\"${status}\", pay_index=${pay_index}, msatoshi_received=${msatoshi_received}, paid_at=${paid_at} WHERE bolt11=\"${bolt11}\""
row=$(sql "SELECT id, label, bolt11, callback_url, payment_hash, msatoshi, status, pay_index, msatoshi_received, paid_at, description, expires_at FROM ln_invoice WHERE NOT calledback AND bolt11=\"${bolt11}\"") row=$(sql "SELECT id, label, bolt11, callback_url, payment_hash, msatoshi, status, pay_index, msatoshi_received, paid_at, description, expires_at FROM ln_invoice WHERE callback_url<>\"\" AND NOT calledback AND bolt11=\"${bolt11}\"")
if [ -n "${row}" ]; then if [ -n "${row}" ]; then
ln_manage_callback ${row} ln_manage_callback ${row}

View File

@@ -10,7 +10,7 @@ spend() {
local request=${1} local request=${1}
local address=$(echo "${request}" | jq -r ".address") local address=$(echo "${request}" | jq -r ".address")
trace "[spend] address=${address}" trace "[spend] address=${address}"
local amount=$(echo "${request}" | jq ".amount" | awk '{ printf "%.8f", $0 }') local amount=$(echo "${request}" | jq -r ".amount" | awk '{ printf "%.8f", $0 }')
trace "[spend] amount=${amount}" trace "[spend] amount=${amount}"
local response local response
local id_inserted local id_inserted
@@ -39,10 +39,30 @@ spend() {
local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"') local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"')
tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0) tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0)
local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }') local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
local rawtx=$(echo "${tx_details}" | jq '.result.hex') # Sometimes raw tx are too long to be passed as paramater, so let's write
# it to a temp file for it to be read by sqlite3 and then delete the file
echo "${tx_raw_details}" > rawtx-${txid}.blob
########################################################################################################
# Let's publish the event if needed
local event_message
event_message=$(echo "${request}" | jq -er ".eventMessage")
if [ "$?" -ne "0" ]; then
# event_message tag null, so there's no event_message
trace "[spend] event_message="
event_message=
else
# There's an event message, let's publish it!
trace "[spend] mosquitto_pub -h broker -t spend -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"eventMessage\":\"${event_message}\"}\""
response=$(mosquitto_pub -h broker -t spend -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"eventMessage\":\"${event_message}\"}")
returncode=$?
trace_rc ${returncode}
fi
########################################################################################################
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation # Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${rawtx})" sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('rawtx-${txid}.blob'))"
trace_rc $? trace_rc $?
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"") id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
trace_rc $? trace_rc $?
@@ -51,6 +71,9 @@ spend() {
data="{\"status\":\"accepted\"" data="{\"status\":\"accepted\""
data="${data},\"hash\":\"${txid}\"}" data="${data},\"hash\":\"${txid}\"}"
# Delete the temp file containing the raw tx (see above)
rm rawtx-${txid}.blob
else else
local message=$(echo "${response}" | jq -e ".error.message") local message=$(echo "${response}" | jq -e ".error.message")
data="{\"message\":${message}}" data="{\"message\":${message}}"
@@ -100,6 +123,32 @@ bumpfee() {
return ${returncode} return ${returncode}
} }
get_txns_spending() {
trace "Entering get_txns_spending()... with count: $1 , skip: $2"
local count="$1"
local skip="$2"
local response
local data="{\"method\":\"listtransactions\",\"params\":[\"*\",${count:-10},${skip:-0}]}"
response=$(send_to_spender_node "${data}")
local returncode=$?
trace_rc ${returncode}
trace "[get_txns_spending] response=${response}"
if [ "${returncode}" -eq 0 ]; then
local txns=$(echo ${response} | jq -rc ".result")
trace "[get_txns_spending] txns=${txns}"
data="{\"txns\":${txns}}"
else
trace "[get_txns_spending] Coudn't get txns!"
data=""
fi
trace "[get_txns_spending] responding=${data}"
echo "${data}"
return ${returncode}
}
getbalance() { getbalance() {
trace "Entering getbalance()..." trace "Entering getbalance()..."
@@ -185,24 +234,22 @@ getbalancebyxpub() {
local returncode local returncode
# addresses=$(./bitcoin-cli -rpcwallet=xpubwatching01.dat getaddressesbylabel upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb | jq "keys" | tr -d '\n ') # addresses=$(./bitcoin-cli -rpcwallet=xpubwatching01.dat getaddressesbylabel upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb | jq "keys" | tr -d '\n ')
data="{\"method\":\"getaddressesbylabel\",\"params\":[${xpub}]}" data="{\"method\":\"getaddressesbylabel\",\"params\":[\"${xpub}\"]}"
trace "[getbalancebyxpub] data=${data}" trace "[getbalancebyxpub] data=${data}"
addresses=$(send_to_xpub_watcher_wallet ${data} | jq "keys" | tr -d '\n ') addresses=$(send_to_xpub_watcher_wallet ${data} | jq ".result | keys" | tr -d '\n ')
# ./bitcoin-cli -rpcwallet=xpubwatching01.dat listunspent 0 9999999 "$addresses" | jq "[.[].amount] | add" # ./bitcoin-cli -rpcwallet=xpubwatching01.dat listunspent 0 9999999 "$addresses" | jq "[.[].amount] | add"
data="{\"method\":\"listunspent\",\"params\":[0,9999999,${addresses}]}"
data="{\"method\":\"listunspent\",\"params\":[0, 9999999, \"${addresses}\"]}"
trace "[getbalancebyxpub] data=${data}" trace "[getbalancebyxpub] data=${data}"
balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.[].amount] | add | . * 100000000 | trunc | . / 100000000") balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.result[].amount // 0 ] | add | . * 100000000 | trunc | . / 100000000")
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
trace "[getbalancebyxpub] balance=${balance}" trace "[getbalancebyxpub] balance=${balance}"
data="{\"event\":\"${event}\",\"xpub\":\"${xpub}\",\"balance\":${balance}}" data="{\"event\":\"${event}\",\"xpub\":\"${xpub}\",\"balance\":${balance:-0}}"
echo "${data}" echo "${data}"
return ${returncode} return "${returncode}"
} }
getnewaddress() { getnewaddress() {
@@ -315,10 +362,12 @@ batchspend() {
local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"') local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"')
tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0) tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0)
local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }') local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
local rawtx=$(echo "${tx_details}" | jq '.result.hex') # Sometimes raw tx are too long to be passed as paramater, so let's write
# it to a temp file for it to be read by sqlite3 and then delete the file
echo "${tx_raw_details}" > rawtx-${txid}.blob
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation # Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${rawtx})" sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('rawtx-${txid}.blob'))"
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
if [ "${returncode}" -eq 0 ]; then if [ "${returncode}" -eq 0 ]; then
@@ -330,6 +379,9 @@ batchspend() {
data="{\"status\":\"accepted\"" data="{\"status\":\"accepted\""
data="${data},\"hash\":\"${txid}\"}" data="${data},\"hash\":\"${txid}\"}"
# Delete the temp file containing the raw tx (see above)
rm rawtx-${txid}.blob
else else
local message=$(echo "${response}" | jq -e ".error.message") local message=$(echo "${response}" | jq -e ".error.message")
data="{\"message\":${message}}" data="{\"message\":${message}}"

View File

@@ -11,14 +11,33 @@ watchrequest() {
local returncode local returncode
local request=${1} local request=${1}
local address=$(echo "${request}" | jq -r ".address") local address=$(echo "${request}" | jq -er ".address")
local cb0conf_url=$(echo "${request}" | jq -r ".unconfirmedCallbackURL") local cb0conf_url
local cb1conf_url=$(echo "${request}" | jq -r ".confirmedCallbackURL") cb0conf_url=$(echo "${request}" | jq -er ".unconfirmedCallbackURL")
if [ "$?" -ne "0" ]; then
# unconfirmedCallbackURL tag null, so there's no unconfirmedCallbackURL
trace "[watchrequest] unconfirmedCallbackURL="
unconfirmedCallbackURL=
fi
local cb1conf_url
cb1conf_url=$(echo "${request}" | jq -er ".confirmedCallbackURL")
if [ "$?" -ne "0" ]; then
# confirmedCallbackURL tag null, so there's no confirmedCallbackURL
trace "[watchrequest] confirmedCallbackURL="
confirmedCallbackURL=
fi
local event_message
event_message=$(echo "${request}" | jq -er ".eventMessage")
if [ "$?" -ne "0" ]; then
# event_message tag null, so there's no event_message
trace "[watchrequest] event_message="
event_message=
fi
local imported local imported
local inserted local inserted
local id_inserted local id_inserted
local result local result
trace "[watchrequest] Watch request on address (${address}), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url})" trace "[watchrequest] Watch request on address (${address}), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url}) with event_message=${event_message}"
result=$(importaddress_rpc "${address}") result=$(importaddress_rpc "${address}")
returncode=$? returncode=$?
@@ -29,7 +48,7 @@ watchrequest() {
imported=0 imported=0
fi fi
sql "INSERT OR REPLACE INTO watching (address, watching, callback0conf, callback1conf, imported) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported})" sql "INSERT OR REPLACE INTO watching (address, watching, callback0conf, callback1conf, imported, event_message) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported}, '${event_message}')"
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
if [ "${returncode}" -eq 0 ]; then if [ "${returncode}" -eq 0 ]; then
@@ -63,7 +82,8 @@ watchrequest() {
\"estimatesmartfee2blocks\":\"${fees2blocks}\", \"estimatesmartfee2blocks\":\"${fees2blocks}\",
\"estimatesmartfee6blocks\":\"${fees6blocks}\", \"estimatesmartfee6blocks\":\"${fees6blocks}\",
\"estimatesmartfee36blocks\":\"${fees36blocks}\", \"estimatesmartfee36blocks\":\"${fees36blocks}\",
\"estimatesmartfee144blocks\":\"${fees144blocks}\"}" \"estimatesmartfee144blocks\":\"${fees144blocks}\",
\"eventMessage\":\"${event_message}\"}"
trace "[watchrequest] responding=${data}" trace "[watchrequest] responding=${data}"
echo "${data}" echo "${data}"