UNPKG

@ducatus/ducatus-wallet-client-rev

Version:

Client for @ducatus/ducatus-wallet-service-rev

417 lines 21.2 kB
'use strict'; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var superagent = require('superagent'); var query = require('querystring'); var url = require('url'); var Errors = require('./errors'); var dfltTrustedKeys = require('../util/JsonPaymentProtocolKeys.js'); var Bitcore = require('@ducatus/ducatus-crypto-wallet-core-rev').BitcoreLib; var _ = require('lodash'); var sha256 = Bitcore.crypto.Hash.sha256; var BN = Bitcore.crypto.BN; var Bitcore_ = { btc: Bitcore, bch: require('@ducatus/ducatus-crypto-wallet-core-rev').BitcoreLibCash }; var MAX_FEE_PER_KB = { btc: 10000 * 1000, bch: 10000 * 1000, eth: 50000000000, xrp: 50000000000, duc: 10000 * 1000, ducx: 50000000000 }; var NetworkMap; (function (NetworkMap) { NetworkMap["main"] = "livenet"; NetworkMap["test"] = "testnet"; NetworkMap["regtest"] = "testnet"; })(NetworkMap = exports.NetworkMap || (exports.NetworkMap = {})); var PayProV2 = (function () { function PayProV2(requestOptions, trustedKeys) { if (requestOptions === void 0) { requestOptions = {}; } if (trustedKeys === void 0) { trustedKeys = dfltTrustedKeys; } PayProV2.options = Object.assign({}, { agent: false }, requestOptions); PayProV2.trustedKeys = trustedKeys; if (!PayProV2.trustedKeys || !Object.keys(PayProV2.trustedKeys).length) { throw new Error('Invalid constructor, no trusted keys added to agent'); } } PayProV2._asyncRequest = function (options) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2, new Promise(function (resolve, reject) { var requestOptions = Object.assign({}, PayProV2.options, options); requestOptions.headers = Object.assign({}, PayProV2.options.headers, options.headers); var r = _this.request[requestOptions.method](requestOptions.url); _.each(requestOptions.headers, function (v, k) { if (v) r.set(k, v); }); r.agent(requestOptions.agent); if (requestOptions.args) { if (requestOptions.method == 'post' || requestOptions.method == 'put') { r.send(requestOptions.args); } else { r.query(requestOptions.args); } } r.end(function (err, res) { if (err) { if (res && res.statusCode !== 200) { if ((res.statusCode == 400 || res.statusCode == 422) && res.body && res.body.msg) { return reject(_this.getError(res.body.msg)); } else if (res.statusCode == 404) { return reject(new Errors.INVOICE_NOT_AVAILABLE()); } else if (res.statusCode == 504) { return reject(new Errors.REQUEST_TIMEOUT()); } else if (res.statusCode == 500 && res.body && res.body.msg) { return reject(new Error(res.body.msg)); } else { return reject(new Errors.INVALID_REQUEST()); } } return reject(err); } return resolve({ rawBody: res.text, headers: res.headers }); }); })]; }); }); }; PayProV2.getError = function (errMsg) { switch (true) { case errMsg.includes('Invoice no longer accepting payments'): return new Errors.INVOICE_EXPIRED(); case errMsg.includes('We were unable to parse your payment.'): return new Errors.UNABLE_TO_PARSE_PAYMENT(); case errMsg.includes('Request must include exactly one'): return new Errors.NO_TRASACTION(); case errMsg.includes('Your transaction was an in an invalid format'): return new Errors.INVALID_TX_FORMAT(); case errMsg.includes('We were unable to parse the transaction you sent'): return new Errors.UNABLE_TO_PARSE_TX(); case errMsg.includes('The transaction you sent does not have any output to the bitcoin address on the invoice'): return new Errors.WRONG_ADDRESS(); case errMsg.includes('The amount on the transaction (X BTC) does'): return new Errors.WRONG_AMOUNT(); case errMsg.includes('Transaction fee (X sat/kb) is below'): return new Errors.NOT_ENOUGH_FEE(); case errMsg.includes('This invoice is priced in BTC, not BCH.'): return new Errors.BTC_NOT_BCH(); case errMsg.includes(' One or more input transactions for your transaction were not found on the blockchain.'): return new Errors.INPUT_NOT_FOUND(); case errMsg.includes('The PayPro request has timed out. Please connect to the internet or try again later.'): return new Errors.REQUEST_TIMEOUT(); case errMsg.includes('One or more input transactions for your transactions are not yet confirmed in at least one block.'): return new Errors.UNCONFIRMED_INPUTS_NOT_ACCEPTED(); default: return new Error(errMsg); } }; PayProV2.getPaymentOptions = function (_a) { var paymentUrl = _a.paymentUrl, _b = _a.unsafeBypassValidation, unsafeBypassValidation = _b === void 0 ? false : _b; return __awaiter(this, void 0, void 0, function () { var paymentUrlObject, uriQuery, _c, rawBody, headers; return __generator(this, function (_d) { switch (_d.label) { case 0: paymentUrlObject = url.parse(paymentUrl); if (paymentUrlObject.protocol !== 'http:' && paymentUrlObject.protocol !== 'https:') { uriQuery = query.decode(paymentUrlObject.query); if (!uriQuery.r) { throw new Error('Invalid payment protocol url'); } else { paymentUrl = uriQuery.r; } } return [4, PayProV2._asyncRequest({ method: 'get', url: paymentUrl, headers: { Accept: 'application/payment-options', 'x-paypro-version': 2, Connection: 'Keep-Alive', 'Keep-Alive': 'timeout=30, max=10' } })]; case 1: _c = _d.sent(), rawBody = _c.rawBody, headers = _c.headers; return [4, this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation)]; case 2: return [2, _d.sent()]; } }); }); }; PayProV2.selectPaymentOption = function (_a) { var paymentUrl = _a.paymentUrl, chain = _a.chain, currency = _a.currency, _b = _a.unsafeBypassValidation, unsafeBypassValidation = _b === void 0 ? false : _b; return __awaiter(this, void 0, void 0, function () { var _c, rawBody, headers; return __generator(this, function (_d) { switch (_d.label) { case 0: return [4, PayProV2._asyncRequest({ url: paymentUrl, method: 'post', headers: { 'Content-Type': 'application/payment-request', 'x-paypro-version': 2, Connection: 'Keep-Alive', 'Keep-Alive': 'timeout=30, max=10' }, args: JSON.stringify({ chain: chain, currency: currency }) })]; case 1: _c = _d.sent(), rawBody = _c.rawBody, headers = _c.headers; return [4, PayProV2.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation)]; case 2: return [2, _d.sent()]; } }); }); }; PayProV2.verifyUnsignedPayment = function (_a) { var paymentUrl = _a.paymentUrl, chain = _a.chain, currency = _a.currency, unsignedTransactions = _a.unsignedTransactions, _b = _a.unsafeBypassValidation, unsafeBypassValidation = _b === void 0 ? false : _b; return __awaiter(this, void 0, void 0, function () { var _c, rawBody, headers; return __generator(this, function (_d) { switch (_d.label) { case 0: return [4, PayProV2._asyncRequest({ url: paymentUrl, method: 'post', headers: { 'Content-Type': 'application/payment-verification', 'x-paypro-version': 2, Connection: 'Keep-Alive', 'Keep-Alive': 'timeout=30, max=10' }, args: JSON.stringify({ chain: chain, currency: currency, transactions: unsignedTransactions }) })]; case 1: _c = _d.sent(), rawBody = _c.rawBody, headers = _c.headers; return [4, this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation)]; case 2: return [2, _d.sent()]; } }); }); }; PayProV2.sendSignedPayment = function (_a) { var paymentUrl = _a.paymentUrl, chain = _a.chain, currency = _a.currency, signedTransactions = _a.signedTransactions, _b = _a.unsafeBypassValidation, unsafeBypassValidation = _b === void 0 ? false : _b, bpPartner = _a.bpPartner; return __awaiter(this, void 0, void 0, function () { var _c, rawBody, headers; return __generator(this, function (_d) { switch (_d.label) { case 0: return [4, this._asyncRequest({ url: paymentUrl, method: 'post', headers: { 'Content-Type': 'application/payment', 'x-paypro-version': 2, BP_PARTNER: bpPartner.bp_partner, BP_PARTNER_VERSION: bpPartner.bp_partner_version, Connection: 'Keep-Alive', 'Keep-Alive': 'timeout=30, max=10' }, args: JSON.stringify({ chain: chain, currency: currency, transactions: signedTransactions }) })]; case 1: _c = _d.sent(), rawBody = _c.rawBody, headers = _c.headers; return [4, this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation)]; case 2: return [2, _d.sent()]; } }); }); }; PayProV2.verifyResponse = function (requestUrl, rawBody, headers, unsafeBypassValidation) { return __awaiter(this, void 0, void 0, function () { var responseData, payProDetails, hash, signature, signatureType, identity, host, keyData, actualHash, hashbuf, sigbuf, s_r, s_s, pub, sig, valid; return __generator(this, function (_a) { if (!requestUrl) { throw new Error('Parameter requestUrl is required'); } if (!rawBody) { throw new Error('Parameter rawBody is required'); } if (!headers) { throw new Error('Parameter headers is required'); } try { responseData = JSON.parse(rawBody); } catch (e) { throw new Error('Invalid JSON in response body'); } try { payProDetails = this.processResponse(responseData); } catch (e) { throw e; } if (unsafeBypassValidation) { return [2, payProDetails]; } hash = headers.digest.split('=')[1]; signature = headers.signature; signatureType = headers['x-signature-type']; identity = headers['x-identity']; try { host = url.parse(requestUrl).hostname; } catch (e) { } if (!host) { throw new Error('Invalid requestUrl'); } if (!signatureType) { throw new Error('Response missing x-signature-type header'); } if (typeof signatureType !== 'string') { throw new Error('Invalid x-signature-type header'); } if (signatureType !== 'ecc') { throw new Error("Unknown signature type " + signatureType); } if (!signature) { throw new Error('Response missing signature header'); } if (typeof signature !== 'string') { throw new Error('Invalid signature header'); } if (!identity) { throw new Error('Response missing x-identity header'); } if (typeof identity !== 'string') { throw new Error('Invalid identity header'); } if (!PayProV2.trustedKeys[identity]) { throw new Error("Response signed by unknown key (" + identity + "), unable to validate"); } keyData = PayProV2.trustedKeys[identity]; actualHash = sha256(Buffer.from(rawBody, 'utf8')).toString('hex'); if (hash !== actualHash) { throw new Error("Response body hash does not match digest header. Actual: " + actualHash + " Expected: " + hash); } if (!keyData.domains.includes(host)) { throw new Error("The key on the response (" + identity + ") is not trusted for domain " + host); } hashbuf = Buffer.from(hash, 'hex'); sigbuf = Buffer.from(signature, 'hex'); s_r = BN.fromBuffer(sigbuf.slice(0, 32)); s_s = BN.fromBuffer(sigbuf.slice(32)); pub = Bitcore.PublicKey.fromString(keyData.publicKey); sig = new Bitcore.crypto.Signature(s_r, s_s); valid = Bitcore.crypto.ECDSA.verify(hashbuf, sig, pub); if (!valid) { throw new Error('Response signature invalid'); } return [2, payProDetails]; }); }); }; PayProV2.processResponse = function (responseData) { var payProDetails = { payProUrl: responseData.paymentUrl, memo: responseData.memo }; payProDetails.verified = true; if (responseData.paymentOptions) { payProDetails.paymentOptions = responseData.paymentOptions; payProDetails.paymentOptions.forEach(function (option) { option.network = NetworkMap[option.network]; }); } if (responseData.network) { payProDetails.network = NetworkMap[responseData.network]; } if (responseData.chain) { payProDetails.coin = responseData.chain.toLowerCase(); } if (responseData.expires) { try { payProDetails.expires = new Date(responseData.expires).toISOString(); } catch (e) { throw new Error('Bad expiration'); } } if (responseData.instructions) { payProDetails.instructions = responseData.instructions; payProDetails.instructions.forEach(function (output) { output.toAddress = output.to || output.outputs[0].address; output.amount = output.value !== undefined ? output.value : output.outputs[0].amount; }); var _a = responseData.instructions[0], requiredFeeRate = _a.requiredFeeRate, gasPrice = _a.gasPrice; payProDetails.requiredFeeRate = requiredFeeRate || gasPrice; if (payProDetails.requiredFeeRate) { if (payProDetails.requiredFeeRate > MAX_FEE_PER_KB[payProDetails.coin]) { throw new Error('Fee rate too high:' + payProDetails.requiredFeeRate); } } } return payProDetails; }; PayProV2.options = { headers: {}, args: '', agent: false }; PayProV2.request = superagent; PayProV2.trustedKeys = dfltTrustedKeys; return PayProV2; }()); exports.PayProV2 = PayProV2; //# sourceMappingURL=payproV2.js.map