Compare commits

..

4 Commits
master ... dev

Author SHA1 Message Date
Nicolas Dorier
1ebc542be6
Merge pull request #32 from johnbailon/update_readme
Docs: Fix instructions on generating private key
2019-10-06 23:20:11 +09:00
John Bailon
f9f3624305 Docs: Fix instructions on generating private key 2019-10-06 22:12:04 +08:00
Daniel
78e56ee75e Docs: Make easier to understand, use ES6 Javascript 2018-10-18 15:36:50 +02:00
Daniel
1017448362 Keymanagment: new Buffer() is deprecated, use new Buffer.from() instead
Also, you only generate your initial keypair once, so every time you use the key in your app, it is not derived, from the previously keypair var, but e.g. from environment variables
2018-10-18 13:58:29 +02:00
27 changed files with 243 additions and 16054 deletions

View File

@ -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
View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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

View File

@ -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);
});

View File

@ -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" -- $@

View File

@ -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
View File

@ -0,0 +1,5 @@
let crypto = require('./cryptography')
let BTCPayClient = require('./client')
exports.crypto = crypto
exports.BTCPayClient = BTCPayClient

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 };

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -1,7 +0,0 @@
export interface Rate {
name: string;
cryptoCode: string;
currencyPair: string;
code: string;
rate: number;
}

View File

@ -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();
});
});

View File

@ -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',
);
});
});

View File

@ -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();
});
});

View File

@ -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"
]
}

View File

@ -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"
]
}
}