Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ac61f1e7 | ||
|
|
7ee35f911e | ||
|
|
92cfe4342c | ||
|
|
18f99d0958 | ||
|
|
1a505e4bd1 | ||
|
|
8e31ca5376 | ||
|
|
b586ac5c1e | ||
|
|
8c88098652 | ||
|
|
60ef5b938a | ||
|
|
f8abf8d9a1 | ||
|
|
67ea5e6236 | ||
|
|
d09292a7e9 | ||
|
|
65618dc27e | ||
|
|
b99ba5409b | ||
|
|
76eeaff72a | ||
|
|
53b02890a6 | ||
|
|
99d5fd4b35 | ||
|
|
0a0038c040 | ||
|
|
67cb31d832 | ||
|
|
7f18735ee1 | ||
|
|
0170ccc774 | ||
|
|
65d503a3b5 | ||
|
|
5f1b9357d5 | ||
|
|
db7c5fe825 | ||
|
|
d94d8ce89f | ||
|
|
d2020e83b8 | ||
|
|
9df008114d | ||
|
|
f7f552f663 | ||
|
|
564385d1ba | ||
|
|
d6d04e660f | ||
|
|
f3ea5077a7 | ||
|
|
85cf06876d | ||
|
|
f7b7e9795b | ||
|
|
8accbffb4f | ||
|
|
6c370218c2 | ||
|
|
c506084a41 | ||
|
|
b9429cb1ee | ||
|
|
095114ede8 | ||
|
|
7b7d81f7e9 | ||
|
|
69dc50dfdc | ||
|
|
b102037cf8 | ||
|
|
0124a0c059 | ||
|
|
100d28dfbe | ||
|
|
84832111cb | ||
|
|
9a9f108cd2 |
55
.github/workflows/main_ci.yml
vendored
Normal file
55
.github/workflows/main_ci.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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,2 +1,62 @@
|
|||||||
node_modules/
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
package-lock.json
|
|
||||||
|
# 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/
|
||||||
89
README.md
89
README.md
@ -1,62 +1,71 @@
|
|||||||
|
> :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
|
# node-btcpay
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
```shell
|
```shell
|
||||||
npm install https://github.com/tanjalo/node-btcpay
|
npm i btcpay
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Private key generation
|
||||||
|
* Generate and save private key:
|
||||||
|
```bash
|
||||||
|
$ node -p "require('btcpay').crypto.generate_keypair().getPrivate('hex')"
|
||||||
|
XXXXXXXXXXXXXXXXXXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
Store the printed value in a safe place, e.g. environment variables
|
||||||
|
|
||||||
## Pairing
|
## Pairing
|
||||||
* Generate and save private key:
|
|
||||||
```js
|
After generating your private key, you have to pair your client with your BTCPay store:
|
||||||
let btcpay = require('btcpay')
|
|
||||||
var keypair = btcpay.crypto.generate_keypair()
|
|
||||||
```
|
|
||||||
* Create client:
|
|
||||||
```js
|
|
||||||
var client = new btcpay.BTCPayClient('https://btcpayserverhostname', keypair)
|
|
||||||
```
|
|
||||||
* On BTCPay Server > Stores > Settings > Access Tokens > Create a new token, (leave PublicKey blank) > Request pairing
|
* On BTCPay Server > Stores > Settings > Access Tokens > Create a new token, (leave PublicKey blank) > Request pairing
|
||||||
* Copy pairing code:
|
* Copy pairing code:
|
||||||
* Pair client to server and save returned token:
|
* Pair client to server and save returned token:
|
||||||
```js
|
|
||||||
client.pair_client(<pairing-code>).then(res => console.log(res))
|
```bash
|
||||||
>>> { merchant: '6gi59fB1LKxHuyY29m8tR6tRysWppk9TnuoM7wT77Las' }
|
# Replace the BTCPAY_XXX envirnoment variables with your values and run:
|
||||||
```
|
|
||||||
* Recreate client:
|
$ [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)"
|
||||||
```js
|
|
||||||
var client = new btcpay.BTCPayClient('https://btcpayserverhostname', keypair, {merchant: '6gi59fB1LKxHuyY29m8tR6tRysWppk9TnuoM7wT77Las'})
|
# (prepend the line with a space to prevent BTCPAY_KEY from being saved to your bash history)
|
||||||
|
|
||||||
|
>>> { merchant: 'XXXXXX' }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Store the value of "merchant" in a safe place, e.g. environment variables
|
||||||
|
|
||||||
## Creating a client
|
## Recreating a client
|
||||||
|
After pairing your client to the store, you can recreate the client as needed and use it in your code
|
||||||
```js
|
```js
|
||||||
var client = new btcpay.BTCPayClient('https://btcpayserverhostname', keypair, {merchant: '6gi59fB1LKxHuyY29m8tR6tRysWppk9TnuoM7wT77Las'})
|
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>})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get rates
|
||||||
## Get rates
|
Fetches current rates from BitcoinAverage (using your BTCPayServer)
|
||||||
```js
|
```js
|
||||||
client.get_rates('BTC_USD').then(rates => console.log(rates))
|
client.get_rates(['BTC_USD'], <STOREID>)
|
||||||
|
.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)
|
||||||
|
```js
|
||||||
|
client.create_invoice({price: 20, currency: 'USD'})
|
||||||
|
.then(invoice => console.log(invoice.url))
|
||||||
|
.catch(err => console.log(err))
|
||||||
```
|
```
|
||||||
|
|
||||||
The first argument accept comma-separated list of currency pair.
|
### Get invoice
|
||||||
|
|
||||||
## Create invoice
|
|
||||||
See BitPay API documentation: https://bitpay.com/api#resource-Invoices
|
|
||||||
```js
|
```js
|
||||||
client.create_invoice({"price": 20, "currency": "USD"}).then(invoice => console.log(invoice.url))
|
client.get_invoice(<invoice-id>)
|
||||||
|
.then(invoice => console.log(invoice.status))
|
||||||
|
.catch(err => console.log(err))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Get invoice
|
|
||||||
```js
|
|
||||||
client.get_invoice(<invoice-id>).then(invoice => console.log(invoice.status))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Key Management
|
|
||||||
```js
|
|
||||||
var privateKey = keypair.getPrivate().toString('hex')
|
|
||||||
var keypair = btcpay.crypto.load_keypair(new Buffer(privateKey, "hex"))
|
|
||||||
```
|
|
||||||
161
client.js
161
client.js
@ -1,161 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
80
docker/Dockerfile
Normal file
80
docker/Dockerfile
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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
|
||||||
30
docker/README.md
Normal file
30
docker/README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
25
docker/build.sh
Executable file
25
docker/build.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/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
|
||||||
27
docker/build_stage2.sh
Executable file
27
docker/build_stage2.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/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
|
||||||
237
docker/registerAdmin.js
Normal file
237
docker/registerAdmin.js
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
14
docker/start_btcpay.sh
Normal file
14
docker/start_btcpay.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/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" -- $@
|
||||||
30
docker/start_everything.sh
Executable file
30
docker/start_everything.sh
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/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
5
index.js
@ -1,5 +0,0 @@
|
|||||||
let crypto = require('./cryptography')
|
|
||||||
let BTCPayClient = require('./client')
|
|
||||||
|
|
||||||
exports.crypto = crypto
|
|
||||||
exports.BTCPayClient = BTCPayClient
|
|
||||||
31
jest.json
Normal file
31
jest.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
14729
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@ -1,20 +1,55 @@
|
|||||||
{
|
{
|
||||||
"name": "btcpay",
|
"name": "btcpay",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"description": "A nodejs client implementation for BTCPay",
|
"description": "A nodejs client implementation for BTCPay",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"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"
|
||||||
},
|
},
|
||||||
"author": "Tim Akinbo <tim@tanjalo.com>",
|
"contributors": [
|
||||||
|
"Tim Akinbo <tim@tanjalo.com>",
|
||||||
|
"Christoph Ott <christoph.ott@lean-coders.at>",
|
||||||
|
"Jonathan Underwood <junderwood@bitcoinbank.co.jp>"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/btcpayserver/node-btcpay.git"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^3.5.1",
|
|
||||||
"bn.js": "^4.11.8",
|
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"elliptic": "^6.4.0",
|
"elliptic": "^6.5.4",
|
||||||
"request": "^2.85.0",
|
"request": "^2.88.2",
|
||||||
"request-promise": "^4.2.2",
|
"request-promise": "^4.2.6",
|
||||||
"underscore": "^1.8.3"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
153
src/core/client.ts
Normal file
153
src/core/client.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/core/cryptography.ts
Normal file
55
src/core/cryptography.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/index.ts
Normal file
6
src/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
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 };
|
||||||
49
src/models/client.ts
Normal file
49
src/models/client.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
73
src/models/invoice.ts
Normal file
73
src/models/invoice.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
7
src/models/rate.ts
Normal file
7
src/models/rate.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface Rate {
|
||||||
|
name: string;
|
||||||
|
cryptoCode: string;
|
||||||
|
currencyPair: string;
|
||||||
|
code: string;
|
||||||
|
rate: number;
|
||||||
|
}
|
||||||
182
tests/core/client.spec.ts
Normal file
182
tests/core/client.spec.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
33
tests/core/cryptography.spec.ts
Normal file
33
tests/core/cryptography.spec.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
9
tests/index.spec.ts
Normal file
9
tests/index.spec.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as btcpay from '../src/index';
|
||||||
|
|
||||||
|
describe('btcpay.index', () => {
|
||||||
|
it('should import', () => {
|
||||||
|
expect(btcpay).toBeDefined();
|
||||||
|
expect(btcpay.crypto).toBeDefined();
|
||||||
|
expect(btcpay.BTCPayClient).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
80
tslint.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"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