├── .gitignore ├── LICENSE ├── README.md ├── config.yaml-example ├── package-lock.json ├── package.json ├── transfer.js ├── utils ├── AddressData.js ├── Config.js ├── Request.js └── function.js └── wallet.csv-example /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | logs 5 | 6 | # Editor directories and files 7 | .idea 8 | 9 | config.yaml 10 | wallet.csv 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 北京翻转极光科技有限责任公司 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1. 基本介绍 2 | 3 | - 本项目是一个基于 `nodejs` 的 BTC P2TR 地址收款地址批量转账脚本,用于在 BTC 主网、Signet Test、Testnet3、Fractal 4 | 四个网络之间进行批量转账 5 | - 本项目基于 `mempool.space` 提供的 `API` 6 | 进行开发,具体可以参考 [https://mempool.space/docs/api](https://mempool.space/docs/api) 7 | - issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。 8 | - 作者 [https://x.com/ByteJason](https://x.com/ByteJason) 9 | - 本项目仅供学习研究使用,请勿用于非法用途,否则后果自负! 10 | 11 | # 2. 使用说明 12 | 13 | ## 2.1 安装环境 14 | 15 | - 安装 nodejs >= 18.18 的版本 16 | - 可以在 [https://nodejs.org/](https://nodejs.org/zh-cn/download/package-manager) 进行下载适合系统的环境安装包进行安装 17 | - 安装好之后使用如下命令查看是否成功安装,如果都能看到版本号信息,既代表安装完成 18 | - ```shell 19 | node -v 20 | npm -v 21 | ``` 22 | 23 | ## 2.2 安装依赖 24 | 25 | - 在项目根目录下执行如下命令,安装依赖 26 | - ``` 27 | npm install 28 | ``` 29 | 30 | ## 2.3 编辑配置文件 31 | 32 | - 复制 `config.yaml-example` 文件,粘贴名称为 `config.yaml` ,然后,修改里面 `config.yaml` 的配置信息 33 | - `networkType`: 修改为您需要交互的 BTC 网络,可选值暂时为下面六个选项 34 | - `mainnet`: BTC 主网 35 | - `testnet`: BTC testnet测试网 36 | - `testnet4`: BTC testnet4测试网 37 | - `signet`: BTC signet测试网 38 | - `fractal`: Fractal 分型比特币主网 39 | - `fractal-testnet`: Fractal 分型比特币测试网 40 | - `wif`: 需要支出的 BTC P2TR 或 p2wpkh 地址的 `WIF` 私钥 41 | - `addressType`: WIF 私钥对应的地址类型,可选值如下 42 | - `p2tr`: WIF 私钥对应的地址类型为 `p2tr`,即 `bc1p` 开头的地址 43 | - `p2wpkh`: WIF 私钥对应的地址类型为 `p2wpkh`,即 `bc1q` 开头的地址 44 | - `gas`: 转账时交易的矿工费,可选值如下:(high medium low +n n) 45 | - `high`: 获取区块链浏览器上的 高优先级 46 | - `medium`: 获取区块链浏览器上的 中优先级 47 | - `low`: 获取区块链浏览器上的 低优先级 48 | - `+n`: n表示正整数,获取区块链浏览器上的 高优先级再加n,比如 +5 49 | - `n`: n表示正整数,直接用 n 做 gas 50 | 51 | ## 2.4 编辑 `wallet.csv` 收款钱包文件 52 | 53 | - 在项目根目录下找到 `wallet.csv` 文件,编辑里面的收款钱包地址与金额,一行一个 54 | - 55 | - 复制 `wallet.csv-example` 文件,粘贴名称为 `wallet.csv` ,然后,编辑 `wallet.csv` 里面的收款钱包地址与金额,一行一个 56 | 57 | ## 2.5 执行脚本 58 | 59 | - 在项目根目录下执行如下命令,执行脚本 60 | - ```shell 61 | node transfer.js 62 | ``` 63 | 64 | # 商用注意事项 65 | 66 | 如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。 67 | -------------------------------------------------------------------------------- /config.yaml-example: -------------------------------------------------------------------------------- 1 | # 网络类型,可选值(mainnet, "testnet", "testnet4", "signet", "fractal", "fractal-testnet") 2 | networkType: "mainnet" 3 | 4 | # 暂时只支持 p2tr 和 p2wpkh 地址 WIF,主网是bc1开头,测试网是tb1开头 5 | wif: "KwPRDW2QfCqfkJSWh7Cqm7jDJV8guqcmR8vumFY53giQACzjrxDV" 6 | 7 | # 设置加速的地址,可选值("p2tr", "p2wpkh") 8 | # - p2tr 是 bc1p 开头的 taproot 地址 9 | # - p2wpkh 是 bc1q 开头的 segwit 地址 10 | addressType: "p2tr" 11 | 12 | # 设置的gas,可选值如下:(high medium low +n n) 13 | # high: 获取区块链浏览器的 feeRate 高优先级 14 | # medium: 获取区块链浏览器的 feeRate 中优先级 15 | # low: 获取区块链浏览器的 feeRate 低优先级 16 | # +n: n表示正整数,获取区块链浏览器的 feeRate 的高优先级再加n,比如 +5, 17 | # n: n表示正整数,直接用 n 做gas 18 | gas: "high" 19 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BTC-Script", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "axios": "^1.7.2", 9 | "bip32": "^4.0.0", 10 | "bip39": "^3.1.0", 11 | "bitcoinjs-lib": "^6.1.6", 12 | "ecpair": "^2.1.0", 13 | "fast-csv": "^5.0.1", 14 | "https-proxy-agent": "^7.0.5", 15 | "js-yaml": "^4.1.0", 16 | "qs": "^6.12.3", 17 | "tiny-secp256k1": "^2.2.3", 18 | "winston": "^3.13.1" 19 | } 20 | }, 21 | "node_modules/@colors/colors": { 22 | "version": "1.6.0", 23 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", 24 | "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", 25 | "engines": { 26 | "node": ">=0.1.90" 27 | } 28 | }, 29 | "node_modules/@dabh/diagnostics": { 30 | "version": "2.0.3", 31 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", 32 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", 33 | "dependencies": { 34 | "colorspace": "1.1.x", 35 | "enabled": "2.0.x", 36 | "kuler": "^2.0.0" 37 | } 38 | }, 39 | "node_modules/@fast-csv/format": { 40 | "version": "5.0.0", 41 | "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-5.0.0.tgz", 42 | "integrity": "sha512-IyMpHwYIOGa2f0BJi6Wk55UF0oBA5urdIydoEDYxPo88LFbeb3Yr4rgpu98OAO1glUWheSnNtUgS80LE+/dqmw==", 43 | "dependencies": { 44 | "lodash.escaperegexp": "^4.1.2", 45 | "lodash.isboolean": "^3.0.3", 46 | "lodash.isequal": "^4.5.0", 47 | "lodash.isfunction": "^3.0.9", 48 | "lodash.isnil": "^4.0.0" 49 | } 50 | }, 51 | "node_modules/@fast-csv/parse": { 52 | "version": "5.0.0", 53 | "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-5.0.0.tgz", 54 | "integrity": "sha512-ecF8tCm3jVxeRjEB6VPzmA+1wGaJ5JgaUX2uesOXdXD6qQp0B3EdshOIed4yT1Xlj/F2f8v4zHSo0Oi31L697g==", 55 | "dependencies": { 56 | "lodash.escaperegexp": "^4.1.2", 57 | "lodash.groupby": "^4.6.0", 58 | "lodash.isfunction": "^3.0.9", 59 | "lodash.isnil": "^4.0.0", 60 | "lodash.isundefined": "^3.0.1", 61 | "lodash.uniq": "^4.5.0" 62 | } 63 | }, 64 | "node_modules/@noble/hashes": { 65 | "version": "1.4.0", 66 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", 67 | "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", 68 | "engines": { 69 | "node": ">= 16" 70 | }, 71 | "funding": { 72 | "url": "https://paulmillr.com/funding/" 73 | } 74 | }, 75 | "node_modules/@scure/base": { 76 | "version": "1.1.7", 77 | "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", 78 | "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", 79 | "funding": { 80 | "url": "https://paulmillr.com/funding/" 81 | } 82 | }, 83 | "node_modules/@types/triple-beam": { 84 | "version": "1.3.5", 85 | "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", 86 | "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" 87 | }, 88 | "node_modules/agent-base": { 89 | "version": "7.1.1", 90 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", 91 | "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", 92 | "dependencies": { 93 | "debug": "^4.3.4" 94 | }, 95 | "engines": { 96 | "node": ">= 14" 97 | } 98 | }, 99 | "node_modules/argparse": { 100 | "version": "2.0.1", 101 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 102 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 103 | }, 104 | "node_modules/async": { 105 | "version": "3.2.5", 106 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", 107 | "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" 108 | }, 109 | "node_modules/asynckit": { 110 | "version": "0.4.0", 111 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 112 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 113 | }, 114 | "node_modules/axios": { 115 | "version": "1.7.2", 116 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 117 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 118 | "dependencies": { 119 | "follow-redirects": "^1.15.6", 120 | "form-data": "^4.0.0", 121 | "proxy-from-env": "^1.1.0" 122 | } 123 | }, 124 | "node_modules/base-x": { 125 | "version": "4.0.0", 126 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", 127 | "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" 128 | }, 129 | "node_modules/bech32": { 130 | "version": "2.0.0", 131 | "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", 132 | "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" 133 | }, 134 | "node_modules/bip174": { 135 | "version": "2.1.1", 136 | "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", 137 | "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", 138 | "engines": { 139 | "node": ">=8.0.0" 140 | } 141 | }, 142 | "node_modules/bip32": { 143 | "version": "4.0.0", 144 | "resolved": "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz", 145 | "integrity": "sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ==", 146 | "dependencies": { 147 | "@noble/hashes": "^1.2.0", 148 | "@scure/base": "^1.1.1", 149 | "typeforce": "^1.11.5", 150 | "wif": "^2.0.6" 151 | }, 152 | "engines": { 153 | "node": ">=6.0.0" 154 | } 155 | }, 156 | "node_modules/bip39": { 157 | "version": "3.1.0", 158 | "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", 159 | "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", 160 | "dependencies": { 161 | "@noble/hashes": "^1.2.0" 162 | } 163 | }, 164 | "node_modules/bitcoinjs-lib": { 165 | "version": "6.1.6", 166 | "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz", 167 | "integrity": "sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA==", 168 | "dependencies": { 169 | "@noble/hashes": "^1.2.0", 170 | "bech32": "^2.0.0", 171 | "bip174": "^2.1.1", 172 | "bs58check": "^3.0.1", 173 | "typeforce": "^1.11.3", 174 | "varuint-bitcoin": "^1.1.2" 175 | }, 176 | "engines": { 177 | "node": ">=8.0.0" 178 | } 179 | }, 180 | "node_modules/bs58": { 181 | "version": "5.0.0", 182 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", 183 | "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", 184 | "dependencies": { 185 | "base-x": "^4.0.0" 186 | } 187 | }, 188 | "node_modules/bs58check": { 189 | "version": "3.0.1", 190 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", 191 | "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", 192 | "dependencies": { 193 | "@noble/hashes": "^1.2.0", 194 | "bs58": "^5.0.0" 195 | } 196 | }, 197 | "node_modules/call-bind": { 198 | "version": "1.0.7", 199 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 200 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 201 | "dependencies": { 202 | "es-define-property": "^1.0.0", 203 | "es-errors": "^1.3.0", 204 | "function-bind": "^1.1.2", 205 | "get-intrinsic": "^1.2.4", 206 | "set-function-length": "^1.2.1" 207 | }, 208 | "engines": { 209 | "node": ">= 0.4" 210 | }, 211 | "funding": { 212 | "url": "https://github.com/sponsors/ljharb" 213 | } 214 | }, 215 | "node_modules/cipher-base": { 216 | "version": "1.0.4", 217 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 218 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 219 | "dependencies": { 220 | "inherits": "^2.0.1", 221 | "safe-buffer": "^5.0.1" 222 | } 223 | }, 224 | "node_modules/color": { 225 | "version": "3.2.1", 226 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 227 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 228 | "dependencies": { 229 | "color-convert": "^1.9.3", 230 | "color-string": "^1.6.0" 231 | } 232 | }, 233 | "node_modules/color-convert": { 234 | "version": "1.9.3", 235 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 236 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 237 | "dependencies": { 238 | "color-name": "1.1.3" 239 | } 240 | }, 241 | "node_modules/color-name": { 242 | "version": "1.1.3", 243 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 244 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 245 | }, 246 | "node_modules/color-string": { 247 | "version": "1.9.1", 248 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 249 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 250 | "dependencies": { 251 | "color-name": "^1.0.0", 252 | "simple-swizzle": "^0.2.2" 253 | } 254 | }, 255 | "node_modules/colorspace": { 256 | "version": "1.1.4", 257 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", 258 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", 259 | "dependencies": { 260 | "color": "^3.1.3", 261 | "text-hex": "1.0.x" 262 | } 263 | }, 264 | "node_modules/combined-stream": { 265 | "version": "1.0.8", 266 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 267 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 268 | "dependencies": { 269 | "delayed-stream": "~1.0.0" 270 | }, 271 | "engines": { 272 | "node": ">= 0.8" 273 | } 274 | }, 275 | "node_modules/create-hash": { 276 | "version": "1.2.0", 277 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 278 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 279 | "dependencies": { 280 | "cipher-base": "^1.0.1", 281 | "inherits": "^2.0.1", 282 | "md5.js": "^1.3.4", 283 | "ripemd160": "^2.0.1", 284 | "sha.js": "^2.4.0" 285 | } 286 | }, 287 | "node_modules/debug": { 288 | "version": "4.3.5", 289 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 290 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 291 | "dependencies": { 292 | "ms": "2.1.2" 293 | }, 294 | "engines": { 295 | "node": ">=6.0" 296 | }, 297 | "peerDependenciesMeta": { 298 | "supports-color": { 299 | "optional": true 300 | } 301 | } 302 | }, 303 | "node_modules/define-data-property": { 304 | "version": "1.1.4", 305 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 306 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 307 | "dependencies": { 308 | "es-define-property": "^1.0.0", 309 | "es-errors": "^1.3.0", 310 | "gopd": "^1.0.1" 311 | }, 312 | "engines": { 313 | "node": ">= 0.4" 314 | }, 315 | "funding": { 316 | "url": "https://github.com/sponsors/ljharb" 317 | } 318 | }, 319 | "node_modules/delayed-stream": { 320 | "version": "1.0.0", 321 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 322 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 323 | "engines": { 324 | "node": ">=0.4.0" 325 | } 326 | }, 327 | "node_modules/ecpair": { 328 | "version": "2.1.0", 329 | "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", 330 | "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", 331 | "dependencies": { 332 | "randombytes": "^2.1.0", 333 | "typeforce": "^1.18.0", 334 | "wif": "^2.0.6" 335 | }, 336 | "engines": { 337 | "node": ">=8.0.0" 338 | } 339 | }, 340 | "node_modules/enabled": { 341 | "version": "2.0.0", 342 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 343 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" 344 | }, 345 | "node_modules/es-define-property": { 346 | "version": "1.0.0", 347 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 348 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 349 | "dependencies": { 350 | "get-intrinsic": "^1.2.4" 351 | }, 352 | "engines": { 353 | "node": ">= 0.4" 354 | } 355 | }, 356 | "node_modules/es-errors": { 357 | "version": "1.3.0", 358 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 359 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 360 | "engines": { 361 | "node": ">= 0.4" 362 | } 363 | }, 364 | "node_modules/fast-csv": { 365 | "version": "5.0.1", 366 | "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-5.0.1.tgz", 367 | "integrity": "sha512-Q43zC4NdQD5MAWOVQOF8KA+D6ddvTJjX2ib8zqysm74jZhtk6+dc8C75/OqRV6Y9CLc4kgvbC3PLG8YL4YZfgw==", 368 | "dependencies": { 369 | "@fast-csv/format": "5.0.0", 370 | "@fast-csv/parse": "5.0.0" 371 | }, 372 | "engines": { 373 | "node": ">=10.0.0" 374 | } 375 | }, 376 | "node_modules/fecha": { 377 | "version": "4.2.3", 378 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", 379 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" 380 | }, 381 | "node_modules/fn.name": { 382 | "version": "1.1.0", 383 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 384 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" 385 | }, 386 | "node_modules/follow-redirects": { 387 | "version": "1.15.6", 388 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 389 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 390 | "funding": [ 391 | { 392 | "type": "individual", 393 | "url": "https://github.com/sponsors/RubenVerborgh" 394 | } 395 | ], 396 | "engines": { 397 | "node": ">=4.0" 398 | }, 399 | "peerDependenciesMeta": { 400 | "debug": { 401 | "optional": true 402 | } 403 | } 404 | }, 405 | "node_modules/form-data": { 406 | "version": "4.0.0", 407 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 408 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 409 | "dependencies": { 410 | "asynckit": "^0.4.0", 411 | "combined-stream": "^1.0.8", 412 | "mime-types": "^2.1.12" 413 | }, 414 | "engines": { 415 | "node": ">= 6" 416 | } 417 | }, 418 | "node_modules/function-bind": { 419 | "version": "1.1.2", 420 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 421 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 422 | "funding": { 423 | "url": "https://github.com/sponsors/ljharb" 424 | } 425 | }, 426 | "node_modules/get-intrinsic": { 427 | "version": "1.2.4", 428 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 429 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 430 | "dependencies": { 431 | "es-errors": "^1.3.0", 432 | "function-bind": "^1.1.2", 433 | "has-proto": "^1.0.1", 434 | "has-symbols": "^1.0.3", 435 | "hasown": "^2.0.0" 436 | }, 437 | "engines": { 438 | "node": ">= 0.4" 439 | }, 440 | "funding": { 441 | "url": "https://github.com/sponsors/ljharb" 442 | } 443 | }, 444 | "node_modules/gopd": { 445 | "version": "1.0.1", 446 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 447 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 448 | "dependencies": { 449 | "get-intrinsic": "^1.1.3" 450 | }, 451 | "funding": { 452 | "url": "https://github.com/sponsors/ljharb" 453 | } 454 | }, 455 | "node_modules/has-property-descriptors": { 456 | "version": "1.0.2", 457 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 458 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 459 | "dependencies": { 460 | "es-define-property": "^1.0.0" 461 | }, 462 | "funding": { 463 | "url": "https://github.com/sponsors/ljharb" 464 | } 465 | }, 466 | "node_modules/has-proto": { 467 | "version": "1.0.3", 468 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 469 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 470 | "engines": { 471 | "node": ">= 0.4" 472 | }, 473 | "funding": { 474 | "url": "https://github.com/sponsors/ljharb" 475 | } 476 | }, 477 | "node_modules/has-symbols": { 478 | "version": "1.0.3", 479 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 480 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 481 | "engines": { 482 | "node": ">= 0.4" 483 | }, 484 | "funding": { 485 | "url": "https://github.com/sponsors/ljharb" 486 | } 487 | }, 488 | "node_modules/hash-base": { 489 | "version": "3.1.0", 490 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 491 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 492 | "dependencies": { 493 | "inherits": "^2.0.4", 494 | "readable-stream": "^3.6.0", 495 | "safe-buffer": "^5.2.0" 496 | }, 497 | "engines": { 498 | "node": ">=4" 499 | } 500 | }, 501 | "node_modules/hasown": { 502 | "version": "2.0.2", 503 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 504 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 505 | "dependencies": { 506 | "function-bind": "^1.1.2" 507 | }, 508 | "engines": { 509 | "node": ">= 0.4" 510 | } 511 | }, 512 | "node_modules/https-proxy-agent": { 513 | "version": "7.0.5", 514 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", 515 | "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", 516 | "dependencies": { 517 | "agent-base": "^7.0.2", 518 | "debug": "4" 519 | }, 520 | "engines": { 521 | "node": ">= 14" 522 | } 523 | }, 524 | "node_modules/inherits": { 525 | "version": "2.0.4", 526 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 527 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 528 | }, 529 | "node_modules/is-arrayish": { 530 | "version": "0.3.2", 531 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 532 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 533 | }, 534 | "node_modules/is-stream": { 535 | "version": "2.0.1", 536 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 537 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 538 | "engines": { 539 | "node": ">=8" 540 | }, 541 | "funding": { 542 | "url": "https://github.com/sponsors/sindresorhus" 543 | } 544 | }, 545 | "node_modules/js-yaml": { 546 | "version": "4.1.0", 547 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 548 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 549 | "dependencies": { 550 | "argparse": "^2.0.1" 551 | }, 552 | "bin": { 553 | "js-yaml": "bin/js-yaml.js" 554 | } 555 | }, 556 | "node_modules/kuler": { 557 | "version": "2.0.0", 558 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 559 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" 560 | }, 561 | "node_modules/lodash.escaperegexp": { 562 | "version": "4.1.2", 563 | "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", 564 | "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" 565 | }, 566 | "node_modules/lodash.groupby": { 567 | "version": "4.6.0", 568 | "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", 569 | "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" 570 | }, 571 | "node_modules/lodash.isboolean": { 572 | "version": "3.0.3", 573 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 574 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 575 | }, 576 | "node_modules/lodash.isequal": { 577 | "version": "4.5.0", 578 | "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", 579 | "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" 580 | }, 581 | "node_modules/lodash.isfunction": { 582 | "version": "3.0.9", 583 | "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", 584 | "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" 585 | }, 586 | "node_modules/lodash.isnil": { 587 | "version": "4.0.0", 588 | "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", 589 | "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" 590 | }, 591 | "node_modules/lodash.isundefined": { 592 | "version": "3.0.1", 593 | "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", 594 | "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" 595 | }, 596 | "node_modules/lodash.uniq": { 597 | "version": "4.5.0", 598 | "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", 599 | "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" 600 | }, 601 | "node_modules/logform": { 602 | "version": "2.6.1", 603 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", 604 | "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", 605 | "dependencies": { 606 | "@colors/colors": "1.6.0", 607 | "@types/triple-beam": "^1.3.2", 608 | "fecha": "^4.2.0", 609 | "ms": "^2.1.1", 610 | "safe-stable-stringify": "^2.3.1", 611 | "triple-beam": "^1.3.0" 612 | }, 613 | "engines": { 614 | "node": ">= 12.0.0" 615 | } 616 | }, 617 | "node_modules/md5.js": { 618 | "version": "1.3.5", 619 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 620 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 621 | "dependencies": { 622 | "hash-base": "^3.0.0", 623 | "inherits": "^2.0.1", 624 | "safe-buffer": "^5.1.2" 625 | } 626 | }, 627 | "node_modules/mime-db": { 628 | "version": "1.52.0", 629 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 630 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 631 | "engines": { 632 | "node": ">= 0.6" 633 | } 634 | }, 635 | "node_modules/mime-types": { 636 | "version": "2.1.35", 637 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 638 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 639 | "dependencies": { 640 | "mime-db": "1.52.0" 641 | }, 642 | "engines": { 643 | "node": ">= 0.6" 644 | } 645 | }, 646 | "node_modules/ms": { 647 | "version": "2.1.2", 648 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 649 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 650 | }, 651 | "node_modules/object-inspect": { 652 | "version": "1.13.2", 653 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", 654 | "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", 655 | "engines": { 656 | "node": ">= 0.4" 657 | }, 658 | "funding": { 659 | "url": "https://github.com/sponsors/ljharb" 660 | } 661 | }, 662 | "node_modules/one-time": { 663 | "version": "1.0.0", 664 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 665 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 666 | "dependencies": { 667 | "fn.name": "1.x.x" 668 | } 669 | }, 670 | "node_modules/proxy-from-env": { 671 | "version": "1.1.0", 672 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 673 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 674 | }, 675 | "node_modules/qs": { 676 | "version": "6.12.3", 677 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", 678 | "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", 679 | "dependencies": { 680 | "side-channel": "^1.0.6" 681 | }, 682 | "engines": { 683 | "node": ">=0.6" 684 | }, 685 | "funding": { 686 | "url": "https://github.com/sponsors/ljharb" 687 | } 688 | }, 689 | "node_modules/randombytes": { 690 | "version": "2.1.0", 691 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 692 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 693 | "dependencies": { 694 | "safe-buffer": "^5.1.0" 695 | } 696 | }, 697 | "node_modules/readable-stream": { 698 | "version": "3.6.2", 699 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 700 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 701 | "dependencies": { 702 | "inherits": "^2.0.3", 703 | "string_decoder": "^1.1.1", 704 | "util-deprecate": "^1.0.1" 705 | }, 706 | "engines": { 707 | "node": ">= 6" 708 | } 709 | }, 710 | "node_modules/ripemd160": { 711 | "version": "2.0.2", 712 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 713 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 714 | "dependencies": { 715 | "hash-base": "^3.0.0", 716 | "inherits": "^2.0.1" 717 | } 718 | }, 719 | "node_modules/safe-buffer": { 720 | "version": "5.2.1", 721 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 722 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 723 | "funding": [ 724 | { 725 | "type": "github", 726 | "url": "https://github.com/sponsors/feross" 727 | }, 728 | { 729 | "type": "patreon", 730 | "url": "https://www.patreon.com/feross" 731 | }, 732 | { 733 | "type": "consulting", 734 | "url": "https://feross.org/support" 735 | } 736 | ] 737 | }, 738 | "node_modules/safe-stable-stringify": { 739 | "version": "2.4.3", 740 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", 741 | "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", 742 | "engines": { 743 | "node": ">=10" 744 | } 745 | }, 746 | "node_modules/set-function-length": { 747 | "version": "1.2.2", 748 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 749 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 750 | "dependencies": { 751 | "define-data-property": "^1.1.4", 752 | "es-errors": "^1.3.0", 753 | "function-bind": "^1.1.2", 754 | "get-intrinsic": "^1.2.4", 755 | "gopd": "^1.0.1", 756 | "has-property-descriptors": "^1.0.2" 757 | }, 758 | "engines": { 759 | "node": ">= 0.4" 760 | } 761 | }, 762 | "node_modules/sha.js": { 763 | "version": "2.4.11", 764 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 765 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 766 | "dependencies": { 767 | "inherits": "^2.0.1", 768 | "safe-buffer": "^5.0.1" 769 | }, 770 | "bin": { 771 | "sha.js": "bin.js" 772 | } 773 | }, 774 | "node_modules/side-channel": { 775 | "version": "1.0.6", 776 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 777 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 778 | "dependencies": { 779 | "call-bind": "^1.0.7", 780 | "es-errors": "^1.3.0", 781 | "get-intrinsic": "^1.2.4", 782 | "object-inspect": "^1.13.1" 783 | }, 784 | "engines": { 785 | "node": ">= 0.4" 786 | }, 787 | "funding": { 788 | "url": "https://github.com/sponsors/ljharb" 789 | } 790 | }, 791 | "node_modules/simple-swizzle": { 792 | "version": "0.2.2", 793 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 794 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 795 | "dependencies": { 796 | "is-arrayish": "^0.3.1" 797 | } 798 | }, 799 | "node_modules/stack-trace": { 800 | "version": "0.0.10", 801 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 802 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", 803 | "engines": { 804 | "node": "*" 805 | } 806 | }, 807 | "node_modules/string_decoder": { 808 | "version": "1.3.0", 809 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 810 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 811 | "dependencies": { 812 | "safe-buffer": "~5.2.0" 813 | } 814 | }, 815 | "node_modules/text-hex": { 816 | "version": "1.0.0", 817 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 818 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 819 | }, 820 | "node_modules/tiny-secp256k1": { 821 | "version": "2.2.3", 822 | "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", 823 | "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", 824 | "dependencies": { 825 | "uint8array-tools": "0.0.7" 826 | }, 827 | "engines": { 828 | "node": ">=14.0.0" 829 | } 830 | }, 831 | "node_modules/triple-beam": { 832 | "version": "1.4.1", 833 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", 834 | "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", 835 | "engines": { 836 | "node": ">= 14.0.0" 837 | } 838 | }, 839 | "node_modules/typeforce": { 840 | "version": "1.18.0", 841 | "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", 842 | "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" 843 | }, 844 | "node_modules/uint8array-tools": { 845 | "version": "0.0.7", 846 | "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", 847 | "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", 848 | "engines": { 849 | "node": ">=14.0.0" 850 | } 851 | }, 852 | "node_modules/util-deprecate": { 853 | "version": "1.0.2", 854 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 855 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 856 | }, 857 | "node_modules/varuint-bitcoin": { 858 | "version": "1.1.2", 859 | "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", 860 | "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", 861 | "dependencies": { 862 | "safe-buffer": "^5.1.1" 863 | } 864 | }, 865 | "node_modules/wif": { 866 | "version": "2.0.6", 867 | "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", 868 | "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", 869 | "dependencies": { 870 | "bs58check": "<3.0.0" 871 | } 872 | }, 873 | "node_modules/wif/node_modules/base-x": { 874 | "version": "3.0.10", 875 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", 876 | "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", 877 | "dependencies": { 878 | "safe-buffer": "^5.0.1" 879 | } 880 | }, 881 | "node_modules/wif/node_modules/bs58": { 882 | "version": "4.0.1", 883 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 884 | "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", 885 | "dependencies": { 886 | "base-x": "^3.0.2" 887 | } 888 | }, 889 | "node_modules/wif/node_modules/bs58check": { 890 | "version": "2.1.2", 891 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", 892 | "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", 893 | "dependencies": { 894 | "bs58": "^4.0.0", 895 | "create-hash": "^1.1.0", 896 | "safe-buffer": "^5.1.2" 897 | } 898 | }, 899 | "node_modules/winston": { 900 | "version": "3.13.1", 901 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", 902 | "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", 903 | "dependencies": { 904 | "@colors/colors": "^1.6.0", 905 | "@dabh/diagnostics": "^2.0.2", 906 | "async": "^3.2.3", 907 | "is-stream": "^2.0.0", 908 | "logform": "^2.6.0", 909 | "one-time": "^1.0.0", 910 | "readable-stream": "^3.4.0", 911 | "safe-stable-stringify": "^2.3.1", 912 | "stack-trace": "0.0.x", 913 | "triple-beam": "^1.3.0", 914 | "winston-transport": "^4.7.0" 915 | }, 916 | "engines": { 917 | "node": ">= 12.0.0" 918 | } 919 | }, 920 | "node_modules/winston-transport": { 921 | "version": "4.7.1", 922 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz", 923 | "integrity": "sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==", 924 | "dependencies": { 925 | "logform": "^2.6.1", 926 | "readable-stream": "^3.6.2", 927 | "triple-beam": "^1.3.0" 928 | }, 929 | "engines": { 930 | "node": ">= 12.0.0" 931 | } 932 | } 933 | } 934 | } 935 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^1.7.2", 4 | "bip32": "^4.0.0", 5 | "bip39": "^3.1.0", 6 | "bitcoinjs-lib": "^6.1.6", 7 | "ecpair": "^2.1.0", 8 | "fast-csv": "^5.0.1", 9 | "https-proxy-agent": "^7.0.5", 10 | "js-yaml": "^4.1.0", 11 | "qs": "^6.12.3", 12 | "tiny-secp256k1": "^2.2.3", 13 | "winston": "^3.13.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /transfer.js: -------------------------------------------------------------------------------- 1 | const bitcoin = require('bitcoinjs-lib'); 2 | const { 3 | exchangeRate, 4 | logger, 5 | isValidBitcoinAddress, 6 | randomNumber, 7 | getAddress, 8 | getKeyPairByWif, 9 | toXOnly, 10 | isValidWif, 11 | calculateWeight, 12 | } = require("./utils/function"); 13 | const AddressDataClass = require("./utils/AddressData"); 14 | const Request = require("./utils/Request"); 15 | const ConfigClass = require("./utils/Config"); 16 | const readline = require('node:readline/promises'); 17 | 18 | const config = new ConfigClass('./config.yaml'); 19 | const network = config.network; 20 | const request = new Request(config); 21 | 22 | // 转账 23 | async function transfer(wifString, toAddresses, toAmountSATSAll) { 24 | const keyPair = getKeyPairByWif(wifString, network); 25 | // 发送方地址 26 | let fromAddress = getAddress(wifString, config.addressType, network); 27 | 28 | // 动态查询 UTXO 29 | const availableUTXO = await request.getUTXO(fromAddress); 30 | if (availableUTXO.length === 0) { 31 | return 'No UTXO'; 32 | } 33 | 34 | const psbt = new bitcoin.Psbt({network}); 35 | let inputValue = 0; 36 | 37 | const gas = await request.getGas(); 38 | 39 | let utxoStr = ''; 40 | let index = 1; 41 | for (const utxo of availableUTXO) { 42 | const input = { 43 | index: utxo.vout, 44 | hash: utxo.txid, 45 | witnessUtxo: { 46 | script: Buffer.from(utxo.scriptPk, 'hex'), 47 | value: utxo.satoshis, 48 | } 49 | } 50 | if (config.addressType === 'p2tr') { 51 | input.tapInternalKey = toXOnly(keyPair.publicKey); // 添加 Taproot 内部密钥 52 | } 53 | psbt.addInput(input); 54 | 55 | utxoStr += ` utxo${index}-txid: (${utxo.txid}:${utxo.vout} ${utxo.satoshis / exchangeRate} BTC)\n` 56 | inputValue += utxo.satoshis; 57 | 58 | if (inputValue >= toAmountSATSAll + Math.ceil(gas * calculateWeight(index, toAddresses.length + 1) / 4)) { 59 | break; 60 | } 61 | index++; 62 | } 63 | 64 | let outputValue = 0; 65 | for (let toAddress of toAddresses) { 66 | psbt.addOutput({ 67 | // 接收方地址 68 | address: toAddress.Address, 69 | // 金额 70 | value: parseInt(toAddress.Amount * exchangeRate), 71 | }); 72 | outputValue += parseInt(toAddress.Amount * exchangeRate); 73 | } 74 | 75 | // 设置 gas 76 | const fee = Math.ceil(gas * calculateWeight(psbt.data.inputs.length, toAddresses.length + 1) / 4); 77 | // 找零输出 78 | const changeValue = inputValue - outputValue - fee; 79 | 80 | if (changeValue < 0) { 81 | logger().error('支出超过输出的 UTXO'); 82 | return; 83 | } else if (changeValue > 0) { 84 | // 找零 85 | psbt.addOutput({ 86 | // 接收方地址 87 | address: fromAddress, 88 | // 金额 89 | value: changeValue, 90 | }); 91 | } 92 | 93 | let signer = null; 94 | if (config.addressType === 'p2tr') { 95 | signer = keyPair.tweak( 96 | bitcoin.crypto.taggedHash('TapTweak', toXOnly(keyPair.publicKey)), 97 | ) 98 | } else { 99 | signer = keyPair; 100 | } 101 | 102 | // 签名所有输入 103 | psbt.data.inputs.forEach((input, index) => { 104 | psbt.signInput(index, signer); 105 | }); 106 | 107 | // // 定义验证函数,用于校验签名是否有效 108 | // const validator = (pubkey, msghash, signature) => { 109 | // return ECPair.fromPublicKey(pubkey).verify(msghash, signature); 110 | // }; 111 | // // 验证输入签名 112 | // psbt.validateSignaturesOfInput(0, validator); 113 | // 终结所有输入,表示签名完成 114 | psbt.finalizeAllInputs(); 115 | 116 | // 提取交易事务 117 | const tx = psbt.extractTransaction(); 118 | const psbtHex = tx.toHex(); 119 | 120 | let msg = `\n支出账户: ${fromAddress} 使用了 ${psbt.data.inputs.length} 条 UTXO 作为输入(已经排除了UTXO值小于546的,避免误烧资产)\n`; 121 | msg += `${utxoStr}`; 122 | msg += `接收账户数量 ${toAddresses.length} 个地址,共 ${toAmountSATSAll / exchangeRate} BTC ( ${toAmountSATSAll} sat )\n`; 123 | msg += `矿工费用: ${fee / exchangeRate} BTC ( ${fee} sat ) gas: ${gas} sat/vB 虚拟大小: ${tx.virtualSize()}\n`; 124 | msg += `找零 ${changeValue / exchangeRate} BTC ( ${changeValue} sat ) 到 ${fromAddress}\n`; 125 | console.log(`\x1b[33m${msg}\x1b[39m`); 126 | 127 | const rl = readline.createInterface({ 128 | input: process.stdin, 129 | output: process.stdout 130 | }); 131 | 132 | const question = "是否确认将该交易进行广播,广播后将无法反悔交易;输入 'y'或'Y' 并回车确认,其他字符取消广播: "; 133 | const answer = await rl.question(`\x1b[33m${question}\x1b[39m`); 134 | rl.close(); 135 | 136 | if (answer === 'Y' || answer === 'y') { 137 | // 广播交易到比特币网络,等待确认 138 | logger().info(`正在广播交易 hex: ${psbtHex}`); 139 | const res = await request.broadcastTx(psbtHex); 140 | if (res.code === 0 && res.data.length === 64) { 141 | logger().success(`广播交易成功,Transaction Hash: ${res.data}`); 142 | return true; 143 | } 144 | 145 | logger().error(`广播交易失败: ` + JSON.stringify(res)); 146 | return false; 147 | } 148 | logger().warn('取消广播交易'); 149 | return false; 150 | } 151 | 152 | async function main() { 153 | const toAddresses = await (new AddressDataClass("wallet.csv")).load(['Address', 'Amount']); 154 | 155 | // 支出 BTC 的账户 156 | const fromAddressWIF = config.data.wif.trim(); 157 | if (!isValidWif(fromAddressWIF)){ 158 | logger().error(`wif 私钥有误: ${fromAddressWIF}`); 159 | return; 160 | } 161 | 162 | let fromAddress = getAddress(fromAddressWIF, config.addressType, network); 163 | 164 | let balance = await request.getBalance(fromAddress); 165 | if (balance === null) { 166 | logger().error(`获取余额失败`); 167 | return; 168 | } 169 | 170 | let balanceSATS = 0; 171 | if (balance) { 172 | balanceSATS = balance.btcSatoshis; 173 | logger().info(`支出账户可用: ${fromAddress} 余额: ${balanceSATS} sat, ${balanceSATS / exchangeRate} BTC`); 174 | } 175 | 176 | let toAmountSATSAll = 0; 177 | for (const index in toAddresses) { 178 | const {Address, Amount} = toAddresses[index]; 179 | if (!isValidBitcoinAddress(Address, network)) { 180 | logger().error(`请检查第${parseInt(index) + 2}行地址: ${Address} 格式是否正确`); 181 | return 182 | } 183 | const amountSATS = parseInt(Amount * exchangeRate); 184 | if (amountSATS <= 0) { 185 | logger().error(`请检查第${parseInt(index) + 2}行地址: ${Address} 的金额是否正确`); 186 | return 187 | } 188 | 189 | toAmountSATSAll += amountSATS; 190 | } 191 | 192 | if (toAmountSATSAll > balanceSATS) { 193 | logger().error(`${toAddresses.length} 个收款账户,共 ${toAmountSATSAll / exchangeRate} BTC ( ${toAmountSATSAll} sat ), 余额不足`); 194 | return 195 | } 196 | 197 | logger().info(`${toAddresses.length} 个收款账户,共 ${toAmountSATSAll / exchangeRate} BTC ( ${toAmountSATSAll} sat )`); 198 | 199 | let res = false; 200 | try { 201 | res = await transfer(fromAddressWIF, toAddresses, toAmountSATSAll); 202 | } catch (e) { 203 | console.log(e); 204 | console.log(e.toString()); 205 | } 206 | 207 | if (res === true) { 208 | logger().success(`转账结果: ${res}`); 209 | } else { 210 | logger().error(`转账结果: ${res}`); 211 | } 212 | } 213 | 214 | main(); 215 | 216 | -------------------------------------------------------------------------------- /utils/AddressData.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const csv = require('fast-csv'); 3 | 4 | class AddressData { 5 | filePath = null; 6 | data = []; 7 | 8 | constructor(filePath = "wallet.csv") { 9 | // TODO: 如果没有 wallet.csv 创建它 10 | this.filePath = filePath; 11 | } 12 | 13 | async load(expectedHeaders = ["Address", "Amount"]) { 14 | this.data = await this.readCSV(expectedHeaders); 15 | return this.data; 16 | } 17 | 18 | readCSV(expectedHeaders) { 19 | const results = []; 20 | 21 | try { 22 | const stream = fs.createReadStream(this.filePath); 23 | const parser = csv.parse({headers: true}); 24 | 25 | return new Promise((resolve, reject) => { 26 | stream.pipe(parser) 27 | .on('error', reject) 28 | .on('data', row => { 29 | const isBlankRow = Object.values(row).every(value => /^\s*$/.test(value)); 30 | if (!isBlankRow) { 31 | const rowData = {}; 32 | expectedHeaders.forEach(header => { 33 | rowData[header] = row[header] ? row[header].trim() : ''; 34 | }); 35 | results.push(rowData); 36 | } 37 | }).on('end', () => resolve(results)); 38 | }); 39 | } catch (error) { 40 | console.error('Error parsing CSV:', error); 41 | return []; 42 | } 43 | } 44 | } 45 | 46 | module.exports = AddressData; 47 | -------------------------------------------------------------------------------- /utils/Config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const yaml = require('js-yaml'); 3 | const bitcoin = require("bitcoinjs-lib"); 4 | 5 | class Config { 6 | constructor(filePath = "config.yaml") { 7 | this.data = yaml.load(fs.readFileSync(filePath, 'utf8')); 8 | this.networkType = this.data.networkType; 9 | 10 | switch (this.data.networkType) { 11 | case "mainnet": 12 | default: 13 | this.networkType = "mainnet"; 14 | this.unisatWalletUri = "https://wallet-api.unisat.io"; 15 | this.mempoolUri = "https://mempool.space"; 16 | this.network = bitcoin.networks.bitcoin; 17 | break; 18 | case "testnet": 19 | this.unisatWalletUri = "https://wallet-api-testnet.unisat.io"; 20 | this.mempoolUri = "https://mempool.space/testnet"; 21 | this.network = bitcoin.networks.testnet; 22 | break; 23 | case "testnet4": 24 | this.unisatWalletUri = "https://wallet-api-testnet4.unisat.io"; 25 | this.mempoolUri = "https://mempool.space/testnet4"; 26 | this.network = bitcoin.networks.testnet; 27 | break; 28 | case "signet": 29 | this.unisatWalletUri = "https://wallet-api-signet.unisat.io"; 30 | this.mempoolUri = "https://mempool.space/signet"; 31 | this.network = bitcoin.networks.testnet; 32 | break; 33 | case "fractal": 34 | this.unisatWalletUri = "https://wallet-api-fractal.unisat.io"; 35 | this.mempoolUri = "https://mempool.fractalbitcoin.io"; 36 | this.network = bitcoin.networks.bitcoin; 37 | break; 38 | case "fractal-testnet": 39 | this.unisatWalletUri = "https://wallet-api-fractal-testnet.unisat.io"; 40 | this.mempoolUri = "https://mempool-testnet.fractalbitcoin.io"; 41 | this.network = bitcoin.networks.bitcoin; 42 | break; 43 | } 44 | 45 | switch (this.data.addressType) { 46 | case "p2tr": 47 | case "p2wpkh": 48 | this.addressType = this.data.addressType; 49 | break; 50 | default: 51 | this.addressType = "p2tr"; 52 | break; 53 | } 54 | } 55 | } 56 | 57 | module.exports = Config; 58 | -------------------------------------------------------------------------------- /utils/Request.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const {logger} = require("./function"); 3 | 4 | class Request { 5 | constructor(config) { 6 | this.config = config; 7 | this.unisatWalletUri = config.unisatWalletUri; 8 | this.mempoolUri = config.mempoolUri; 9 | this.networkType = config.networkType; 10 | } 11 | 12 | // 获取TX列表 13 | async getTXs(address) { 14 | const url = `${this.URI}/address/${address}/txs`; 15 | const res = await this.request({url: url}); 16 | // status: { confirmed: false } // 该TX是否已确认 17 | return res.data; 18 | } 19 | 20 | // 获取TX详情 21 | async getTXDetail(txHash) { 22 | const url = `${this.mempoolUri}/tx/${txHash}`; 23 | const res = await this.request({url: url}); 24 | return res.data; 25 | } 26 | 27 | async getBalance(address) { 28 | const url = `${this.unisatWalletUri}/v5/address/summary?address=${address}`; 29 | const res = await this.request({url: url}); 30 | // { 31 | // "code": 0, 32 | // "msg": "ok", 33 | // "data": { 34 | // "totalSatoshis": 2533683544, 35 | // "btcSatoshis": 2525186176, 36 | // "assetSatoshis": 8497368, 37 | // "inscriptionCount": 28318, 38 | // "atomicalsCount": 0, 39 | // "brc20Count": 0, 40 | // "brc20Count5Byte": 0, 41 | // "arc20Count": 0, 42 | // "runesCount": 0 43 | // } 44 | // } 45 | if (res && res.data && res.data.data) { 46 | return res.data.data; 47 | } 48 | return null; 49 | } 50 | 51 | // 查询 UTXO 52 | async getUTXO(address) { 53 | const url = `${this.unisatWalletUri}/v5/address/btc-utxo?address=${address}`; 54 | const res = await this.request({url: url}); 55 | if (res.status === 200 && res.data && res.data.code === 0) { 56 | return res.data.data; 57 | } 58 | 59 | return []; 60 | } 61 | 62 | // 获取建议费用 63 | async getGas() { 64 | const configFeeRate = this.config.data.gas; 65 | let feeRate = 0; 66 | if (/^\d+\.?\d*$/.test(configFeeRate)) { 67 | feeRate = Number(configFeeRate) 68 | } else { 69 | const url = `${this.unisatWalletUri}/v5/default/fee-summary`; 70 | const result = await this.request({url: url}); 71 | const res = result.data.data.list; 72 | logger().info(`当前gas (${res[0].title} = ${res[0].feeRate} sat/vB), (${res[1].title} = ${res[1].feeRate} sat/vB), (${res[2].title} = ${res[2].feeRate} sat/vB)`); 73 | if (/^\+[1-9]\d*$/.test(configFeeRate)) { 74 | feeRate = res[2].feeRate + Number(configFeeRate); 75 | } else { 76 | switch (configFeeRate) { 77 | case "low": 78 | feeRate = res[0].feeRate; 79 | break; 80 | case "medium": 81 | feeRate = res[1].feeRate; 82 | break; 83 | case "high": 84 | feeRate = res[2].feeRate; 85 | break; 86 | default: 87 | logger().warn(`config.yaml 的 gas 设置有误,默认使用 high`); 88 | feeRate = res[2].feeRate; 89 | break; 90 | } 91 | } 92 | } 93 | 94 | logger().info(`使用gas ${feeRate}`); 95 | return feeRate; 96 | } 97 | 98 | // 广播交易 99 | async broadcastTx(psbtHex) { 100 | const url = `${this.unisatWalletUri}/v5/tx/broadcast`; 101 | const res = await this.request({ 102 | url: url, method: 'post', body: { 103 | "rawtx": psbtHex, 104 | }, headers: { 105 | 'Content-Type': 'application/json', 106 | } 107 | }); 108 | return res.data; 109 | } 110 | 111 | async request({url, method = 'get', body = null, headers = null, agent = null}) { 112 | const config = { 113 | url: url, 114 | method: method, 115 | timeout: 30 * 1000, 116 | headers: { 117 | "accept-language": 'zh-CN,zh;q=0.9', 118 | "sec-ch-ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 119 | "sec-ch-ua-mobile": '?0', 120 | "sec-ch-ua-platform": '"Windows"', 121 | "sec-fetch-dest": 'empty', 122 | "sec-fetch-mode": 'cors', 123 | "sec-fetch-site": 'same-origin', 124 | "user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 125 | }, 126 | }; 127 | 128 | if (headers !== null) { 129 | config.headers = {...config.headers, ...headers} 130 | } 131 | 132 | if (body !== null) { 133 | if (typeof body === 'string') { 134 | config.data = body; 135 | } else { 136 | config.data = JSON.stringify(body); 137 | } 138 | } 139 | 140 | if (agent !== null) { 141 | config.httpsAgent = agent; 142 | config.httpAgent = agent; 143 | } 144 | 145 | try { 146 | const response = await axios(config); 147 | return response; 148 | } catch (error) { 149 | logger().error(`${url} ${error.toString()}`); 150 | return error.response; 151 | } 152 | } 153 | 154 | } 155 | 156 | module.exports = Request; 157 | -------------------------------------------------------------------------------- /utils/function.js: -------------------------------------------------------------------------------- 1 | const {createLogger, transports, format} = require('winston'); 2 | const bitcoin = require('bitcoinjs-lib'); 3 | const bip39 = require("bip39"); 4 | const bip32 = require("bip32"); 5 | 6 | const ecc = require("tiny-secp256k1"); 7 | const {ECPairFactory} = require("ecpair"); 8 | 9 | bitcoin.initEccLib(ecc); 10 | 11 | const exchangeRate = 1e8; 12 | 13 | /** 14 | * 睡眠 15 | * @param seconds 16 | * @returns {Promise} 17 | */ 18 | const sleep = (seconds) => { 19 | const milliseconds = seconds * 1000; 20 | return new Promise(resolve => setTimeout(resolve, milliseconds)); 21 | }; 22 | 23 | /** 24 | * 随机数 25 | * @param min 26 | * @param max 27 | * @returns {number} 28 | */ 29 | function randomNumber(min, max) { 30 | min = parseInt(min) 31 | max = parseInt(max) 32 | // 确保 min 小于等于 max 33 | if (min > max) { 34 | [min, max] = [max, min]; 35 | } 36 | 37 | // 计算生成随机整数的范围 38 | const range = max - min + 1; 39 | 40 | // 生成随机数并将其映射到指定范围内 41 | return Math.floor(Math.random() * range) + min; 42 | } 43 | 44 | /** 45 | * 打乱数组 46 | * @param arr 47 | */ 48 | function shuffle(arr) { 49 | for (let i = arr.length - 1; i > 0; i--) { 50 | const j = Math.floor(Math.random() * (i + 1)); //生成[0, i]之间的随机索引 51 | [arr[i], arr[j]] = [arr[j], arr[i]]; //交换位置 52 | } 53 | } 54 | 55 | /** 56 | * 获取当前时间 57 | * @returns {string} 58 | */ 59 | function getCurrentDateTime() { 60 | const currentDate = new Date(); 61 | const year = currentDate.getFullYear(); 62 | const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要加1 63 | const day = String(currentDate.getDate()).padStart(2, '0'); 64 | const hours = String(currentDate.getHours()).padStart(2, '0'); 65 | const minutes = String(currentDate.getMinutes()).padStart(2, '0'); 66 | const seconds = String(currentDate.getSeconds()).padStart(2, '0'); 67 | 68 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 69 | } 70 | 71 | function dd(msg, level = 'info') { 72 | let formattedMsg = ''; 73 | 74 | // 检查参数的类型 75 | if (typeof msg === 'string' || typeof msg === 'number' || typeof msg === 'boolean') { 76 | // 如果是字符串、数字或布尔值,直接添加到格式化后的消息中 77 | formattedMsg += msg; 78 | } else if (Array.isArray(msg)) { 79 | // 如果是数组,将数组元素格式化后拼接成字符串 80 | formattedMsg += msg.map(item => JSON.stringify(item)).join(', '); 81 | } else if (typeof msg === 'object' && msg !== null) { 82 | // 如果是对象,将对象转换为字符串格式 83 | formattedMsg += JSON.stringify(msg); 84 | } 85 | 86 | formattedMsg = getCurrentDateTime() + ' ' + formattedMsg 87 | 88 | switch (level) { 89 | case 'success': 90 | console.log(`\x1b[32m${formattedMsg}\x1b[39m`); 91 | break; 92 | default: 93 | case 'info': 94 | console.log(`\x1b[34m${formattedMsg}\x1b[39m`); 95 | break; 96 | case 'error': 97 | console.log(`\x1b[41m${formattedMsg}\x1b[49m`); 98 | break; 99 | case 'warning': 100 | console.log(`\x1b[33m${formattedMsg}\x1b[39m`); 101 | break; 102 | } 103 | } 104 | 105 | /** 106 | * 返回短地址 107 | * @param address 108 | * @param num 109 | * @returns {*|string} 110 | */ 111 | function shortAddress(address, num = 4) { 112 | if (address.length <= num * 2) { 113 | return address; 114 | } else { 115 | // 截取前4位和后4位,中间用"***"代替 116 | return address.slice(0, num + 2) + "***" + address.slice(-num); 117 | } 118 | } 119 | 120 | function logger() { 121 | const path = require('path'); 122 | // 自定义日志级别,包括 success 123 | const customLevels = { 124 | levels: { 125 | error: 0, 126 | warn: 1, 127 | info: 2, 128 | success: 3, 129 | 130 | http: 4, 131 | verbose: 5, 132 | debug: 6, 133 | silly: 7 134 | }, 135 | colors: { 136 | error: 'red', 137 | warn: 'yellow', 138 | info: 'blue', 139 | success: 'green', 140 | http: 'magenta', 141 | verbose: 'cyan', 142 | debug: 'white', 143 | silly: 'grey' 144 | } 145 | }; 146 | 147 | const newLogger = createLogger({ 148 | levels: customLevels.levels, 149 | format: format.combine( 150 | format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}), 151 | format.printf(info => `${info.timestamp} | ${info.level}: ${info.message}`) 152 | ), 153 | transports: [ 154 | new transports.Console({ 155 | level: 'success', 156 | format: format.combine( 157 | format.colorize(), 158 | format.printf(info => `${info.timestamp} | ${info.level}: ${info.message}`) 159 | ) 160 | }), 161 | new transports.File({ 162 | filename: path.join(process.cwd(), 'logs', 'app.log'), 163 | level: 'success', 164 | format: format.combine( 165 | format.uncolorize(), 166 | format.json() 167 | ), 168 | maxsize: 5242880, // 5MB 169 | maxFiles: 5, 170 | }) 171 | ] 172 | }); 173 | 174 | // 添加颜色 175 | require('winston').addColors(customLevels.colors); 176 | 177 | return newLogger; 178 | } 179 | 180 | function getKeyPairByMnemonic(mnemonic, network, addressType = 'p2tr') { 181 | // 通过助记词生成种子 182 | const seed = bip39.mnemonicToSeedSync(mnemonic); 183 | // 通过种子生成根秘钥 184 | const root = bip32.BIP32Factory(ecc).fromSeed(seed, network); 185 | // 定义路径 186 | const path = addressType === 'p2tr' ? "m/86'/0'/0'/0/0" : "m/84'/0'/0'/0/0"; 187 | // 通过路径生成密钥对 188 | const childNode = root.derivePath(path); 189 | 190 | // keyPairInstance 191 | return ECPairFactory(ecc).fromPrivateKey(childNode.privateKey, {network}); 192 | } 193 | 194 | function isValidWif(wif) { 195 | const wifRegex = /^[LKc][1-9A-HJ-NP-Za-km-z]{51}$/; 196 | return wifRegex.test(wif); 197 | } 198 | 199 | function getKeyPairByWif(wifString, network) { 200 | return ECPairFactory(ecc).fromWIF(wifString, network); 201 | } 202 | 203 | const toXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); 204 | 205 | function getAddress(wifString, addressType, network) { 206 | const keyPair = getKeyPairByWif(wifString); 207 | 208 | let fromAddress = ""; 209 | if (addressType === "p2tr") { 210 | const p2tr = bitcoin.payments.p2tr({internalPubkey: toXOnly(keyPair.publicKey), network}); 211 | fromAddress = p2tr.address; 212 | } else if (addressType === "p2wpkh") { 213 | const p2wpkh = bitcoin.payments.p2wpkh({pubkey: keyPair.publicKey, network}); 214 | fromAddress = p2wpkh.address; 215 | } else { 216 | logger().error(`config.yaml 的 addressType 设置有误, ${addressType}`); 217 | } 218 | 219 | return fromAddress 220 | } 221 | 222 | /** 223 | * 计算转账的交易权重 224 | * @param inputCount 225 | * @param outputCount 226 | * @returns {*} 227 | */ 228 | function calculateWeight(inputCount, outputCount) { 229 | // 定义每个部分的大小(以字节为单位) 230 | const baseTransactionSize = 10; // 包含版本号和锁定时间,通常为10字节 231 | const inputNonWitnessSize = 70; // 每个输入的非 Witness 大小 232 | const outputSize = 58; // 每个输出的大小 233 | 234 | let nonWitnessSize = baseTransactionSize + (inputCount * inputNonWitnessSize) + (outputCount * outputSize); 235 | 236 | // TODO: 需要根据地址类型判断大小 237 | // Witness 数据大小 238 | const p2wpkhWitnessDataSize = 105; // 普通 P2WPKH Witness 数据大小(签名 + 公钥) 239 | const p2trWitnessDataSize = 64; // P2TR Witness 数据大小(Schnorr 签名) 240 | let totalWitnessSize = inputCount * p2trWitnessDataSize; // 计算 Witness 大小 241 | 242 | // 计算交易的总 weight 243 | return 3 * nonWitnessSize + totalWitnessSize; 244 | } 245 | 246 | /** 247 | * 验证比特币地址的合法性 248 | * @param {string} address - 要验证的比特币地址 249 | * @param network 250 | * @returns {boolean} - 返回地址是否合法 251 | */ 252 | function isValidBitcoinAddress(address, network) { 253 | const rules = network === bitcoin.networks.bitcoin ? 254 | [ 255 | {type: 'P2WPKH', prefix: 'bc1q', length: 42}, 256 | {type: 'P2SH-P2WPKH', prefix: '3', length: 34}, 257 | {type: 'P2TR', prefix: 'bc1p', length: 62}, 258 | {type: 'P2PKH', prefix: '1', length: 34}, 259 | ] : 260 | [ 261 | {type: 'P2WPKH', prefix: 'tb1q', length: 42}, 262 | {type: 'P2SH-P2WPKH', prefix: '2', length: 35}, 263 | {type: 'P2TR', prefix: 'tb1p', length: 62}, 264 | {type: 'P2PKH', prefix: 'm', length: 34}, 265 | ]; 266 | 267 | // 校验地址 268 | for (const rule of rules) { 269 | if (address.startsWith(rule.prefix) && address.length === rule.length) { 270 | return true; 271 | } 272 | } 273 | 274 | // 地址不符合任何规则 275 | return false; 276 | } 277 | 278 | module.exports = { 279 | exchangeRate, 280 | sleep, 281 | randomNumber, 282 | shuffle, 283 | getCurrentDateTime, 284 | dd, 285 | shortAddress, 286 | logger, 287 | isValidBitcoinAddress, 288 | getKeyPairByWif, 289 | toXOnly, 290 | getAddress, 291 | isValidWif, 292 | calculateWeight, 293 | } 294 | 295 | -------------------------------------------------------------------------------- /wallet.csv-example: -------------------------------------------------------------------------------- 1 | Address,Amount 2 | bc1pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,0.0001 3 | bc1pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,0.0001 4 | bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,0.0001 5 | 6 | --------------------------------------------------------------------------------