diff --git a/web/cashtab/package-lock.json b/web/cashtab/package-lock.json --- a/web/cashtab/package-lock.json +++ b/web/cashtab/package-lock.json @@ -99,7 +99,6 @@ "xecjs-message": "^1.0.6" }, "devDependencies": { - "@psf/bch-js": "^6.5.3", "husky": "^8.0.1", "jest-when": "^3.5.1" } @@ -1863,19 +1862,6 @@ "version": "0.2.3", "license": "MIT" }, - "node_modules/@chris.troutner/bip32-utils": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@chris.troutner/bip32-utils/-/bip32-utils-1.0.5.tgz", - "integrity": "sha512-pa9dh5VpPmfol1bdLy+FyqONmxlf/QH6Q01a57OP6C9gTVOZM1Rt0kCLXxXKC6e2AnNIrXpYN1UtlyBm+r6P0g==", - "dev": true, - "dependencies": { - "keccak": "^3.0.1", - "tape": "*" - }, - "engines": { - "node": ">=10.15.1" - } - }, "node_modules/@csstools/normalize.css": { "version": "12.0.0", "license": "CC0-1.0" @@ -3315,74 +3301,6 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, - "node_modules/@psf/bch-js": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.5.3.tgz", - "integrity": "sha512-38UB7NRrVzslZydWZWnB8FVWr5UpxG0cs0Xb1zmovfyOnp96XrAXfWLEuNKstAkpLApegeMY055b7vaKop/wRQ==", - "dev": true, - "dependencies": { - "@chris.troutner/bip32-utils": "1.0.5", - "@psf/bip21": "2.0.1", - "@psf/bitcoincash-ops": "2.0.0", - "@psf/bitcoincashjs-lib": "4.0.2", - "@psf/coininfo": "4.0.0", - "axios": "0.26.1", - "bc-bip68": "1.0.5", - "bchaddrjs-slp": "0.2.5", - "bigi": "1.4.2", - "bignumber.js": "9.0.0", - "bip-schnorr": "0.3.0", - "bip38": "2.0.2", - "bip39": "3.0.2", - "bip66": "1.1.5", - "bitcoinjs-message": "2.0.0", - "bs58": "4.0.1", - "ecashaddrjs": "1.0.7", - "ini": "1.3.8", - "randombytes": "2.0.6", - "safe-buffer": "5.1.2", - "satoshi-bitcoin": "1.0.4", - "slp-mdm": "0.0.6", - "slp-parser": "0.0.4", - "wif": "2.0.6" - } - }, - "node_modules/@psf/bch-js/node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/@psf/bch-js/node_modules/bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/@psf/bch-js/node_modules/randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/@psf/bip21": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@psf/bip21/-/bip21-2.0.1.tgz", - "integrity": "sha512-U9c8xBV31n+D7qxOPBO0vQ015DNvKskWCUbVgoMfH5AUNHLYrSDWIrCx4P7v9etfdu6LpPdsYr53KDSAIk0b7Q==", - "dev": true, - "dependencies": { - "qs": "^6.3.0" - } - }, "node_modules/@psf/bitcoincash-ops": { "version": "2.0.0", "license": "MIT" @@ -3411,15 +3329,6 @@ "node": ">=10.15.1" } }, - "node_modules/@psf/coininfo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@psf/coininfo/-/coininfo-4.0.0.tgz", - "integrity": "sha512-RwBc09790kbaOt8uZJMyvLqf1UziTd20FXu78bM8bMlkClnZQTJyNDdLCsFSBkJQYAJtGMkjdQ/o3/UaSC7c2Q==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.1" - } - }, "node_modules/@psf/pushdata-bitcoin": { "version": "1.2.2", "license": "MIT", @@ -4965,24 +4874,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.every": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.every/-/array.prototype.every-1.1.4.tgz", - "integrity": "sha512-Aui35iRZk1HHLRAyF7QP0KAnOnduaQ6fo6k1NVWfRc0xTs2AZ70ytlXvOmkC6Di4JmUs2Wv3DYzGtCQFSk5uGg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.flat": { "version": "1.3.0", "license": "MIT", @@ -5449,19 +5340,6 @@ "node": ">=4.5.0" } }, - "node_modules/bchaddrjs-slp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", - "integrity": "sha512-33flmPcqMFswerKu7477DSUNMVMQR3tHDk3lvbmsdkEva+TxVGGWWE/p5Lqx9M/8t3vkbe7fzmVhj4QhChcCyA==", - "dev": true, - "dependencies": { - "bs58check": "^2.1.2", - "cashaddrjs-slp": "^0.2.11" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/bech32": { "version": "1.1.4", "license": "MIT" @@ -5517,36 +5395,6 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bip-schnorr": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", - "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", - "dev": true, - "dependencies": { - "ecurve": "^1.0.6", - "js-sha256": "^0.9.0", - "random-bytes": "^1.0.0", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/bip38": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.2.tgz", - "integrity": "sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A==", - "dev": true, - "dependencies": { - "bigi": "^1.2.0", - "browserify-aes": "^1.0.1", - "bs58check": "<3.0.0", - "buffer-xor": "^1.0.2", - "create-hash": "^1.1.1", - "ecurve": "^1.0.0", - "scryptsy": "^2.0.0" - } - }, "node_modules/bip39": { "version": "3.0.2", "license": "ISC", @@ -5568,22 +5416,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/bitcoinjs-message": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", - "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", - "dev": true, - "dependencies": { - "bs58check": "^2.0.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "^3.0.1", - "varuint-bitcoin": "^1.0.1" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/bluebird": { "version": "3.7.2", "license": "MIT" @@ -5948,15 +5780,6 @@ "node": ">=4" } }, - "node_modules/cashaddrjs-slp": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/cashaddrjs-slp/-/cashaddrjs-slp-0.2.12.tgz", - "integrity": "sha512-n2TTIuW6vZZxYvjvsUAA+wOM0Zkj+3RRKUtDC1XSu4Ic4XVr0yFJkl1bzQkHWda7nkVT51sxjZneygz7D0SyrQ==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.34" - } - }, "node_modules/chalk": { "version": "2.4.2", "license": "MIT", @@ -6786,32 +6609,6 @@ "version": "0.7.0", "license": "MIT" }, - "node_modules/deep-equal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz", - "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "license": "MIT" @@ -7100,18 +6897,6 @@ "version": "5.1.0", "license": "BSD-2-Clause" }, - "node_modules/dotignore": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", - "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.4" - }, - "bin": { - "ignored": "bin/ignored" - } - }, "node_modules/drbg.js": { "version": "1.0.1", "license": "MIT", @@ -7283,25 +7068,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-module-lexer": { "version": "0.9.3", "license": "MIT" @@ -9035,19 +8801,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-dynamic-import": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.0.1.tgz", - "integrity": "sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "3.0.0", "license": "MIT", @@ -9682,15 +9435,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-module": { "version": "1.0.0", "license": "MIT" @@ -9788,15 +9532,6 @@ "node": ">=6" } }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "license": "MIT", @@ -9864,15 +9599,6 @@ "version": "1.0.0", "license": "MIT" }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "license": "MIT", @@ -9883,19 +9609,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-what": { "version": "3.14.1", "license": "MIT" @@ -9910,12 +9623,6 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "license": "ISC" @@ -12228,12 +11935,6 @@ "version": "4.1.4", "license": "MIT" }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", - "dev": true - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -12383,21 +12084,6 @@ "node": ">=4.0" } }, - "node_modules/keccak": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", - "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "license": "MIT", @@ -12908,12 +12594,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, "node_modules/node-forge": { "version": "1.3.1", "license": "(BSD-3-Clause OR GPL-2.0)", @@ -12921,17 +12601,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-int64": { "version": "0.4.0", "license": "MIT" @@ -14690,21 +14359,6 @@ "react": "^15.5.3 || ^16.0.0 || ^17.0.0" } }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/querystringify": { "version": "2.2.0", "license": "MIT" @@ -14744,15 +14398,6 @@ "performance-now": "^2.1.0" } }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/randombytes": { "version": "2.1.0", "license": "MIT", @@ -15934,15 +15579,6 @@ "node": ">=10" } }, - "node_modules/resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w==", - "dev": true, - "dependencies": { - "through": "~2.3.4" - } - }, "node_modules/retry": { "version": "0.13.1", "license": "MIT", @@ -16123,24 +15759,6 @@ } } }, - "node_modules/satoshi-bitcoin": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", - "integrity": "sha512-YuHOmw5wsz6wuHIQdsz5b2cgtuKtV/jEcZ4NGmWN5tM/gz5T9Q+DSuLlnuf5BP/jsQWgR0ofmY4f8Dm6JnqSug==", - "dev": true, - "dependencies": { - "big.js": "^3.1.3" - } - }, - "node_modules/satoshi-bitcoin/node_modules/big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/saxes": { "version": "5.0.1", "license": "ISC", @@ -16182,12 +15800,6 @@ "compute-scroll-into-view": "^1.0.17" } }, - "node_modules/scryptsy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true - }, "node_modules/secp256k1": { "version": "3.8.0", "hasInstallScript": true, @@ -16430,15 +16042,6 @@ "bignumber.js": "^9.0.0" } }, - "node_modules/slp-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slp-parser/-/slp-parser-0.0.4.tgz", - "integrity": "sha512-AvbslJumkzGfMGWNvuE2pWx2nyHEk/VgQ7l119kDKIFRTuRUWOkyOULLauw5laGRQsBRThg6NCx/TsR3grX6GA==", - "dev": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, "node_modules/sockjs": { "version": "0.3.24", "license": "MIT", @@ -16651,23 +16254,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", @@ -16959,58 +16545,6 @@ "node": ">=6" } }, - "node_modules/tape": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/tape/-/tape-5.6.1.tgz", - "integrity": "sha512-reNzS3rzsJtKk0f+zJx2XlzIsjJXlIcOIrIxk5shHAG/DzW3BKyMg8UfN79oluYlcWo4lIt56ahLqwgpRT4idg==", - "dev": true, - "dependencies": { - "array.prototype.every": "^1.1.3", - "call-bind": "^1.0.2", - "deep-equal": "^2.0.5", - "defined": "^1.0.0", - "dotignore": "^0.1.2", - "for-each": "^0.3.3", - "get-package-type": "^0.1.0", - "glob": "^7.2.3", - "has": "^1.0.3", - "has-dynamic-import": "^2.0.1", - "inherits": "^2.0.4", - "is-regex": "^1.1.4", - "minimist": "^1.2.6", - "object-inspect": "^1.12.2", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "resolve": "^2.0.0-next.3", - "resumer": "^0.0.0", - "string.prototype.trim": "^1.2.6", - "through": "^2.3.8" - }, - "bin": { - "tape": "bin/tape" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tape/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/temp-dir": { "version": "2.0.0", "license": "MIT", @@ -17135,12 +16669,6 @@ "version": "6.0.1", "license": "MIT" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/thunky": { "version": "1.1.0", "license": "MIT" @@ -17937,21 +17465,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/which-typed-array": { "version": "1.1.8", "license": "MIT", @@ -19424,16 +18937,6 @@ "@bcoe/v8-coverage": { "version": "0.2.3" }, - "@chris.troutner/bip32-utils": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@chris.troutner/bip32-utils/-/bip32-utils-1.0.5.tgz", - "integrity": "sha512-pa9dh5VpPmfol1bdLy+FyqONmxlf/QH6Q01a57OP6C9gTVOZM1Rt0kCLXxXKC6e2AnNIrXpYN1UtlyBm+r6P0g==", - "dev": true, - "requires": { - "keccak": "^3.0.1", - "tape": "*" - } - }, "@csstools/normalize.css": { "version": "12.0.0" }, @@ -20264,73 +19767,6 @@ "@protobufjs/utf8": { "version": "1.1.0" }, - "@psf/bch-js": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.5.3.tgz", - "integrity": "sha512-38UB7NRrVzslZydWZWnB8FVWr5UpxG0cs0Xb1zmovfyOnp96XrAXfWLEuNKstAkpLApegeMY055b7vaKop/wRQ==", - "dev": true, - "requires": { - "@chris.troutner/bip32-utils": "1.0.5", - "@psf/bip21": "2.0.1", - "@psf/bitcoincash-ops": "2.0.0", - "@psf/bitcoincashjs-lib": "4.0.2", - "@psf/coininfo": "4.0.0", - "axios": "0.26.1", - "bc-bip68": "1.0.5", - "bchaddrjs-slp": "0.2.5", - "bigi": "1.4.2", - "bignumber.js": "9.0.0", - "bip-schnorr": "0.3.0", - "bip38": "2.0.2", - "bip39": "3.0.2", - "bip66": "1.1.5", - "bitcoinjs-message": "2.0.0", - "bs58": "4.0.1", - "ecashaddrjs": "1.0.7", - "ini": "1.3.8", - "randombytes": "2.0.6", - "safe-buffer": "5.1.2", - "satoshi-bitcoin": "1.0.4", - "slp-mdm": "0.0.6", - "slp-parser": "0.0.4", - "wif": "2.0.6" - }, - "dependencies": { - "axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "requires": { - "follow-redirects": "^1.14.8" - } - }, - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "dev": true - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - } - } - }, - "@psf/bip21": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@psf/bip21/-/bip21-2.0.1.tgz", - "integrity": "sha512-U9c8xBV31n+D7qxOPBO0vQ015DNvKskWCUbVgoMfH5AUNHLYrSDWIrCx4P7v9etfdu6LpPdsYr53KDSAIk0b7Q==", - "dev": true, - "requires": { - "qs": "^6.3.0" - } - }, "@psf/bitcoincash-ops": { "version": "2.0.0" }, @@ -20354,15 +19790,6 @@ "wif": "^2.0.1" } }, - "@psf/coininfo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@psf/coininfo/-/coininfo-4.0.0.tgz", - "integrity": "sha512-RwBc09790kbaOt8uZJMyvLqf1UziTd20FXu78bM8bMlkClnZQTJyNDdLCsFSBkJQYAJtGMkjdQ/o3/UaSC7c2Q==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.1" - } - }, "@psf/pushdata-bitcoin": { "version": "1.2.2", "requires": { @@ -21321,18 +20748,6 @@ "array-union": { "version": "2.1.0" }, - "array.prototype.every": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.every/-/array.prototype.every-1.1.4.tgz", - "integrity": "sha512-Aui35iRZk1HHLRAyF7QP0KAnOnduaQ6fo6k1NVWfRc0xTs2AZ70ytlXvOmkC6Di4JmUs2Wv3DYzGtCQFSk5uGg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "is-string": "^1.0.7" - } - }, "array.prototype.flat": { "version": "1.3.0", "requires": { @@ -21625,16 +21040,6 @@ "bc-bip68": { "version": "1.0.5" }, - "bchaddrjs-slp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", - "integrity": "sha512-33flmPcqMFswerKu7477DSUNMVMQR3tHDk3lvbmsdkEva+TxVGGWWE/p5Lqx9M/8t3vkbe7fzmVhj4QhChcCyA==", - "dev": true, - "requires": { - "bs58check": "^2.1.2", - "cashaddrjs-slp": "^0.2.11" - } - }, "bech32": { "version": "1.1.4" }, @@ -21668,33 +21073,6 @@ "file-uri-to-path": "1.0.0" } }, - "bip-schnorr": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", - "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", - "dev": true, - "requires": { - "ecurve": "^1.0.6", - "js-sha256": "^0.9.0", - "random-bytes": "^1.0.0", - "safe-buffer": "^5.0.1" - } - }, - "bip38": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.2.tgz", - "integrity": "sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A==", - "dev": true, - "requires": { - "bigi": "^1.2.0", - "browserify-aes": "^1.0.1", - "bs58check": "<3.0.0", - "buffer-xor": "^1.0.2", - "create-hash": "^1.1.1", - "ecurve": "^1.0.0", - "scryptsy": "^2.0.0" - } - }, "bip39": { "version": "3.0.2", "requires": { @@ -21715,19 +21093,6 @@ "safe-buffer": "^5.0.1" } }, - "bitcoinjs-message": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", - "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", - "dev": true, - "requires": { - "bs58check": "^2.0.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "^3.0.1", - "varuint-bitcoin": "^1.0.1" - } - }, "bluebird": { "version": "3.7.2" }, @@ -21954,15 +21319,6 @@ "case-sensitive-paths-webpack-plugin": { "version": "2.4.0" }, - "cashaddrjs-slp": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/cashaddrjs-slp/-/cashaddrjs-slp-0.2.12.tgz", - "integrity": "sha512-n2TTIuW6vZZxYvjvsUAA+wOM0Zkj+3RRKUtDC1XSu4Ic4XVr0yFJkl1bzQkHWda7nkVT51sxjZneygz7D0SyrQ==", - "dev": true, - "requires": { - "big-integer": "^1.6.34" - } - }, "chalk": { "version": "2.4.2", "requires": { @@ -22467,29 +21823,6 @@ "dedent": { "version": "0.7.0" }, - "deep-equal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz", - "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.8" - } - }, "deep-is": { "version": "0.1.4" }, @@ -22667,15 +22000,6 @@ "dotenv-expand": { "version": "5.1.0" }, - "dotignore": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", - "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, "drbg.js": { "version": "1.0.1", "requires": { @@ -22798,22 +22122,6 @@ "unbox-primitive": "^1.0.2" } }, - "es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, "es-module-lexer": { "version": "0.9.3" }, @@ -23870,16 +23178,6 @@ "has-bigints": { "version": "1.0.2" }, - "has-dynamic-import": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.0.1.tgz", - "integrity": "sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, "has-flag": { "version": "3.0.0" }, @@ -24235,12 +23533,6 @@ "is-extglob": "^2.1.1" } }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, "is-module": { "version": "1.0.0" }, @@ -24285,12 +23577,6 @@ "is-root": { "version": "2.1.0" }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, "is-shared-array-buffer": { "version": "1.0.2", "requires": { @@ -24325,28 +23611,12 @@ "is-typedarray": { "version": "1.0.0" }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, "is-weakref": { "version": "1.0.2", "requires": { "call-bind": "^1.0.2" } }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, "is-what": { "version": "3.14.1" }, @@ -24356,12 +23626,6 @@ "is-docker": "^2.0.0" } }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "isexe": { "version": "2.0.0" }, @@ -25812,12 +25076,6 @@ "js-sdsl": { "version": "4.1.4" }, - "js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", - "dev": true - }, "js-tokens": { "version": "4.0.0" }, @@ -25907,17 +25165,6 @@ "object.assign": "^4.1.3" } }, - "keccak": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", - "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", - "dev": true, - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - } - }, "kind-of": { "version": "6.0.3" }, @@ -26228,21 +25475,9 @@ "tslib": "^2.0.3" } }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, "node-forge": { "version": "1.3.1" }, - "node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", - "dev": true - }, "node-int64": { "version": "0.4.0" }, @@ -27151,15 +26386,6 @@ "qr.js": "0.0.0" } }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, "querystringify": { "version": "2.2.0" }, @@ -27175,12 +26401,6 @@ "performance-now": "^2.1.0" } }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "dev": true - }, "randombytes": { "version": "2.1.0", "requires": { @@ -27918,15 +27138,6 @@ "resolve.exports": { "version": "1.1.0" }, - "resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w==", - "dev": true, - "requires": { - "through": "~2.3.4" - } - }, "retry": { "version": "0.13.1" }, @@ -28018,23 +27229,6 @@ "neo-async": "^2.6.2" } }, - "satoshi-bitcoin": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", - "integrity": "sha512-YuHOmw5wsz6wuHIQdsz5b2cgtuKtV/jEcZ4NGmWN5tM/gz5T9Q+DSuLlnuf5BP/jsQWgR0ofmY4f8Dm6JnqSug==", - "dev": true, - "requires": { - "big.js": "^3.1.3" - }, - "dependencies": { - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - } - } - }, "saxes": { "version": "5.0.1", "requires": { @@ -28062,12 +27256,6 @@ "compute-scroll-into-view": "^1.0.17" } }, - "scryptsy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true - }, "secp256k1": { "version": "3.8.0", "requires": { @@ -28242,15 +27430,6 @@ "bignumber.js": "^9.0.0" } }, - "slp-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slp-parser/-/slp-parser-0.0.4.tgz", - "integrity": "sha512-AvbslJumkzGfMGWNvuE2pWx2nyHEk/VgQ7l119kDKIFRTuRUWOkyOULLauw5laGRQsBRThg6NCx/TsR3grX6GA==", - "dev": true, - "requires": { - "bignumber.js": "^9.0.0" - } - }, "sockjs": { "version": "0.3.24", "requires": { @@ -28392,17 +27571,6 @@ "side-channel": "^1.0.4" } }, - "string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, "string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", @@ -28584,48 +27752,6 @@ "tapable": { "version": "2.2.1" }, - "tape": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/tape/-/tape-5.6.1.tgz", - "integrity": "sha512-reNzS3rzsJtKk0f+zJx2XlzIsjJXlIcOIrIxk5shHAG/DzW3BKyMg8UfN79oluYlcWo4lIt56ahLqwgpRT4idg==", - "dev": true, - "requires": { - "array.prototype.every": "^1.1.3", - "call-bind": "^1.0.2", - "deep-equal": "^2.0.5", - "defined": "^1.0.0", - "dotignore": "^0.1.2", - "for-each": "^0.3.3", - "get-package-type": "^0.1.0", - "glob": "^7.2.3", - "has": "^1.0.3", - "has-dynamic-import": "^2.0.1", - "inherits": "^2.0.4", - "is-regex": "^1.1.4", - "minimist": "^1.2.6", - "object-inspect": "^1.12.2", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "resolve": "^2.0.0-next.3", - "resumer": "^0.0.0", - "string.prototype.trim": "^1.2.6", - "through": "^2.3.8" - }, - "dependencies": { - "resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, "temp-dir": { "version": "2.0.0" }, @@ -28692,12 +27818,6 @@ "throat": { "version": "6.0.1" }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "thunky": { "version": "1.1.0" }, @@ -29182,18 +28302,6 @@ "is-symbol": "^1.0.3" } }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, "which-typed-array": { "version": "1.1.8", "requires": { diff --git a/web/cashtab/package.json b/web/cashtab/package.json --- a/web/cashtab/package.json +++ b/web/cashtab/package.json @@ -188,7 +188,6 @@ ] }, "devDependencies": { - "@psf/bch-js": "^6.5.3", "husky": "^8.0.1", "jest-when": "^3.5.1" } diff --git a/web/cashtab/src/components/Airdrop/Airdrop.js b/web/cashtab/src/components/Airdrop/Airdrop.js --- a/web/cashtab/src/components/Airdrop/Airdrop.js +++ b/web/cashtab/src/components/Airdrop/Airdrop.js @@ -83,7 +83,7 @@ color: ${props => props.theme.lightWhite} !important; } `; -// Note jestBCH is only used for unit tests; BCHJS must be mocked for jest + const Airdrop = ({ passLoadingStatus }) => { const ContextValue = React.useContext(WalletContext); const { @@ -869,7 +869,6 @@ }; Airdrop.propTypes = { - jestBCH: PropTypes.object, passLoadingStatus: PropTypes.func, }; diff --git a/web/cashtab/src/components/Home/Home.js b/web/cashtab/src/components/Home/Home.js --- a/web/cashtab/src/components/Home/Home.js +++ b/web/cashtab/src/components/Home/Home.js @@ -235,11 +235,7 @@ Create eToken {tokens && tokens.length > 0 ? ( - + ) : (

Tokens sent to your {currency.tokenTicker} address diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js --- a/web/cashtab/src/components/Send/Send.js +++ b/web/cashtab/src/components/Send/Send.js @@ -13,7 +13,6 @@ import { Form, message, Modal, Alert, Input } from 'antd'; import { Row, Col, Switch } from 'antd'; import PrimaryButton, { DisabledButton } from 'components/Common/PrimaryButton'; -import useBCH from 'hooks/useBCH'; import useWindowDimensions from 'hooks/useWindowDimensions'; import { sendXecNotification, @@ -40,6 +39,7 @@ FormLabel, } from 'components/Common/Atoms'; import { getWalletState, fromSatoshisToXec, calcFee } from 'utils/cashMethods'; +import { sendXec } from 'utils/transactions'; import ApiError from 'components/Common/ApiError'; import { formatFiatBalance, formatBalance } from 'utils/formatting'; import styled from 'styled-components'; @@ -195,8 +195,6 @@ setIsModalVisible(false); }; - const { sendXec } = useBCH(); - // If the balance has changed, unlock the UI // This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked useEffect(() => { diff --git a/web/cashtab/src/components/Send/SendToken.js b/web/cashtab/src/components/Send/SendToken.js --- a/web/cashtab/src/components/Send/SendToken.js +++ b/web/cashtab/src/components/Send/SendToken.js @@ -22,7 +22,6 @@ DestinationAddressSingle, AntdFormWrapper, } from 'components/Common/EnhancedInputs'; -import useBCH from 'hooks/useBCH'; import { SidePaddingCtn } from 'components/Common/Atoms'; import BalanceHeaderToken from 'components/Common/BalanceHeaderToken'; import { Redirect } from 'react-router-dom'; @@ -35,6 +34,7 @@ import { currency, parseAddressForParams } from 'components/Common/Ticker.js'; import { Event } from 'utils/GoogleAnalytics'; import { getWalletState } from 'utils/cashMethods'; +import { sendToken, burnToken } from 'utils/transactions'; import ApiError from 'components/Common/ApiError'; import { sendTokenNotification, @@ -130,8 +130,6 @@ address: '', }); - const { sendToken, burnToken } = useBCH(); - // Fetch token stats if you do not have them and API did not return an error if (tokenStats === null) { getTokenStats(chronik, tokenId).then( diff --git a/web/cashtab/src/components/Tokens/Tokens.js b/web/cashtab/src/components/Tokens/Tokens.js --- a/web/cashtab/src/components/Tokens/Tokens.js +++ b/web/cashtab/src/components/Tokens/Tokens.js @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import { WalletContext } from 'utils/context'; import { fromSatoshisToXec, getWalletState } from 'utils/cashMethods'; +import { createToken } from 'utils/transactions'; import CreateTokenForm from 'components/Tokens/CreateTokenForm'; import { currency } from 'components/Common/Ticker.js'; -import useBCH from 'hooks/useBCH'; import BalanceHeader from 'components/Common/BalanceHeader'; import BalanceHeaderFiat from 'components/Common/BalanceHeaderFiat'; import { @@ -26,7 +26,6 @@ } = React.useContext(WalletContext); const walletState = getWalletState(wallet); const { balances } = walletState; - const { createToken } = useBCH(); return ( <> diff --git a/web/cashtab/src/components/Tokens/__tests__/CreateTokenForm.test.js b/web/cashtab/src/components/Tokens/__tests__/CreateTokenForm.test.js --- a/web/cashtab/src/components/Tokens/__tests__/CreateTokenForm.test.js +++ b/web/cashtab/src/components/Tokens/__tests__/CreateTokenForm.test.js @@ -3,11 +3,11 @@ import { ThemeProvider } from 'styled-components'; import { theme } from 'assets/styles/theme'; import CreateTokenForm from 'components/Tokens/CreateTokenForm'; -import useBCH from 'hooks/useBCH'; import { walletWithBalancesAndTokensWithCorrectState } from '../../Home/__mocks__/walletAndBalancesMock'; import { WalletContext } from 'utils/context'; import BigNumber from 'bignumber.js'; import { currency } from 'components/Common/Ticker'; +import { createToken } from 'utils/transactions'; beforeEach(() => { // Mock method not implemented in JSDOM @@ -28,7 +28,6 @@ }); test('Wallet with BCH balances and tokens and state field', () => { - const { createToken } = useBCH(); const component = renderer.create( { @@ -10,28 +9,21 @@ ); }); it('Replicate 8-decimal return value from instance of toSatoshi in TransactionBuilder with fromXecToSatoshis', () => { - const BCH = new BCHJS(); const testSendAmount = '0.12345678'; expect( parseInt(fromXecToSatoshis(new BigNumber(testSendAmount), 8)), - ).toBe(BCH.BitcoinCash.toSatoshi(Number(testSendAmount).toFixed(8))); + ).toBe(12345678); }); it('Replicate 2-decimal return value from instance of toSatoshi in TransactionBuilder with fromXecToSatoshis', () => { - const BCH = new BCHJS(); const testSendAmount = '0.12'; expect( parseInt(fromXecToSatoshis(new BigNumber(testSendAmount), 8)), - ).toBe(BCH.BitcoinCash.toSatoshi(Number(testSendAmount).toFixed(8))); + ).toBe(12000000); }); it('Replicate 8-decimal return value from instance of toSatoshi in remainder comparison with fromXecToSatoshis', () => { - const BCH = new BCHJS(); expect( parseFloat(fromXecToSatoshis(new BigNumber('0.00000546'), 8)), - ).toBe( - BCH.BitcoinCash.toSatoshi( - parseFloat(new BigNumber('0.00000546').toFixed(8)), - ), - ); + ).toBe(546); }); it('fromXecToSatoshis() returns false if input is not a BigNumber', () => { const testInput = 132.12345678; @@ -60,13 +52,10 @@ ); }); it('fromXecToSatoshis() returns exact result as toSatoshi but in BigNumber format', () => { - const BCH = new BCHJS(); const testAmount = new BigNumber('0.12345678'); // Match legacy implementation, inputting a BigNumber converted to a string by .toFixed(8) - const testAmountInSatoshis = BCH.BitcoinCash.toSatoshi( - testAmount.toFixed(8), - ); + const testAmountInSatoshis = 12345678; const testAmountInCashDecimals = fromXecToSatoshis(testAmount, 8); @@ -101,26 +90,21 @@ expect(parseInt(remainder)).toStrictEqual(12345678); }); it('Replicates return value from instance of toBitcoinCash with fromSatoshisToXec and cashDecimals = 8', () => { - const BCH = new BCHJS(); const testSendAmount = '12345678'; expect(fromSatoshisToXec(testSendAmount, 8).toNumber()).toBe( - BCH.BitcoinCash.toBitcoinCash(testSendAmount), + 0.12345678, ); }); it('Replicates largest possible digits return value from instance of toBitcoinCash with fromSatoshisToXec and cashDecimals = 8', () => { - const BCH = new BCHJS(); const testSendAmount = '1000000012345678'; expect(fromSatoshisToXec(testSendAmount, 8).toNumber()).toBe( - BCH.BitcoinCash.toBitcoinCash(testSendAmount), + 10000000.12345678, ); }); it('Replicates smallest unit value return value from instance of toBitcoinCash with fromSatoshisToXec and cashDecimals = 8', () => { - const BCH = new BCHJS(); const testSendAmount = '1'; - expect(fromSatoshisToXec(testSendAmount, 8).toNumber()).toBe( - BCH.BitcoinCash.toBitcoinCash(testSendAmount), - ); + expect(fromSatoshisToXec(testSendAmount, 8).toNumber()).toBe(1e-8); }); it(`Converts dust limit in satoshis to dust limit in current app setting`, () => { diff --git a/web/cashtab/src/hooks/useBCH.js b/web/cashtab/src/hooks/useBCH.js deleted file mode 100644 --- a/web/cashtab/src/hooks/useBCH.js +++ /dev/null @@ -1,499 +0,0 @@ -import { currency } from 'components/Common/Ticker'; -import { - fromXecToSatoshis, - isValidStoredWallet, - parseXecSendValue, - generateOpReturnScript, - generateTxInput, - generateTxOutput, - generateTokenTxInput, - generateTokenTxOutput, - signAndBuildTx, - getChangeAddressFromInputUtxos, - toHash160, -} from 'utils/cashMethods'; -import ecies from 'ecies-lite'; -import TransactionBuilder from 'utils/txBuilder'; - -export default function useBCH() { - const SEND_XEC_ERRORS = { - INSUFFICIENT_FUNDS: 0, - NETWORK_ERROR: 1, - INSUFFICIENT_PRIORITY: 66, // ~insufficient fee - DOUBLE_SPENDING: 18, - MAX_UNCONFIRMED_TXS: 64, - }; - - const createToken = async ( - chronik, - wallet, - feeInSatsPerByte, - configObj, - ) => { - try { - // Throw error if wallet does not have utxo set in state - if (!isValidStoredWallet(wallet)) { - const walletError = new Error(`Invalid wallet`); - throw walletError; - } - const utxos = wallet.state.nonSlpUtxos; - const CREATION_ADDR = wallet.Path1899.cashAddress; - let txBuilder = new TransactionBuilder(); - - let tokenTxInputObj = generateTokenTxInput( - 'GENESIS', - utxos, - null, // total token UTXOS - not applicable for GENESIS tx - null, // token ID - not applicable for GENESIS tx - null, // token amount - not applicable for GENESIS tx - feeInSatsPerByte, - txBuilder, - ); - // update txBuilder object with inputs - txBuilder = tokenTxInputObj.txBuilder; - - let tokenTxOutputObj = generateTokenTxOutput( - txBuilder, - 'GENESIS', - CREATION_ADDR, - null, // token UTXOS being spent - not applicable for GENESIS tx - tokenTxInputObj.remainderXecValue, - configObj, - ); - // update txBuilder object with outputs - txBuilder = tokenTxOutputObj; - - // sign the collated inputUtxos and build the raw tx hex - // returns the raw tx hex string - const rawTxHex = signAndBuildTx( - tokenTxInputObj.inputXecUtxos, - txBuilder, - wallet, - ); - - // Broadcast transaction to the network via the chronik client - // sample chronik.broadcastTx() response: - // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} - let broadcastResponse; - try { - broadcastResponse = await chronik.broadcastTx( - rawTxHex, - true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns - // if the wallet has existing burns via bch-api then chronik will throw 'invalid-slp-burns' errors without this flag - ); - if (!broadcastResponse) { - throw new Error('Empty chronik broadcast response'); - } - } catch (err) { - console.log('Error broadcasting tx to chronik client'); - throw err; - } - - // return the explorer link for the broadcasted tx - return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; - } catch (err) { - if (err.error === 'insufficient priority (code 66)') { - err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; - } else if (err.error === 'txn-mempool-conflict (code 18)') { - err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; - } else if (err.error === 'Network Error') { - err.code = SEND_XEC_ERRORS.NETWORK_ERROR; - } else if ( - err.error === - 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)' - ) { - err.code = SEND_XEC_ERRORS.MAX_UNCONFIRMED_TXS; - } - console.log(`error: `, err); - throw err; - } - }; - - const sendToken = async ( - chronik, - wallet, - { tokenId, amount, tokenReceiverAddress }, - ) => { - const { slpUtxos, nonSlpUtxos } = wallet.state; - const CREATION_ADDR = wallet.Path1899.cashAddress; - - // Handle error of user having no XEC - if (!nonSlpUtxos || nonSlpUtxos.length === 0) { - throw new Error( - `You need some ${currency.ticker} to send ${currency.tokenTicker}`, - ); - } - - // instance of transaction builder - let txBuilder = new TransactionBuilder(); - - let tokenTxInputObj = generateTokenTxInput( - 'SEND', - nonSlpUtxos, - slpUtxos, - tokenId, - amount, - currency.defaultFee, - txBuilder, - ); - // update txBuilder object with inputs - txBuilder = tokenTxInputObj.txBuilder; - - let tokenTxOutputObj = generateTokenTxOutput( - txBuilder, - 'SEND', - CREATION_ADDR, - tokenTxInputObj.inputTokenUtxos, - tokenTxInputObj.remainderXecValue, - null, // token config object - for GENESIS tx only - tokenReceiverAddress, - amount, - ); - // update txBuilder object with outputs - txBuilder = tokenTxOutputObj; - - // append the token input UTXOs to the array of XEC input UTXOs for signing - const combinedInputUtxos = tokenTxInputObj.inputXecUtxos.concat( - tokenTxInputObj.inputTokenUtxos, - ); - - // sign the collated inputUtxos and build the raw tx hex - // returns the raw tx hex string - const rawTxHex = signAndBuildTx(combinedInputUtxos, txBuilder, wallet); - - // Broadcast transaction to the network via the chronik client - // sample chronik.broadcastTx() response: - // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} - let broadcastResponse; - try { - broadcastResponse = await chronik.broadcastTx( - rawTxHex, - true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns - // if the wallet has existing burns via bch-api then chronik will throw 'invalid-slp-burns' errors without this flag - ); - if (!broadcastResponse) { - throw new Error('Empty chronik broadcast response'); - } - } catch (err) { - console.log('Error broadcasting tx to chronik client'); - throw err; - } - - // return the explorer link for the broadcasted tx - return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; - }; - - const burnToken = async (chronik, wallet, { tokenId, amount }) => { - const { slpUtxos, nonSlpUtxos } = wallet.state; - const CREATION_ADDR = wallet.Path1899.cashAddress; - - // Handle error of user having no XEC - if (!nonSlpUtxos || nonSlpUtxos.length === 0) { - throw new Error(`You need some ${currency.ticker} to burn eTokens`); - } - - // instance of transaction builder - let txBuilder = new TransactionBuilder(); - - let tokenTxInputObj = generateTokenTxInput( - 'BURN', - nonSlpUtxos, - slpUtxos, - tokenId, - amount, - currency.defaultFee, - txBuilder, - ); - // update txBuilder object with inputs - txBuilder = tokenTxInputObj.txBuilder; - - let tokenTxOutputObj = generateTokenTxOutput( - txBuilder, - 'BURN', - CREATION_ADDR, - tokenTxInputObj.inputTokenUtxos, - tokenTxInputObj.remainderXecValue, - null, // token config object - for GENESIS tx only - null, // token receiver address - for SEND tx only - amount, - ); - // update txBuilder object with outputs - txBuilder = tokenTxOutputObj; - - // append the token input UTXOs to the array of XEC input UTXOs for signing - const combinedInputUtxos = tokenTxInputObj.inputXecUtxos.concat( - tokenTxInputObj.inputTokenUtxos, - ); - - // sign the collated inputUtxos and build the raw tx hex - // returns the raw tx hex string - const rawTxHex = signAndBuildTx(combinedInputUtxos, txBuilder, wallet); - - // Broadcast transaction to the network via the chronik client - // sample chronik.broadcastTx() response: - // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} - let broadcastResponse; - try { - broadcastResponse = await chronik.broadcastTx( - rawTxHex, - true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns - ); - if (!broadcastResponse) { - throw new Error('Empty chronik broadcast response'); - } - } catch (err) { - console.log('Error broadcasting tx to chronik client'); - throw err; - } - - // return the explorer link for the broadcasted tx - return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; - }; - - const getRecipientPublicKey = async ( - chronik, - recipientAddress, - optionalMockPubKeyResponse = false, - ) => { - // Necessary because jest can't mock - // chronikTxHistoryAtAddress = await chronik.script('p2pkh', recipientAddressHash160).history(/*page=*/ 0, /*page_size=*/ 10); - if (optionalMockPubKeyResponse) { - return optionalMockPubKeyResponse; - } - - // get hash160 of address - let recipientAddressHash160; - try { - recipientAddressHash160 = toHash160(recipientAddress); - } catch (err) { - console.log( - `Error determining toHash160(${recipientAddress} in getRecipientPublicKey())`, - err, - ); - throw new Error( - `Error determining toHash160(${recipientAddress} in getRecipientPublicKey())`, - ); - } - - let chronikTxHistoryAtAddress; - try { - // Get 20 txs. If no outgoing txs in those 20 txs, just don't send the tx - chronikTxHistoryAtAddress = await chronik - .script('p2pkh', recipientAddressHash160) - .history(/*page=*/ 0, /*page_size=*/ 20); - } catch (err) { - console.log( - `Error getting await chronik.script('p2pkh', ${recipientAddressHash160}).history();`, - err, - ); - throw new Error( - 'Error fetching tx history to parse for public key', - ); - } - let recipientPubKeyChronik; - - // Iterate over tx history to find an outgoing tx - for (let i = 0; i < chronikTxHistoryAtAddress.txs.length; i += 1) { - const { inputs } = chronikTxHistoryAtAddress.txs[i]; - for (let j = 0; j < inputs.length; j += 1) { - const thisInput = inputs[j]; - const thisInputSendingHash160 = thisInput.outputScript; - if (thisInputSendingHash160.includes(recipientAddressHash160)) { - // Then this is an outgoing tx, you can get the public key from this tx - // Get the public key - try { - recipientPubKeyChronik = - chronikTxHistoryAtAddress.txs[i].inputs[ - j - ].inputScript.slice(-66); - } catch (err) { - throw new Error( - 'Cannot send an encrypted message to a wallet with no outgoing transactions', - ); - } - return recipientPubKeyChronik; - } - } - } - // You get here if you find no outgoing txs in the chronik tx history - throw new Error( - 'Cannot send an encrypted message to a wallet with no outgoing transactions in the last 20 txs', - ); - }; - - const sendXec = async ( - chronik, - wallet, - utxos, - feeInSatsPerByte, - optionalOpReturnMsg, - isOneToMany, - destinationAddressAndValueArray, - destinationAddress, - sendAmount, - encryptionFlag, - airdropFlag, - airdropTokenId, - optionalMockPubKeyResponse = false, - ) => { - try { - let txBuilder = new TransactionBuilder(); - - // parse the input value of XECs to send - const value = parseXecSendValue( - isOneToMany, - sendAmount, - destinationAddressAndValueArray, - ); - - const satoshisToSend = fromXecToSatoshis(value); - - // Throw validation error if fromXecToSatoshis returns false - if (!satoshisToSend) { - const error = new Error( - `Invalid decimal places for send amount`, - ); - throw error; - } - - let encryptedEj; // serialized encryption data object - - // if the user has opted to encrypt this message - if (encryptionFlag) { - try { - // get the pub key for the recipient address - let recipientPubKey = await getRecipientPublicKey( - chronik, - destinationAddress, - optionalMockPubKeyResponse, - ); - - // if the API can't find a pub key, it is due to the wallet having no outbound tx - if (recipientPubKey === 'not found') { - throw new Error( - 'Cannot send an encrypted message to a wallet with no outgoing transactions', - ); - } - - // encrypt the message - const pubKeyBuf = Buffer.from(recipientPubKey, 'hex'); - const bufferedFile = Buffer.from(optionalOpReturnMsg); - const structuredEj = await ecies.encrypt( - pubKeyBuf, - bufferedFile, - { compressEpk: true }, - ); - - // Serialize the encrypted data object - encryptedEj = Buffer.concat([ - structuredEj.epk, - structuredEj.iv, - structuredEj.ct, - structuredEj.mac, - ]); - } catch (err) { - console.log(`sendXec() encryption error.`); - throw err; - } - } - - // Start of building the OP_RETURN output. - // only build the OP_RETURN output if the user supplied it - if ( - (optionalOpReturnMsg && - typeof optionalOpReturnMsg !== 'undefined' && - optionalOpReturnMsg.trim() !== '') || - airdropFlag - ) { - const opReturnData = generateOpReturnScript( - optionalOpReturnMsg, - encryptionFlag, - airdropFlag, - airdropTokenId, - encryptedEj, - ); - txBuilder.addOutput(opReturnData, 0); - } - - // generate the tx inputs and add to txBuilder instance - // returns the updated txBuilder, txFee, totalInputUtxoValue and inputUtxos - let txInputObj = generateTxInput( - isOneToMany, - utxos, - txBuilder, - destinationAddressAndValueArray, - satoshisToSend, - feeInSatsPerByte, - ); - - const changeAddress = getChangeAddressFromInputUtxos( - txInputObj.inputUtxos, - wallet, - ); - txBuilder = txInputObj.txBuilder; // update the local txBuilder with the generated tx inputs - - // generate the tx outputs and add to txBuilder instance - // returns the updated txBuilder - const txOutputObj = generateTxOutput( - isOneToMany, - value, - satoshisToSend, - txInputObj.totalInputUtxoValue, - destinationAddress, - destinationAddressAndValueArray, - changeAddress, - txInputObj.txFee, - txBuilder, - ); - txBuilder = txOutputObj; // update the local txBuilder with the generated tx outputs - - // sign the collated inputUtxos and build the raw tx hex - // returns the raw tx hex string - const rawTxHex = signAndBuildTx( - txInputObj.inputUtxos, - txBuilder, - wallet, - ); - - // Broadcast transaction to the network via the chronik client - // sample chronik.broadcastTx() response: - // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} - let broadcastResponse; - try { - broadcastResponse = await chronik.broadcastTx(rawTxHex); - if (!broadcastResponse) { - throw new Error('Empty chronik broadcast response'); - } - } catch (err) { - console.log('Error broadcasting tx to chronik client'); - throw err; - } - - // return the explorer link for the broadcasted tx - return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; - } catch (err) { - if (err.error === 'insufficient priority (code 66)') { - err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; - } else if (err.error === 'txn-mempool-conflict (code 18)') { - err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; - } else if (err.error === 'Network Error') { - err.code = SEND_XEC_ERRORS.NETWORK_ERROR; - } else if ( - err.error === - 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)' - ) { - err.code = SEND_XEC_ERRORS.MAX_UNCONFIRMED_TXS; - } - console.log(`error: `, err); - throw err; - } - }; - - return { - sendXec, - sendToken, - createToken, - getRecipientPublicKey, - burnToken, - }; -} diff --git a/web/cashtab/src/hooks/__mocks__/burnToken.js b/web/cashtab/src/utils/__mocks__/burnToken.js rename from web/cashtab/src/hooks/__mocks__/burnToken.js rename to web/cashtab/src/utils/__mocks__/burnToken.js diff --git a/web/cashtab/src/hooks/__mocks__/createToken.js b/web/cashtab/src/utils/__mocks__/createToken.js rename from web/cashtab/src/hooks/__mocks__/createToken.js rename to web/cashtab/src/utils/__mocks__/createToken.js diff --git a/web/cashtab/src/hooks/__mocks__/sendBCH.js b/web/cashtab/src/utils/__mocks__/sendBCH.js rename from web/cashtab/src/hooks/__mocks__/sendBCH.js rename to web/cashtab/src/utils/__mocks__/sendBCH.js diff --git a/web/cashtab/src/utils/__tests__/cashMethods.test.js b/web/cashtab/src/utils/__tests__/cashMethods.test.js --- a/web/cashtab/src/utils/__tests__/cashMethods.test.js +++ b/web/cashtab/src/utils/__tests__/cashMethods.test.js @@ -83,8 +83,7 @@ } from '../__mocks__/mockOpReturnParsedArray'; import mockLegacyWallets from 'hooks/__mocks__/mockLegacyWallets'; -import BCHJS from '@psf/bch-js'; -import sendBCHMock from '../../hooks/__mocks__/sendBCH'; +import sendBCHMock from '../__mocks__/sendBCH'; import { activeWebsocketAlpha, disconnectedWebsocketAlpha, @@ -108,28 +107,20 @@ mockSingleOutput, mockMultipleOutputs, } from '../__mocks__/mockTxBuilderData'; -import createTokenMock from '../../hooks/__mocks__/createToken'; +import createTokenMock from '../__mocks__/createToken'; import TransactionBuilder from 'utils/txBuilder'; import { mockWif, mockStringifiedECPair } from '../__mocks__/mockECPair'; it(`generateSendOpReturn() returns correct script object for valid tokenUtxo and send quantity`, () => { - const BCH = new BCHJS(); const tokensToSend = 50; const sendOpReturnScriptObj = generateSendOpReturn( mockSendOpReturnTokenUtxos, tokensToSend, ); - const legacySendOpReturnScriptObj = BCH.SLP.TokenType1.generateSendOpReturn( - mockSendOpReturnTokenUtxos, - tokensToSend.toString(), - ); expect(JSON.stringify(sendOpReturnScriptObj.script)).toStrictEqual( JSON.stringify(mockSendOpReturnScript), ); - expect(JSON.stringify(sendOpReturnScriptObj.script)).toStrictEqual( - JSON.stringify(legacySendOpReturnScriptObj.script), - ); }); it(`generateSendOpReturnScript() throws error on invalid input`, () => { @@ -146,22 +137,15 @@ }); it(`generateBurnOpReturn() returns correct script for valid tokenUtxo and burn quantity`, () => { - const BCH = new BCHJS(); const tokensToBurn = 7000; const burnOpReturnScript = generateBurnOpReturn( mockBurnOpReturnTokenUtxos, tokensToBurn, ); - const legacyBurnOpReturnScript = BCH.SLP.TokenType1.generateBurnOpReturn( - mockBurnOpReturnTokenUtxos, - tokensToBurn, - ); + expect(JSON.stringify(burnOpReturnScript)).toStrictEqual( JSON.stringify(mockBurnOpReturnScript), ); - expect(JSON.stringify(burnOpReturnScript)).toStrictEqual( - JSON.stringify(legacyBurnOpReturnScript), - ); }); it(`generateBurnOpReturn() throws error on invalid input`, () => { @@ -176,7 +160,6 @@ }); it(`generateGenesisOpReturn() returns correct script for a valid configObj`, () => { - const BCH = new BCHJS(); const configObj = { name: 'ethantest', ticker: 'ETN', @@ -188,16 +171,10 @@ }; const genesisOpReturnScript = generateGenesisOpReturn(configObj); - const legacyGenesisOpReturnScript = - BCH.SLP.TokenType1.generateGenesisOpReturn(configObj); expect(JSON.stringify(genesisOpReturnScript)).toStrictEqual( JSON.stringify(mockGenesisOpReturnScript), ); - - expect(JSON.stringify(genesisOpReturnScript)).toStrictEqual( - JSON.stringify(legacyGenesisOpReturnScript), - ); }); it(`generateGenesisOpReturn() throws error on invalid configObj`, () => { @@ -1550,38 +1527,25 @@ { P2PKH: utxos.length }, { P2PKH: p2pkhOutputNumber }, );`, () => { - const BCH = new BCHJS(); - // 374 - expect(getCashtabByteCount(2, 2)).toBe( - BCH.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 2 }), - ); + expect(getCashtabByteCount(2, 2)).toBe(374); }); it(`getCashtabByteCount for 1 input, 2 outputs returns the same value as BCH.BitcoinCash.getByteCount( { P2PKH: utxos.length }, { P2PKH: p2pkhOutputNumber }, );`, () => { - const BCH = new BCHJS(); - expect(getCashtabByteCount(1, 2)).toBe( - BCH.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 }), - ); + expect(getCashtabByteCount(1, 2)).toBe(226); }); it(`getCashtabByteCount for 173 input, 1 outputs returns the same value as BCH.BitcoinCash.getByteCount( { P2PKH: utxos.length }, { P2PKH: p2pkhOutputNumber }, );`, () => { - const BCH = new BCHJS(); - expect(getCashtabByteCount(173, 1)).toBe( - BCH.BitcoinCash.getByteCount({ P2PKH: 173 }, { P2PKH: 1 }), - ); + expect(getCashtabByteCount(173, 1)).toBe(25648); }); it(`getCashtabByteCount for 1 input, 2000 outputs returns the same value as BCH.BitcoinCash.getByteCount( { P2PKH: utxos.length }, { P2PKH: p2pkhOutputNumber }, );`, () => { - const BCH = new BCHJS(); - expect(getCashtabByteCount(1, 2000)).toBe( - BCH.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2000 }), - ); + expect(getCashtabByteCount(1, 2000)).toBe(68158); }); it('calculates fee correctly for 2 P2PKH outputs', () => { const utxosMock = [{}, {}]; diff --git a/web/cashtab/src/hooks/__tests__/useBCH.test.js b/web/cashtab/src/utils/__tests__/transactions.test.js rename from web/cashtab/src/hooks/__tests__/useBCH.test.js rename to web/cashtab/src/utils/__tests__/transactions.test.js --- a/web/cashtab/src/hooks/__tests__/useBCH.test.js +++ b/web/cashtab/src/utils/__tests__/transactions.test.js @@ -1,5 +1,4 @@ /* eslint-disable no-native-reassign */ -import useBCH from '../useBCH'; import sendBCHMock from '../__mocks__/sendBCH'; import createTokenMock from '../__mocks__/createToken'; import { burnTokenWallet } from '../__mocks__/burnToken'; @@ -7,10 +6,15 @@ import BigNumber from 'bignumber.js'; import { fromSatoshisToXec } from 'utils/cashMethods'; import { ChronikClient } from 'chronik-client'; // for mocking purposes +import { + sendXec, + burnToken, + createToken, + getRecipientPublicKey, +} from 'utils/transactions'; -describe('useBCH hook', () => { +describe('Cashtab transaction broadcasting functions', () => { it('sends XEC correctly', async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -36,7 +40,6 @@ }); it('sends XEC correctly with an encrypted OP_RETURN message', async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -68,7 +71,6 @@ }); it('sends one to many XEC correctly', async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -98,7 +100,6 @@ }); it(`Throws error if called trying to send one base unit ${currency.ticker} more than available in utxo set`, async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -149,13 +150,13 @@ destinationAddress, null, ); - expect(nullValuesSendBch).rejects.toThrow( + + await expect(nullValuesSendBch).rejects.toThrow( new Error('Invalid singleSendValue'), ); }); it('Throws error on attempt to send one satoshi less than backend dust limit', async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -173,11 +174,10 @@ .minus(new BigNumber('0.00000001')) .toString(), ); - expect(failedSendBch).rejects.toThrow(new Error('dust')); + await expect(failedSendBch).rejects.toThrow(new Error('dust')); }); it("Throws error attempting to burn an eToken ID that is not within the wallet's utxo", async () => { - const { burnToken } = useBCH(); const wallet = burnTokenWallet; const burnAmount = 10; const eTokenId = '0203c768a66eba24affNOTVALID103b772de4d9f8f63ba79e'; @@ -200,7 +200,6 @@ }); it('receives errors from the network and parses it', async () => { - const { sendXec } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -283,9 +282,7 @@ }); it('creates a token correctly', async () => { - const { createToken } = useBCH(); - const { expectedTxId, expectedHex, wallet, configObj } = - createTokenMock; + const { expectedTxId, wallet, configObj } = createTokenMock; const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); @@ -298,7 +295,6 @@ }); it('Throws correct error if user attempts to create a token with an invalid wallet', async () => { - const { createToken } = useBCH(); const { invalidWallet, configObj } = createTokenMock; const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', @@ -315,7 +311,6 @@ }); it(`getRecipientPublicKey() correctly retrieves the public key of a cash address`, async () => { - const { getRecipientPublicKey } = useBCH(); const chronik = new ChronikClient( 'https://FakeChronikUrlToEnsureMocksOnly.com', ); diff --git a/web/cashtab/src/utils/__tests__/txBuilder.test.js b/web/cashtab/src/utils/__tests__/txBuilder.test.js --- a/web/cashtab/src/utils/__tests__/txBuilder.test.js +++ b/web/cashtab/src/utils/__tests__/txBuilder.test.js @@ -1,7 +1,7 @@ /* eslint-disable no-native-reassign */ import BigNumber from 'bignumber.js'; import { currency } from 'components/Common/Ticker.js'; -import sendBCHMock from 'hooks/__mocks__/sendBCH'; +import sendBCHMock from '../__mocks__/sendBCH'; import { generateTxInput, generateTxOutput, diff --git a/web/cashtab/src/utils/transactions.js b/web/cashtab/src/utils/transactions.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/utils/transactions.js @@ -0,0 +1,485 @@ +import { currency } from 'components/Common/Ticker'; +import { + fromXecToSatoshis, + isValidStoredWallet, + parseXecSendValue, + generateOpReturnScript, + generateTxInput, + generateTxOutput, + generateTokenTxInput, + generateTokenTxOutput, + signAndBuildTx, + getChangeAddressFromInputUtxos, + toHash160, +} from 'utils/cashMethods'; +import ecies from 'ecies-lite'; +import TransactionBuilder from 'utils/txBuilder'; + +const SEND_XEC_ERRORS = { + INSUFFICIENT_FUNDS: 0, + NETWORK_ERROR: 1, + INSUFFICIENT_PRIORITY: 66, // ~insufficient fee + DOUBLE_SPENDING: 18, + MAX_UNCONFIRMED_TXS: 64, +}; + +export const createToken = async ( + chronik, + wallet, + feeInSatsPerByte, + configObj, +) => { + try { + // Throw error if wallet does not have utxo set in state + if (!isValidStoredWallet(wallet)) { + const walletError = new Error(`Invalid wallet`); + throw walletError; + } + const utxos = wallet.state.nonSlpUtxos; + const CREATION_ADDR = wallet.Path1899.cashAddress; + let txBuilder = new TransactionBuilder(); + + let tokenTxInputObj = generateTokenTxInput( + 'GENESIS', + utxos, + null, // total token UTXOS - not applicable for GENESIS tx + null, // token ID - not applicable for GENESIS tx + null, // token amount - not applicable for GENESIS tx + feeInSatsPerByte, + txBuilder, + ); + // update txBuilder object with inputs + txBuilder = tokenTxInputObj.txBuilder; + + let tokenTxOutputObj = generateTokenTxOutput( + txBuilder, + 'GENESIS', + CREATION_ADDR, + null, // token UTXOS being spent - not applicable for GENESIS tx + tokenTxInputObj.remainderXecValue, + configObj, + ); + // update txBuilder object with outputs + txBuilder = tokenTxOutputObj; + + // sign the collated inputUtxos and build the raw tx hex + // returns the raw tx hex string + const rawTxHex = signAndBuildTx( + tokenTxInputObj.inputXecUtxos, + txBuilder, + wallet, + ); + + // Broadcast transaction to the network via the chronik client + // sample chronik.broadcastTx() response: + // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} + let broadcastResponse; + try { + broadcastResponse = await chronik.broadcastTx( + rawTxHex, + true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns + // if the wallet has existing burns via bch-api then chronik will throw 'invalid-slp-burns' errors without this flag + ); + if (!broadcastResponse) { + throw new Error('Empty chronik broadcast response'); + } + } catch (err) { + console.log('Error broadcasting tx to chronik client'); + throw err; + } + + // return the explorer link for the broadcasted tx + return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; + } catch (err) { + if (err.error === 'insufficient priority (code 66)') { + err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; + } else if (err.error === 'txn-mempool-conflict (code 18)') { + err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; + } else if (err.error === 'Network Error') { + err.code = SEND_XEC_ERRORS.NETWORK_ERROR; + } else if ( + err.error === + 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)' + ) { + err.code = SEND_XEC_ERRORS.MAX_UNCONFIRMED_TXS; + } + console.log(`error: `, err); + throw err; + } +}; + +export const sendToken = async ( + chronik, + wallet, + { tokenId, amount, tokenReceiverAddress }, +) => { + const { slpUtxos, nonSlpUtxos } = wallet.state; + const CREATION_ADDR = wallet.Path1899.cashAddress; + + // Handle error of user having no XEC + if (!nonSlpUtxos || nonSlpUtxos.length === 0) { + throw new Error( + `You need some ${currency.ticker} to send ${currency.tokenTicker}`, + ); + } + + // instance of transaction builder + let txBuilder = new TransactionBuilder(); + + let tokenTxInputObj = generateTokenTxInput( + 'SEND', + nonSlpUtxos, + slpUtxos, + tokenId, + amount, + currency.defaultFee, + txBuilder, + ); + // update txBuilder object with inputs + txBuilder = tokenTxInputObj.txBuilder; + + let tokenTxOutputObj = generateTokenTxOutput( + txBuilder, + 'SEND', + CREATION_ADDR, + tokenTxInputObj.inputTokenUtxos, + tokenTxInputObj.remainderXecValue, + null, // token config object - for GENESIS tx only + tokenReceiverAddress, + amount, + ); + // update txBuilder object with outputs + txBuilder = tokenTxOutputObj; + + // append the token input UTXOs to the array of XEC input UTXOs for signing + const combinedInputUtxos = tokenTxInputObj.inputXecUtxos.concat( + tokenTxInputObj.inputTokenUtxos, + ); + + // sign the collated inputUtxos and build the raw tx hex + // returns the raw tx hex string + const rawTxHex = signAndBuildTx(combinedInputUtxos, txBuilder, wallet); + + // Broadcast transaction to the network via the chronik client + // sample chronik.broadcastTx() response: + // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} + let broadcastResponse; + try { + broadcastResponse = await chronik.broadcastTx( + rawTxHex, + true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns + // if the wallet has existing burns via bch-api then chronik will throw 'invalid-slp-burns' errors without this flag + ); + if (!broadcastResponse) { + throw new Error('Empty chronik broadcast response'); + } + } catch (err) { + console.log('Error broadcasting tx to chronik client'); + throw err; + } + + // return the explorer link for the broadcasted tx + return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; +}; + +export const burnToken = async (chronik, wallet, { tokenId, amount }) => { + const { slpUtxos, nonSlpUtxos } = wallet.state; + const CREATION_ADDR = wallet.Path1899.cashAddress; + + // Handle error of user having no XEC + if (!nonSlpUtxos || nonSlpUtxos.length === 0) { + throw new Error(`You need some ${currency.ticker} to burn eTokens`); + } + + // instance of transaction builder + let txBuilder = new TransactionBuilder(); + + let tokenTxInputObj = generateTokenTxInput( + 'BURN', + nonSlpUtxos, + slpUtxos, + tokenId, + amount, + currency.defaultFee, + txBuilder, + ); + // update txBuilder object with inputs + txBuilder = tokenTxInputObj.txBuilder; + + let tokenTxOutputObj = generateTokenTxOutput( + txBuilder, + 'BURN', + CREATION_ADDR, + tokenTxInputObj.inputTokenUtxos, + tokenTxInputObj.remainderXecValue, + null, // token config object - for GENESIS tx only + null, // token receiver address - for SEND tx only + amount, + ); + // update txBuilder object with outputs + txBuilder = tokenTxOutputObj; + + // append the token input UTXOs to the array of XEC input UTXOs for signing + const combinedInputUtxos = tokenTxInputObj.inputXecUtxos.concat( + tokenTxInputObj.inputTokenUtxos, + ); + + // sign the collated inputUtxos and build the raw tx hex + // returns the raw tx hex string + const rawTxHex = signAndBuildTx(combinedInputUtxos, txBuilder, wallet); + + // Broadcast transaction to the network via the chronik client + // sample chronik.broadcastTx() response: + // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} + let broadcastResponse; + try { + broadcastResponse = await chronik.broadcastTx( + rawTxHex, + true, // skipSlpCheck to bypass chronik safety mechanism in place to avoid accidental burns + ); + if (!broadcastResponse) { + throw new Error('Empty chronik broadcast response'); + } + } catch (err) { + console.log('Error broadcasting tx to chronik client'); + throw err; + } + + // return the explorer link for the broadcasted tx + return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; +}; + +export const getRecipientPublicKey = async ( + chronik, + recipientAddress, + optionalMockPubKeyResponse = false, +) => { + // Necessary because jest can't mock + // chronikTxHistoryAtAddress = await chronik.script('p2pkh', recipientAddressHash160).history(/*page=*/ 0, /*page_size=*/ 10); + if (optionalMockPubKeyResponse) { + return optionalMockPubKeyResponse; + } + + // get hash160 of address + let recipientAddressHash160; + try { + recipientAddressHash160 = toHash160(recipientAddress); + } catch (err) { + console.log( + `Error determining toHash160(${recipientAddress} in getRecipientPublicKey())`, + err, + ); + throw new Error( + `Error determining toHash160(${recipientAddress} in getRecipientPublicKey())`, + ); + } + + let chronikTxHistoryAtAddress; + try { + // Get 20 txs. If no outgoing txs in those 20 txs, just don't send the tx + chronikTxHistoryAtAddress = await chronik + .script('p2pkh', recipientAddressHash160) + .history(/*page=*/ 0, /*page_size=*/ 20); + } catch (err) { + console.log( + `Error getting await chronik.script('p2pkh', ${recipientAddressHash160}).history();`, + err, + ); + throw new Error('Error fetching tx history to parse for public key'); + } + let recipientPubKeyChronik; + + // Iterate over tx history to find an outgoing tx + for (let i = 0; i < chronikTxHistoryAtAddress.txs.length; i += 1) { + const { inputs } = chronikTxHistoryAtAddress.txs[i]; + for (let j = 0; j < inputs.length; j += 1) { + const thisInput = inputs[j]; + const thisInputSendingHash160 = thisInput.outputScript; + if (thisInputSendingHash160.includes(recipientAddressHash160)) { + // Then this is an outgoing tx, you can get the public key from this tx + // Get the public key + try { + recipientPubKeyChronik = + chronikTxHistoryAtAddress.txs[i].inputs[ + j + ].inputScript.slice(-66); + } catch (err) { + throw new Error( + 'Cannot send an encrypted message to a wallet with no outgoing transactions', + ); + } + return recipientPubKeyChronik; + } + } + } + // You get here if you find no outgoing txs in the chronik tx history + throw new Error( + 'Cannot send an encrypted message to a wallet with no outgoing transactions in the last 20 txs', + ); +}; + +export const sendXec = async ( + chronik, + wallet, + utxos, + feeInSatsPerByte, + optionalOpReturnMsg, + isOneToMany, + destinationAddressAndValueArray, + destinationAddress, + sendAmount, + encryptionFlag, + airdropFlag, + airdropTokenId, + optionalMockPubKeyResponse = false, +) => { + try { + let txBuilder = new TransactionBuilder(); + + // parse the input value of XECs to send + const value = parseXecSendValue( + isOneToMany, + sendAmount, + destinationAddressAndValueArray, + ); + + const satoshisToSend = fromXecToSatoshis(value); + + // Throw validation error if fromXecToSatoshis returns false + if (!satoshisToSend) { + const error = new Error(`Invalid decimal places for send amount`); + throw error; + } + + let encryptedEj; // serialized encryption data object + + // if the user has opted to encrypt this message + if (encryptionFlag) { + try { + // get the pub key for the recipient address + let recipientPubKey = await getRecipientPublicKey( + chronik, + destinationAddress, + optionalMockPubKeyResponse, + ); + + // if the API can't find a pub key, it is due to the wallet having no outbound tx + if (recipientPubKey === 'not found') { + throw new Error( + 'Cannot send an encrypted message to a wallet with no outgoing transactions', + ); + } + + // encrypt the message + const pubKeyBuf = Buffer.from(recipientPubKey, 'hex'); + const bufferedFile = Buffer.from(optionalOpReturnMsg); + const structuredEj = await ecies.encrypt( + pubKeyBuf, + bufferedFile, + { compressEpk: true }, + ); + + // Serialize the encrypted data object + encryptedEj = Buffer.concat([ + structuredEj.epk, + structuredEj.iv, + structuredEj.ct, + structuredEj.mac, + ]); + } catch (err) { + console.log(`sendXec() encryption error.`); + throw err; + } + } + + // Start of building the OP_RETURN output. + // only build the OP_RETURN output if the user supplied it + if ( + (optionalOpReturnMsg && + typeof optionalOpReturnMsg !== 'undefined' && + optionalOpReturnMsg.trim() !== '') || + airdropFlag + ) { + const opReturnData = generateOpReturnScript( + optionalOpReturnMsg, + encryptionFlag, + airdropFlag, + airdropTokenId, + encryptedEj, + ); + txBuilder.addOutput(opReturnData, 0); + } + + // generate the tx inputs and add to txBuilder instance + // returns the updated txBuilder, txFee, totalInputUtxoValue and inputUtxos + let txInputObj = generateTxInput( + isOneToMany, + utxos, + txBuilder, + destinationAddressAndValueArray, + satoshisToSend, + feeInSatsPerByte, + ); + + const changeAddress = getChangeAddressFromInputUtxos( + txInputObj.inputUtxos, + wallet, + ); + txBuilder = txInputObj.txBuilder; // update the local txBuilder with the generated tx inputs + + // generate the tx outputs and add to txBuilder instance + // returns the updated txBuilder + const txOutputObj = generateTxOutput( + isOneToMany, + value, + satoshisToSend, + txInputObj.totalInputUtxoValue, + destinationAddress, + destinationAddressAndValueArray, + changeAddress, + txInputObj.txFee, + txBuilder, + ); + txBuilder = txOutputObj; // update the local txBuilder with the generated tx outputs + + // sign the collated inputUtxos and build the raw tx hex + // returns the raw tx hex string + const rawTxHex = signAndBuildTx( + txInputObj.inputUtxos, + txBuilder, + wallet, + ); + + // Broadcast transaction to the network via the chronik client + // sample chronik.broadcastTx() response: + // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} + let broadcastResponse; + try { + broadcastResponse = await chronik.broadcastTx(rawTxHex); + if (!broadcastResponse) { + throw new Error('Empty chronik broadcast response'); + } + } catch (err) { + console.log('Error broadcasting tx to chronik client'); + throw err; + } + + // return the explorer link for the broadcasted tx + return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; + } catch (err) { + if (err.error === 'insufficient priority (code 66)') { + err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; + } else if (err.error === 'txn-mempool-conflict (code 18)') { + err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; + } else if (err.error === 'Network Error') { + err.code = SEND_XEC_ERRORS.NETWORK_ERROR; + } else if ( + err.error === + 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)' + ) { + err.code = SEND_XEC_ERRORS.MAX_UNCONFIRMED_TXS; + } + console.log(`error: `, err); + throw err; + } +};