Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebc542be6 | ||
|
|
f9f3624305 | ||
|
|
78e56ee75e | ||
|
|
1017448362 |
55
.github/workflows/main_ci.yml
vendored
55
.github/workflows/main_ci.yml
vendored
@ -1,55 +0,0 @@
|
||||
name: Run Tests
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
run-test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
btcpay:
|
||||
image: junderw/btcpay-client-test-server@sha256:2ab65051329e0250cd7d5ff3f3182a95d81228e469b518beb570a288f313e5c1
|
||||
ports:
|
||||
- 49392:49392
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
coverage-check:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
btcpay:
|
||||
image: junderw/btcpay-client-test-server@sha256:2ab65051329e0250cd7d5ff3f3182a95d81228e469b518beb570a288f313e5c1
|
||||
ports:
|
||||
- 49392:49392
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm run coverage
|
||||
lint-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
format-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm run format:ci
|
||||
64
.gitignore
vendored
64
.gitignore
vendored
@ -1,62 +1,2 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# Packages #
|
||||
############
|
||||
# it's better to unpack these files and commit the raw source
|
||||
# git has its own built in compression methods
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# Logs and databases #
|
||||
######################
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Environment variables #
|
||||
#########################
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Node.js #
|
||||
################
|
||||
/node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE #
|
||||
#######
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
dist/
|
||||
coverage/
|
||||
node_modules/
|
||||
package-lock.json
|
||||
39
README.md
39
README.md
@ -1,20 +1,20 @@
|
||||
> :warning: This package is deprecated, BTCPay Server is exposing a new, more complete and easy to use API called `Greenfield`. [Check the doc](https://docs.btcpayserver.org/Development/GreenFieldExample/)
|
||||
|
||||
# node-btcpay
|
||||
|
||||
## Install
|
||||
```shell
|
||||
npm i btcpay
|
||||
npm install https://github.com/tanjalo/node-btcpay
|
||||
```
|
||||
|
||||
## Private key generation
|
||||
* Generate and save private key:
|
||||
```bash
|
||||
$ node -p "require('btcpay').crypto.generate_keypair().getPrivate('hex')"
|
||||
XXXXXXXXXXXXXXXXXXXXX
|
||||
```js
|
||||
const btcpay = require('btcpay')
|
||||
const privatekey = btcpay.crypto.generate_keypair().getPrivate('hex')
|
||||
|
||||
console.log(`PRIVATEKEY: ${privatekey}`)
|
||||
```
|
||||
|
||||
Store the printed value in a safe place, e.g. environment variables
|
||||
Store the value of "priv" in a save place, e.g. environment variables
|
||||
|
||||
## Pairing
|
||||
|
||||
@ -23,18 +23,20 @@ After generating your private key, you have to pair your client with your BTCPay
|
||||
* On BTCPay Server > Stores > Settings > Access Tokens > Create a new token, (leave PublicKey blank) > Request pairing
|
||||
* Copy pairing code:
|
||||
* Pair client to server and save returned token:
|
||||
```js
|
||||
const btcpay = require('btcpay')
|
||||
const keypair = btcpay.crypto.load_keypair(new Buffer.from(<PRIVATEKEY>, 'hex'))
|
||||
const client = new btcpay.BTCPayClient(<BTCPAYURL>, btcpay.crypto.load_keypair(Buffer.from(<PRIVATEKEY>, 'hex')))
|
||||
|
||||
```bash
|
||||
# Replace the BTCPAY_XXX envirnoment variables with your values and run:
|
||||
|
||||
$ [space] BTCPAY_URL=https://mydomain.com/ BTCPAY_KEY=... BTCPAY_PAIRCODE=... node -e "const btcpay=require('btcpay'); new btcpay.BTCPayClient(process.env.BTCPAY_URL, btcpay.crypto.load_keypair(Buffer.from(process.env.BTCPAY_KEY, 'hex'))).pair_client(process.env.BTCPAY_PAIRCODE).then(console.log).catch(console.error)"
|
||||
|
||||
# (prepend the line with a space to prevent BTCPAY_KEY from being saved to your bash history)
|
||||
// Pair client to server
|
||||
client
|
||||
.pair_client(<PAIRINGCODE>)
|
||||
.then(res => console.log(res))
|
||||
.catch(err => console.log(err))
|
||||
|
||||
>>> { merchant: 'XXXXXX' }
|
||||
```
|
||||
|
||||
Store the value of "merchant" in a safe place, e.g. environment variables
|
||||
Store the value of "merchant" in a save place, e.g. environment variables
|
||||
|
||||
## Recreating a client
|
||||
After pairing your client to the store, you can recreate the client as needed and use it in your code
|
||||
@ -43,17 +45,16 @@ const btcpay = require('btcpay')
|
||||
const keypair = btcpay.crypto.load_keypair(new Buffer.from(<PRIVATEKEY>, 'hex'))
|
||||
|
||||
// Recreate client
|
||||
const client = new btcpay.BTCPayClient(<BTCPAYURL>, keypair, {merchant: <MERCHANT>})
|
||||
const client = new btcpay.BTCPayClient(<BCTPAYURL>, keypair, {merchant: <MERCHANT>})
|
||||
```
|
||||
|
||||
### Get rates
|
||||
Fetches current rates from BitcoinAverage (using your BTCPayServer)
|
||||
```js
|
||||
client.get_rates(['BTC_USD'], <STOREID>)
|
||||
client.get_rates('BTC_USD')
|
||||
.then(rates => console.log(rates))
|
||||
.catch(err => console.log(err))
|
||||
```
|
||||
The first argument accepts a comma-separated list of currency pair.
|
||||
|
||||
### Create invoice
|
||||
See [BitPay Invoice API documentation](https://bitpay.com/api#resource-Invoices)
|
||||
@ -68,4 +69,4 @@ client.create_invoice({price: 20, currency: 'USD'})
|
||||
client.get_invoice(<invoice-id>)
|
||||
.then(invoice => console.log(invoice.status))
|
||||
.catch(err => console.log(err))
|
||||
```
|
||||
```
|
||||
161
client.js
Normal file
161
client.js
Normal file
@ -0,0 +1,161 @@
|
||||
let _ = require('underscore')
|
||||
let Promise = require('bluebird')
|
||||
let crypto = require('./cryptography')
|
||||
let qs = require('querystring')
|
||||
let rp = require('request-promise')
|
||||
|
||||
|
||||
// TODO: peer certificate verification
|
||||
function BTCPayClient(host, keypair, tokens) {
|
||||
this.host = host
|
||||
this.kp = keypair
|
||||
this.tokens = tokens == undefined ? {} : tokens
|
||||
this.client_id = crypto.get_sin_from_key(keypair)
|
||||
this.user_agent = 'node-btcpay'
|
||||
this.options = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Accept-Version': '2.0.0'
|
||||
},
|
||||
json: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype._create_signed_headers = function (uri, payload) {
|
||||
return {
|
||||
'X-Identity': Buffer.from(this.kp.getPublic().encodeCompressed()).toString('hex'),
|
||||
'X-Signature': crypto.sign(uri + payload, this.kp).toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype._signed_get_request = function (path, params, token) {
|
||||
let _token = token ? token : _.values(this.tokens)[0]
|
||||
let _params = params ? params : {}
|
||||
_params['token'] = _token
|
||||
|
||||
let _uri = this.host + path
|
||||
let _payload = '?' + qs.stringify(_params)
|
||||
let _options = JSON.parse(JSON.stringify(this.options))
|
||||
|
||||
_.extend(_options.headers, this._create_signed_headers(_uri, _payload))
|
||||
_options['uri'] = _uri
|
||||
_options['qs'] = _params
|
||||
|
||||
return rp.get(_options).then(resp => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
resolve(resp['data'])
|
||||
})
|
||||
}).catch(err => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype._signed_post_request = function (path, payload, token) {
|
||||
let _token = token ? token : _.values(this.tokens)[0]
|
||||
payload['token'] = _token
|
||||
|
||||
let _uri = this.host + path
|
||||
let _payload = JSON.stringify(payload)
|
||||
let _options = JSON.parse(JSON.stringify(this.options))
|
||||
|
||||
_.extend(_options.headers, this._create_signed_headers(_uri, _payload))
|
||||
_options['uri'] = _uri
|
||||
_options['body'] = payload
|
||||
|
||||
return rp.post(_options).then(resp => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
resolve(resp['data'])
|
||||
})
|
||||
}).catch(err => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype._unsigned_request = function (path, payload) {
|
||||
let _uri = this.host + path
|
||||
let _options = JSON.parse(JSON.stringify(this.options))
|
||||
|
||||
if (payload) {
|
||||
_options['uri'] = _uri
|
||||
_options['body'] = payload
|
||||
|
||||
return rp.post(_options).then(resp => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
resolve(resp['data'])
|
||||
})
|
||||
}).catch(err => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
_options['uri'] = _uri
|
||||
|
||||
return rp.get(_options).then(resp => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
resolve(resp['data'])
|
||||
})
|
||||
}).catch(err => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BTCPayClient.prototype.get_rates = function (currencyPairs) {
|
||||
let _params = {currencyPairs: currencyPairs}
|
||||
return this._signed_get_request('/rates', _params)
|
||||
}
|
||||
|
||||
BTCPayClient.prototype.create_invoice = function (payload, token) {
|
||||
let re = new RegExp('^[A-Z]{3,3}$')
|
||||
|
||||
if (!re.test(payload['currency'])) {
|
||||
throw 'Currency is invalid'
|
||||
}
|
||||
|
||||
if (isNaN(parseFloat(payload['price']))) {
|
||||
throw 'Price must be a float'
|
||||
}
|
||||
|
||||
return this._signed_post_request('/invoices', payload, token)
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype.get_invoice = function (invoice_id, token) {
|
||||
return this._signed_get_request('/invoices/' + invoice_id, token)
|
||||
}
|
||||
|
||||
|
||||
BTCPayClient.prototype.pair_client = function (code) {
|
||||
let re = new RegExp('^\\w{7,7}$')
|
||||
|
||||
if (!re.test(code)) {
|
||||
throw 'pairing code is not valid'
|
||||
}
|
||||
|
||||
let payload = {id: this.client_id, pairingCode: code}
|
||||
|
||||
return this._unsigned_request('/tokens', payload).then(data => {
|
||||
let _data = data[0]
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
let _res = {}
|
||||
_res[_data.facade] = _data.token
|
||||
|
||||
resolve(_res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = BTCPayClient
|
||||
45
cryptography.js
Normal file
45
cryptography.js
Normal file
@ -0,0 +1,45 @@
|
||||
let crypto = require('crypto')
|
||||
let EC = require('elliptic').ec,
|
||||
ec = new EC('secp256k1');
|
||||
let bs58 = require('bs58')
|
||||
const BN = require('bn.js')
|
||||
|
||||
let generate_keypair = function () {
|
||||
let kp = ec.genKeyPair()
|
||||
return kp
|
||||
}
|
||||
|
||||
let load_keypair = function (buf) {
|
||||
return ec.keyFromPrivate(buf)
|
||||
}
|
||||
|
||||
let get_sin_from_key = function (kp) {
|
||||
let pk = Buffer.from(kp.getPublic().encodeCompressed())
|
||||
let version = get_version_from_compressed_key(pk)
|
||||
let checksum = get_checksum_from_version(version)
|
||||
return bs58.encode(Buffer.concat([version, checksum]))
|
||||
}
|
||||
|
||||
let sign = function (message, kp) {
|
||||
let digest = crypto.createHash('sha256').update(message).digest()
|
||||
return Buffer.from(kp.sign(digest).toDER())
|
||||
}
|
||||
|
||||
let get_version_from_compressed_key = function (pk) {
|
||||
let sh2 = crypto.createHash('sha256').update(pk).digest()
|
||||
let rp = crypto.createHash('ripemd160').update(sh2).digest()
|
||||
|
||||
return Buffer.concat([Buffer.from('0F', 'hex'), Buffer.from('02', 'hex'), rp])
|
||||
}
|
||||
|
||||
let get_checksum_from_version = function (version) {
|
||||
let h1 = crypto.createHash('sha256').update(version).digest()
|
||||
let h2 = crypto.createHash('sha256').update(h1).digest()
|
||||
|
||||
return h2.slice(0, 4)
|
||||
}
|
||||
|
||||
exports.generate_keypair = generate_keypair
|
||||
exports.load_keypair = load_keypair
|
||||
exports.get_sin_from_key = get_sin_from_key
|
||||
exports.sign = sign
|
||||
@ -1,80 +0,0 @@
|
||||
FROM junderw/nbxplorer-client-test-server
|
||||
MAINTAINER Jonathan Underwood
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
WORKDIR /root
|
||||
|
||||
# Install postgres
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/pgdg.list
|
||||
RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN apt update && \
|
||||
apt install -y \
|
||||
postgresql-11 \
|
||||
dotnet-sdk-3.1
|
||||
|
||||
RUN mkdir -p /pgsql/data
|
||||
RUN chmod 700 /pgsql/data && \
|
||||
chown postgres:postgres /pgsql/data
|
||||
|
||||
RUN su postgres -c "\
|
||||
cd /pgsql/data; \
|
||||
/usr/lib/postgresql/11/bin/pg_ctl initdb --pgdata=/pgsql/data; \
|
||||
/usr/lib/postgresql/11/bin/postgres -D /pgsql/data -h 0.0.0.0 -i & \
|
||||
sleep 5; \
|
||||
psql -h 127.0.0.1 -c \"CREATE DATABASE btcpayserverregtest;\"; \
|
||||
sleep 3; \
|
||||
/usr/lib/postgresql/11/bin/pg_ctl stop --pgdata=/pgsql/data -m f"
|
||||
|
||||
# Install BTCPayServer
|
||||
RUN git clone https://github.com/btcpayserver/btcpayserver.git && \
|
||||
cd btcpayserver/ && \
|
||||
git checkout 80e46db && \
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT=1 dotnet build -c Release BTCPayServer/BTCPayServer.csproj
|
||||
|
||||
# Re-install NBXplorer because I'm an idiot
|
||||
RUN cd NBXplorer/ && \
|
||||
git fetch origin && \
|
||||
git checkout ce2f21f && \
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT=1 dotnet build -c Release NBXplorer/NBXplorer.csproj
|
||||
|
||||
RUN sed -i 's/generate 432/generatetoaddress 432 \$\(bitcoin-cli -regtest getnewaddress\)/g' /root/run_bitcoind_service.sh
|
||||
|
||||
RUN apt install -y \
|
||||
libx11-6 libx11-xcb1 libxcb1 libxcb-dri3-0 libxcomposite1 \
|
||||
libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 \
|
||||
libxss1 libdrm2 libxrandr2 libgbm1 libasound2 libatk1.0-0 \
|
||||
libatk-bridge2.0-0 libpangocairo-1.0-0 libgtk-3-0
|
||||
|
||||
COPY \
|
||||
start_everything.sh \
|
||||
start_btcpay.sh \
|
||||
./
|
||||
RUN chmod +x start_everything.sh && \
|
||||
chmod +x start_btcpay.sh
|
||||
|
||||
RUN mkdir -p /root/registerAdmin && \
|
||||
cd /root/registerAdmin && \
|
||||
npm init -y && \
|
||||
npm install puppeteer@3.0.1 btcpay@0.2.4
|
||||
COPY \
|
||||
registerAdmin.js \
|
||||
./registerAdmin
|
||||
|
||||
RUN /root/start_everything.sh >/dev/null 2>&1 & \
|
||||
sleep 20 && \
|
||||
BTCPAY_IGNORE_SANDBOX_ERROR=1 node /root/registerAdmin/registerAdmin.js
|
||||
|
||||
RUN apt-get purge -y \
|
||||
libx11-6 libx11-xcb1 libxcb1 libxcb-dri3-0 libxcomposite1 \
|
||||
libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 \
|
||||
libxss1 libdrm2 libxrandr2 libgbm1 libasound2 libatk1.0-0 \
|
||||
libatk-bridge2.0-0 libpangocairo-1.0-0 libgtk-3-0 && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /root/registerAdmin
|
||||
|
||||
ENTRYPOINT ["/root/start_everything.sh"]
|
||||
|
||||
EXPOSE 18271
|
||||
EXPOSE 23828
|
||||
EXPOSE 49392
|
||||
EXPOSE 8080
|
||||
@ -1,30 +0,0 @@
|
||||
# Docker Image
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cd ./docker
|
||||
./build.sh btcpay-client-test-server
|
||||
```
|
||||
|
||||
## OR Pull from docker hub
|
||||
|
||||
```bash
|
||||
docker pull junderw/btcpay-client-test-server
|
||||
```
|
||||
|
||||
## run on localhost then run tests
|
||||
|
||||
```bash
|
||||
# If you built it
|
||||
docker run -d -p 49392:49392 btcpay-client-test-server
|
||||
# OR, if you pulled from docker hub
|
||||
docker run -d -p 49392:49392 junderw/btcpay-client-test-server
|
||||
|
||||
# Wait 6 seconds before running the tests
|
||||
|
||||
# Install deps
|
||||
npm install
|
||||
# Run tests
|
||||
npm test
|
||||
```
|
||||
@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
TEMP=$1-temp
|
||||
CONT=tempcont$(date +%s)
|
||||
|
||||
docker build $DIR -t $TEMP
|
||||
docker run --name $CONT $TEMP &
|
||||
sleep 120
|
||||
docker exec $CONT bash -c "bitcoin-cli -regtest sendtoaddress \$(cat /root/btcpay.address.p2wpkh) 0.1337"
|
||||
docker exec $CONT bash -c "bitcoin-cli -regtest sendtoaddress \$(cat /root/btcpay.address.p2shp2wpkh) 0.1337"
|
||||
docker exec $CONT bash -c "bitcoin-cli -regtest generatetoaddress 6 mwhw4fySPWHZ4CUT1QHXWqAdE2wj9pJsa4"
|
||||
sleep 20
|
||||
docker exec $CONT bash -c "kill \$(pgrep BTCPayServer)"
|
||||
docker exec $CONT su postgres -c "/usr/lib/postgresql/11/bin/pg_ctl stop --pgdata=/pgsql/data -m f"
|
||||
docker exec $CONT bash -c "kill \$(pgrep NBXplorer)"
|
||||
docker exec $CONT bash -c "kill \$(pgrep node)"
|
||||
docker exec $CONT bitcoin-cli -regtest stop
|
||||
docker exec $CONT bash -c "sed -i 's/\/usr\/bin\/bitcoin-cli -regtest generatetoaddress 432 mwhw4fySPWHZ4CUT1QHXWqAdE2wj9pJsa4//g' /root/run_bitcoind_service.sh"
|
||||
docker exec $CONT bash -c "sed -i 's/sleep 2//g' /root/run_bitcoind_service.sh"
|
||||
docker exec $CONT bash -c "sed -i 's/\/cookie/\/tokens/g' /root/stopAndCookie/stopAndCookie.js"
|
||||
docker exec $CONT bash -c "sed -i 's/\/datadir\/RegTest\/.cookie/\/root\/btcpaytokens/g' /root/stopAndCookie/stopAndCookie.js"
|
||||
docker exec $CONT bash -c "sed -i 's/sleep infinity/node \/root\/stopAndCookie\/stopAndCookie.js \&\nsleep infinity/g' /root/start_everything.sh"
|
||||
docker stop $CONT
|
||||
docker commit $CONT $TEMP
|
||||
$DIR/build_stage2.sh $1
|
||||
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
TEMP=$1-temp
|
||||
CONT=tempcont$(date +%s)
|
||||
|
||||
# Start up the temp image and wait for BTCPayServer to calm down, then shutdown
|
||||
docker run --name $CONT $TEMP &
|
||||
sleep 15
|
||||
docker exec $CONT bash -c "kill \$(pgrep BTCPayServer)"
|
||||
docker exec $CONT su postgres -c "/usr/lib/postgresql/11/bin/pg_ctl stop --pgdata=/pgsql/data -m f"
|
||||
docker exec $CONT bash -c "kill \$(pgrep NBXplorer)"
|
||||
docker exec $CONT bash -c "kill \$(pgrep node)"
|
||||
docker exec $CONT bitcoin-cli -regtest stop
|
||||
|
||||
# New checkout (for testing)
|
||||
# docker exec $CONT bash -c "cd /root/btcpayserver; \
|
||||
# git fetch origin; \
|
||||
# git checkout 79c70b3;"
|
||||
|
||||
# Replace login files
|
||||
docker exec $CONT bash -c "sed -i 's/input asp-for=\"Email\"/input asp-for=\"Email\" value=\"test\@example.com\"/g' /root/btcpayserver/BTCPayServer/Views/Account/Login.cshtml"
|
||||
docker exec $CONT bash -c "sed -i 's/input asp-for=\"Password\"/input asp-for=\"Password\" value=\"satoshinakamoto\"/g' /root/btcpayserver/BTCPayServer/Views/Account/Login.cshtml"
|
||||
# Rebuild BTCPayServer
|
||||
docker exec $CONT bash -c "cd /root/btcpayserver; DOTNET_CLI_TELEMETRY_OPTOUT=1 dotnet build -c Release BTCPayServer/BTCPayServer.csproj"
|
||||
sleep 2
|
||||
docker stop $CONT
|
||||
docker commit $CONT $1
|
||||
@ -1,237 +0,0 @@
|
||||
const puppeteer = require('puppeteer')
|
||||
const fs = require('fs')
|
||||
const btcpay = require('btcpay')
|
||||
|
||||
const IGNORE_SANDBOX_ERROR = process.env['BTCPAY_IGNORE_SANDBOX_ERROR'];
|
||||
const URL = 'http://127.0.0.1:49392';
|
||||
const STORENAME = 'Test Store for testing';
|
||||
const USER_NAME = 'test@example.com';
|
||||
const PASSWORD = 'satoshinakamoto';
|
||||
const WINDOW_WIDTH = 1920
|
||||
const WINDOW_HEIGHT = 1080
|
||||
const HEADLESS = true
|
||||
|
||||
// const sleep = ms => new Promise(r => setTimeout(r,ms))
|
||||
|
||||
function writeAddress(address, type) {
|
||||
fs.writeFileSync('/root/btcpay.address.' + type, address)
|
||||
}
|
||||
|
||||
function writeTokens(tokens) {
|
||||
fs.writeFileSync('/root/btcpaytokens', tokens)
|
||||
}
|
||||
|
||||
async function getElValue(page, qs) {
|
||||
const idElement = await page.$$(qs);
|
||||
return idElement[0]
|
||||
.getProperty('value')
|
||||
.then(v => v.jsonValue());
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: HEADLESS,
|
||||
args: ['--window-size=' + WINDOW_WIDTH + ',' + WINDOW_HEIGHT],
|
||||
}).then(
|
||||
v => v, // if success, passthrough
|
||||
// if error, check for env and ignore sandbox and warn.
|
||||
err => {
|
||||
if (IGNORE_SANDBOX_ERROR === '1') {
|
||||
console.warn(
|
||||
'WARNING!!! Error occurred, Chromium will be started ' +
|
||||
"without sandbox. This won't guarantee success.",
|
||||
);
|
||||
return puppeteer.launch({
|
||||
headless: HEADLESS,
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--window-size=' + WINDOW_WIDTH + ',' + WINDOW_HEIGHT,
|
||||
],
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
'If "No usable sandbox!" error, retry test with ' +
|
||||
'BTCPAY_IGNORE_SANDBOX_ERROR=1',
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
);
|
||||
const page = (await browser.pages())[0];
|
||||
await page.setViewport({ width: WINDOW_WIDTH, height: WINDOW_HEIGHT });
|
||||
|
||||
await page.goto(URL + '/Account/Register');
|
||||
|
||||
// Set up admin account
|
||||
await page.type('#Email', USER_NAME);
|
||||
await page.type('#Password', PASSWORD);
|
||||
await page.type('#ConfirmPassword', PASSWORD);
|
||||
await page.click('#RegisterButton');
|
||||
await page.waitForSelector('#Stores');
|
||||
|
||||
// On main screen, setup first store
|
||||
await page.click('#Stores');
|
||||
await page.waitForSelector('#CreateStore');
|
||||
await page.click('#CreateStore');
|
||||
await page.type('#Name', STORENAME);
|
||||
await page.click('#Create');
|
||||
await page.waitForSelector('#Id');
|
||||
const STORE_ID = await getElValue(page, '#Id');
|
||||
|
||||
// Add new wallet
|
||||
await page.click('#ModifyBTC');
|
||||
await page.waitForSelector('#import-from-btn');
|
||||
await page.click('#import-from-btn');
|
||||
await page.waitForSelector('#nbxplorergeneratewalletbtn');
|
||||
await page.click('#nbxplorergeneratewalletbtn');
|
||||
await page.waitForSelector('#btn-generate');
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#SavePrivateKeys').click();
|
||||
document.querySelector('#btn-generate').click();
|
||||
});
|
||||
await page.waitForSelector('#confirm');
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#confirm').click();
|
||||
document.querySelector('#submit').click();
|
||||
});
|
||||
await page.waitForSelector('#PayJoinEnabled');
|
||||
|
||||
// Enable PayJoin
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#PayJoinEnabled').click();
|
||||
document.querySelector('#Save').click();
|
||||
});
|
||||
const alertQS = 'div.alert.alert-success.alert-dismissible'
|
||||
await page.waitForSelector(alertQS);
|
||||
|
||||
// Get first address and write to disk
|
||||
await page.click('#Wallets');
|
||||
const firstManageLinkQS = 'table.table.table-sm.table-responsive-md > tbody > ' +
|
||||
'tr:nth-of-type(1) > td:nth-of-type(4) > a:nth-of-type(1)'
|
||||
await page.waitForSelector(firstManageLinkQS);
|
||||
await page.click(firstManageLinkQS);
|
||||
await page.waitForSelector('#WalletReceive');
|
||||
await page.click('#WalletReceive');
|
||||
await page.waitForSelector('#generateButton');
|
||||
await page.click('#generateButton');
|
||||
await page.waitForSelector('#vue-address');
|
||||
const address = await getElValue(page, '#vue-address');
|
||||
writeAddress(address, 'p2wpkh');
|
||||
|
||||
// On main screen, setup second store
|
||||
await page.goto(URL + '/stores');
|
||||
await page.waitForSelector('#CreateStore');
|
||||
await page.click('#CreateStore');
|
||||
await page.type('#Name', STORENAME + ' 2');
|
||||
await page.click('#Create');
|
||||
await page.waitForSelector('#Id');
|
||||
const STORE_ID2 = await getElValue(page, '#Id');
|
||||
|
||||
// Add new wallet
|
||||
await page.click('#ModifyBTC');
|
||||
await page.waitForSelector('#import-from-btn');
|
||||
await page.click('#import-from-btn');
|
||||
await page.waitForSelector('#nbxplorergeneratewalletbtn');
|
||||
await page.click('#nbxplorergeneratewalletbtn');
|
||||
await page.waitForSelector('#btn-generate');
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#ScriptPubKeyType').value = 'SegwitP2SH';
|
||||
document.querySelector('#SavePrivateKeys').click();
|
||||
document.querySelector('#btn-generate').click();
|
||||
});
|
||||
await page.waitForSelector('#confirm');
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#confirm').click();
|
||||
document.querySelector('#submit').click();
|
||||
});
|
||||
await page.waitForSelector('#PayJoinEnabled');
|
||||
|
||||
// Enable PayJoin
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#PayJoinEnabled').click();
|
||||
document.querySelector('#Save').click();
|
||||
});
|
||||
await page.waitForSelector(alertQS);
|
||||
|
||||
// Get first address and write to disk
|
||||
await page.click('#Wallets');
|
||||
const secondManageLinkQS = 'table.table.table-sm.table-responsive-md > tbody > ' +
|
||||
'tr:nth-of-type(2) > td:nth-of-type(4) > a:nth-of-type(1)'
|
||||
await page.waitForSelector(secondManageLinkQS);
|
||||
await page.click(secondManageLinkQS);
|
||||
await page.waitForSelector('#WalletReceive');
|
||||
await page.click('#WalletReceive');
|
||||
await page.waitForSelector('#generateButton');
|
||||
await page.click('#generateButton');
|
||||
await page.waitForSelector('#vue-address');
|
||||
const address2 = await getElValue(page, '#vue-address');
|
||||
writeAddress(address2, 'p2shp2wpkh');
|
||||
|
||||
const tokens = {};
|
||||
|
||||
// Get token for store 1
|
||||
await page.goto(URL + '/stores/' + STORE_ID + '/Tokens/Create');
|
||||
await page.waitForSelector('input#Label');
|
||||
await page.waitForSelector('[type="submit"]');
|
||||
|
||||
await page.type('#Label', 'token1');
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('button[type="submit"]');
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('div.alert.alert-success.alert-dismissible');
|
||||
const contents1 = await page.evaluate(() => {
|
||||
const el = document.querySelector(
|
||||
'div.alert.alert-success.alert-dismissible',
|
||||
);
|
||||
if (el === null) return '';
|
||||
return el.innerHTML;
|
||||
});
|
||||
const pairingCode1 = (contents1.match(
|
||||
/Server initiated pairing code: (\S{7})/,
|
||||
) || [])[1];
|
||||
const kp1 = btcpay.crypto.generate_keypair()
|
||||
const client1 = new btcpay.BTCPayClient(URL, kp1)
|
||||
const token1 = await client1.pair_client(pairingCode1)
|
||||
tokens.p2wpkh = token1;
|
||||
|
||||
// Get token for store 2
|
||||
await page.goto(URL + '/stores/' + STORE_ID2 + '/Tokens/Create');
|
||||
await page.waitForSelector('input#Label');
|
||||
await page.waitForSelector('[type="submit"]');
|
||||
|
||||
await page.type('#Label', 'token2');
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('button[type="submit"]');
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('div.alert.alert-success.alert-dismissible');
|
||||
const contents2 = await page.evaluate(() => {
|
||||
const el = document.querySelector(
|
||||
'div.alert.alert-success.alert-dismissible',
|
||||
);
|
||||
if (el === null) return '';
|
||||
return el.innerHTML;
|
||||
});
|
||||
const pairingCode2 = (contents2.match(
|
||||
/Server initiated pairing code: (\S{7})/,
|
||||
) || [])[1];
|
||||
const kp2 = btcpay.crypto.generate_keypair()
|
||||
const client2 = new btcpay.BTCPayClient(URL, kp2)
|
||||
const token2 = await client2.pair_client(pairingCode2)
|
||||
tokens.p2shp2wpkh = token2;
|
||||
tokens.privateKeys = {
|
||||
p2wpkh: kp1.getPrivate('hex'),
|
||||
p2shp2wpkh: kp2.getPrivate('hex'),
|
||||
}
|
||||
writeTokens(JSON.stringify(tokens))
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1);
|
||||
});
|
||||
@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd /root/btcpayserver
|
||||
|
||||
export BTCPAY_POSTGRES="User ID=postgres;Host=127.0.0.1;Port=5432;Database=btcpayserverregtest"
|
||||
export BTCPAY_NETWORK=regtest
|
||||
export BTCPAY_BIND=0.0.0.0:49392
|
||||
export BTCPAY_ROOTPATH=/
|
||||
export BTCPAY_CHAINS=btc
|
||||
export BTCPAY_BTCEXPLORERURL=http://127.0.0.1:23828
|
||||
export BTCPAY_BTCEXPLORERCOOKIEFILE=/datadir/RegTest/.cookie
|
||||
|
||||
# Run NBXplorer
|
||||
dotnet run --no-launch-profile --no-build -c Release -p "BTCPayServer/BTCPayServer.csproj" -- $@
|
||||
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# trap ctrl-c and call ctrl_c()
|
||||
trap ctrl_c INT
|
||||
|
||||
function ctrl_c() {
|
||||
echo "** Trapped CTRL-C"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run regtest app for using regtest-client
|
||||
/root/run_regtest_app.sh &
|
||||
disown
|
||||
|
||||
# Run NBXplorer
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
$DIR/start_nbx.sh &
|
||||
disown
|
||||
|
||||
# Run postgres
|
||||
chmod 777 /root
|
||||
su postgres -c "/usr/lib/postgresql/11/bin/postgres -D /pgsql/data -h 0.0.0.0 -i" &
|
||||
disown
|
||||
sleep 5
|
||||
|
||||
# Run BTCPayServer
|
||||
$DIR/start_btcpay.sh &
|
||||
disown
|
||||
|
||||
sleep infinity
|
||||
5
index.js
Normal file
5
index.js
Normal file
@ -0,0 +1,5 @@
|
||||
let crypto = require('./cryptography')
|
||||
let BTCPayClient = require('./client')
|
||||
|
||||
exports.crypto = crypto
|
||||
exports.BTCPayClient = BTCPayClient
|
||||
31
jest.json
31
jest.json
@ -1,31 +0,0 @@
|
||||
{
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"js",
|
||||
"json"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testEnvironment" : "node",
|
||||
"testRegex": "/tests/.*\\.(test|spec)\\.(ts)$",
|
||||
"testURL": "http://localhost/",
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"statements": 90,
|
||||
"branches": 90,
|
||||
"functions": 90,
|
||||
"lines": 90
|
||||
}
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.ts",
|
||||
"!**/node_modules/**",
|
||||
"!**/vendor/**"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"lcov",
|
||||
"text"
|
||||
],
|
||||
"verbose": true
|
||||
}
|
||||
14729
package-lock.json
generated
14729
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@ -1,55 +1,20 @@
|
||||
{
|
||||
"name": "btcpay",
|
||||
"version": "0.2.5",
|
||||
"version": "0.1.0",
|
||||
"description": "A nodejs client implementation for BTCPay",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rimraf dist && tsc -p ./tsconfig.json",
|
||||
"coverage": "npm run unit -- --coverage",
|
||||
"format": "npm run prettier -- --write",
|
||||
"format:ci": "npm run prettier -- --check",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"prepare": "npm run build",
|
||||
"prettier": "prettier src/**/*.ts tests/**/*.spec.ts --single-quote --trailing-comma all",
|
||||
"test": "npm run format:ci && npm run lint && npm run coverage",
|
||||
"unit": "jest --config=jest.json --runInBand"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"contributors": [
|
||||
"Tim Akinbo <tim@tanjalo.com>",
|
||||
"Christoph Ott <christoph.ott@lean-coders.at>",
|
||||
"Jonathan Underwood <junderwood@bitcoinbank.co.jp>"
|
||||
],
|
||||
"author": "Tim Akinbo <tim@tanjalo.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/btcpayserver/node-btcpay.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.1",
|
||||
"bn.js": "^4.11.8",
|
||||
"bs58": "^4.0.1",
|
||||
"elliptic": "^6.5.4",
|
||||
"request": "^2.88.2",
|
||||
"request-promise": "^4.2.6",
|
||||
"underscore": "^1.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/elliptic": "^6.4.12",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.0.2",
|
||||
"@types/puppeteer": "^5.4.3",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/request-promise": "^4.1.47",
|
||||
"@types/underscore": "^1.11.2",
|
||||
"jest": "^26.6.3",
|
||||
"prettier": "^2.3.0",
|
||||
"puppeteer": "^9.1.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.5.6",
|
||||
"tslint": "^5.20.1",
|
||||
"typescript": "^4.2.4"
|
||||
"elliptic": "^6.4.0",
|
||||
"request": "^2.85.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"underscore": "^1.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,153 +0,0 @@
|
||||
import * as elliptic from 'elliptic';
|
||||
import * as qs from 'querystring';
|
||||
import * as rp from 'request-promise';
|
||||
import * as _ from 'underscore';
|
||||
import {
|
||||
CreateInvoiceArgs,
|
||||
GetInvoicesArgs,
|
||||
PairClientResponse,
|
||||
} from '../models/client';
|
||||
import { Cryptography as crypto } from './cryptography';
|
||||
import { Invoice } from '../models/invoice';
|
||||
import { Rate } from '../models/rate';
|
||||
|
||||
export class BTCPayClient {
|
||||
private clientId: string;
|
||||
private userAgent: string;
|
||||
private options: any;
|
||||
|
||||
constructor(
|
||||
private host: string,
|
||||
private kp: elliptic.ec.KeyPair,
|
||||
private tokens: any = {},
|
||||
) {
|
||||
this.host = this.host.replace(/\/+$/, '');
|
||||
this.clientId = crypto.get_sin_from_key(this.kp);
|
||||
this.userAgent = 'node-btcpay';
|
||||
this.options = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'User-Agent': this.userAgent,
|
||||
'X-Accept-Version': '2.0.0',
|
||||
},
|
||||
json: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async pair_client(code: string): Promise<PairClientResponse> {
|
||||
const re = new RegExp('^\\w{7}$');
|
||||
|
||||
if (!re.test(code)) {
|
||||
throw new Error('pairing code is not valid');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: this.clientId,
|
||||
pairingCode: code,
|
||||
};
|
||||
|
||||
return this.unsigned_request('/tokens', payload).then((data: any) => {
|
||||
const _data = data[0];
|
||||
const _res: any = {};
|
||||
_res[_data.facade] = _data.token;
|
||||
return _res;
|
||||
});
|
||||
}
|
||||
|
||||
public async get_rates(
|
||||
currencyPairs: string[],
|
||||
storeID: string,
|
||||
): Promise<Rate[]> {
|
||||
return this.signed_get_request('/rates', {
|
||||
currencyPairs: currencyPairs.join(','),
|
||||
storeID,
|
||||
});
|
||||
}
|
||||
|
||||
public async create_invoice(
|
||||
payload: CreateInvoiceArgs,
|
||||
token?: any,
|
||||
): Promise<Invoice> {
|
||||
const re = new RegExp('^[A-Z]{3}$');
|
||||
|
||||
if (!re.test(payload.currency)) {
|
||||
throw new Error('Currency is invalid');
|
||||
}
|
||||
|
||||
if (isNaN(parseFloat(payload.price as any))) {
|
||||
throw new Error('Price must be a float');
|
||||
}
|
||||
|
||||
return this.signed_post_request('/invoices', payload, token);
|
||||
}
|
||||
|
||||
public async get_invoice(invoiceId: string, token?: any): Promise<Invoice> {
|
||||
return this.signed_get_request('/invoices/' + invoiceId, token);
|
||||
}
|
||||
|
||||
public async get_invoices(
|
||||
params?: GetInvoicesArgs,
|
||||
token?: any,
|
||||
): Promise<Invoice[]> {
|
||||
return this.signed_get_request('/invoices', params, token);
|
||||
}
|
||||
|
||||
private create_signed_headers(uri: string, payload: string) {
|
||||
return {
|
||||
'X-Identity': Buffer.from(
|
||||
this.kp.getPublic().encodeCompressed(),
|
||||
).toString('hex'),
|
||||
'X-Signature': crypto.sign(uri + payload, this.kp).toString('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
private async signed_get_request(
|
||||
path: string,
|
||||
params: any = {},
|
||||
token: any = _.values(this.tokens)[0],
|
||||
): Promise<any> {
|
||||
params.token = token;
|
||||
|
||||
const _options = JSON.parse(JSON.stringify(this.options));
|
||||
|
||||
const _uri = this.host + path;
|
||||
const _payload = '?' + qs.stringify(params);
|
||||
|
||||
_.extend(_options.headers, this.create_signed_headers(_uri, _payload));
|
||||
_options.uri = _uri;
|
||||
_options.qs = params;
|
||||
|
||||
return rp.get(_options).then((resp: any) => resp.data);
|
||||
}
|
||||
|
||||
private async signed_post_request(
|
||||
path: string,
|
||||
payload: any,
|
||||
token: any = _.values(this.tokens)[0],
|
||||
): Promise<any> {
|
||||
payload.token = token;
|
||||
|
||||
const _uri = this.host + path;
|
||||
const _payload = JSON.stringify(payload);
|
||||
const _options = JSON.parse(JSON.stringify(this.options));
|
||||
|
||||
_.extend(_options.headers, this.create_signed_headers(_uri, _payload));
|
||||
_options.uri = _uri;
|
||||
_options.body = payload;
|
||||
|
||||
return rp.post(_options).then((resp: any) => resp.data);
|
||||
}
|
||||
|
||||
private async unsigned_request(path: string, payload: any): Promise<any> {
|
||||
const _mixin: any = {
|
||||
method: 'POST',
|
||||
uri: this.host + path,
|
||||
body: payload,
|
||||
};
|
||||
|
||||
const _options = { ...JSON.parse(JSON.stringify(this.options)), ..._mixin };
|
||||
|
||||
return rp(_options).then((resp: any) => resp.data);
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import * as elliptic from 'elliptic';
|
||||
import * as bs58 from 'bs58';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const ec = new elliptic.ec('secp256k1');
|
||||
|
||||
export class Cryptography {
|
||||
public static generate_keypair(): elliptic.ec.KeyPair {
|
||||
const kp = ec.genKeyPair();
|
||||
return kp;
|
||||
}
|
||||
|
||||
public static load_keypair(
|
||||
buf: Buffer | string | elliptic.ec.KeyPair,
|
||||
): elliptic.ec.KeyPair {
|
||||
return ec.keyFromPrivate(buf);
|
||||
}
|
||||
|
||||
public static get_sin_from_key(kp: elliptic.ec.KeyPair): string {
|
||||
const pk: crypto.BinaryLike = Buffer.from(
|
||||
kp.getPublic().encodeCompressed(),
|
||||
);
|
||||
const version: Buffer = Cryptography.get_version_from_compressed_key(pk);
|
||||
const checksum: Buffer = Cryptography.get_checksum_from_version(version);
|
||||
return bs58.encode(Buffer.concat([version, checksum]));
|
||||
}
|
||||
|
||||
public static sign(
|
||||
message: crypto.BinaryLike,
|
||||
kp: elliptic.ec.KeyPair,
|
||||
): Buffer {
|
||||
const digest = crypto.createHash('sha256').update(message).digest();
|
||||
return Buffer.from(kp.sign(digest).toDER());
|
||||
}
|
||||
|
||||
private static get_version_from_compressed_key(
|
||||
pk: crypto.BinaryLike,
|
||||
): Buffer {
|
||||
const sh2 = crypto.createHash('sha256').update(pk).digest();
|
||||
const rp = crypto.createHash('ripemd160').update(sh2).digest();
|
||||
|
||||
return Buffer.concat([
|
||||
Buffer.from('0F', 'hex'),
|
||||
Buffer.from('02', 'hex'),
|
||||
rp,
|
||||
]);
|
||||
}
|
||||
|
||||
private static get_checksum_from_version(version: crypto.BinaryLike): Buffer {
|
||||
const h1 = crypto.createHash('sha256').update(version).digest();
|
||||
const h2 = crypto.createHash('sha256').update(h1).digest();
|
||||
|
||||
return h2.slice(0, 4);
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { Cryptography } from './core/cryptography';
|
||||
import { BTCPayClient } from './core/client';
|
||||
|
||||
export { Invoice } from './models/invoice';
|
||||
export { Rate } from './models/rate';
|
||||
export { Cryptography as crypto, BTCPayClient };
|
||||
@ -1,49 +0,0 @@
|
||||
export interface PairClientResponse {
|
||||
merchant: string;
|
||||
}
|
||||
|
||||
export interface GetInvoicesArgs {
|
||||
status?: string;
|
||||
orderId?: string;
|
||||
itemCode?: string;
|
||||
dateStart?: string;
|
||||
dateEnd?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface CreateInvoiceArgs {
|
||||
currency: string;
|
||||
price: number;
|
||||
orderId?: string | number;
|
||||
expirationTime?: string;
|
||||
itemDesc?: string;
|
||||
itemCode?: string;
|
||||
posData?: string;
|
||||
status?: string;
|
||||
redirectUrl?: string;
|
||||
transactionSpeed?: 'low' | 'low-medium' | 'medium' | 'high';
|
||||
physical?: boolean;
|
||||
supportedTransactionCurrencies?: {
|
||||
[index: string]: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
refundable?: boolean;
|
||||
taxIncluded?: number;
|
||||
token?: string;
|
||||
redirectAutomatically?: boolean;
|
||||
notificationEmail?: string;
|
||||
notificationURL?: string;
|
||||
extendedNotifications?: boolean;
|
||||
fullNotifications?: boolean;
|
||||
buyerEmail?: string;
|
||||
buyerPhone?: string;
|
||||
buyerCountry?: string;
|
||||
buyerZip?: string;
|
||||
buyerState?: string;
|
||||
buyerCity?: string;
|
||||
buyerAddress2?: string;
|
||||
buyerAddress1?: string;
|
||||
buyerName?: string;
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
export interface Invoice {
|
||||
url: string;
|
||||
posData: string | null;
|
||||
status: 'new' | 'paid' | 'confirmed' | 'complete' | 'expired' | 'invalid';
|
||||
btcPrice: string;
|
||||
btcDue: string;
|
||||
cryptoInfo: Array<{
|
||||
cryptoCode: string;
|
||||
paymentType: string;
|
||||
rate: number;
|
||||
exRates: any[];
|
||||
paid: string;
|
||||
price: string;
|
||||
due: string;
|
||||
paymentUrls: any[];
|
||||
address: string;
|
||||
url: string;
|
||||
totalDue: string;
|
||||
networkFee: string;
|
||||
txCount: number;
|
||||
cryptoPaid: string;
|
||||
payments: any[];
|
||||
}>;
|
||||
price: number;
|
||||
currency: string;
|
||||
exRates: any;
|
||||
buyerTotalBtcAmount: string | null;
|
||||
itemDesc: string | null;
|
||||
itemCode: string | null;
|
||||
orderId: string | null;
|
||||
guid: string;
|
||||
id: string;
|
||||
invoiceTime: number;
|
||||
expirationTime: number;
|
||||
currentTime: number;
|
||||
lowFeeDetected: boolean;
|
||||
btcPaid: string;
|
||||
rate: number;
|
||||
exceptionStatus: false | 'paidOver' | 'paidLate' | 'paidPartial' | 'marked';
|
||||
paymentUrls: {
|
||||
BIP21: string | null;
|
||||
BIP72: string | null;
|
||||
BIP72b: string | null;
|
||||
BIP73: string | null;
|
||||
BOLT11: string | null;
|
||||
};
|
||||
refundAddressRequestPending: boolean;
|
||||
buyerPaidBtcMinerFee: string | null;
|
||||
bitcoinAddress: string;
|
||||
token: string;
|
||||
flags: {
|
||||
refundable: boolean;
|
||||
};
|
||||
paymentSubtotals: any;
|
||||
paymentTotals: any;
|
||||
amountPaid: number;
|
||||
minerFees: any;
|
||||
exchangeRates: any;
|
||||
supportedTransactionCurrencies: any;
|
||||
addresses: any;
|
||||
paymentCodes: any;
|
||||
buyer: {
|
||||
name: string | null;
|
||||
address1: string | null;
|
||||
address2: string | null;
|
||||
locality: string | null;
|
||||
region: string | null;
|
||||
postalCode: string | null;
|
||||
country: string | null;
|
||||
phone: string | null;
|
||||
email: string | null;
|
||||
};
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
export interface Rate {
|
||||
name: string;
|
||||
cryptoCode: string;
|
||||
currencyPair: string;
|
||||
code: string;
|
||||
rate: number;
|
||||
}
|
||||
@ -1,182 +0,0 @@
|
||||
import * as elliptic from 'elliptic';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { Browser, Page } from 'puppeteer';
|
||||
import { BTCPayClient } from '../../src/core/client';
|
||||
import { Cryptography as myCrypto } from '../../src/core/cryptography';
|
||||
|
||||
const IGNORE_SANDBOX_ERROR = process.env['BTCPAY_IGNORE_SANDBOX_ERROR'];
|
||||
const USER_NAME = 'test@example.com';
|
||||
const PASSWORD = 'satoshinakamoto';
|
||||
const URL = 'http://127.0.0.1:49392';
|
||||
|
||||
const MY_PRIVATE_KEY = Buffer.from(
|
||||
'31eb31ecf1a640c9d1e0a1105501f36235f8c7d51d67dcf74ccc968d74cb6b25',
|
||||
'hex',
|
||||
);
|
||||
let STORE_ID = '';
|
||||
const WINDOW_WIDTH = 1920;
|
||||
const WINDOW_HEIGHT = 1080;
|
||||
|
||||
let INVOICE_ID = '';
|
||||
const HEADLESS = true;
|
||||
|
||||
const loginAndGetPairingCode = async (): Promise<{
|
||||
browser: Browser;
|
||||
page: Page;
|
||||
pairingCode: string;
|
||||
}> => {
|
||||
const newTokenName = 'autotest ' + new Date().getTime();
|
||||
|
||||
const browser = await puppeteer
|
||||
.launch({
|
||||
headless: HEADLESS,
|
||||
args: ['--window-size=' + WINDOW_WIDTH + ',' + WINDOW_HEIGHT],
|
||||
})
|
||||
.then(
|
||||
(v) => v, // if success, passthrough
|
||||
// if error, check for env and ignore sandbox and warn.
|
||||
(err) => {
|
||||
if (IGNORE_SANDBOX_ERROR === '1') {
|
||||
console.warn(
|
||||
'WARNING!!! Error occurred, Chromium will be started ' +
|
||||
"without sandbox. This won't guarantee success.",
|
||||
);
|
||||
return puppeteer.launch({
|
||||
headless: HEADLESS,
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--window-size=' + WINDOW_WIDTH + ',' + WINDOW_HEIGHT,
|
||||
],
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
'If "No usable sandbox!" error, retry test with ' +
|
||||
'BTCPAY_IGNORE_SANDBOX_ERROR=1',
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
);
|
||||
const page = (await browser.pages())[0];
|
||||
await page.setViewport({ width: WINDOW_WIDTH, height: WINDOW_HEIGHT });
|
||||
try {
|
||||
await page.goto(URL + '/Account/Login');
|
||||
} catch (e) {
|
||||
if (e.message === `net::ERR_CONNECTION_REFUSED at ${URL}/Account/Login`) {
|
||||
browser.close();
|
||||
console.log(
|
||||
'Please start docker container locally:\n' +
|
||||
'docker run -p 127.0.0.1:49392:49392 junderw/btcpay-client-test-server',
|
||||
);
|
||||
return {
|
||||
page,
|
||||
browser,
|
||||
pairingCode: '',
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
await page.click('#LoginButton');
|
||||
await page.goto(URL + '/stores');
|
||||
await page.waitForSelector('#CreateStore');
|
||||
await page.click(
|
||||
'table.table.table-sm.table-responsive-md > tbody > ' +
|
||||
'tr:nth-of-type(1) > td:nth-of-type(3) > a:nth-of-type(2)',
|
||||
);
|
||||
await page.waitForSelector('#Id');
|
||||
const idElement = await page.$$('#Id');
|
||||
STORE_ID = (await idElement[0]
|
||||
.getProperty('value')
|
||||
.then((v) => v?.jsonValue())) as string;
|
||||
await page.goto(URL + '/stores/' + STORE_ID + '/Tokens/Create');
|
||||
await page.waitForSelector('input#Label');
|
||||
await page.waitForSelector('[type="submit"]');
|
||||
|
||||
await page.type('#Label', newTokenName);
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('button[type="submit"]');
|
||||
await page.click('[type="submit"]');
|
||||
await page.waitForSelector('div.alert.alert-success.alert-dismissible');
|
||||
const contents = await page.evaluate(() => {
|
||||
const el = document.querySelector(
|
||||
'div.alert.alert-success.alert-dismissible',
|
||||
);
|
||||
if (el === null) {
|
||||
return '';
|
||||
}
|
||||
return el.innerHTML;
|
||||
});
|
||||
const pairingCode = (contents.match(
|
||||
/Server initiated pairing code: (\S{7})/,
|
||||
) || [])[1];
|
||||
if (!pairingCode) {
|
||||
throw new Error('Could not get pairing code');
|
||||
}
|
||||
return {
|
||||
browser,
|
||||
page,
|
||||
pairingCode,
|
||||
};
|
||||
};
|
||||
|
||||
let MY_KEYPAIR: elliptic.ec.KeyPair;
|
||||
let client: BTCPayClient;
|
||||
describe('btcpay.core.client', () => {
|
||||
beforeAll(async () => {
|
||||
jest.setTimeout(20000); // browser takes a while
|
||||
MY_KEYPAIR = myCrypto.load_keypair(MY_PRIVATE_KEY);
|
||||
client = new BTCPayClient(URL, MY_KEYPAIR);
|
||||
});
|
||||
|
||||
it('should pair with server', async () => {
|
||||
const pairingData = await loginAndGetPairingCode();
|
||||
const myClient = new BTCPayClient(URL, MY_KEYPAIR);
|
||||
const result = await myClient.pair_client(pairingData.pairingCode);
|
||||
client = new BTCPayClient(URL, MY_KEYPAIR, result);
|
||||
expect(result.merchant).toBeDefined();
|
||||
pairingData.browser.close();
|
||||
await expect(myClient.pair_client('hduheufhfuf')).rejects.toThrow(
|
||||
/^pairing code is not valid$/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should get rates', async () => {
|
||||
const results = await client.get_rates(['LTC_USD', 'BTC_USD'], STORE_ID);
|
||||
expect(results[0].rate).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create an invoice', async () => {
|
||||
const results = await client.create_invoice({
|
||||
currency: 'USD',
|
||||
price: 1.12,
|
||||
});
|
||||
INVOICE_ID = results.id;
|
||||
expect(results.bitcoinAddress).toBeDefined();
|
||||
await expect(
|
||||
client.create_invoice({
|
||||
currency: 'KDFAHKJFKJ',
|
||||
price: 1.12,
|
||||
}),
|
||||
).rejects.toThrow(/^Currency is invalid$/);
|
||||
await expect(
|
||||
client.create_invoice({
|
||||
currency: 'USD',
|
||||
// @ts-ignore
|
||||
price: 'xkhdfhu',
|
||||
}),
|
||||
).rejects.toThrow(/^Price must be a float$/);
|
||||
});
|
||||
|
||||
it('should get invoice', async () => {
|
||||
const results = await client.get_invoice(INVOICE_ID);
|
||||
expect(results.id).toBe(INVOICE_ID);
|
||||
});
|
||||
|
||||
it('should get multiple invoices', async () => {
|
||||
const results = await client.get_invoices();
|
||||
expect(results[0].bitcoinAddress).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,33 +0,0 @@
|
||||
import { Cryptography as myCrypto } from '../../src/core/cryptography';
|
||||
|
||||
const MY_PRIVATE_KEY = Buffer.from(
|
||||
'31eb31ecf1a640cd91e0a1105501f36235f8c7d51d67dcf74ccc968d74cb6b25',
|
||||
'hex',
|
||||
);
|
||||
|
||||
describe('btcpay.core.cryptography', () => {
|
||||
it('should generate a keypair', () => {
|
||||
const kp = myCrypto.generate_keypair();
|
||||
const priv = kp.getPrivate();
|
||||
expect(priv).toBeDefined();
|
||||
});
|
||||
it('should load a keypair from Buffer', () => {
|
||||
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
|
||||
const priv = kp.getPrivate();
|
||||
expect(priv).toBeDefined();
|
||||
});
|
||||
it('should get sin from key', () => {
|
||||
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
|
||||
const sin = myCrypto.get_sin_from_key(kp);
|
||||
expect(sin).toBe('TfDnXWvj6bBhkduYiZnohg5qhtDu5VWohhw');
|
||||
});
|
||||
it('should sign a message', () => {
|
||||
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
|
||||
const message = Buffer.from('Satoshi', 'utf8');
|
||||
const sig = myCrypto.sign(message, kp);
|
||||
expect(sig.toString('hex')).toBe(
|
||||
'304402205b0a505c180bddbd4a8836de0f2ac10b52b327d0e932352d28d170fb81517a' +
|
||||
'770220307ac2ec2134d81fd04df6a1662b0962ad1322209c2e45ff8af63d3f12e0d089',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
import * as btcpay from '../src/index';
|
||||
|
||||
describe('btcpay.index', () => {
|
||||
it('should import', () => {
|
||||
expect(btcpay).toBeDefined();
|
||||
expect(btcpay.crypto).toBeDefined();
|
||||
expect(btcpay.BTCPayClient).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": false
|
||||
},
|
||||
"lib": [
|
||||
"es2017",
|
||||
"node"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"node_modules",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
80
tslint.json
80
tslint.json
@ -1,80 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"align": [false, "parameters", "arguments", "statements"],
|
||||
"ban": [true, ["angular", "forEach"]],
|
||||
"class-name": true,
|
||||
"comment-format": [false, "check-space", "check-lowercase"],
|
||||
"curly": true,
|
||||
"eofline": false,
|
||||
"forin": true,
|
||||
"indent": [true, 4],
|
||||
"interface-name": false,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"max-line-length": [false, 140],
|
||||
"member-ordering": [
|
||||
false,
|
||||
"public-before-private",
|
||||
"static-before-instance",
|
||||
"variables-before-functions"
|
||||
],
|
||||
"no-any": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [false, "debug", "info", "time", "timeEnd", "trace"],
|
||||
"no-construct": true,
|
||||
"no-constructor-vars": false,
|
||||
"no-debugger": false,
|
||||
"no-shadowed-variable": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-require-imports": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"no-var-requires": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [true, "single"],
|
||||
"radix": false,
|
||||
"semicolon": true,
|
||||
"triple-equals": [true, "allow-null-check"],
|
||||
"typedef": [
|
||||
true,
|
||||
"callSignature",
|
||||
"catchClause",
|
||||
"indexSignature",
|
||||
"parameter",
|
||||
"propertySignature",
|
||||
"variableDeclarator"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [true, "allow-leading-underscore"],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user