UNPKG

bitcore-wallet-client

Version:
377 lines 17.1 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PayProV2 = exports.NetworkMap = void 0; const crypto_wallet_core_1 = require("crypto-wallet-core"); const querystring_1 = __importDefault(require("querystring")); const superagent_1 = __importDefault(require("superagent")); const url_1 = __importDefault(require("url")); const JsonPaymentProtocolKeys_1 = __importDefault(require("../util/JsonPaymentProtocolKeys")); const errors_1 = require("./errors"); const sha256 = crypto_wallet_core_1.BitcoreLib.crypto.Hash.sha256; const BN = crypto_wallet_core_1.BitcoreLib.crypto.BN; const MAX_FEE_PER_KB = { btc: 10000 * 1000, bch: 10000 * 1000, eth: 1000000000000, matic: 1000000000000, arb: 1000000000000, base: 1000000000000, op: 1000000000000, xrp: 1000000000000, doge: 10000 * 1000, ltc: 10000 * 1000, sol: 15000 }; var NetworkMap; (function (NetworkMap) { NetworkMap["main"] = "livenet"; NetworkMap["test"] = "testnet"; NetworkMap["regtest"] = "regtest"; })(NetworkMap || (exports.NetworkMap = NetworkMap = {})); ; class PayProV2 { constructor(requestOptions = {}, trustedKeys = JsonPaymentProtocolKeys_1.default) { 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'); } } static _asyncRequest(options) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { let requestOptions = Object.assign({}, PayProV2.options, options); requestOptions.headers = Object.assign({}, PayProV2.options.headers, options.headers); var r = this.request[requestOptions.method](requestOptions.url); for (const [k, v] of Object.entries(requestOptions.headers || {})) { 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((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_1.Errors.INVOICE_NOT_AVAILABLE()); } else if (res.statusCode == 504) { return reject(new errors_1.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_1.Errors.INVALID_REQUEST()); } } return reject(err); } return resolve({ rawBody: res.text, headers: res.headers }); }); }); }); } static getError(errMsg) { switch (true) { case errMsg.includes('Invoice no longer accepting payments'): return new errors_1.Errors.INVOICE_EXPIRED(); case errMsg.includes('We were unable to parse your payment.'): return new errors_1.Errors.UNABLE_TO_PARSE_PAYMENT(); case errMsg.includes('Request must include exactly one'): return new errors_1.Errors.NO_TRASACTION(); case errMsg.includes('Your transaction was an in an invalid format'): return new errors_1.Errors.INVALID_TX_FORMAT(); case errMsg.includes('We were unable to parse the transaction you sent'): return new errors_1.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_1.Errors.WRONG_ADDRESS(); case errMsg.includes('The amount on the transaction (X BTC) does'): return new errors_1.Errors.WRONG_AMOUNT(); case errMsg.includes('Transaction fee (X sat/kb) is below'): return new errors_1.Errors.NOT_ENOUGH_FEE(); case errMsg.includes('This invoice is priced in BTC, not BCH.'): return new errors_1.Errors.BTC_NOT_BCH(); case errMsg.includes(' One or more input transactions for your transaction were not found on the blockchain.'): return new errors_1.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_1.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_1.Errors.UNCONFIRMED_INPUTS_NOT_ACCEPTED(); default: return new Error(errMsg); } } static getPaymentOptions(_a) { return __awaiter(this, arguments, void 0, function* ({ paymentUrl, unsafeBypassValidation = false }) { const paymentUrlObject = url_1.default.parse(paymentUrl); if (paymentUrlObject.protocol !== 'http:' && paymentUrlObject.protocol !== 'https:') { let uriQuery = querystring_1.default.decode(paymentUrlObject.query); if (!uriQuery.r) { throw new Error('Invalid payment protocol url'); } else { paymentUrl = uriQuery.r; } } const { rawBody, headers } = yield PayProV2._asyncRequest({ method: 'get', url: paymentUrl, headers: { Accept: 'application/payment-options', 'x-paypro-version': 2, Connection: 'Keep-Alive', 'Keep-Alive': 'timeout=30, max=10' } }); return yield this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation); }); } static selectPaymentOption(_a) { return __awaiter(this, arguments, void 0, function* ({ paymentUrl, chain, currency, payload, unsafeBypassValidation = false }) { if (currency === 'USDP') currency = 'PAX'; let { rawBody, headers } = yield 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 === null || chain === void 0 ? void 0 : chain.toUpperCase(), currency, payload }) }); return yield PayProV2.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation); }); } static verifyUnsignedPayment(_a) { return __awaiter(this, arguments, void 0, function* ({ paymentUrl, chain, currency, unsignedTransactions, unsafeBypassValidation = false }) { if (currency === 'USDP') currency = 'PAX'; let { rawBody, headers } = yield 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 === null || chain === void 0 ? void 0 : chain.toUpperCase(), currency, transactions: unsignedTransactions }) }); return yield this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation); }); } static sendSignedPayment(_a) { return __awaiter(this, arguments, void 0, function* ({ paymentUrl, chain, currency, signedTransactions, unsafeBypassValidation = false, bpPartner }) { if (currency === 'USDP') currency = 'PAX'; let { rawBody, headers } = yield 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 === null || chain === void 0 ? void 0 : chain.toUpperCase(), currency, transactions: signedTransactions }) }); return yield this.verifyResponse(paymentUrl, rawBody, headers, unsafeBypassValidation); }); } static verifyResponse(requestUrl, rawBody, headers, unsafeBypassValidation) { return __awaiter(this, void 0, void 0, function* () { 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'); } let responseData; try { responseData = JSON.parse(rawBody); } catch (e) { throw new Error('Invalid JSON in response body'); } let payProDetails; try { payProDetails = this.processResponse(responseData); } catch (e) { throw e; } if (unsafeBypassValidation) { return payProDetails; } const hash = headers.digest.split('=')[1]; const signature = headers.signature; const signatureType = headers['x-signature-type']; const identity = headers['x-identity']; let host; try { host = url_1.default.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`); } const keyData = PayProV2.trustedKeys[identity]; const 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}`); } const hashbuf = Buffer.from(hash, 'hex'); const sigbuf = Buffer.from(signature, 'hex'); let s_r = BN.fromBuffer(sigbuf.slice(0, 32)); let s_s = BN.fromBuffer(sigbuf.slice(32)); let pub = crypto_wallet_core_1.BitcoreLib.PublicKey.fromString(keyData.publicKey); let sig = new crypto_wallet_core_1.BitcoreLib.crypto.Signature(s_r, s_s); let valid = crypto_wallet_core_1.BitcoreLib.crypto.ECDSA.verify(hashbuf, sig, pub); if (!valid) { throw new Error('Response signature invalid'); } return payProDetails; }); } static processResponse(responseData) { var _a; let payProDetails = { paymentId: responseData.paymentId, payProUrl: responseData.paymentUrl, memo: responseData.memo }; payProDetails.verified = true; if (responseData.paymentOptions) { payProDetails.paymentOptions = responseData.paymentOptions; payProDetails.paymentOptions.forEach(option => { option.network = NetworkMap[option.network]; }); } if (responseData.network) { payProDetails.network = NetworkMap[responseData.network]; } if (responseData.chain) { payProDetails.chain = (_a = responseData.chain) === null || _a === void 0 ? void 0 : _a.toLowerCase(); } if (responseData.currency) { payProDetails.currency = responseData.currency; } if (responseData.expires) { try { payProDetails.expires = new Date(responseData.expires).toISOString(); } catch (e) { throw new Error('Bad expiration'); } } if (responseData.time) { try { payProDetails.time = new Date(responseData.time).toISOString(); } catch (e) { throw new Error('Bad time'); } } if (responseData.instructions) { payProDetails.instructions = responseData.instructions; payProDetails.instructions.forEach(output => { output.toAddress = output.to || output.outputs[0].address; output.amount = output.value !== undefined ? output.value : output.outputs[0].amount; }); const { requiredFeeRate, gasPrice } = responseData.instructions[0]; payProDetails.requiredFeeRate = requiredFeeRate || gasPrice; if (payProDetails.requiredFeeRate) { if (payProDetails.requiredFeeRate > MAX_FEE_PER_KB[payProDetails.chain]) { throw new Error('Fee rate too high:' + payProDetails.requiredFeeRate); } } } return payProDetails; } } exports.PayProV2 = PayProV2; PayProV2.options = { headers: {}, args: '', agent: false }; PayProV2.request = superagent_1.default; PayProV2.trustedKeys = JsonPaymentProtocolKeys_1.default; ; //# sourceMappingURL=payproV2.js.map