From b74f460a9a5e4c85a99d093c7259ed3fd71001ca Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 6 Jul 2022 18:30:33 -0700 Subject: [PATCH 1/2] feat(sweep-nfts.js): Added script to reclaim NFTs --- package-lock.json | 86 ++++++++++++----- package.json | 2 +- production/docker/docker-compose.yml | 8 +- production/docker/p2wdb/Dockerfile | 2 +- production/scripts/sweep-nfts.js | 139 +++++++++++++++++++++++++++ production/scripts/sweep-wallet.js | 2 + 6 files changed, 210 insertions(+), 29 deletions(-) create mode 100644 production/scripts/sweep-nfts.js diff --git a/package-lock.json b/package-lock.json index e5386ba..c5cf6d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "4.6.3", + "minimal-slp-wallet": "4.6.5", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.7.5", @@ -65,6 +65,37 @@ "uuid": "8.3.2" } }, + "../minimal-slp-wallet": { + "version": "4.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@psf/bch-js": "6.4.1", + "apidoc": "0.51.0", + "bch-consumer": "1.3.0", + "bch-donation": "1.1.2", + "crypto-js": "4.0.0" + }, + "devDependencies": { + "browserify": "17.0.0", + "chai": "4.2.0", + "coveralls": "3.1.0", + "eslint": "7.17.0", + "eslint-config-prettier": "7.1.0", + "eslint-config-standard": "16.0.2", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "3.3.1", + "eslint-plugin-standard": "4.0.1", + "husky": "4.3.8", + "lodash.clonedeep": "4.5.0", + "mocha": "9.2.1", + "nyc": "15.1.0", + "semantic-release": "19.0.3", + "sinon": "9.2.0", + "standard": "16.0.4", + "tinyify": "3.0.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -2988,8 +3019,9 @@ }, "node_modules/bch-consumer": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bch-consumer/-/bch-consumer-1.3.0.tgz", + "resolved": "http://94.130.170.209:4873/bch-consumer/-/bch-consumer-1.3.0.tgz", "integrity": "sha512-Lec0g7h2SynC/NlqMED+Kl/UKbFfeuAH5WPSk5k9OjMnI+W0Mqc/MsB0VCzte5Cw85ouL45n/3RWtNQmmZwcyA==", + "license": "MIT", "dependencies": { "@psf/bch-js": "6.3.4", "apidoc": "0.51.0", @@ -2998,8 +3030,9 @@ }, "node_modules/bch-consumer/node_modules/@psf/bch-js": { "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.3.4.tgz", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.3.4.tgz", "integrity": "sha512-TgT1R+PUJomdpUUuOY/PDb8Nl6PDbF9qohlbkEv7cvkT1WvkN1Obma4b2tziL45mxhKVM3ASYBgAGw8yURiiXQ==", + "license": "MIT", "dependencies": { "@chris.troutner/bip32-utils": "1.0.5", "@psf/bip21": "2.0.1", @@ -3029,16 +3062,18 @@ }, "node_modules/bch-consumer/node_modules/@psf/bch-js/node_modules/axios": { "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz", "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.14.8" } }, "node_modules/bch-consumer/node_modules/apidoc": { "version": "0.51.0", - "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.51.0.tgz", "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", + "license": "MIT", "os": [ "darwin", "freebsd", @@ -3079,16 +3114,18 @@ }, "node_modules/bch-consumer/node_modules/axios": { "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.25.0.tgz", "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.14.7" } }, "node_modules/bch-consumer/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "resolved": "http://94.130.170.209:4873/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12029,9 +12066,10 @@ } }, "node_modules/minimal-slp-wallet": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-4.6.3.tgz", - "integrity": "sha512-1/EWrkqZsNoOYqV/do/NaCrzodh10wCm5PZTO2qV+a5d0ku65c/Y/yT5ljmIyYAM/m8Ob1bonjVq1kzE7o/lPA==", + "version": "4.6.5", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.6.5.tgz", + "integrity": "sha512-B3SkLVahcvsec9qwVIy1MxisyEfICDT9ftO6aXhPAATbbR3lSws+1FZdymlObYl99dOu233wEyz4Y3vyJpJ4Jg==", + "license": "MIT", "dependencies": { "@psf/bch-js": "6.4.1", "apidoc": "0.51.0", @@ -12042,8 +12080,9 @@ }, "node_modules/minimal-slp-wallet/node_modules/apidoc": { "version": "0.51.0", - "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.51.0.tgz", "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", + "license": "MIT", "os": [ "darwin", "freebsd", @@ -12084,8 +12123,9 @@ }, "node_modules/minimal-slp-wallet/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "resolved": "http://94.130.170.209:4873/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -24016,7 +24056,7 @@ }, "bch-consumer": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bch-consumer/-/bch-consumer-1.3.0.tgz", + "resolved": "http://94.130.170.209:4873/bch-consumer/-/bch-consumer-1.3.0.tgz", "integrity": "sha512-Lec0g7h2SynC/NlqMED+Kl/UKbFfeuAH5WPSk5k9OjMnI+W0Mqc/MsB0VCzte5Cw85ouL45n/3RWtNQmmZwcyA==", "requires": { "@psf/bch-js": "6.3.4", @@ -24026,7 +24066,7 @@ "dependencies": { "@psf/bch-js": { "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.3.4.tgz", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.3.4.tgz", "integrity": "sha512-TgT1R+PUJomdpUUuOY/PDb8Nl6PDbF9qohlbkEv7cvkT1WvkN1Obma4b2tziL45mxhKVM3ASYBgAGw8yURiiXQ==", "requires": { "@chris.troutner/bip32-utils": "1.0.5", @@ -24057,7 +24097,7 @@ "dependencies": { "axios": { "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz", "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", "requires": { "follow-redirects": "^1.14.8" @@ -24067,7 +24107,7 @@ }, "apidoc": { "version": "0.51.0", - "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.51.0.tgz", "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", "requires": { "bootstrap": "3.4.1", @@ -24096,7 +24136,7 @@ }, "axios": { "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.25.0.tgz", "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", "requires": { "follow-redirects": "^1.14.7" @@ -24104,7 +24144,7 @@ }, "glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "resolved": "http://94.130.170.209:4873/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", @@ -31160,9 +31200,9 @@ "dev": true }, "minimal-slp-wallet": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-4.6.3.tgz", - "integrity": "sha512-1/EWrkqZsNoOYqV/do/NaCrzodh10wCm5PZTO2qV+a5d0ku65c/Y/yT5ljmIyYAM/m8Ob1bonjVq1kzE7o/lPA==", + "version": "4.6.5", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.6.5.tgz", + "integrity": "sha512-B3SkLVahcvsec9qwVIy1MxisyEfICDT9ftO6aXhPAATbbR3lSws+1FZdymlObYl99dOu233wEyz4Y3vyJpJ4Jg==", "requires": { "@psf/bch-js": "6.4.1", "apidoc": "0.51.0", @@ -31173,7 +31213,7 @@ "dependencies": { "apidoc": { "version": "0.51.0", - "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.51.0.tgz", "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", "requires": { "bootstrap": "3.4.1", @@ -31202,7 +31242,7 @@ }, "glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "resolved": "http://94.130.170.209:4873/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", diff --git a/package.json b/package.json index 791138c..dc753f2 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "4.6.3", + "minimal-slp-wallet": "4.6.5", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.7.5", diff --git a/production/docker/docker-compose.yml b/production/docker/docker-compose.yml index e0877fb..48fc4b2 100644 --- a/production/docker/docker-compose.yml +++ b/production/docker/docker-compose.yml @@ -43,10 +43,10 @@ services: restart: always p2wdb: - #build: - # context: ./p2wdb/ - # dockerfile: Dockerfile - image: christroutner/ipfs-p2wdb-service:v3.0.4 + build: + context: ./p2wdb/ + dockerfile: Dockerfile + #image: christroutner/ipfs-p2wdb-service:v3.0.4 container_name: p2wdb environment: CONSUMER_URL: 'https://free-bch.fullstack.cash' diff --git a/production/docker/p2wdb/Dockerfile b/production/docker/p2wdb/Dockerfile index 317fc42..6bc5965 100644 --- a/production/docker/p2wdb/Dockerfile +++ b/production/docker/p2wdb/Dockerfile @@ -61,7 +61,7 @@ RUN npm run docs #EXPOSE 5010 # Start the application. -COPY start-production.sh start-production.sh +#COPY start-production.sh start-production.sh CMD ["./start-production.sh"] #CMD ["npm", "start"] diff --git a/production/scripts/sweep-nfts.js b/production/scripts/sweep-nfts.js new file mode 100644 index 0000000..a011c77 --- /dev/null +++ b/production/scripts/sweep-nfts.js @@ -0,0 +1,139 @@ +/* + This script will sweep the HD wallet, looking for NFTs. When found, it will + send the NFT to the root (index 0) address. It will pay transaction fees from + the root address. +*/ + +// Global npm libraries +const BchWallet = require('minimal-slp-wallet/index') + +// Local libraries +const WalletAdapter = require('../../src/adapters/wallet') + +// Constants +const EMTPY_ADDR_CUTOFF = 10 +const WEB3_SERVER = 'https://free-bch.fullstack.cash' + +async function sweepNfts () { + try { + // Open the wallet files. + const walletLib = new WalletAdapter() + // const walletInfo = await walletLib.openWallet() + const walletInfo = await openWallet(walletLib) + // const walletRoot = await walletLib.instanceWallet(walletInfo) + const walletRoot = new BchWallet(walletInfo.mnemonic, { + interface: 'consumer-api', + restURL: WEB3_SERVER + }) + await walletRoot.walletInfoPromise + console.log('walletInfo: ', walletInfo) + + const rootAddr = walletInfo.cashAddress + // const rootWif = walletInfo.privateKey + console.log(`Sweeping all funds into root address ${rootAddr}...`) + + // Generate an HD tree + const bchjs = walletRoot.bchjs + const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic) + const masterHDNode = bchjs.HDNode.fromSeed(rootSeed) + + let emptyAddrCnt = 0 + let hdIndex = 1 + + do { + // Generate a keypair from the HD wallet. + const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`) + const cashAddress = bchjs.HDNode.toCashAddress(childNode) + const wifToSweep = bchjs.HDNode.toWIF(childNode) + + console.log(`\n${hdIndex}: Sweeping ${cashAddress} with private key ${wifToSweep}`) + + const walletChild = new BchWallet(wifToSweep, { interface: 'consumer-api', restURL: WEB3_SERVER }) + await walletChild.walletInfoPromise + + try { + const tokens = await walletChild.tokens.listTokensFromAddress(cashAddress) + // console.log(`tokens: ${JSON.stringify(tokens, null, 2)}`) + + await walletChild.utxos.initUtxoStore(cashAddress) + // console.log(`utxoStore: ${JSON.stringify(utxoStore, null, 2)}`) + + for (let i = 0; i < tokens.length; i++) { + const thisToken = tokens[i] + + if (thisToken.tokenType === 65) { + console.log(`NFT ${thisToken.ticker} (${thisToken.name}) found. Sweeping to ${rootAddr}`) + + // Send BCH to the child address, to pay for transaction fees. + const receiver1 = [{ + address: cashAddress, + amountSat: 10000 + }] + const txid1 = await walletRoot.send(receiver1) + console.log(`10,000 sats sent to child address. TXID: ${txid1}`) + + // Wait for indexers to update. + console.log('Waiting for indexers to update...') + await bchjs.Util.sleep(20000) + + // Send NFT to root address. + const receiver2 = [{ + address: rootAddr, + tokenId: thisToken.tokenId, + qty: thisToken.qty + }] + const txid2 = await walletChild.sendTokens(receiver2) + console.log(`NFT sent from child address to root address. TXID: ${txid2}`) + + // Wait for indexers to update. + console.log('Waiting for indexers to update...') + await bchjs.Util.sleep(3000) + + // Refresh the UTXO store. + await walletChild.utxos.initUtxoStore(cashAddress) + + // Send the rest of the BCH back to the root address. + const txid3 = await walletChild.sendAll(rootAddr) + console.log(`Leftover BCH in child address sent to root address. TXID: ${txid3}`) + + emptyAddrCnt = 0 + } + } + + // Wait between loop iterations. + await bchjs.Util.sleep(3000) + + // emptyAddrCnt++ + } catch (err) { + console.log(`error message with index ${hdIndex}: ${err.message}`) + console.log(err) + // emptyAddrCnt++ + } + + emptyAddrCnt++ + hdIndex++ + } while (emptyAddrCnt < EMTPY_ADDR_CUTOFF) + + console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`) + + console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n') + } catch (err) { + console.log('Error while sweeping NFTs: ', err) + } +} +sweepNfts() + +// Open the wallet file, or create one if the file doesn't exist. +// Does not instance the wallet. The output of this function is expected to +// be passed to instanceWallet(). +async function openWallet (walletLib) { + try { + // console.log('this.WALLET_FILE: ', this.WALLET_FILE) + const walletData = await walletLib.jsonFiles.readJSON('./wallet.json') + + return walletData + } catch (err) { + console.error('Error in openWallet()') + throw err + } +} diff --git a/production/scripts/sweep-wallet.js b/production/scripts/sweep-wallet.js index 0a00205..1f42d36 100644 --- a/production/scripts/sweep-wallet.js +++ b/production/scripts/sweep-wallet.js @@ -2,6 +2,8 @@ This script will travers the HD wallet and sweep funds and tokens back into the root address (index 0). That root address needs to have funds to pay for the transactions. + + This script will sweep BCH and fungible (Type 1) tokens, but not NFTs. */ // Public npm libraries From 5c4dc21d3e2f7d6e5f5aea38adcc465c0847e4ad Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 6 Jul 2022 18:39:45 -0700 Subject: [PATCH 2/2] Making e2e tests a little faster --- src/adapters/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/adapters/index.js b/src/adapters/index.js index 70fea70..9d69aec 100644 --- a/src/adapters/index.js +++ b/src/adapters/index.js @@ -62,8 +62,10 @@ class Adapters { const walletData = await this.wallet.openWallet() // console.log('walletData: ', walletData) - // Instance the wallet. - await this.wallet.instanceWallet(walletData, this.bchjs) + if (this.config.env !== 'test') { + // Instance the wallet. + await this.wallet.instanceWallet(walletData, this.bchjs) + } console.log('Async Adapters have been started.')