├── .dockerignore ├── .gitignore ├── AUTHORS ├── Dockerfile ├── Dockerfile.bitcoind-core ├── Dockerfile.build ├── LICENSE ├── Makefile ├── README.md ├── REFERENCE ├── RELEASE ├── TODO ├── VERSION ├── api ├── base.go ├── createaccount.go ├── createaccount_test.go ├── getaccount.go ├── getaddress.go ├── getaddressesbyaccount.go ├── getblockcount.go ├── getnewaddress.go ├── http.go ├── listaccounts.go ├── listunspentmin.go ├── move.go ├── sendfrom.go ├── sendtoaddress.go └── test.go ├── cmd ├── flags.go ├── gateway.go ├── logger.go ├── main.go ├── valuga.go └── version.go ├── docker-entrypoint.sh ├── docs ├── BTC-APIs.md ├── ETH-APIs.md ├── ETH-ARGS.md └── OMNI-APIs.md ├── go.mod ├── go.sum ├── keeper ├── btc │ └── client.go ├── eth │ ├── client.go │ └── helper.go ├── keeper.go └── usdt │ └── client.go ├── logo └── logo.png ├── omnilayer ├── bitcoin_core.go ├── client.go ├── errors.go ├── infra.go ├── omni_core.go ├── omnijson │ ├── createrawtransaction.go │ ├── getaccountaddress.go │ ├── getaddressesbyaccount.go │ ├── getblockchaininfo.go │ ├── getnewaddress.go │ ├── importaddress.go │ ├── listaccounts.go │ ├── listunspent.go │ ├── omni_createpayload_simplesend.go │ ├── omni_createrawtx_change.go │ ├── omni_createrawtx_opreturn.go │ ├── omni_createrawtx_reference.go │ ├── omni_funded_send.go │ ├── omni_getbalance.go │ ├── omni_getinfo.go │ ├── omni_gettransaction.go │ ├── omni_listblocktransactions.go │ ├── sendrawtransaction.go │ ├── signrawtransaction.go │ └── signrawtransactionwithkey.go └── rpc.go ├── run.sh └── scripts ├── btc_usdt_call.sh ├── build.sh ├── build_bitcoind_core_image.sh ├── eth_rpc_call.sh ├── eth_rpc_call_curl.sh ├── genauth.sh ├── rpcauth.py ├── run_bitcoind.sh └── run_wallet_keeper_docker.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | bitcoind-data/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | bitcoin.conf 3 | bitcoind-data/ 4 | btc.log 5 | usdt.log 6 | eth.log 7 | eth-accounts.json 8 | accounts.json 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | cming.xu@gmail.com 2 | xiaods@gmail.com 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.1-alpine3.9 as builder 2 | 3 | # 更新安装源 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories 5 | 6 | WORKDIR /go/src/app 7 | 8 | RUN apk add git && apk add make && apk add gcc && apk add libc-dev \ 9 | && apk add --update gcc musl-dev 10 | 11 | ENV GOPROXY=https://goproxy.io 12 | ADD . . 13 | 14 | RUN make 15 | 16 | 17 | 18 | FROM alpine:latest 19 | 20 | COPY --from=builder /go/src/app/bin/wallet-keeper / 21 | 22 | EXPOSE 8000 23 | WORKDIR / 24 | 25 | CMD ["/wallet-keeper", "run"] 26 | -------------------------------------------------------------------------------- /Dockerfile.bitcoind-core: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | LABEL maintainer.0="João Fonseca (@joaopaulofonseca)" \ 4 | maintainer.1="Pedro Branco (@pedrobranco)" \ 5 | maintainer.2="Rui Marinho (@ruimarinho)" 6 | 7 | RUN useradd -r bitcoin \ 8 | && apt-get update -y \ 9 | && apt-get install -y curl gnupg gosu lsof strace iproute \ 10 | && apt-get clean \ 11 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 12 | 13 | ENV BITCOIN_VERSION=0.17.1 14 | ENV BITCOIN_DATA=/home/bitcoin/.bitcoin 15 | ENV PATH=/opt/bitcoin-${BITCOIN_VERSION}/bin:$PATH 16 | 17 | RUN curl -SL https://bitcoin.org/laanwj-releases.asc | gpg --batch --import \ 18 | && curl -SLO https://bitcoin.org/bin/bitcoin-core-${BITCOIN_VERSION}/SHA256SUMS.asc \ 19 | && curl -SLO https://bitcoin.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 20 | && gpg --verify SHA256SUMS.asc \ 21 | && grep " bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz\$" SHA256SUMS.asc | sha256sum -c - \ 22 | && tar -xzf *.tar.gz -C /opt \ 23 | && rm *.tar.gz *.asc 24 | 25 | COPY docker-entrypoint.sh /entrypoint.sh 26 | 27 | VOLUME ["/home/bitcoin/.bitcoin"] 28 | 29 | EXPOSE 8332 8333 18332 18333 18443 18444 30 | 31 | ENTRYPOINT ["/entrypoint.sh"] 32 | 33 | CMD ["bitcoind"] 34 | 35 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.1-alpine3.9 2 | 3 | # 更新安装源 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories 5 | 6 | WORKDIR /go/src/app 7 | 8 | RUN apk update && \ 9 | apk add --virtual build-dependencies \ 10 | build-base \ 11 | gcc \ 12 | git 13 | -------------------------------------------------------------------------------- /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 {} 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=$(shell cat ./VERSION) 2 | PKG=github.com/cmingxu/wallet-keeper 3 | GOBUILD=GO111MODULE=on CGO_ENABLED=1 go build -a -ldflags "-X main.Version=${VERSION}" 4 | CROSS_GOBUILD=CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -a -ldflags "-X main.Version=${VERSION}" 5 | CMDS = $(shell go list ${PKG}/cmd ) 6 | PKG_ALL = $(shell go list ${PKG}/...) 7 | DOCKER=$(shell which docker) 8 | BUILD_DIR=./bin 9 | 10 | all: build 11 | 12 | build: 13 | ${GOBUILD} -o ${BUILD_DIR}/wallet-keeper ./cmd/*.go 14 | 15 | install: binaries 16 | 17 | test: 18 | go test ./... -v 19 | 20 | .PHONY: clean 21 | clean: 22 | @rm bin/* 23 | 24 | .PHONY: coverage 25 | coverage: 26 | go test -cover -coverprofile=test.coverage 27 | go tool cover -html=test.coverage -o coverage.html 28 | rm -f test.coverage 29 | 30 | .PHONY: fmt 31 | fmt: 32 | go fmt ${PKG_ALL} 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Wallet Keeper** 2 | 3 | 4 | 5 | ---- 6 | 7 | simplified access to BTC/OmniProtocol/ETH backends with unified APIs, help cryptocurrency developers focus their bussiness logic instead of RPC details of different backend. 8 | 9 | **Notice**: currently under *heavy development*. 10 | 11 | [![version](https://img.shields.io/badge/version-0.2.0-yellow.svg)](https://semver.org) 12 | 13 | ## Run 14 | ```bash 15 | $ make 16 | $ ./bin/wallet-keeper --log-level debug --log-dir=/tmp/ \ 17 | run --http-listen-addr=http://0.0.0.0:8080 18 | ``` 19 | 20 | ## Docker Run 21 | ```bash 22 | $ ./scripts/build.sh 23 | $ ./scripts/run_wallet_keeper_docker.sh 24 | ``` 25 | 26 | 27 | ## How to config 28 | 29 | ```bash 30 | $ ./bin/wallet-keeper 31 | use stdout as default log output 32 | NAME: 33 | Universal BTC/USDT wallet gateway - A new cli application 34 | 35 | USAGE: 36 | wallet-keeper [global options] command [command options] [arguments...] 37 | 38 | VERSION: 39 | 0.2.0 40 | 41 | COMMANDS: 42 | run, r serve api gateway 43 | help, h Shows a list of commands or help for one command 44 | 45 | GLOBAL OPTIONS: 46 | --log-level value default log level (default: "info") [$LOG_LEVEL] 47 | --log-dir value [$LOG_DIR] 48 | --env value (default: "production") [$ENV] 49 | --help, -h show help 50 | --version, -v print the version 51 | 52 | $ ./bin/wallet-keeper run --help 53 | use stdout as default log output 54 | NAME: 55 | wallet-keeper run - serve api gateway 56 | 57 | USAGE: 58 | wallet-keeper run [command options] [arguments...] 59 | 60 | OPTIONS: 61 | --http-listen-addr value http address of web application (default: "0.0.0.0:8000") [$HTTP_LISTEN_ADDR] 62 | --btc-rpc-addr value [NOTICE] testnet and mainnet have different default port (default: "192.168.0.101:8332") [$BTC_RPCADDR] 63 | --btc-rpc-user value (default: "foo") [$BTC_RPCUSER] 64 | --btc-rpc-pass value password can be generate through scripts/rcpauth.py (default: "qDDZdeQ5vw9XXFeVnXT4PZ--tGN2xNjjR4nrtyszZx0=") [$BTC_PRCPASS] 65 | --usdt-rpc-addr value [NOTICE] testnet and mainnet have different default port (default: "localhost:18332") [$USDT_RPCADDR] 66 | --usdt-rpc-user value (default: "foo") [$USDT_RPCUSER] 67 | --usdt-rpc-pass value password can be generate through scripts/rcpauth.py (default: "usdtpass") [$USDT_PRCPASS] 68 | --usdt-property-id value property id of usdt, default is 2 (default: 2) [$USDT_PROPERTY_ID] 69 | --eth-rpc-addr value (default: "http://192.168.0.101:8545") [$ETH_RPCADDR] 70 | --eth-wallet-dir value (default: "/data/eth-wallet") [$ETH_WALLET_DIR] 71 | --eth-account-path value (default: "/data/eth-accounts.json") [$ETH_ACCOUNT_PATH] 72 | --backends value (default: "btc,usdt,eth") [$BACKENDS] 73 | ``` 74 | 75 | 76 | 77 | ## BTC - bitcored 78 | 79 | ### getblockcount 80 | 81 | ```bash 82 | $ curl -sSL -H "CoinType:btc" localhost:8000/getblockcount | jq . 83 | { 84 | "message": "603443" 85 | } 86 | ``` 87 | 88 | ### getaddress 89 | ```bash 90 | $ curl -sSL -H "CoinType:btc" localhost:8000/getaddress?account=barfoox | jq . 91 | { 92 | "message": [ 93 | "2MwFt5ZbGfK2yqCWHb1hyGKkm8K6DUByPj8", 94 | "2N3Mqnjq9KUnLqUNjRdgkqh1VY4DJBjPoie", 95 | "2N4MbjrKuBD9KYFitz6nmSZFBJxdSWguC7Y", 96 | "2NDuD1sWwsuZeVdBCv8pusjSQUCNTbJTR7x" 97 | ] 98 | } 99 | ``` 100 | 101 | ### getnewaddress 102 | ```bash 103 | $ curl -sSL -H "CoinType:btc" localhost:8000/getnewaddress?accounts=barfoox | jq . 104 | { 105 | "message": "2MwTYicqDegSuR2MsTeRVFLwkhMYSZKHPiP" 106 | } 107 | ``` 108 | 109 | ### listaccounts 110 | ```bash 111 | $ curl -sSL -H "CoinType:btc" localhost:8000/listaccounts | jq . 112 | { 113 | "message": { 114 | "barfoo": 0, 115 | "barfoox": 0, 116 | "duckduck": 0, 117 | "foobar": 0 118 | } 119 | } 120 | ``` 121 | 122 | ### sendtoaddress 123 | ```bash 124 | $ curl -sSL -H "CoinType:btc" 'localhost:8000/sendtoaddress?address=2N2VJhke2sWspswJKWTFjqfibRY1wfZPbEQ&amount=0.1' | jq . 125 | { 126 | "message": "-6: Insufficient funds" 127 | } 128 | ``` 129 | 130 | ### getaddressesbyaccount 131 | ```bash 132 | $ curl -sSL -H "CoinType:btc" 'localhost:8000/getaddressesbyaccount?account=foobar' | jq . 133 | { 134 | "message": [ 135 | "2MziFYWKdptgkDn9esKhQnQXt86H6taGM3f", 136 | "2MziSR87om6fZyknsTMF447Yftt5afQB9GN", 137 | "2NBEYgwGWTiTaPvCKp64hDeP5xnwckdrYNK", 138 | "2NGTBdRezf7TNtF3X1ptmWzVr4XWc8MHnnP" 139 | ] 140 | } 141 | ``` 142 | 143 | ### getaccountinfo 144 | ```bash 145 | $ curl -sSL -H "CoinType:btc" 'localhost:8000/getaccountinfo?account=barfoo' | jq . 146 | { 147 | "message": { 148 | "account": "barfoo", 149 | "balance": 0.0015, 150 | "addresses": [ 151 | "2Mtb1opq1JvzfdLdGRPFSWwbEmvRGBXYdos", 152 | "2MxK75vSZLABDgqZRmUPMt6kfyabXZD81SJ", 153 | "2N5ETGDkhFFZqKQWhcbfYMihv9rSorMQLP9", 154 | "2ND1nUpT3in3HeMjssd7XkPa3j6nHXQgs1G" 155 | ] 156 | } 157 | } 158 | ``` 159 | 160 | ### createaccount 161 | ```bash 162 | $ curl -sSL -H "CoinType:btc" 'localhost:8000/createaccount?account=barfoo1' | jq . 163 | { 164 | "message": { 165 | "account": "barfoo1", 166 | "balance": 0, 167 | "addresses": [ 168 | "2N7y28aMq3xfxEZBFDvfvVzQ6aZLXr7np64" 169 | ] 170 | } 171 | } 172 | ``` 173 | 174 | ## USDT - omnicore 175 | 176 | ### getblockcount 177 | 178 | ```bash 179 | $ curl -sSL -H "CoinType:usdt" localhost:8000/getblockcount | jq . 180 | { 181 | "message": "1488490" 182 | } 183 | ``` 184 | 185 | 186 | 187 | ### getaddress 188 | 189 | ```bash 190 | $ curl -sSL -H "CoinType:usdt" localhost:8000/getaddress??account=test | jq . 191 | { 192 | "message": "mqyY2dscucMNBHAiUid88mEgkvZ3ADJLeZ" 193 | } 194 | ``` 195 | 196 | 197 | 198 | ### getaddressesbyaccount 199 | 200 | ```bash 201 | $ curl -sSL -H "CoinType:usdt" 'localhost:8000/getaddressesbyaccount?account=test' | jq . 202 | { 203 | "message": [ 204 | "mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4", 205 | "mvePubvtfpAo18ykLeZ1hD6BMYDismUqxf" 206 | ] 207 | } 208 | ``` 209 | 210 | 211 | 212 | ### getaccountinfo 213 | 214 | ```bash 215 | $ curl -sSL -H "CoinType:usdt" 'localhost:8000/getaccountinfo?account=test' | jq . 216 | { 217 | "message": { 218 | "account": "test", 219 | "balance": 10, 220 | "addresses": [ 221 | "mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4", 222 | "mvePubvtfpAo18ykLeZ1hD6BMYDismUqxf" 223 | ] 224 | } 225 | } 226 | ``` 227 | 228 | 229 | 230 | ### move 231 | 232 | ```bash 233 | $ curl -sSL -H "CoinType:usdt" 'localhost:8000/move?from=mvePubvtfpAo18ykLeZ1hD6BMYDismUqxf&to=mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4&amount=3' | jq . 234 | { 235 | "message": "success" 236 | } 237 | ``` 238 | 239 | 240 | 241 | ### sendfrom 242 | 243 | ```bash 244 | $ curl -sSL -H "CoinType:usdt" 'localhost:8000/sendfrom?from=test&address=mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4&amount=3' | jq . 245 | { 246 | "message": "success" 247 | } 248 | ``` 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /REFERENCE: -------------------------------------------------------------------------------- 1 | # bitcoind docker / dockerfile repository 2 | https://github.com/kylemanna/docker-bitcoind 3 | 4 | 5 | 6 | # golang rpc client api docs 7 | https://godoc.org/github.com/btcsuite/btcd/rpcclient 8 | 9 | 10 | # bitcoind doc for developers 11 | https://bitcoin.org/en/developer-guide#detecting-forks 12 | # ALL `accounts` related JSON RPC are depreciated, need update 13 | # to `labels` 14 | https://bitcointalk.org/index.php?topic=5043017.0 15 | 16 | # Multiple wallets 17 | https://bitcointechtalk.com/whats-new-in-bitcoin-core-v0-15-part-4-7c01c553783e 18 | 19 | # Very detailed explain what is account 20 | https://en.bitcoin.it/wiki/Help:Accounts_explained 21 | 22 | https://goethereumbook.org/client-setup/ 23 | 24 | 25 | # smart contract 26 | https://auth0.com/blog/an-introduction-to-ethereum-and-smart-contracts-part-2/ 27 | https://www.ethereum.org/token 28 | 29 | 30 | 31 | # Ominilayer RPC 32 | https://github.com/OmniLayer/omnicore/blob/master/src/omnicore/doc/rpc-api.md 33 | 34 | # address decode param 35 | https://github.com/btcsuite/btcd/blob/master/chaincfg/params.go 36 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmingxu/wallet-keeper/8024cb9cd6c9b6cdca163abac5549385de6f3d97/RELEASE -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | ## TODO list 2 | 3 | 4 | - ~~ use sendfrom instead of sendtoaddress(sendfrom will check sufficent balance) ~~ 5 | - ~~create unique account for each user, and setup default account~~ 6 | - use `move` to collect deposit coin regularly 7 | - separately log file to record coin operation 8 | 9 | [2019-04-26 14:51] 10 | 11 | - usdt need integration test 12 | - eth implementation 13 | 14 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.2 2 | -------------------------------------------------------------------------------- /api/base.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | type Base struct { 8 | } 9 | 10 | func R(data interface{}) gin.H { 11 | return gin.H{ 12 | "message": data, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /api/createaccount.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | var ACCOUNT_REGEXP = regexp.MustCompile("^([a-z|A-Z|0-9])+$") 15 | 16 | // Create func 17 | func (api *ApiServer) CreateAccount(c *gin.Context) { 18 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 19 | keeper := value.(keeper.Keeper) 20 | 21 | // retrive account from query 22 | account, found := c.GetQuery("account") 23 | if !found { 24 | c.JSON(http.StatusBadRequest, R("no account name specified")) 25 | return 26 | } 27 | 28 | // account validation 29 | if !accountValid(account) { 30 | c.JSON(http.StatusBadRequest, R("account not valid, ^([a-z|A-Z|0-9])+$")) 31 | return 32 | } 33 | 34 | if _, err := keeper.GetAccountInfo(account, 0); err == nil { 35 | c.JSON(http.StatusBadRequest, R("account exists")) 36 | return 37 | } 38 | 39 | created, err := keeper.CreateAccount(account) 40 | if err != nil { 41 | log.Error(err) 42 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 43 | } else { 44 | c.JSON(http.StatusOK, R(created)) 45 | } 46 | } 47 | 48 | func accountValid(account string) bool { 49 | return ACCOUNT_REGEXP.MatchString(account) 50 | } 51 | -------------------------------------------------------------------------------- /api/createaccount_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAccountValid(t *testing.T) { 8 | var accountFormat = map[string]bool{ 9 | "valid": true, 10 | " valid": false, 11 | "_valid": false, 12 | "xxx/valid": false, 13 | "xxx&valid": false, 14 | "xxx*alid": false, 15 | "xxx alid": false, 16 | } 17 | 18 | for k, v := range accountFormat { 19 | if accountValid(k) != v { 20 | t.Errorf("`%s` format should %+v", k, v) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/getaccount.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // Create func 14 | func (api *ApiServer) GetAccountInfo(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | // retrive account from query 19 | account, found := c.GetQuery("account") 20 | if !found { 21 | c.JSON(http.StatusBadRequest, R("no account name specified")) 22 | return 23 | } 24 | 25 | confarg, found := c.GetQuery("minconf") 26 | if !found { 27 | confarg = "6" 28 | } 29 | 30 | conf, err := strconv.ParseUint(confarg, 10, 32) 31 | if err != nil { 32 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 33 | return 34 | } 35 | 36 | accountInfo, err := keeper.GetAccountInfo(account, int(conf)) 37 | if err != nil { 38 | c.JSON(http.StatusNotFound, R("account not found")) 39 | } else { 40 | c.JSON(http.StatusOK, R(accountInfo)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/getaddress.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | "github.com/cmingxu/wallet-keeper/keeper/btc" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (api *ApiServer) GetAddress(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | account, found := c.GetQuery("account") 19 | if !found { 20 | account = btc.DEFAULT_ACCOUNT 21 | } 22 | address, err := keeper.GetAddress(account) 23 | if err != nil { 24 | log.Error(err) 25 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 26 | } else { 27 | c.JSON(http.StatusOK, R(address)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/getaddressesbyaccount.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | "github.com/cmingxu/wallet-keeper/keeper/btc" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (api *ApiServer) GetAddressesByAccount(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | account, found := c.GetQuery("account") 19 | if !found { 20 | account = btc.DEFAULT_ACCOUNT 21 | } 22 | 23 | addresses, err := keeper.GetAddressesByAccount(account) 24 | if err != nil { 25 | log.Error(err) 26 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 27 | } else { 28 | c.JSON(http.StatusOK, R(addresses)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api/getblockcount.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | 9 | "github.com/gin-gonic/gin" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func (api *ApiServer) GetBlockCount(c *gin.Context) { 14 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 15 | keeper := value.(keeper.Keeper) 16 | 17 | height, err := keeper.GetBlockCount() 18 | if err != nil { 19 | log.Error(err) 20 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 21 | } else { 22 | c.JSON(http.StatusOK, R(height)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/getnewaddress.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | "github.com/cmingxu/wallet-keeper/keeper/btc" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (api *ApiServer) GetNewAddress(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | account, found := c.GetQuery("account") 19 | if !found { 20 | account = btc.DEFAULT_ACCOUNT 21 | } 22 | 23 | address, err := keeper.GetNewAddress(account) 24 | if err != nil { 25 | log.Error(err) 26 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 27 | } else { 28 | c.JSON(http.StatusOK, R(address)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api/http.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | "github.com/cmingxu/wallet-keeper/keeper/btc" 9 | "github.com/cmingxu/wallet-keeper/keeper/eth" 10 | "github.com/cmingxu/wallet-keeper/keeper/usdt" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | const KEEPER_KEY = "keeper" 17 | const COIN_TYPE_HEADER = "CoinType" 18 | 19 | // http api list 20 | var METHODS_SUPPORTED = map[string]string{ 21 | // misc 22 | "/ping": "check if api service valid and backend bitcoin service healthy", 23 | "/health": "check system status", 24 | "/help": "display this message", 25 | 26 | // useful APIs here 27 | "/getaddress": "return address of specified account or default", 28 | "/getbalance": "sum balances of all accounts", 29 | "/listaccounts": "list accounts with amount, minconf is 6", 30 | "/getaccountinfo": "return account with corresponding bablance and addresses", 31 | "/createaccount": "create account and return receive address, error if account exists", 32 | "/sendfrom": "send amount of satoshi from some account to targets address", 33 | "/move": "move from one account to another", 34 | // private 35 | //"/getnewaddress": "return a new address of specified account or default", 36 | //"/getblockcount": "return height of the blockchain", 37 | //"/listunspentmin": "list all unspent transactions", 38 | //"/sendtoaddress": "Deprecicated: send amount of satoshi to address", 39 | //"/getaddress_with_balances": "all addresses together with balances", 40 | } 41 | 42 | type ApiServer struct { 43 | httpListenAddr string 44 | engine *gin.Engine 45 | btcKeeper keeper.Keeper 46 | usdtKeeper keeper.Keeper 47 | ethKeeper keeper.Keeper 48 | } 49 | 50 | //TODO valid host is valid 51 | func (api *ApiServer) InitBtcClient(host, user, pass, logDir string) (err error) { 52 | api.btcKeeper, err = btc.NewClient(host, user, pass, logDir) 53 | return err 54 | } 55 | 56 | func (api *ApiServer) InitUsdtClient(host, user, pass, logDir string, propertyId int64) (err error) { 57 | api.usdtKeeper, err = usdt.NewClient(host, user, pass, logDir, propertyId) 58 | return err 59 | } 60 | 61 | func (api *ApiServer) InitEthClient(host, walletDir, password, accountPath, logDir string) (err error) { 62 | api.ethKeeper, err = eth.NewClient(host, walletDir, password, accountPath, logDir) 63 | return err 64 | } 65 | 66 | //Check 67 | func (api *ApiServer) KeeperCheck() (err error) { 68 | if api.btcKeeper != nil { 69 | err = api.btcKeeper.Ping() 70 | if err != nil { 71 | err = errors.Wrap(err, "btc: ") 72 | } 73 | } 74 | 75 | if api.usdtKeeper != nil { 76 | err = api.usdtKeeper.Ping() 77 | if err != nil { 78 | err = errors.Wrap(err, "usdt: ") 79 | } 80 | } 81 | 82 | if api.ethKeeper != nil { 83 | err = api.ethKeeper.Ping() 84 | if err != nil { 85 | err = errors.Wrap(err, "eth: ") 86 | } 87 | } 88 | 89 | return err 90 | } 91 | 92 | func NewApiServer(addr string) *ApiServer { 93 | apiServer := &ApiServer{ 94 | httpListenAddr: addr, 95 | } 96 | 97 | // build gin.Engine and register routers 98 | apiServer.buildEngine() 99 | 100 | return apiServer 101 | } 102 | 103 | func (api *ApiServer) buildEngine() { 104 | r := gin.Default() 105 | 106 | // with midlleware determine which currency is active 107 | // within this very session, should be either `btc` or `usdt`. 108 | // if none of these present, abort this request and return caller 109 | // with bad request instantly. 110 | r.Use(func(c *gin.Context) { 111 | coin_type := strings.ToLower(c.Request.Header.Get(COIN_TYPE_HEADER)) 112 | switch coin_type { 113 | case "btc": 114 | c.Set(KEEPER_KEY, api.btcKeeper) 115 | break 116 | case "usdt": 117 | c.Set(KEEPER_KEY, api.usdtKeeper) 118 | break 119 | case "eth": 120 | c.Set(KEEPER_KEY, api.ethKeeper) 121 | break 122 | default: 123 | c.JSON(400, gin.H{"message": "no coin type specified, should be btc or usdt"}) 124 | } 125 | }) 126 | 127 | // APIs related to wallet 128 | r.GET("/getblockcount", api.GetBlockCount) 129 | r.GET("/getaddress", api.GetAddress) 130 | r.GET("/getaddressesbyaccount", api.GetAddressesByAccount) 131 | r.GET("/getnewaddress", api.GetNewAddress) 132 | r.GET("/createaccount", api.CreateAccount) 133 | r.GET("/getaccountinfo", api.GetAccountInfo) 134 | r.GET("/listaccounts", api.ListAccounts) 135 | r.GET("/sendtoaddress", api.SendToAddress) 136 | r.GET("/sendfrom", api.SendFrom) 137 | r.GET("/listunspentmin", api.ListUnspentMin) 138 | r.GET("/move", api.Move) 139 | // r.GET("/test", api.Test) 140 | 141 | // misc API 142 | r.GET("/ping", func(c *gin.Context) { 143 | c.JSON(200, gin.H{ 144 | "message": "pong", 145 | }) 146 | }) 147 | 148 | r.GET("/health", func(c *gin.Context) { 149 | err := api.KeeperCheck() 150 | if err != nil { 151 | c.JSON(500, gin.H{ 152 | "message": fmt.Sprint(err), 153 | }) 154 | } else { 155 | c.JSON(200, gin.H{ 156 | "message": "healthy", 157 | }) 158 | } 159 | }) 160 | 161 | r.GET("/help", func(c *gin.Context) { 162 | c.JSON(200, gin.H{ 163 | "methods": METHODS_SUPPORTED, 164 | }) 165 | }) 166 | 167 | api.engine = r 168 | } 169 | 170 | func (api *ApiServer) HttpListen() error { 171 | return api.engine.Run(api.httpListenAddr) 172 | } 173 | -------------------------------------------------------------------------------- /api/listaccounts.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // ListAccounts func 15 | func (api *ApiServer) ListAccounts(c *gin.Context) { 16 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 17 | keeper := value.(keeper.Keeper) 18 | 19 | confarg, found := c.GetQuery("minconf") 20 | if !found { 21 | confarg = "6" 22 | } 23 | 24 | conf, err := strconv.ParseUint(confarg, 10, 32) 25 | if err != nil { 26 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 27 | return 28 | } 29 | 30 | accounts, err := keeper.ListAccountsMinConf(int(conf)) 31 | if err != nil { 32 | log.Error(err) 33 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 34 | } else { 35 | c.JSON(http.StatusOK, R(accounts)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/listunspentmin.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | "github.com/gin-gonic/gin" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func (api *ApiServer) ListUnspentMin(c *gin.Context) { 14 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 15 | keeper := value.(keeper.Keeper) 16 | 17 | confarg, found := c.GetQuery("minconf") 18 | if !found { 19 | confarg = "1" 20 | } 21 | 22 | conf, err := strconv.ParseUint(confarg, 10, 32) 23 | if err != nil { 24 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 25 | return 26 | } 27 | 28 | result, err := keeper.ListUnspentMin(int(conf)) 29 | if err != nil { 30 | log.Error(err) 31 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 32 | } else { 33 | c.JSON(http.StatusOK, R(result)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /api/move.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | "github.com/gin-gonic/gin" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func (api *ApiServer) Move(c *gin.Context) { 14 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 15 | keeper := value.(keeper.Keeper) 16 | 17 | from, fromFound := c.GetQuery("from") 18 | to, toFound := c.GetQuery("to") 19 | amountarg, amountFound := c.GetQuery("amount") 20 | 21 | if !fromFound || !toFound || !amountFound { 22 | c.JSON(http.StatusBadRequest, R("from account/to account/amount are all mandatory field")) 23 | return 24 | } 25 | 26 | amount, err := strconv.ParseFloat(amountarg, 32) 27 | if err != nil { 28 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 29 | return 30 | } 31 | 32 | result, err := keeper.Move(from, to, amount) 33 | if err != nil { 34 | log.Error(err) 35 | c.JSON(http.StatusInternalServerError, R(fmt.Sprint(err))) 36 | return 37 | } 38 | 39 | if result { 40 | c.JSON(http.StatusOK, R("success")) 41 | } else { 42 | c.JSON(http.StatusBadRequest, R("fail")) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/sendfrom.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (api *ApiServer) SendFrom(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | from, fromFound := c.GetQuery("from") 19 | address, addrFound := c.GetQuery("address") 20 | amountArg, amountFound := c.GetQuery("amount") 21 | if !fromFound || !addrFound || !amountFound { 22 | c.JSON(http.StatusBadRequest, R("from, address, amount are mandatory fields")) 23 | return 24 | } 25 | 26 | amount, err := strconv.ParseFloat(amountArg, 64) 27 | if err != nil { 28 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 29 | return 30 | } 31 | 32 | // simple validation 33 | if amount <= 0 { 34 | c.JSON(http.StatusBadRequest, R("amount should at least greater than 0")) 35 | return 36 | } 37 | 38 | hash, err := keeper.SendFrom(from, address, amount) 39 | if err != nil { 40 | log.Error(err) 41 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 42 | } else { 43 | c.JSON(http.StatusOK, R(gin.H{"address": address, "txid": hash})) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/sendtoaddress.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | 10 | "github.com/gin-gonic/gin" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (api *ApiServer) SendToAddress(c *gin.Context) { 15 | value, _ := c.Get(KEEPER_KEY) // sure about the presence of this value 16 | keeper := value.(keeper.Keeper) 17 | 18 | address, addrFound := c.GetQuery("address") 19 | amountArg, amountFound := c.GetQuery("amount") 20 | if !addrFound || !amountFound { 21 | c.JSON(http.StatusBadRequest, R("both address and amount should present")) 22 | return 23 | } 24 | 25 | amount, err := strconv.ParseFloat(amountArg, 64) 26 | if err != nil { 27 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 28 | return 29 | } 30 | 31 | // simple validation 32 | if amount <= 0 { 33 | c.JSON(http.StatusBadRequest, R("amount should at least greater than 0")) 34 | return 35 | } 36 | 37 | hash, err := keeper.SendToAddress(address, amount) 38 | if err != nil { 39 | log.Error(err) 40 | c.JSON(http.StatusBadRequest, R(fmt.Sprint(err))) 41 | } else { 42 | c.JSON(http.StatusOK, R(gin.H{"address": address, "txid": hash})) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // import ( 4 | // "fmt" 5 | // 6 | // "github.com/cmingxu/wallet-keeper/keeper/eth" 7 | // "github.com/gin-gonic/gin" 8 | // log "github.com/sirupsen/logrus" 9 | // ) 10 | // 11 | // func (api *ApiServer) Test(c *gin.Context) { 12 | // log.Println("xxxxxxxxxxxx") 13 | // c.JSON(http.StatusOK, R("success")) 14 | // } 15 | -------------------------------------------------------------------------------- /cmd/flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/urfave/cli" 5 | ) 6 | 7 | var httpAddrFlag = cli.StringFlag{ 8 | Name: "http-listen-addr", 9 | Value: "0.0.0.0:8000", 10 | Usage: "http address of web application", 11 | EnvVar: "HTTP_LISTEN_ADDR", 12 | } 13 | 14 | var logLevelFlag = cli.StringFlag{ 15 | Name: "log-level", 16 | Value: "info", 17 | Usage: "default log level", 18 | EnvVar: "LOG_LEVEL", 19 | } 20 | 21 | var logDirFlag = cli.StringFlag{ 22 | Name: "log-dir", 23 | EnvVar: "LOG_DIR", 24 | } 25 | 26 | var btcRpcAddrFlag = cli.StringFlag{ 27 | Name: "btc-rpc-addr", 28 | Value: "192.168.0.101:8332", 29 | EnvVar: "BTC_RPCADDR", 30 | Usage: "[NOTICE] testnet and mainnet have different default port", 31 | } 32 | 33 | var btcRpcUserFlag = cli.StringFlag{ 34 | Name: "btc-rpc-user", 35 | Value: "foo", 36 | EnvVar: "BTC_RPCUSER", 37 | } 38 | 39 | var btcRpcPassFlag = cli.StringFlag{ 40 | Name: "btc-rpc-pass", 41 | Value: "qDDZdeQ5vw9XXFeVnXT4PZ--tGN2xNjjR4nrtyszZx0=", 42 | EnvVar: "BTC_PRCPASS", 43 | Usage: "password can be generate through scripts/rcpauth.py", 44 | } 45 | 46 | var usdtRpcAddrFlag = cli.StringFlag{ 47 | Name: "usdt-rpc-addr", 48 | Value: "localhost:18332", 49 | EnvVar: "USDT_RPCADDR", 50 | Usage: "[NOTICE] testnet and mainnet have different default port", 51 | } 52 | 53 | var usdtRpcUserFlag = cli.StringFlag{ 54 | Name: "usdt-rpc-user", 55 | Value: "foo", 56 | EnvVar: "USDT_RPCUSER", 57 | } 58 | 59 | var usdtRpcPassFlag = cli.StringFlag{ 60 | Name: "usdt-rpc-pass", 61 | Value: "usdtpass", 62 | EnvVar: "USDT_PRCPASS", 63 | Usage: "password can be generate through scripts/rcpauth.py", 64 | } 65 | 66 | var usdtPropertyIdFlag = cli.IntFlag{ 67 | Name: "usdt-property-id", 68 | Value: 2, 69 | EnvVar: "USDT_PROPERTY_ID", 70 | Usage: "property id of usdt, default is 2", 71 | } 72 | 73 | var ethRpcAddrFlag = cli.StringFlag{ 74 | Name: "eth-rpc-addr", 75 | Value: "http://192.168.0.101:8545", 76 | EnvVar: "ETH_RPCADDR", 77 | } 78 | 79 | var ethWalletDirFlag = cli.StringFlag{ 80 | Name: "eth-wallet-dir", 81 | Value: "/data/eth-wallet", 82 | EnvVar: "ETH_WALLET_DIR", 83 | } 84 | 85 | var ethAccountPasswordFlag = cli.StringFlag{ 86 | Name: "eth-account-password", 87 | Value: "password", 88 | EnvVar: "ETH_ACCOUNT_PASSWORD", 89 | } 90 | 91 | var ethAccountFlag = cli.StringFlag{ 92 | Name: "eth-account-path", 93 | Value: "/data/eth-accounts.json", 94 | EnvVar: "ETH_ACCOUNT_PATH", 95 | } 96 | 97 | var envFlag = cli.StringFlag{ 98 | Name: "env", 99 | Value: "production", 100 | EnvVar: "ENV", 101 | } 102 | 103 | var backendsFlag = cli.StringFlag{ 104 | Name: "backends", 105 | Value: "btc,usdt,eth", 106 | EnvVar: "BACKENDS", 107 | } 108 | -------------------------------------------------------------------------------- /cmd/gateway.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/cmingxu/wallet-keeper/api" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | type Backends string 15 | 16 | func (b Backends) Contains(target string) bool { 17 | return strings.Contains(strings.ToLower(string(b)), strings.ToLower(target)) 18 | } 19 | 20 | var gateCmd = cli.Command{ 21 | Name: "run", 22 | Aliases: []string{"r"}, 23 | Flags: []cli.Flag{ 24 | httpAddrFlag, 25 | btcRpcAddrFlag, 26 | btcRpcUserFlag, 27 | btcRpcPassFlag, 28 | usdtRpcAddrFlag, 29 | usdtRpcUserFlag, 30 | usdtRpcPassFlag, 31 | usdtPropertyIdFlag, 32 | ethRpcAddrFlag, 33 | ethWalletDirFlag, 34 | ethAccountPasswordFlag, 35 | ethAccountFlag, 36 | backendsFlag, 37 | }, 38 | Usage: "serve api gateway", 39 | Action: func(c *cli.Context) error { 40 | 41 | var err error 42 | apiServer := api.NewApiServer(c.String("http-listen-addr")) 43 | 44 | backends := Backends(c.String("backends")) 45 | if backends.Contains("btc") { 46 | log.Infof("connecting to btc addr: %s", c.String("btc-rpc-addr")) 47 | err = apiServer.InitBtcClient( 48 | c.String("btc-rpc-addr"), // host 49 | c.String("btc-rpc-user"), // user 50 | c.String("btc-rpc-pass"), // password 51 | c.GlobalString("log-dir"), // logDir 52 | ) 53 | if err != nil { 54 | log.Error(err) 55 | return err 56 | } 57 | } 58 | 59 | if backends.Contains("usdt") { 60 | log.Infof("connecting to usdt addr: %s", c.String("usdt-rpc-addr")) 61 | err = apiServer.InitUsdtClient( 62 | c.String("usdt-rpc-addr"), // host 63 | c.String("usdt-rpc-user"), // user 64 | c.String("usdt-rpc-pass"), // password 65 | c.GlobalString("log-dir"), // logDir 66 | int64(c.Int("usdt-property-id")), // propertyId 67 | ) 68 | if err != nil { 69 | log.Error(err) 70 | return err 71 | } 72 | } 73 | 74 | if backends.Contains("eth") { 75 | log.Infof("connecting to eth addr: %s", c.String("eth-rpc-addr")) 76 | err = apiServer.InitEthClient( 77 | c.String("eth-rpc-addr"), // host 78 | c.String("eth-wallet-dir"), 79 | c.String("eth-account-password"), 80 | c.String("eth-account-path"), 81 | c.GlobalString("log-dir"), // logDir 82 | ) 83 | if err != nil { 84 | log.Error(err) 85 | return err 86 | } 87 | } 88 | 89 | // Check btc/usdt rpc call connectivity 90 | err = apiServer.KeeperCheck() 91 | if err != nil { 92 | fmt.Fprintln(os.Stderr, err) 93 | return err 94 | } 95 | 96 | fmt.Fprintf(os.Stdout, "starting api gateway with addr: %s", c.String("http-listen-addr")) 97 | // start accepting http requests 98 | return apiServer.HttpListen() 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /cmd/logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func setupLogger(logLevel string, logDir string, formatter string) error { 13 | // set log output to local file if logPath specified, os.Stdout by default. 14 | if len(logDir) == 0 { 15 | fmt.Fprintln(os.Stdout, "use stdout as default log output") 16 | log.SetOutput(os.Stdout) 17 | } else { 18 | stat, err := os.Stat(logDir) 19 | if err != nil && os.IsNotExist(err) { 20 | fmt.Fprintf(os.Stderr, "log-dir %s not exists", logDir) 21 | return err 22 | } 23 | 24 | if !stat.IsDir() { 25 | fmt.Fprintf(os.Stderr, "log-dir %s is not a valid directory", logDir) 26 | return errors.New("log-dir is not a directory") 27 | } 28 | 29 | logPath := filepath.Join(logDir, "web.log") 30 | logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) 31 | if err != nil { 32 | return err 33 | } 34 | log.SetOutput(logFile) 35 | } 36 | 37 | // set log as json or text format 38 | if formatter == "json" { 39 | log.SetFormatter(&log.JSONFormatter{}) 40 | } else { 41 | log.SetFormatter(&log.TextFormatter{ 42 | DisableColors: true, 43 | FullTimestamp: true, 44 | }) 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | func main() { 11 | app := cli.NewApp() 12 | app.Name = "Universal BTC/USDT wallet gateway" 13 | app.Version = Version 14 | app.Commands = []cli.Command{ 15 | gateCmd, 16 | } 17 | 18 | app.Flags = []cli.Flag{ 19 | logLevelFlag, 20 | logDirFlag, 21 | envFlag, 22 | } 23 | 24 | app.Before = func(c *cli.Context) error { 25 | return setupLogger(c.String("log-level"), c.String("log-dir"), "json") 26 | } 27 | 28 | err := app.Run(os.Args) 29 | if err != nil { 30 | fmt.Fprintln(os.Stderr, err) 31 | os.Exit(1) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd/valuga.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "golang.org/x/net/proxy" 10 | ) 11 | 12 | func handleHTTP(w http.ResponseWriter, req *http.Request, dialer proxy.Dialer) { 13 | tp := http.Transport{ 14 | Dial: dialer.Dial, 15 | } 16 | resp, err := tp.RoundTrip(req) 17 | if err != nil { 18 | http.Error(w, err.Error(), http.StatusServiceUnavailable) 19 | return 20 | } 21 | defer resp.Body.Close() 22 | copyHeader(w.Header(), resp.Header) 23 | w.WriteHeader(resp.StatusCode) 24 | io.Copy(w, resp.Body) 25 | } 26 | 27 | func copyHeader(dst, src http.Header) { 28 | for k, vv := range src { 29 | for _, v := range vv { 30 | dst.Add(k, v) 31 | } 32 | } 33 | } 34 | 35 | func handleTunnel(w http.ResponseWriter, req *http.Request, dialer proxy.Dialer) { 36 | hijacker, ok := w.(http.Hijacker) 37 | if !ok { 38 | http.Error(w, "Hijacking not supported", http.StatusInternalServerError) 39 | return 40 | } 41 | srcConn, _, err := hijacker.Hijack() 42 | if err != nil { 43 | http.Error(w, err.Error(), http.StatusServiceUnavailable) 44 | return 45 | } 46 | dstConn, err := dialer.Dial("tcp", req.Host) 47 | if err != nil { 48 | srcConn.Close() 49 | return 50 | } 51 | 52 | srcConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) 53 | 54 | go transfer(dstConn, srcConn) 55 | go transfer(srcConn, dstConn) 56 | } 57 | 58 | func transfer(dst io.WriteCloser, src io.ReadCloser) { 59 | defer dst.Close() 60 | defer src.Close() 61 | 62 | io.Copy(dst, src) 63 | } 64 | 65 | func serveHTTP(w http.ResponseWriter, req *http.Request) { 66 | d := &net.Dialer{ 67 | Timeout: 10 * time.Second, 68 | } 69 | dialer, _ := proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, d) 70 | 71 | if req.Method == "CONNECT" { 72 | handleTunnel(w, req, dialer) 73 | } else { 74 | handleHTTP(w, req, dialer) 75 | } 76 | } 77 | 78 | // func main() { 79 | // http.ListenAndServe("127.0.0.1:8124", http.HandlerFunc(serveHTTP)) 80 | // } 81 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var Version string = "" 4 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ $(echo "$1" | cut -c1) = "-" ]; then 5 | echo "$0: assuming arguments for bitcoind" 6 | 7 | set -- bitcoind "$@" 8 | fi 9 | 10 | if [ $(echo "$1" | cut -c1) = "-" ] || [ "$1" = "bitcoind" ]; then 11 | mkdir -p "$BITCOIN_DATA" 12 | chmod 700 "$BITCOIN_DATA" 13 | chown -R bitcoin "$BITCOIN_DATA" 14 | 15 | echo "$0: setting data directory to $BITCOIN_DATA" 16 | 17 | set -- "$@" -datadir="$BITCOIN_DATA" 18 | fi 19 | 20 | if [ "$1" = "bitcoind" ] || [ "$1" = "bitcoin-cli" ] || [ "$1" = "bitcoin-tx" ]; then 21 | echo 22 | exec gosu bitcoin "$@" 23 | fi 24 | 25 | echo 26 | exec "$@" 27 | -------------------------------------------------------------------------------- /docs/BTC-APIs.md: -------------------------------------------------------------------------------- 1 | 2 | == Blockchain == 3 | getbestblockhash 4 | getblock "blockhash" ( verbosity ) 5 | getblockchaininfo 6 | getblockcount 7 | getblockhash height 8 | getblockheader "hash" ( verbose ) 9 | getblockstats hash_or_height ( stats ) 10 | getchaintips 11 | getchaintxstats ( nblocks blockhash ) 12 | getdifficulty 13 | getmempoolancestors txid (verbose) 14 | getmempooldescendants txid (verbose) 15 | getmempoolentry txid 16 | getmempoolinfo 17 | getrawmempool ( verbose ) 18 | gettxout "txid" n ( include_mempool ) 19 | gettxoutproof ["txid",...] ( blockhash ) 20 | gettxoutsetinfo 21 | preciousblock "blockhash" 22 | pruneblockchain 23 | savemempool 24 | scantxoutset ( ) 25 | verifychain ( checklevel nblocks ) 26 | verifytxoutproof "proof" 27 | 28 | == Control == 29 | getmemoryinfo ("mode") 30 | help ( "command" ) 31 | logging ( ) 32 | stop 33 | uptime 34 | 35 | == Generating == 36 | generate nblocks ( maxtries ) 37 | generatetoaddress nblocks address (maxtries) 38 | 39 | == Mining == 40 | getblocktemplate ( TemplateRequest ) 41 | getmininginfo 42 | getnetworkhashps ( nblocks height ) 43 | prioritisetransaction 44 | submitblock "hexdata" ( "dummy" ) 45 | 46 | == Network == 47 | addnode "node" "add|remove|onetry" 48 | clearbanned 49 | disconnectnode "[address]" [nodeid] 50 | getaddednodeinfo ( "node" ) 51 | getconnectioncount 52 | getnettotals 53 | getnetworkinfo 54 | getpeerinfo 55 | listbanned 56 | ping 57 | setban "subnet" "add|remove" (bantime) (absolute) 58 | setnetworkactive true|false 59 | 60 | == Rawtransactions == 61 | combinepsbt ["psbt",...] 62 | combinerawtransaction ["hexstring",...] 63 | converttopsbt "hexstring" ( permitsigdata iswitness ) 64 | createpsbt [{"txid":"id","vout":n},...] [{"address":amount},{"data":"hex"},...] ( locktime ) ( replaceable ) 65 | createrawtransaction [{"txid":"id","vout":n},...] [{"address":amount},{"data":"hex"},...] ( locktime ) ( replaceable ) 66 | decodepsbt "psbt" 67 | decoderawtransaction "hexstring" ( iswitness ) 68 | decodescript "hexstring" 69 | finalizepsbt "psbt" ( extract ) 70 | fundrawtransaction "hexstring" ( options iswitness ) 71 | getrawtransaction "txid" ( verbose "blockhash" ) 72 | sendrawtransaction "hexstring" ( allowhighfees ) 73 | signrawtransaction "hexstring" ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] ["privatekey1",...] sighashtype ) 74 | signrawtransactionwithkey "hexstring" ["privatekey1",...] ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] sighashtype ) 75 | testmempoolaccept ["rawtxs"] ( allowhighfees ) 76 | 77 | == Util == 78 | createmultisig nrequired ["key",...] ( "address_type" ) 79 | estimatesmartfee conf_target ("estimate_mode") 80 | signmessagewithprivkey "privkey" "message" 81 | validateaddress "address" 82 | verifymessage "address" "signature" "message" 83 | 84 | == Wallet == 85 | abandontransaction "txid" 86 | abortrescan 87 | addmultisigaddress nrequired ["key",...] ( "label" "address_type" ) 88 | backupwallet "destination" 89 | bumpfee "txid" ( options ) 90 | createwallet "wallet_name" ( disable_private_keys ) 91 | dumpprivkey "address" 92 | dumpwallet "filename" 93 | encryptwallet "passphrase" 94 | getaccount "address" 95 | getaccountaddress "account" 96 | getaddressesbyaccount "account" 97 | getaddressesbylabel "label" 98 | getaddressinfo "address" 99 | getbalance ( "account" minconf include_watchonly ) 100 | getnewaddress ( "label" "address_type" ) 101 | getrawchangeaddress ( "address_type" ) 102 | getreceivedbylabel "label" ( minconf ) 103 | getreceivedbyaddress "address" ( minconf ) 104 | gettransaction "txid" ( include_watchonly ) 105 | getunconfirmedbalance 106 | getwalletinfo 107 | importaddress "address" ( "label" rescan p2sh ) 108 | importmulti "requests" ( "options" ) 109 | importprivkey "privkey" ( "label" ) ( rescan ) 110 | importprunedfunds 111 | importpubkey "pubkey" ( "label" rescan ) 112 | importwallet "filename" 113 | keypoolrefill ( newsize ) 114 | listaccounts ( minconf include_watchonly) 115 | listaddressgroupings 116 | listlabels ( "purpose" ) 117 | listlockunspent 118 | listreceivedbylabel ( minconf include_empty include_watchonly) 119 | listreceivedbyaddress ( minconf include_empty include_watchonly address_filter ) 120 | listsinceblock ( "blockhash" target_confirmations include_watchonly include_removed ) 121 | listtransactions ( "account" count skip include_watchonly) 122 | listunspent ( minconf maxconf ["addresses",...] [include_unsafe] [query_options]) 123 | listwallets 124 | loadwallet "filename" 125 | lockunspent unlock ([{"txid":"txid","vout":n},...]) 126 | move "fromaccount" "toaccount" amount ( minconf "comment" ) 127 | removeprunedfunds "txid" 128 | rescanblockchain ("start_height") ("stop_height") 129 | sendfrom "fromaccount" "toaddress" amount ( minconf "comment" "comment_to" ) 130 | sendmany "" "fromaccount" {"address":amount,...} ( minconf "comment" ["address",...] replaceable conf_target "estimate_mode") 131 | sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode") 132 | setlabel "address" "label" 133 | sethdseed ( "newkeypool" "seed" ) 134 | settxfee amount 135 | signmessage "address" "message" 136 | signrawtransactionwithwallet "hexstring" ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] sighashtype ) 137 | unloadwallet ( "wallet_name" ) 138 | walletcreatefundedpsbt [{"txid":"id","vout":n},...] [{"address":amount},{"data":"hex"},...] ( locktime ) ( replaceable ) ( options bip32derivs ) 139 | walletlock 140 | walletpassphrase "passphrase" timeout 141 | walletpassphrasechange "oldpassphrase" "newpassphrase" 142 | walletprocesspsbt "psbt" ( sign "sighashtype" bip32derivs ) 143 | 144 | == Zmq == 145 | getzmqnotifications 146 | -------------------------------------------------------------------------------- /docs/ETH-ARGS.md: -------------------------------------------------------------------------------- 1 | / # geth --help 2 | NAME: 3 | geth - the go-ethereum command line interface 4 | 5 | Copyright 2013-2019 The go-ethereum Authors 6 | 7 | USAGE: 8 | geth [options] command [command options] [arguments...] 9 | 10 | VERSION: 11 | 1.9.0-unstable-504f88b6 12 | 13 | COMMANDS: 14 | account Manage accounts 15 | attach Start an interactive JavaScript environment (connect to node) 16 | console Start an interactive JavaScript environment 17 | copydb Create a local chain from a target chaindata folder 18 | dump Dump a specific block from storage 19 | dumpconfig Show configuration values 20 | export Export blockchain into file 21 | export-preimages Export the preimage database into an RLP stream 22 | import Import a blockchain file 23 | import-preimages Import the preimage database from an RLP stream 24 | init Bootstrap and initialize a new genesis block 25 | js Execute the specified JavaScript files 26 | license Display license information 27 | makecache Generate ethash verification cache (for testing) 28 | makedag Generate ethash mining DAG (for testing) 29 | removedb Remove blockchain and state databases 30 | version Print version numbers 31 | wallet Manage Ethereum presale wallets 32 | help, h Shows a list of commands or help for one command 33 | 34 | ETHEREUM OPTIONS: 35 | --config value TOML configuration file 36 | --datadir "/root/.ethereum" Data directory for the databases and keystore 37 | --keystore Directory for the keystore (default = inside the datadir) 38 | --nousb Disables monitoring for and managing USB hardware wallets 39 | --networkid value Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby) (default: 1) 40 | --testnet Ropsten network: pre-configured proof-of-work test network 41 | --rinkeby Rinkeby network: pre-configured proof-of-authority test network 42 | --goerli Görli network: pre-configured proof-of-authority test network 43 | --syncmode "fast" Blockchain sync mode ("fast", "full", or "light") 44 | --exitwhensynced Exits after block synchronisation completes 45 | --gcmode value Blockchain garbage collection mode ("full", "archive") (default: "full") 46 | --ethstats value Reporting URL of a ethstats service (nodename:secret@host:port) 47 | --identity value Custom node name 48 | --lightserv value Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100) (default: 0) 49 | --lightbwin value Incoming bandwidth limit for light server (1000 bytes/sec, 0 = unlimited) (default: 1000) 50 | --lightbwout value Outgoing bandwidth limit for light server (1000 bytes/sec, 0 = unlimited) (default: 5000) 51 | --lightpeers value Maximum number of LES client peers (default: 100) 52 | --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength 53 | --whitelist value Comma separated block number-to-hash mappings to enforce (=) 54 | 55 | DEVELOPER CHAIN OPTIONS: 56 | --dev Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled 57 | --dev.period value Block period to use in developer mode (0 = mine only if transaction pending) (default: 0) 58 | 59 | ETHASH OPTIONS: 60 | --ethash.cachedir Directory to store the ethash verification caches (default = inside the datadir) 61 | --ethash.cachesinmem value Number of recent ethash caches to keep in memory (16MB each) (default: 2) 62 | --ethash.cachesondisk value Number of recent ethash caches to keep on disk (16MB each) (default: 3) 63 | --ethash.dagdir "/root/.ethash" Directory to store the ethash mining DAGs (default = inside home folder) 64 | --ethash.dagsinmem value Number of recent ethash mining DAGs to keep in memory (1+GB each) (default: 1) 65 | --ethash.dagsondisk value Number of recent ethash mining DAGs to keep on disk (1+GB each) (default: 2) 66 | 67 | TRANSACTION POOL OPTIONS: 68 | --txpool.locals value Comma separated accounts to treat as locals (no flush, priority inclusion) 69 | --txpool.nolocals Disables price exemptions for locally submitted transactions 70 | --txpool.journal value Disk journal for local transaction to survive node restarts (default: "transactions.rlp") 71 | --txpool.rejournal value Time interval to regenerate the local transaction journal (default: 1h0m0s) 72 | --txpool.pricelimit value Minimum gas price limit to enforce for acceptance into the pool (default: 1) 73 | --txpool.pricebump value Price bump percentage to replace an already existing transaction (default: 10) 74 | --txpool.accountslots value Minimum number of executable transaction slots guaranteed per account (default: 16) 75 | --txpool.globalslots value Maximum number of executable transaction slots for all accounts (default: 4096) 76 | --txpool.accountqueue value Maximum number of non-executable transaction slots permitted per account (default: 64) 77 | --txpool.globalqueue value Maximum number of non-executable transaction slots for all accounts (default: 1024) 78 | --txpool.lifetime value Maximum amount of time non-executable transaction are queued (default: 3h0m0s) 79 | 80 | PERFORMANCE TUNING OPTIONS: 81 | --cache value Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode) (default: 1024) 82 | --cache.database value Percentage of cache memory allowance to use for database io (default: 50) 83 | --cache.trie value Percentage of cache memory allowance to use for trie caching (default = 25% full mode, 50% archive mode) (default: 25) 84 | --cache.gc value Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode) (default: 25) 85 | --cache.noprefetch Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data) 86 | 87 | ACCOUNT OPTIONS: 88 | --unlock value Comma separated list of accounts to unlock 89 | --password value Password file to use for non-interactive password input 90 | --signer value External signer (url or path to ipc file) 91 | --allow-insecure-unlock Allow insecure account unlocking when account-related RPCs are exposed by http 92 | 93 | API AND CONSOLE OPTIONS: 94 | --rpc Enable the HTTP-RPC server 95 | --rpcaddr value HTTP-RPC server listening interface (default: "localhost") 96 | --rpcport value HTTP-RPC server listening port (default: 8545) 97 | --rpcapi value API's offered over the HTTP-RPC interface 98 | --rpc.gascap value Sets a cap on gas that can be used in eth_call/estimateGas (default: 0) 99 | --ws Enable the WS-RPC server 100 | --wsaddr value WS-RPC server listening interface (default: "localhost") 101 | --wsport value WS-RPC server listening port (default: 8546) 102 | --wsapi value API's offered over the WS-RPC interface 103 | --wsorigins value Origins from which to accept websockets requests 104 | --ipcdisable Disable the IPC-RPC server 105 | --ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it) 106 | --rpccorsdomain value Comma separated list of domains from which to accept cross origin requests (browser enforced) 107 | --rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") 108 | --jspath loadScript JavaScript root path for loadScript (default: ".") 109 | --exec value Execute JavaScript statement 110 | --preload value Comma separated list of JavaScript files to preload into the console 111 | 112 | NETWORKING OPTIONS: 113 | --bootnodes value Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers) 114 | --bootnodesv4 value Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes) 115 | --bootnodesv5 value Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) 116 | --port value Network listening port (default: 30303) 117 | --maxpeers value Maximum number of network peers (network disabled if set to 0) (default: 50) 118 | --maxpendpeers value Maximum number of pending connection attempts (defaults used if set to 0) (default: 0) 119 | --nat value NAT port mapping mechanism (any|none|upnp|pmp|extip:) (default: "any") 120 | --nodiscover Disables the peer discovery mechanism (manual peer addition) 121 | --v5disc Enables the experimental RLPx V5 (Topic Discovery) mechanism 122 | --netrestrict value Restricts network communication to the given IP networks (CIDR masks) 123 | --nodekey value P2P node key file 124 | --nodekeyhex value P2P node key as hex (for testing) 125 | 126 | MINER OPTIONS: 127 | --mine Enable mining 128 | --miner.threads value Number of CPU threads to use for mining (default: 0) 129 | --miner.notify value Comma separated HTTP URL list to notify of new work packages 130 | --miner.gasprice "1000000000" Minimum gas price for mining a transaction 131 | --miner.gastarget value Target gas floor for mined blocks (default: 8000000) 132 | --miner.gaslimit value Target gas ceiling for mined blocks (default: 8000000) 133 | --miner.etherbase value Public address for block mining rewards (default = first account) (default: "0") 134 | --miner.extradata value Block extra data set by the miner (default = client version) 135 | --miner.recommit value Time interval to recreate the block being mined (default: 3s) 136 | --miner.noverify Disable remote sealing verification 137 | 138 | GAS PRICE ORACLE OPTIONS: 139 | --gpoblocks value Number of recent blocks to check for gas prices (default: 20) 140 | --gpopercentile value Suggested gas price is the given percentile of a set of recent transaction gas prices (default: 60) 141 | 142 | VIRTUAL MACHINE OPTIONS: 143 | --vmdebug Record information useful for VM and contract debugging 144 | --vm.evm value External EVM configuration (default = built-in interpreter) 145 | --vm.ewasm value External ewasm configuration (default = built-in interpreter) 146 | 147 | LOGGING AND DEBUGGING OPTIONS: 148 | --fakepow Disables proof-of-work verification 149 | --nocompaction Disables db compaction after import 150 | --verbosity value Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3) 151 | --vmodule value Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4) 152 | --backtrace value Request a stack trace at a specific logging statement (e.g. "block.go:271") 153 | --debug Prepends log messages with call-site location (file and line number) 154 | --pprof Enable the pprof HTTP server 155 | --pprofaddr value pprof HTTP server listening interface (default: "127.0.0.1") 156 | --pprofport value pprof HTTP server listening port (default: 6060) 157 | --memprofilerate value Turn on memory profiling with the given rate (default: 524288) 158 | --blockprofilerate value Turn on block profiling with the given rate (default: 0) 159 | --cpuprofile value Write CPU profile to the given file 160 | --trace value Write execution trace to the given file 161 | 162 | METRICS AND STATS OPTIONS: 163 | --metrics Enable metrics collection and reporting 164 | --metrics.expensive Enable expensive metrics collection and reporting 165 | --metrics.influxdb Enable metrics export/push to an external InfluxDB database 166 | --metrics.influxdb.endpoint value InfluxDB API endpoint to report metrics to (default: "http://localhost:8086") 167 | --metrics.influxdb.database value InfluxDB database name to push reported metrics to (default: "geth") 168 | --metrics.influxdb.username value Username to authorize access to the database (default: "test") 169 | --metrics.influxdb.password value Password to authorize access to the database (default: "test") 170 | --metrics.influxdb.tags value Comma-separated InfluxDB tags (key/values) attached to all measurements (default: "host=localhost") 171 | 172 | WHISPER (EXPERIMENTAL) OPTIONS: 173 | --shh Enable Whisper 174 | --shh.maxmessagesize value Max message size accepted (default: 1048576) 175 | --shh.pow value Minimum POW accepted (default: 0.2) 176 | --shh.restrict-light Restrict connection between two whisper light clients 177 | 178 | DEPRECATED OPTIONS: 179 | --minerthreads value Number of CPU threads to use for mining (deprecated, use --miner.threads) (default: 0) 180 | --targetgaslimit value Target gas floor for mined blocks (deprecated, use --miner.gastarget) (default: 8000000) 181 | --gasprice "1000000000" Minimum gas price for mining a transaction (deprecated, use --miner.gasprice) 182 | --etherbase value Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase) (default: "0") 183 | --extradata value Block extra data set by the miner (default = client version, deprecated, use --miner.extradata) 184 | 185 | MISC OPTIONS: 186 | --ulc.config value Config file to use for ultra light client mode 187 | --ulc.onlyannounce ULC server sends announcements only 188 | --ulc.trusted value List of trusted ULC servers 189 | --ulc.fraction value Minimum % of trusted ULC servers required to announce a new head (default: 0) 190 | --override.constantinople value Manually specify constantinople fork-block, overriding the bundled setting (default: 0) 191 | --graphql Enable the GraphQL server 192 | --graphql.addr value GraphQL server listening interface (default: "localhost") 193 | --graphql.port value GraphQL server listening port (default: 8547) 194 | --graphql.rpccorsdomain value Comma separated list of domains from which to accept cross origin requests (browser enforced) 195 | --graphql.rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") 196 | --help, -h show help 197 | 198 | 199 | COPYRIGHT: 200 | Copyright 2013-2019 The go-ethereum Authors 201 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cmingxu/wallet-keeper 2 | 3 | require ( 4 | github.com/allegro/bigcache v1.2.0 // indirect 5 | github.com/aristanetworks/goarista v0.0.0-20190429220743-799535f6f364 // indirect 6 | github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17 7 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d 8 | github.com/deckarep/golang-set v1.7.1 // indirect 9 | github.com/ethereum/go-ethereum v1.8.27 10 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 // indirect 11 | github.com/gin-gonic/gin v1.3.0 12 | github.com/go-stack/stack v1.8.0 // indirect 13 | github.com/google/uuid v1.1.1 14 | github.com/mattn/go-isatty v0.0.7 // indirect 15 | github.com/pborman/uuid v1.2.0 // indirect 16 | github.com/pkg/errors v0.8.1 17 | github.com/rjeczalik/notify v0.9.2 // indirect 18 | github.com/rs/cors v1.6.0 // indirect 19 | github.com/sirupsen/logrus v1.4.1 20 | github.com/syndtr/goleveldb v1.0.0 // indirect 21 | github.com/ugorji/go v1.1.4 // indirect 22 | github.com/urfave/cli v1.20.0 // indirect 23 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 // indirect 24 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 2 | github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM= 3 | github.com/allegro/bigcache v1.2.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 4 | github.com/aristanetworks/goarista v0.0.0-20190429220743-799535f6f364 h1:oYKfJZsLWjZM2FSZE1MRYvnRBL9cH2LE+Lo6o/lCUQ8= 5 | github.com/aristanetworks/goarista v0.0.0-20190429220743-799535f6f364/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= 6 | github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17 h1:m0N5Vg5nP3zEz8TREZpwX3gt4Biw3/8fbIf4A3hO96g= 7 | github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= 8 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= 9 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 10 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= 11 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 12 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= 13 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 14 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 15 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 16 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= 17 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 18 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 19 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= 22 | github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 23 | github.com/ethereum/go-ethereum v1.8.27 h1:d+gkiLaBDk5fn3Pe/xNVaMrB/ozI+AUB2IlVBp29IrY= 24 | github.com/ethereum/go-ethereum v1.8.27/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= 25 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 26 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= 27 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 28 | github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= 29 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 30 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 31 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 32 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 35 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 36 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 37 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 38 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 39 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 40 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 41 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 42 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 43 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 44 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 45 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 46 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 47 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 48 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 49 | github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 50 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 51 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 52 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 54 | github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= 55 | github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= 56 | github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= 57 | github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 58 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= 59 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 60 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 62 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 63 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 64 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 65 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 66 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 67 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 68 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 69 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 70 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 71 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 72 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo= 73 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 74 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 76 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 78 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 79 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 80 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 81 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 82 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 85 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 86 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 87 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 88 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 89 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 90 | -------------------------------------------------------------------------------- /keeper/btc/client.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/cmingxu/wallet-keeper/keeper" 8 | 9 | "github.com/btcsuite/btcd/btcjson" 10 | "github.com/btcsuite/btcd/chaincfg" 11 | "github.com/btcsuite/btcd/rpcclient" 12 | "github.com/btcsuite/btcutil" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | // default account for reserved usage, which represent 17 | // account belongs to enterpise default 18 | var DEFAULT_ACCOUNT = "duckduck" 19 | 20 | // default confirmation 21 | var DEFAULT_CONFIRMATION = 6 22 | 23 | type Client struct { 24 | rpcClient *rpcclient.Client 25 | l *log.Logger 26 | } 27 | 28 | // connect to bitcoind with HTTP RPC transport 29 | func NewClient(host, user, pass, logDir string) (*Client, error) { 30 | connCfg := &rpcclient.ConnConfig{ 31 | Host: host, 32 | User: user, 33 | Pass: pass, 34 | HTTPPostMode: true, 35 | DisableTLS: true, 36 | } 37 | 38 | client := &Client{} 39 | var err error 40 | client.rpcClient, err = rpcclient.New(connCfg, nil) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | logPath := filepath.Join(logDir, "btc.log") 46 | logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) 47 | if err != nil { 48 | return nil, err 49 | } 50 | client.l = &log.Logger{ 51 | Out: logFile, 52 | Level: log.DebugLevel, 53 | Formatter: new(log.JSONFormatter), 54 | } 55 | 56 | return client, nil 57 | } 58 | 59 | // Ping 60 | func (client *Client) Ping() error { 61 | return client.rpcClient.Ping() 62 | } 63 | 64 | // GetBlockCount 65 | func (client *Client) GetBlockCount() (int64, error) { 66 | client.l.Infof("[GetBlockCount]") 67 | return client.rpcClient.GetBlockCount() 68 | } 69 | 70 | // GetAddress - default address 71 | func (client *Client) GetAddress(account string) (string, error) { 72 | if len(account) == 0 { 73 | account = DEFAULT_ACCOUNT 74 | } 75 | client.l.Infof("[GetAddress] for account %s", account) 76 | 77 | address, err := client.rpcClient.GetAccountAddress(account) 78 | if err != nil { 79 | return "", err 80 | } 81 | 82 | return address.String(), nil 83 | } 84 | 85 | // Create Account 86 | // Returns customized account info 87 | func (client *Client) CreateAccount(account string) (keeper.Account, error) { 88 | client.l.Infof("[CreateAccount] for account %s", account) 89 | // GetAddress will create account if not exists 90 | address, err := client.GetAddress(account) 91 | if err != nil { 92 | return keeper.Account{}, err 93 | } 94 | 95 | return keeper.Account{ 96 | Account: account, 97 | Balance: 0.0, 98 | Addresses: []string{address}, 99 | }, nil 100 | } 101 | 102 | // GetAccountInfo 103 | func (client *Client) GetAccountInfo(account string, minConf int) (keeper.Account, error) { 104 | var accountsMap map[string]float64 105 | var err error 106 | 107 | client.l.Infof("[GetAccountInfo] account %s, with minConf %d", account, minConf) 108 | if accountsMap, err = client.ListAccountsMinConf(minConf); err != nil { 109 | return keeper.Account{}, err 110 | } 111 | 112 | balance, found := accountsMap[account] 113 | if !found { 114 | return keeper.Account{}, keeper.ErrAccountNotFound 115 | } 116 | 117 | addresses, err := client.GetAddressesByAccount(account) 118 | if err != nil { 119 | return keeper.Account{}, err 120 | } 121 | 122 | return keeper.Account{ 123 | Account: account, 124 | Balance: balance, 125 | Addresses: addresses, 126 | }, nil 127 | } 128 | 129 | // TODO 130 | // GetNewAddress does map to `getnewaddress` rpc call now 131 | // rpcclient doesn't have such golang wrapper func. 132 | func (client *Client) GetNewAddress(account string) (string, error) { 133 | if len(account) == 0 { 134 | account = DEFAULT_ACCOUNT 135 | } 136 | 137 | address, err := client.rpcClient.GetNewAddress(account) 138 | if err != nil { 139 | return "", err 140 | } 141 | 142 | client.l.Infof("[GetNewAddress] for account %s, address is %s", account, address.String()) 143 | return address.String(), nil 144 | } 145 | 146 | // GetAddressesByAccount 147 | func (client *Client) GetAddressesByAccount(account string) ([]string, error) { 148 | if len(account) == 0 { 149 | account = DEFAULT_ACCOUNT 150 | } 151 | 152 | client.l.Infof("[GetAddressesByAccount] for account %s", account) 153 | addresses, err := client.rpcClient.GetAddressesByAccount(account) 154 | if err != nil { 155 | return []string{}, err 156 | } 157 | 158 | addrs := make([]string, 0) 159 | for _, addr := range addresses { 160 | addrs = append(addrs, addr.String()) 161 | } 162 | 163 | return addrs, nil 164 | } 165 | 166 | // ListAccountsMinConf 167 | func (client *Client) ListAccountsMinConf(minConf int) (map[string]float64, error) { 168 | accounts := make(map[string]float64) 169 | 170 | client.l.Infof("[ListAccountsMinConf] with minConf %d", minConf) 171 | accountsWithAmount, err := client.rpcClient.ListAccountsMinConf(minConf) 172 | if err != nil { 173 | return accounts, err 174 | } 175 | 176 | for account, amount := range accountsWithAmount { 177 | accounts[account] = amount.ToBTC() 178 | } 179 | 180 | return accounts, nil 181 | } 182 | 183 | // SendToAddress 184 | func (client *Client) SendToAddress(address string, amount float64) (string, error) { 185 | client.l.Infof("[SendToAddress] to address %s: %f", address, amount) 186 | decoded, err := decodeAddress(address, chaincfg.MainNetParams) 187 | if err != nil { 188 | return "", err 189 | } 190 | 191 | btcAmount, err := convertToBtcAmount(amount) 192 | if err != nil { 193 | return "", err 194 | } 195 | 196 | hash, err := client.rpcClient.SendToAddressComment(decoded, btcAmount, "", "") 197 | if err != nil { 198 | return "", err 199 | } 200 | client.l.Infof("[SendToAddress] to address %s: %f, hash is %s", address, amount, hash) 201 | 202 | return hash.String(), nil 203 | } 204 | 205 | // TODO check validity of account and have sufficent balance 206 | func (client *Client) SendFrom(account, address string, amount float64) (string, error) { 207 | client.l.Infof("[SendFrom] from account %s to address %s with amount %f ", account, address, amount) 208 | decoded, err := decodeAddress(address, chaincfg.MainNetParams) 209 | if err != nil { 210 | return "", err 211 | } 212 | 213 | btcAmount, err := convertToBtcAmount(amount) 214 | if err != nil { 215 | return "", err 216 | } 217 | 218 | hash, err := client.rpcClient.SendFrom(account, decoded, btcAmount) 219 | if err != nil { 220 | return "", err 221 | } 222 | client.l.Infof("[SendFrom] from account %s to address %s with amount %f, result hash %s ", account, address, amount, hash) 223 | 224 | return hash.String(), nil 225 | } 226 | 227 | // Move 228 | func (client *Client) Move(from, to string, amount float64) (bool, error) { 229 | client.l.Infof("[Move] from %s to %s with amount %f ", from, to, amount) 230 | btcAmount, err := convertToBtcAmount(amount) 231 | if err != nil { 232 | return false, err 233 | } 234 | 235 | client.l.Infof("[Move] success from %s to %s with amount %f ", from, to, amount) 236 | return client.rpcClient.Move(from, to, btcAmount) 237 | } 238 | 239 | // ListUnspentMin 240 | func (client *Client) ListUnspentMin(minConf int) ([]btcjson.ListUnspentResult, error) { 241 | return client.rpcClient.ListUnspentMin(minConf) 242 | } 243 | 244 | // decodeAddress from string to decodedAddress 245 | func decodeAddress(address string, cfg chaincfg.Params) (btcutil.Address, error) { 246 | decodedAddress, err := btcutil.DecodeAddress(address, &cfg) 247 | if err != nil { 248 | return nil, err 249 | } 250 | 251 | return decodedAddress, nil 252 | } 253 | 254 | func convertToBtcAmount(amount float64) (btcutil.Amount, error) { 255 | return btcutil.NewAmount(amount) 256 | } 257 | -------------------------------------------------------------------------------- /keeper/eth/client.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | "time" 11 | 12 | "github.com/cmingxu/wallet-keeper/keeper" 13 | 14 | "github.com/btcsuite/btcd/btcjson" 15 | "github.com/ethereum/go-ethereum/accounts" 16 | "github.com/ethereum/go-ethereum/accounts/keystore" 17 | "github.com/ethereum/go-ethereum/common" 18 | "github.com/ethereum/go-ethereum/common/hexutil" 19 | "github.com/ethereum/go-ethereum/core/types" 20 | "github.com/ethereum/go-ethereum/rpc" 21 | log "github.com/sirupsen/logrus" 22 | ) 23 | 24 | var ( 25 | // account file not valid 26 | ErrNotValidAccountFile = errors.New("not valid account file") 27 | // target is not valid directory 28 | ErrNotDirectory = errors.New("not valid directory") 29 | // address is not valid 30 | ErrInvalidAddress = errors.New("invalid address") 31 | ) 32 | 33 | type Client struct { 34 | l *log.Logger 35 | 36 | // Checkout https://github.com/ethereum/go-ethereum/blob/master/rpc/client.go 37 | // for more details. 38 | ethRpcClient *rpc.Client 39 | 40 | // fs directory where to store wallet 41 | walletDir string 42 | password string 43 | 44 | // keystore 45 | store *keystore.KeyStore 46 | 47 | accountFilePath string 48 | // account/address map lock, since ethereum doesn't support account 49 | // we should have our own account/address map internally. 50 | // only with this map we can provide services for the upstream services. 51 | accountAddressMap map[string]string 52 | accountAddressLock sync.Mutex 53 | } 54 | 55 | func NewClient(host, walletDir, accountFilePath, password, logDir string) (*Client, error) { 56 | client := &Client{ 57 | walletDir: walletDir, 58 | password: password, 59 | accountFilePath: accountFilePath, 60 | accountAddressMap: make(map[string]string), 61 | accountAddressLock: sync.Mutex{}, 62 | } 63 | 64 | // accountAddressMap initialization 65 | stat, err := os.Stat(client.accountFilePath) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if !stat.Mode().IsRegular() { 71 | return nil, ErrNotValidAccountFile 72 | } 73 | 74 | err = client.loadAccountMap() 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | // keystore initialization 80 | stat, err = os.Stat(walletDir) 81 | if err != nil { 82 | return nil, ErrNotDirectory 83 | } 84 | 85 | if !stat.IsDir() { 86 | return nil, ErrNotDirectory 87 | } 88 | client.store = keystore.NewKeyStore(walletDir, keystore.StandardScryptN, keystore.StandardScryptP) 89 | 90 | // rpcClient initialization 91 | client.ethRpcClient, err = rpc.Dial(host) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | // log initialization 97 | logPath := filepath.Join(logDir, "eth.log") 98 | logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | client.l = &log.Logger{ 104 | Out: logFile, 105 | Formatter: new(log.JSONFormatter), 106 | } 107 | 108 | return client, nil 109 | } 110 | 111 | // Ping 112 | func (client *Client) Ping() error { 113 | return nil 114 | } 115 | 116 | // GetBlockCount 117 | func (client *Client) GetBlockCount() (int64, error) { 118 | var hexHeight string 119 | err := client.ethRpcClient.CallContext(context.Background(), &hexHeight, "eth_blockNumber") 120 | if err != nil { 121 | return 0, err 122 | } 123 | 124 | height, err := hexutil.DecodeBig(hexHeight) 125 | if err != nil { 126 | return 0, err 127 | } 128 | 129 | return height.Int64(), nil 130 | } 131 | 132 | // GetAddress - default address 133 | func (client *Client) GetAddress(account string) (string, error) { 134 | address, ok := client.accountAddressMap[account] 135 | if !ok { 136 | return "", keeper.ErrAccountNotFound 137 | } 138 | 139 | return address, nil 140 | } 141 | 142 | // Create Account 143 | func (client *Client) CreateAccount(account string) (keeper.Account, error) { 144 | address, _ := client.GetAddress(account) 145 | if len(address) > 0 { 146 | return keeper.Account{}, keeper.ErrAccountExists 147 | } 148 | 149 | acc, err := client.store.NewAccount(client.password) 150 | if err != nil { 151 | return keeper.Account{}, err 152 | } 153 | 154 | client.accountAddressLock.Lock() 155 | client.accountAddressMap[account] = acc.Address.Hex() 156 | client.accountAddressLock.Unlock() 157 | 158 | err = client.persistAccountMap() 159 | if err != nil { 160 | return keeper.Account{}, err 161 | } 162 | 163 | return keeper.Account{ 164 | Account: account, 165 | Balance: 0, 166 | Addresses: []string{ 167 | acc.Address.Hex(), 168 | }, 169 | }, nil 170 | } 171 | 172 | // GetAccountInfo 173 | func (client *Client) GetAccountInfo(account string, minConf int) (keeper.Account, error) { 174 | address, found := client.accountAddressMap[account] 175 | if !found { 176 | return keeper.Account{}, keeper.ErrAccountNotFound 177 | } 178 | 179 | balance, err := client.getBalance(address) 180 | if err != nil { 181 | return keeper.Account{}, err 182 | } 183 | 184 | return keeper.Account{ 185 | Account: account, 186 | Balance: balance, 187 | Addresses: []string{address}, 188 | }, nil 189 | } 190 | 191 | func (client *Client) GetNewAddress(account string) (string, error) { 192 | return "", keeper.ErrNotSupport 193 | } 194 | 195 | // GetAddressesByAccount 196 | func (client *Client) GetAddressesByAccount(account string) ([]string, error) { 197 | address, ok := client.accountAddressMap[account] 198 | if !ok { 199 | return []string{}, keeper.ErrAccountNotFound 200 | } 201 | 202 | return []string{address}, nil 203 | } 204 | 205 | // ListAccountsMinConf 206 | func (client *Client) ListAccountsMinConf(conf int) (map[string]float64, error) { 207 | accounts := make(map[string]float64, len(client.accountAddressMap)) 208 | for name, address := range client.accountAddressMap { 209 | balance, err := client.getBalance(address) 210 | if err != nil { 211 | client.l.Errorf("[ListAccountsMinConf] %s", err) 212 | 213 | accounts[name] = 0 214 | } else { 215 | accounts[name] = balance 216 | } 217 | } 218 | 219 | return accounts, nil 220 | } 221 | 222 | // SendToAddress 223 | func (client *Client) SendToAddress(address string, amount float64) (string, error) { 224 | return "", keeper.ErrNotSupport 225 | } 226 | 227 | // TODO check validity of account and have sufficent balance 228 | func (client *Client) SendFrom(account, hexToAddress string, amount float64) (string, error) { 229 | var hexFromAddress string = "" 230 | if !common.IsHexAddress(account) { 231 | hexFromAddress = client.accountAddressMap[account] 232 | } else { 233 | hexFromAddress = account 234 | } 235 | 236 | if !common.IsHexAddress(hexFromAddress) { 237 | return "", ErrInvalidAddress 238 | } 239 | 240 | if !common.IsHexAddress(hexToAddress) { 241 | return "", ErrInvalidAddress 242 | } 243 | 244 | fromAddress := common.HexToAddress(hexFromAddress) 245 | toAddress := common.HexToAddress(hexToAddress) 246 | 247 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 248 | if err != nil { 249 | log.Error(err) 250 | return "", err 251 | } 252 | 253 | value := etherToWei(amount) 254 | gasLimit := uint64(21000) 255 | gasPrice, err := client.SuggestGasPrice(context.Background()) 256 | if err != nil { 257 | log.Error(err) 258 | return "", err 259 | } 260 | 261 | chainID, err := client.NetworkID(context.Background()) 262 | if err != nil { 263 | log.Error(err) 264 | return "", err 265 | } 266 | 267 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, []byte{}) 268 | accountStore := accounts.Account{Address: fromAddress} 269 | err = client.store.TimedUnlock(accountStore, client.password, time.Duration(time.Second*10)) 270 | if err != nil { 271 | log.Error(err) 272 | return "", err 273 | } 274 | 275 | signedTx, err := client.store.SignTx(accountStore, tx, chainID) 276 | if err != nil { 277 | log.Error(err) 278 | return "", err 279 | } 280 | 281 | err = client.SendTransaction(context.Background(), signedTx) 282 | if err != nil { 283 | log.Error(err) 284 | return "", err 285 | } 286 | 287 | return signedTx.Hash().Hex(), nil 288 | } 289 | 290 | // ListUnspentMin 291 | func (client *Client) ListUnspentMin(minConf int) ([]btcjson.ListUnspentResult, error) { 292 | return []btcjson.ListUnspentResult{}, keeper.ErrNotSupport 293 | } 294 | 295 | // Move 296 | func (client *Client) Move(from, to string, amount float64) (bool, error) { 297 | _, err := client.SendFrom(from, to, amount) 298 | if err != nil { 299 | return false, err 300 | } 301 | 302 | return true, nil 303 | } 304 | 305 | // persistAccountMap write `accountAddressMap` into file `client.accountAddressMap`, 306 | // `accountAddressMap` will persist into file with json format, 307 | // 308 | // Error - return if `client.accountFilePath` not found or write permission not right. 309 | func (client *Client) persistAccountMap() error { 310 | stat, err := os.Stat(client.accountFilePath) 311 | if err != nil && os.IsNotExist(err) { 312 | return err 313 | } 314 | 315 | if !stat.Mode().IsRegular() { 316 | return ErrNotValidAccountFile 317 | } 318 | 319 | file, err := os.OpenFile(client.accountFilePath, os.O_WRONLY, 0755) 320 | if err != nil { 321 | return err 322 | } 323 | defer file.Close() 324 | 325 | return json.NewEncoder(file).Encode(client.accountAddressMap) 326 | } 327 | 328 | // loadAccountMap from filesystem. 329 | func (client *Client) loadAccountMap() error { 330 | client.accountAddressMap = make(map[string]string) 331 | file, err := os.Open(client.accountFilePath) 332 | if err != nil { 333 | return err 334 | } 335 | defer file.Close() 336 | 337 | err = json.NewDecoder(file).Decode(&client.accountAddressMap) 338 | if err != nil { 339 | return err 340 | } 341 | 342 | return nil 343 | } 344 | 345 | func (client *Client) getBalance(address string) (float64, error) { 346 | var balance hexutil.Big 347 | err := client.ethRpcClient.CallContext(context.Background(), &balance, 348 | "eth_getBalance", common.HexToAddress(address), "latest") 349 | if err != nil { 350 | return 0, err 351 | } 352 | 353 | float64Value, _ := weiToEther(balance.ToInt()).Float64() 354 | return float64Value, nil 355 | } 356 | -------------------------------------------------------------------------------- /keeper/eth/helper.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/common/hexutil" 12 | "github.com/ethereum/go-ethereum/core/types" 13 | "github.com/ethereum/go-ethereum/params" 14 | "github.com/ethereum/go-ethereum/rlp" 15 | ) 16 | 17 | // NOTICE 18 | // following code copyied from go-ethereum 19 | 20 | // SuggestGasPrice retrieves the currently suggested gas price to allow a timely 21 | // execution of a transaction. 22 | func (client *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 23 | var hex hexutil.Big 24 | if err := client.ethRpcClient.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { 25 | return nil, err 26 | } 27 | return (*big.Int)(&hex), nil 28 | } 29 | 30 | // EstimateGas tries to estimate the gas needed to execute a specific transaction based on 31 | // the current pending state of the backend blockchain. There is no guarantee that this is 32 | // the true gas limit requirement as other transactions may be added or removed by miners, 33 | // but it should provide a basis for setting a reasonable default. 34 | func (client *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { 35 | var hex hexutil.Uint64 36 | err := client.ethRpcClient.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) 37 | if err != nil { 38 | return 0, err 39 | } 40 | return uint64(hex), nil 41 | } 42 | 43 | // PendingNonceAt returns the account nonce of the given account in the pending state. 44 | // This is the nonce that should be used for the next transaction. 45 | func (client *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 46 | var result hexutil.Uint64 47 | err := client.ethRpcClient.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") 48 | return uint64(result), err 49 | } 50 | 51 | // NetworkID returns the network ID (also known as the chain ID) for this chain. 52 | func (client *Client) NetworkID(ctx context.Context) (*big.Int, error) { 53 | version := new(big.Int) 54 | var ver string 55 | if err := client.ethRpcClient.CallContext(ctx, &ver, "net_version"); err != nil { 56 | return nil, err 57 | } 58 | if _, ok := version.SetString(ver, 10); !ok { 59 | return nil, fmt.Errorf("invalid net_version result %q", ver) 60 | } 61 | return version, nil 62 | } 63 | 64 | // SendTransaction injects a signed transaction into the pending pool for execution. 65 | // 66 | // If the transaction was a contract creation use the TransactionReceipt method to get the 67 | // contract address after the transaction has been mined. 68 | func (client *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { 69 | data, err := rlp.EncodeToBytes(tx) 70 | if err != nil { 71 | return err 72 | } 73 | return client.ethRpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) 74 | } 75 | 76 | func toCallArg(msg ethereum.CallMsg) interface{} { 77 | arg := map[string]interface{}{ 78 | "from": msg.From, 79 | "to": msg.To, 80 | } 81 | if len(msg.Data) > 0 { 82 | arg["data"] = hexutil.Bytes(msg.Data) 83 | } 84 | if msg.Value != nil { 85 | arg["value"] = (*hexutil.Big)(msg.Value) 86 | } 87 | if msg.Gas != 0 { 88 | arg["gas"] = hexutil.Uint64(msg.Gas) 89 | } 90 | if msg.GasPrice != nil { 91 | arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) 92 | } 93 | return arg 94 | } 95 | 96 | func weiToEther(wei *big.Int) *big.Float { 97 | weiFloat := new(big.Float) 98 | weiFloat.SetString(wei.String()) 99 | return new(big.Float).Quo(weiFloat, big.NewFloat(math.Pow10(18))) 100 | } 101 | 102 | func etherToWei(ether float64) *big.Int { 103 | weiInt64 := int64(ether * params.Ether) 104 | return big.NewInt(weiInt64) 105 | } 106 | -------------------------------------------------------------------------------- /keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/btcsuite/btcd/btcjson" 7 | ) 8 | 9 | var ErrAccountNotFound = errors.New("error account not found") 10 | var ErrAccountExists = errors.New("error account exists") 11 | var ErrNotSupport = errors.New("error not supported") 12 | 13 | func IsNotFound(err error) bool { 14 | return err.Error() == "error account not found" 15 | } 16 | 17 | type Account struct { 18 | Account string `json:"account"` 19 | Balance float64 `json:"balance"` 20 | Addresses []string `json:"addresses"` 21 | } 22 | 23 | //Keeper interface 24 | type Keeper interface { 25 | GetBlockCount() (int64, error) 26 | 27 | // check if the coin core service avaliable or not, 28 | // the error might caused by misconfiguration or 29 | // runtime error. Error happend indicates fatal and 30 | // could not recover, suicide might be the best choice. 31 | Ping() error 32 | 33 | // create a new account 34 | CreateAccount(account string) (Account, error) 35 | 36 | // Get account, together with balance and address 37 | // return error if account not exist 38 | GetAccountInfo(account string, conf int) (Account, error) 39 | 40 | // Returns address under accont, use default account if 41 | // not provided 42 | GetAddress(account string) (string, error) 43 | 44 | // Return new address under account 45 | GetNewAddress(account string) (string, error) 46 | 47 | // Return addresses under certain account, default account if 48 | // no account specicied 49 | GetAddressesByAccount(account string) ([]string, error) 50 | 51 | // List all accounts/labels together with how much satoshi remains. 52 | ListAccountsMinConf(conf int) (map[string]float64, error) 53 | 54 | // send bitcoin to address 55 | SendToAddress(address string, amount float64) (string, error) 56 | 57 | // send bitcoin from some account to target address 58 | SendFrom(account, address string, amount float64) (string, error) 59 | 60 | // list all UXTO 61 | ListUnspentMin(minConf int) ([]btcjson.ListUnspentResult, error) 62 | 63 | // Move from one account to another under same wallet 64 | Move(from, to string, amount float64) (bool, error) 65 | } 66 | -------------------------------------------------------------------------------- /keeper/usdt/client.go: -------------------------------------------------------------------------------- 1 | package usdt 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | 8 | "github.com/cmingxu/wallet-keeper/keeper" 9 | "github.com/cmingxu/wallet-keeper/omnilayer" 10 | "github.com/cmingxu/wallet-keeper/omnilayer/omnijson" 11 | 12 | "github.com/btcsuite/btcd/btcjson" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | // default account for reserved usage, which represent 17 | // account belongs to enterpise default 18 | var DEFAULT_ACCOUNT = "duckduck" 19 | 20 | // default confirmation 21 | var DEFAULT_CONFIRMATION = 1 22 | 23 | type Client struct { 24 | rpcClient *omnilayer.Client 25 | l *log.Logger 26 | 27 | propertyId int64 28 | } 29 | 30 | // connect to omnicore with HTTP RPC transport 31 | func NewClient(host, user, pass, logDir string, propertyId int64) (*Client, error) { 32 | connCfg := &omnilayer.ConnConfig{ 33 | Host: host, 34 | User: user, 35 | Pass: pass, 36 | } 37 | 38 | client := &Client{propertyId: propertyId} 39 | client.rpcClient = omnilayer.New(connCfg) 40 | 41 | logPath := filepath.Join(logDir, "usdt.log") 42 | logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) 43 | if err != nil { 44 | return nil, err 45 | } 46 | client.l = &log.Logger{ 47 | Out: logFile, 48 | Level: log.DebugLevel, 49 | Formatter: new(log.JSONFormatter), 50 | } 51 | 52 | return client, nil 53 | } 54 | 55 | // GetBlockCount 56 | func (client *Client) GetBlockCount() (int64, error) { 57 | client.l.Infof("[GetBlockCount]") 58 | 59 | var res omnijson.GetBlockChainInfoResult 60 | if res, err := client.rpcClient.GetBlockChainInfo(); err == nil { 61 | return res.Blocks, nil 62 | } 63 | return res.Blocks, nil 64 | } 65 | 66 | // Ping 67 | func (client *Client) Ping() error { 68 | _, err := client.rpcClient.GetInfo() 69 | return err 70 | } 71 | 72 | // GetAddress - default address 73 | func (client *Client) GetAddress(account string) (string, error) { 74 | if len(account) == 0 { 75 | account = DEFAULT_ACCOUNT 76 | } 77 | client.l.Infof("[GetAddress] for account %s", account) 78 | 79 | address, err := client.rpcClient.GetAccountAddress(account) 80 | if err != nil { 81 | return "", err 82 | } 83 | 84 | return address, nil 85 | } 86 | 87 | // Create Account 88 | // Returns customized account info 89 | func (client *Client) CreateAccount(account string) (keeper.Account, error) { 90 | // GetAddress will create account if not exists 91 | client.l.Infof("[CreateAccount] for account %s", account) 92 | address, err := client.GetAddress(account) 93 | if err != nil { 94 | return keeper.Account{}, err 95 | } 96 | 97 | return keeper.Account{ 98 | Account: account, 99 | Balance: 0.0, 100 | Addresses: []string{address}, 101 | }, nil 102 | } 103 | 104 | // GetAccountInfo 105 | func (client *Client) GetAccountInfo(account string, minConf int) (keeper.Account, error) { 106 | addresses, err := client.GetAddressesByAccount(account) 107 | if err != nil { 108 | return keeper.Account{}, err 109 | } 110 | 111 | client.l.Infof("[GetAccountInfo] account %s, with minConf %d", account, minConf) 112 | var balance float64 = 0 113 | for _, addr := range addresses { 114 | cmd := omnijson.OmniGetBalanceCommand{ 115 | Address: addr, 116 | PropertyID: int32(client.propertyId), 117 | } 118 | if curBalance, err := client.rpcClient.OmniGetBalance(cmd); err == nil { 119 | if b, err := strconv.ParseFloat(curBalance.Balance, 64); err == nil { 120 | balance += b 121 | } 122 | } 123 | } 124 | 125 | return keeper.Account{ 126 | Account: account, 127 | Balance: balance, 128 | Addresses: addresses, 129 | }, nil 130 | } 131 | 132 | // GetAddressesByAccount 133 | func (client *Client) GetAddressesByAccount(account string) ([]string, error) { 134 | if len(account) == 0 { 135 | account = DEFAULT_ACCOUNT 136 | } 137 | addresses, err := client.rpcClient.GetAddressesByAccount(account) 138 | if err != nil { 139 | return []string{}, err 140 | } 141 | 142 | addrs := make([]string, 0) 143 | for _, addr := range addresses { 144 | addrs = append(addrs, addr) 145 | } 146 | 147 | client.l.Infof("[GetAddressesByAccount] for account %s", account) 148 | return addrs, nil 149 | } 150 | 151 | // GetNewAddress ... 152 | func (client *Client) GetNewAddress(account string) (string, error) { 153 | client.l.Infof("[GetNewAddress] for account %s", account) 154 | if len(account) == 0 { 155 | account = DEFAULT_ACCOUNT 156 | } 157 | 158 | address, err := client.rpcClient.GetNewAddress(account) 159 | if err != nil { 160 | return "", err 161 | } 162 | 163 | client.l.Infof("[GetNewAddress] for account %s, address is %s", account, address) 164 | return address, nil 165 | } 166 | 167 | // ListAccountsMinConf 168 | func (client *Client) ListAccountsMinConf(minConf int) (map[string]float64, error) { 169 | accounts := make(map[string]float64) 170 | 171 | client.l.Infof("[ListAccountsMinConf] with minConf %d", minConf) 172 | accountsWithAmount, err := client.rpcClient.ListAccounts(int64(minConf)) 173 | if err != nil { 174 | return accounts, err 175 | } 176 | 177 | for account, _ := range accountsWithAmount { 178 | var accountInfo keeper.Account 179 | accountInfo, err = client.GetAccountInfo(account, minConf) 180 | if err != nil { 181 | accounts[account] = -1 182 | } else { 183 | accounts[account] = accountInfo.Balance 184 | } 185 | } 186 | 187 | return accounts, nil 188 | } 189 | 190 | // SendToAddress ... 191 | // USDT RPC don't need this Stub func 192 | func (client *Client) SendToAddress(address string, amount float64) (string, error) { 193 | return "", keeper.ErrNotSupport 194 | } 195 | 196 | //SendFrom ...omni_funded_send 197 | func (client *Client) SendFrom(account, address string, amount float64) (string, error) { 198 | client.l.Infof("[SendFrom] from account %s to address %s with amount %f ", account, address, amount) 199 | fromAddr, err := client.rpcClient.GetAccountAddress(account) 200 | if err != nil { 201 | client.l.Errorf("[SendFrom] go error: %s", err) 202 | return "", err 203 | } 204 | 205 | hash, _ := client.rpcClient.OmniFoundedSend(fromAddr, address, client.propertyId, floatToString(amount), fromAddr) 206 | client.l.Infof("[SendFrom] from account %s to address %s with amount %f, result hash %s ", fromAddr, address, amount, hash) 207 | 208 | return hash, nil 209 | } 210 | 211 | // Move - omni_funded_send 212 | func (client *Client) Move(from, to string, amount float64) (bool, error) { 213 | client.l.Infof("[Move] from %s to %s with amount %f ", from, to, amount) 214 | hash, err := client.rpcClient.OmniFoundedSend(from, to, client.propertyId, floatToString(amount), to) 215 | if err != nil { 216 | return false, err 217 | } 218 | 219 | client.l.Infof("[Move] success from %s to %s with amount %f hash is %s ", from, to, amount, hash) 220 | return true, nil 221 | } 222 | 223 | // ListUnspentMin 224 | func (client *Client) ListUnspentMin(minConf int) ([]btcjson.ListUnspentResult, error) { 225 | return nil, nil 226 | } 227 | 228 | //util 229 | func floatToString(input_num float64) string { 230 | // to convert a float number to a string 231 | return strconv.FormatFloat(input_num, 'f', 6, 64) 232 | } 233 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmingxu/wallet-keeper/8024cb9cd6c9b6cdca163abac5549385de6f3d97/logo/logo.png -------------------------------------------------------------------------------- /omnilayer/bitcoin_core.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cmingxu/wallet-keeper/omnilayer/omnijson" 7 | ) 8 | 9 | // =========ListAccounts================== 10 | type futureListAccounts chan *response 11 | 12 | func (f futureListAccounts) Receive() (omnijson.ListAccountsResult, error) { 13 | var result omnijson.ListAccountsResult 14 | 15 | data, err := receive(f) 16 | if err != nil { 17 | return result, err 18 | } 19 | 20 | err = json.Unmarshal(data, &result) 21 | return result, err 22 | } 23 | 24 | // =========GetAccountAddress================== 25 | type futureGetAccountAddress chan *response 26 | 27 | func (f futureGetAccountAddress) Receive() (omnijson.GetAccountAddressResult, error) { 28 | var result omnijson.GetAccountAddressResult 29 | 30 | data, err := receive(f) 31 | if err != nil { 32 | return result, err 33 | } 34 | 35 | err = json.Unmarshal(data, &result) 36 | return result, err 37 | } 38 | 39 | type futureGetAddressesByAccount chan *response 40 | 41 | func (f futureGetAddressesByAccount) Receive() (omnijson.GetAddressesByAccountResult, error) { 42 | var result omnijson.GetAddressesByAccountResult 43 | 44 | data, err := receive(f) 45 | if err != nil { 46 | return result, err 47 | } 48 | 49 | err = json.Unmarshal(data, &result) 50 | return result, err 51 | } 52 | 53 | type futureGetNewAddress chan *response 54 | 55 | func (f futureGetNewAddress) Receive() (omnijson.GetNewAddressResult, error) { 56 | var result omnijson.GetNewAddressResult 57 | 58 | data, err := receive(f) 59 | if err != nil { 60 | return result, err 61 | } 62 | 63 | err = json.Unmarshal(data, &result) 64 | return result, err 65 | } 66 | 67 | type futureCreateRawTransaction chan *response 68 | 69 | func (f futureCreateRawTransaction) Receive() (omnijson.CreateRawTransactionResult, error) { 70 | var result omnijson.CreateRawTransactionResult 71 | 72 | data, err := receive(f) 73 | if err != nil { 74 | return result, err 75 | } 76 | 77 | err = json.Unmarshal(data, &result) 78 | return result, err 79 | } 80 | 81 | type futureGetBlockChainInfo chan *response 82 | 83 | func (f futureGetBlockChainInfo) Receive() (omnijson.GetBlockChainInfoResult, error) { 84 | var result omnijson.GetBlockChainInfoResult 85 | 86 | data, err := receive(f) 87 | if err != nil { 88 | return result, err 89 | } 90 | 91 | err = json.Unmarshal(data, &result) 92 | return result, err 93 | } 94 | 95 | type futureListUnspent chan *response 96 | 97 | func (f futureListUnspent) Receive() (omnijson.ListUnspentResult, error) { 98 | data, err := receive(f) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | result := make(omnijson.ListUnspentResult, 0) 104 | 105 | err = json.Unmarshal(data, &result) 106 | return result, err 107 | } 108 | 109 | type futureImportAddress chan *response 110 | 111 | func (f futureImportAddress) Receive() error { 112 | _, err := receive(f) 113 | return err 114 | } 115 | 116 | type futureSendRawTransaction chan *response 117 | 118 | func (f futureSendRawTransaction) Receive() (omnijson.SendRawTransactionResult, error) { 119 | var result omnijson.SendRawTransactionResult 120 | 121 | data, err := receive(f) 122 | if err != nil { 123 | return result, err 124 | } 125 | 126 | err = json.Unmarshal(data, &result) 127 | return result, err 128 | } 129 | 130 | type futureSignRawTransaction chan *response 131 | 132 | func (f futureSignRawTransaction) Receive() (omnijson.SignRawTransactionResult, error) { 133 | var result omnijson.SignRawTransactionResult 134 | 135 | data, err := receive(f) 136 | if err != nil { 137 | return result, err 138 | } 139 | 140 | err = json.Unmarshal(data, &result) 141 | return result, err 142 | } 143 | 144 | type futureSignRawTransactionWithKey chan *response 145 | 146 | func (f futureSignRawTransactionWithKey) Receive() (omnijson.SignRawTransactionWithKeyResult, error) { 147 | var result omnijson.SignRawTransactionWithKeyResult 148 | 149 | data, err := receive(f) 150 | if err != nil { 151 | return result, err 152 | } 153 | 154 | err = json.Unmarshal(data, &result) 155 | return result, err 156 | } 157 | -------------------------------------------------------------------------------- /omnilayer/client.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | const ( 13 | contentType = "Content-Type" 14 | contentTypeJSON = "application/json" 15 | ) 16 | 17 | type Client struct { 18 | id uint64 19 | config *ConnConfig 20 | httpClient *http.Client 21 | sendPostChan chan *sendPostDetails 22 | 23 | shutdown chan struct{} 24 | done chan struct{} 25 | } 26 | 27 | func (c *Client) do(cmd command) chan *response { 28 | body, err := marshalCmd(cmd) 29 | if err != nil { 30 | return newFutureError(err) 31 | } 32 | 33 | responseChan := make(chan *response, 1) 34 | jReq := &jsonRequest{ 35 | id: c.NextID(), 36 | cmd: cmd, 37 | body: body, 38 | responseChan: responseChan, 39 | } 40 | 41 | c.sendPost(jReq) 42 | 43 | return responseChan 44 | } 45 | 46 | func (c *Client) sendPost(jReq *jsonRequest) { 47 | req, err := http.NewRequest(http.MethodPost, "http://"+c.config.Host, bytes.NewReader(jReq.body)) 48 | if err != nil { 49 | jReq.responseChan <- &response{result: nil, err: err} 50 | return 51 | } 52 | 53 | // why this 54 | req.Close = true 55 | req.Header.Set(contentType, contentTypeJSON) 56 | req.SetBasicAuth(c.config.User, c.config.Pass) 57 | 58 | select { 59 | case <-c.shutdown: 60 | jReq.responseChan <- &response{err: errClientShutdown()} 61 | default: 62 | c.sendPostChan <- &sendPostDetails{ 63 | jsonRequest: jReq, 64 | httpRequest: req, 65 | } 66 | } 67 | } 68 | 69 | func New(config *ConnConfig) *Client { 70 | httpClient := newHTTPClient() 71 | 72 | client := &Client{ 73 | config: config, 74 | httpClient: httpClient, 75 | sendPostChan: make(chan *sendPostDetails, sendPostBufferSize), 76 | 77 | shutdown: make(chan struct{}, 1), 78 | done: make(chan struct{}, 1), 79 | } 80 | 81 | go client.sendPostHandler() 82 | 83 | return client 84 | } 85 | 86 | func (c *Client) sendPostHandler() { 87 | out: 88 | for { 89 | select { 90 | case details := <-c.sendPostChan: 91 | c.handleSendPostMessage(details) 92 | 93 | case <-c.shutdown: 94 | break out 95 | } 96 | } 97 | 98 | cleanup: 99 | for { 100 | select { 101 | case details := <-c.sendPostChan: 102 | details.jsonRequest.responseChan <- &response{ 103 | result: nil, 104 | err: errClientShutdown(), 105 | } 106 | 107 | default: 108 | break cleanup 109 | } 110 | } 111 | 112 | close(c.done) 113 | } 114 | 115 | func (c *Client) handleSendPostMessage(details *sendPostDetails) { 116 | jReq := details.jsonRequest 117 | httpResponse, err := c.httpClient.Do(details.httpRequest) 118 | if err != nil { 119 | jReq.responseChan <- &response{err: err} 120 | return 121 | } 122 | 123 | respBytes, err := ioutil.ReadAll(httpResponse.Body) 124 | if err != nil { 125 | jReq.responseChan <- &response{err: err} 126 | return 127 | } 128 | err = httpResponse.Body.Close() 129 | if err != nil { 130 | jReq.responseChan <- &response{err: err} 131 | return 132 | } 133 | 134 | var resp rawResponse 135 | err = json.Unmarshal(respBytes, &resp) 136 | if err != nil { 137 | jReq.responseChan <- &response{err: err} 138 | return 139 | } 140 | 141 | res, err := resp.result() 142 | jReq.responseChan <- &response{result: res, err: err} 143 | } 144 | 145 | func newHTTPClient() *http.Client { 146 | return &http.Client{ 147 | Transport: &http.Transport{ 148 | ResponseHeaderTimeout: 5 * time.Second, 149 | ExpectContinueTimeout: 4 * time.Second, 150 | IdleConnTimeout: 5 * 60 * time.Second, 151 | }, 152 | } 153 | } 154 | 155 | func (c *Client) NextID() uint64 { 156 | return atomic.AddUint64(&c.id, 1) 157 | } 158 | 159 | func (c *Client) Shutdown() { 160 | select { 161 | case <-c.shutdown: 162 | return 163 | default: 164 | } 165 | 166 | close(c.shutdown) 167 | <-c.done 168 | } 169 | -------------------------------------------------------------------------------- /omnilayer/errors.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | type ErrClientShutdown struct{} 4 | 5 | func (*ErrClientShutdown) Error() string { 6 | return "the client has been shutdown" 7 | } 8 | 9 | func errClientShutdown() error { 10 | return &ErrClientShutdown{} 11 | } 12 | -------------------------------------------------------------------------------- /omnilayer/infra.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | const sendPostBufferSize = 100 10 | 11 | type SigHashType = string 12 | 13 | // Constants used to indicate the signature hash type for SignRawTransaction. 14 | const ( 15 | // SigHashAll indicates ALL of the outputs should be signed. 16 | SigHashAll SigHashType = "ALL" 17 | 18 | // SigHashNone indicates NONE of the outputs should be signed. This 19 | // can be thought of as specifying the signer does not care where the 20 | // bitcoins go. 21 | SigHashNone SigHashType = "NONE" 22 | 23 | // SigHashSingle indicates that a SINGLE output should be signed. This 24 | // can be thought of specifying the signer only cares about where ONE of 25 | // the outputs goes, but not any of the others. 26 | SigHashSingle SigHashType = "SINGLE" 27 | 28 | // SigHashAllAnyoneCanPay indicates that signer does not care where the 29 | // other inputs to the transaction come from, so it allows other people 30 | // to add inputs. In addition, it uses the SigHashAll signing method 31 | // for outputs. 32 | SigHashAllAnyoneCanPay SigHashType = "ALL|ANYONECANPAY" 33 | 34 | // SigHashNoneAnyoneCanPay indicates that signer does not care where the 35 | // other inputs to the transaction come from, so it allows other people 36 | // to add inputs. In addition, it uses the SigHashNone signing method 37 | // for outputs. 38 | SigHashNoneAnyoneCanPay SigHashType = "NONE|ANYONECANPAY" 39 | 40 | // SigHashSingleAnyoneCanPay indicates that signer does not care where 41 | // the other inputs to the transaction come from, so it allows other 42 | // people to add inputs. In addition, it uses the SigHashSingle signing 43 | // method for outputs. 44 | SigHashSingleAnyoneCanPay SigHashType = "SINGLE|ANYONECANPAY" 45 | ) 46 | 47 | type rawResponse struct { 48 | Result json.RawMessage `json:"result"` 49 | Error *rpcError `json:"error"` 50 | } 51 | 52 | func (r rawResponse) result() (result []byte, err error) { 53 | if r.Error != nil { 54 | return nil, r.Error 55 | } 56 | return r.Result, nil 57 | } 58 | 59 | type sendPostDetails struct { 60 | httpRequest *http.Request 61 | jsonRequest *jsonRequest 62 | } 63 | 64 | type jsonRequest struct { 65 | id uint64 66 | cmd command 67 | body []byte 68 | responseChan chan *response 69 | } 70 | 71 | type response struct { 72 | result []byte 73 | err error 74 | } 75 | 76 | func receive(resp chan *response) ([]byte, error) { 77 | r := <-resp 78 | return r.result, r.err 79 | } 80 | 81 | func newFutureError(err error) chan *response { 82 | responseChan := make(chan *response, 1) 83 | responseChan <- &response{err: err} 84 | return responseChan 85 | } 86 | 87 | type ConnConfig struct { 88 | Host string 89 | Endpoint string 90 | User string 91 | Pass string 92 | Proxy string 93 | ProxyUser string 94 | ProxyPass string 95 | Certificates []byte 96 | DisableAutoReconnect bool 97 | DisableConnectOnNew bool 98 | EnableBCInfoHacks bool 99 | } 100 | 101 | type rpcError struct { 102 | Code int `json:"code"` 103 | Message string `json:"message"` 104 | } 105 | 106 | func (e *rpcError) Error() string { 107 | return fmt.Sprintf("%d: %s", e.Code, e.Message) 108 | } 109 | 110 | type command interface { 111 | ID() string 112 | Method() string 113 | Params() []interface{} 114 | } 115 | 116 | func marshalCmd(cmd command) ([]byte, error) { 117 | rawCmd, err := newRpcRequest(cmd) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return json.Marshal(rawCmd) 123 | } 124 | 125 | func newRpcRequest(cmd command) (*rpcRequest, error) { 126 | method := cmd.Method() 127 | params := cmd.Params() 128 | id := cmd.ID() 129 | 130 | rawParams := make([]json.RawMessage, len(params)) 131 | 132 | for i := range params { 133 | msg, err := json.Marshal(params[i]) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | rawParams[i] = json.RawMessage(msg) 139 | } 140 | 141 | return &rpcRequest{ 142 | JsonRPC: "1.0", 143 | ID: id, 144 | Method: method, 145 | Params: rawParams, 146 | }, nil 147 | } 148 | 149 | type rpcRequest struct { 150 | JsonRPC string `json:"jsonrpc"` 151 | Method string `json:"method"` 152 | Params []json.RawMessage `json:"params"` 153 | ID string `json:"id"` 154 | } 155 | -------------------------------------------------------------------------------- /omnilayer/omni_core.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cmingxu/wallet-keeper/omnilayer/omnijson" 7 | ) 8 | 9 | type futureOmniFoundedSend chan *response 10 | 11 | func (f futureOmniFoundedSend) Receive() (omnijson.OmniFoundedSendResult, error) { 12 | var result omnijson.OmniFoundedSendResult 13 | 14 | data, err := receive(f) 15 | if err != nil { 16 | return result, err 17 | } 18 | 19 | err = json.Unmarshal(data, &result) 20 | return result, err 21 | } 22 | 23 | type futureOmniCreatePayloadSimpleSend chan *response 24 | 25 | func (f futureOmniCreatePayloadSimpleSend) Receive() (omnijson.OmniCreatePayloadSimpleSendResult, error) { 26 | var result omnijson.OmniCreatePayloadSimpleSendResult 27 | 28 | data, err := receive(f) 29 | if err != nil { 30 | return result, err 31 | } 32 | 33 | err = json.Unmarshal(data, &result) 34 | return result, err 35 | } 36 | 37 | type futureOmniCreateRawTxChange chan *response 38 | 39 | func (f futureOmniCreateRawTxChange) Receive() (omnijson.OmniCreateRawTxChangeResult, error) { 40 | var result omnijson.OmniCreateRawTxChangeResult 41 | 42 | data, err := receive(f) 43 | if err != nil { 44 | return result, err 45 | } 46 | 47 | err = json.Unmarshal(data, &result) 48 | return result, err 49 | } 50 | 51 | type futureOmniCreateRawTxOpReturn chan *response 52 | 53 | func (f futureOmniCreateRawTxOpReturn) Receive() (omnijson.OmniCreateRawTxOpReturnResult, error) { 54 | var result omnijson.OmniCreateRawTxOpReturnResult 55 | 56 | data, err := receive(f) 57 | if err != nil { 58 | return result, err 59 | } 60 | 61 | err = json.Unmarshal(data, &result) 62 | return result, err 63 | } 64 | 65 | type futureOmniCreateRawTxReference chan *response 66 | 67 | func (f futureOmniCreateRawTxReference) Receive() (omnijson.OmniCreateRawTxReferenceResult, error) { 68 | var result omnijson.OmniCreateRawTxReferenceResult 69 | 70 | data, err := receive(f) 71 | if err != nil { 72 | return result, err 73 | } 74 | 75 | err = json.Unmarshal(data, &result) 76 | return result, err 77 | } 78 | 79 | type futureGetInfo chan *response 80 | 81 | func (f futureGetInfo) Receive() (omnijson.OmniGetInfoResult, error) { 82 | var result omnijson.OmniGetInfoResult 83 | 84 | data, err := receive(f) 85 | if err != nil { 86 | return result, err 87 | } 88 | 89 | err = json.Unmarshal(data, &result) 90 | return result, err 91 | } 92 | 93 | type futureOmniGetTransaction chan *response 94 | 95 | func (f futureOmniGetTransaction) Receive() (omnijson.OmniGettransactionResult, error) { 96 | var result omnijson.OmniGettransactionResult 97 | 98 | data, err := receive(f) 99 | if err != nil { 100 | return result, err 101 | } 102 | 103 | err = json.Unmarshal(data, &result) 104 | return result, err 105 | } 106 | 107 | type futureOmniListBlockTransactions chan *response 108 | 109 | func (f futureOmniListBlockTransactions) Receive() (omnijson.OmniListBlockTransactionsResult, error) { 110 | data, err := receive(f) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | result := make(omnijson.OmniListBlockTransactionsResult, 0) 116 | 117 | err = json.Unmarshal(data, &result) 118 | return result, err 119 | } 120 | 121 | type futureOmniGetBalance chan *response 122 | 123 | func (f futureOmniGetBalance) Receive() (omnijson.OmniGetBalanceResult, error) { 124 | var result omnijson.OmniGetBalanceResult 125 | 126 | data, err := receive(f) 127 | if err != nil { 128 | return result, err 129 | } 130 | 131 | err = json.Unmarshal(data, &result) 132 | return result, err 133 | } 134 | -------------------------------------------------------------------------------- /omnilayer/omnijson/createrawtransaction.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result: 5 | "transaction" (string) hex string of the transaction 6 | */ 7 | 8 | type CreateRawTransactionResult = string 9 | 10 | type CreateRawTransactionCommand struct { 11 | Parameters []CreateRawTransactionParameter 12 | } 13 | 14 | type CreateRawTransactionParameter struct { 15 | Tx string `json:"txid"` 16 | Vout uint32 `json:"vout"` 17 | } 18 | 19 | type createrawtransactionOutput struct { 20 | Address string `json:"address,omitempty"` 21 | Data string `json:"data,omitempty"` 22 | } 23 | 24 | func (CreateRawTransactionCommand) Method() string { 25 | return "createrawtransaction" 26 | } 27 | 28 | func (CreateRawTransactionCommand) ID() string { 29 | return "1" 30 | } 31 | 32 | func (cmd CreateRawTransactionCommand) Params() []interface{} { 33 | return []interface{}{cmd.Parameters, createrawtransactionOutput{}} 34 | } 35 | -------------------------------------------------------------------------------- /omnilayer/omnijson/getaccountaddress.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | { 6 | "result":"mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4", 7 | "error":null, 8 | "id":"curltext" 9 | } 10 | */ 11 | 12 | //GetAccountAddressResult ... 13 | type GetAccountAddressResult = string 14 | 15 | //GetAccountAddressCommand ... 16 | type GetAccountAddressCommand struct { 17 | Account string 18 | } 19 | 20 | //Method ... 21 | func (GetAccountAddressCommand) Method() string { 22 | return "getaccountaddress" 23 | } 24 | 25 | //ID ... 26 | func (GetAccountAddressCommand) ID() string { 27 | return "1" 28 | } 29 | 30 | //Params ... 31 | func (cmd GetAccountAddressCommand) Params() []interface{} { 32 | return []interface{}{cmd.Account} 33 | } 34 | -------------------------------------------------------------------------------- /omnilayer/omnijson/getaddressesbyaccount.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | {"result":["mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4"],"error":null,"id":"curltext"} 6 | */ 7 | 8 | //GetAddressesByAccountResult ... 9 | type GetAddressesByAccountResult = []string 10 | 11 | //GetAddressesByAccountCommand ... 12 | type GetAddressesByAccountCommand struct { 13 | Account string 14 | } 15 | 16 | //Method ... 17 | func (GetAddressesByAccountCommand) Method() string { 18 | return "getaddressesbyaccount" 19 | } 20 | 21 | //ID ... 22 | func (GetAddressesByAccountCommand) ID() string { 23 | return "1" 24 | } 25 | 26 | //Params ... 27 | func (cmd GetAddressesByAccountCommand) Params() []interface{} { 28 | return []interface{}{cmd.Account} 29 | } 30 | -------------------------------------------------------------------------------- /omnilayer/omnijson/getblockchaininfo.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | { 6 | "chain": "xxxx", (string) current network name as defined in BIP70 (main, test, regtest) 7 | "blocks": xxxxxx, (numeric) the current number of blocks processed in the server 8 | "headers": xxxxxx, (numeric) the current number of headers we have validated 9 | "bestblockhash": "...", (string) the hash of the currently best block 10 | "difficulty": xxxxxx, (numeric) the current difficulty 11 | "mediantime": xxxxxx, (numeric) median time for the current best block 12 | "verificationprogress": xxxx, (numeric) estimate of verification progress [0..1] 13 | "chainwork": "xxxx" (string) total amount of work in active chain, in hexadecimal 14 | "pruned": xx, (boolean) if the blocks are subject to pruning 15 | "pruneheight": xxxxxx, (numeric) lowest-height complete block stored 16 | "softforks": [ (array) status of softforks in progress 17 | { 18 | "id": "xxxx", (string) name of softfork 19 | "version": xx, (numeric) block version 20 | "enforce": { (object) progress toward enforcing the softfork rules for new-version blocks 21 | "status": xx, (boolean) true if threshold reached 22 | "found": xx, (numeric) number of blocks with the new version found 23 | "required": xx, (numeric) number of blocks required to trigger 24 | "window": xx, (numeric) maximum size of examined window of recent blocks 25 | }, 26 | "reject": { ... } (object) progress toward rejecting pre-softfork blocks (same fields as "enforce") 27 | }, ... 28 | ], 29 | "bip9_softforks": { (object) status of BIP9 softforks in progress 30 | "xxxx" : { (string) name of the softfork 31 | "status": "xxxx", (string) one of "defined", "started", "locked_in", "active", "failed" 32 | "bit": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for "started" status) 33 | "startTime": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning 34 | "timeout": xx (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in 35 | } 36 | } 37 | } 38 | */ 39 | 40 | type GetBlockChainInfoResult struct { 41 | Blocks int64 `json:"blocks"` 42 | BestBlockHash string `json:"bestblockhash"` 43 | } 44 | 45 | type GetBlockChainInfoCommand struct{} 46 | 47 | func (GetBlockChainInfoCommand) Method() string { 48 | return "getblockchaininfo" 49 | } 50 | 51 | func (GetBlockChainInfoCommand) ID() string { 52 | return "1" 53 | } 54 | 55 | func (GetBlockChainInfoCommand) Params() []interface{} { 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /omnilayer/omnijson/getnewaddress.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | { 6 | "result":"mkVW5QARPvvAY328y9LMHep5ZhuiTrgdd4", 7 | "error":null, 8 | "id":"curltext" 9 | } 10 | */ 11 | 12 | //GetNewAddressResult ... 13 | type GetNewAddressResult = string 14 | 15 | //GetNewAddressCommand ... 16 | type GetNewAddressCommand struct { 17 | Account string 18 | } 19 | 20 | //Method ... 21 | func (GetNewAddressCommand) Method() string { 22 | return "getnewaddress" 23 | } 24 | 25 | //ID ... 26 | func (GetNewAddressCommand) ID() string { 27 | return "1" 28 | } 29 | 30 | //Params ... 31 | func (cmd GetNewAddressCommand) Params() []interface{} { 32 | return []interface{}{cmd.Account} 33 | } 34 | -------------------------------------------------------------------------------- /omnilayer/omnijson/importaddress.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type ImportAddressCommand struct { 4 | Adress string 5 | Tag string 6 | Rescan bool 7 | } 8 | 9 | func (ImportAddressCommand) Method() string { 10 | return "importaddress" 11 | } 12 | 13 | func (ImportAddressCommand) ID() string { 14 | return "1" 15 | } 16 | 17 | func (cmd ImportAddressCommand) Params() []interface{} { 18 | return []interface{}{cmd.Adress, cmd.Tag, cmd.Rescan} 19 | } 20 | -------------------------------------------------------------------------------- /omnilayer/omnijson/listaccounts.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type ListAccountsResult map[string]float64 4 | 5 | type ListAccountsCommand struct { 6 | MinConf int64 `json:"minConf"` 7 | } 8 | 9 | func (ListAccountsCommand) Method() string { 10 | return "listaccounts" 11 | } 12 | 13 | func (ListAccountsCommand) ID() string { 14 | return "1" 15 | } 16 | 17 | func (cmd ListAccountsCommand) Params() []interface{} { 18 | return []interface{}{cmd.MinConf, 9999999} 19 | } 20 | -------------------------------------------------------------------------------- /omnilayer/omnijson/listunspent.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | [ (array of json object) 6 | { 7 | "txid" : "txid", (string) the transaction id 8 | "vout" : n, (numeric) the vout value 9 | "address" : "address", (string) the bitcoin address 10 | "account" : "account", (string) DEPRECATED. The associated account, or "" for the default account 11 | "scriptPubKey" : "key", (string) the script key 12 | "amount" : x.xxx, (numeric) the transaction amount in BTC 13 | "confirmations" : n, (numeric) The number of confirmations 14 | "redeemScript" : n (string) The redeemScript if scriptPubKey is P2SH 15 | "spendable" : xxx, (bool) Whether we have the private keys to spend this output 16 | "solvable" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys 17 | } 18 | ,... 19 | ] 20 | */ 21 | 22 | type ListUnspentResult = []struct { 23 | Tx string `json:"txid"` 24 | Address string `json:"address"` 25 | ScriptPubKey string `json:"scriptPubKey"` 26 | RedeemScript string `json:"redeemScript"` 27 | Amount float64 `json:"amount"` 28 | Confirmations int64 `json:"confirmations"` 29 | Vout uint32 `json:"vout"` 30 | Spendable bool `json:"spendable"` 31 | Solvable bool `json:"solvable"` 32 | } 33 | 34 | type ListUnspentCommand struct { 35 | Min int 36 | Addresses []string 37 | } 38 | 39 | func (ListUnspentCommand) Method() string { 40 | return "listunspent" 41 | } 42 | 43 | func (ListUnspentCommand) ID() string { 44 | return "1" 45 | } 46 | 47 | func (cmd ListUnspentCommand) Params() []interface{} { 48 | return []interface{}{cmd.Min, 9999999, cmd.Addresses} 49 | } 50 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_createpayload_simplesend.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result: 5 | "payload" (string) the hex-encoded payload 6 | */ 7 | 8 | type OmniCreatePayloadSimpleSendResult = string 9 | 10 | type OmniCreatePayloadSimpleSendCommand struct { 11 | Property int32 12 | Amount string 13 | } 14 | 15 | func (OmniCreatePayloadSimpleSendCommand) Method() string { 16 | return "omni_createpayload_simplesend" 17 | } 18 | 19 | func (OmniCreatePayloadSimpleSendCommand) ID() string { 20 | return "1" 21 | } 22 | 23 | func (cmd OmniCreatePayloadSimpleSendCommand) Params() []interface{} { 24 | return []interface{}{cmd.Property, cmd.Amount} 25 | } 26 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_createrawtx_change.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type OmniCreateRawTxChangeResult = string 4 | 5 | type OmniCreateRawTxChangeCommand struct { 6 | Raw string 7 | Previous []OmniCreateRawTxChangeParameter 8 | Destination string 9 | Fee float64 10 | } 11 | 12 | type OmniCreateRawTxChangeParameter struct { 13 | Tx string `json:"txid"` 14 | Vout uint32 `json:"vout"` 15 | ScriptPubKey string `json:"scriptPubKey"` 16 | Value float64 `json:"value"` 17 | } 18 | 19 | func (OmniCreateRawTxChangeCommand) Method() string { 20 | return "omni_createrawtx_change" 21 | } 22 | 23 | func (OmniCreateRawTxChangeCommand) ID() string { 24 | return "1" 25 | } 26 | 27 | func (cmd OmniCreateRawTxChangeCommand) Params() []interface{} { 28 | return []interface{}{cmd.Raw, cmd.Previous, cmd.Destination, cmd.Fee} 29 | } 30 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_createrawtx_opreturn.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type OmniCreateRawTxOpReturnResult = string 4 | 5 | type OmniCreateRawTxOpReturnCommand struct { 6 | Raw string 7 | Payload string 8 | } 9 | 10 | func (OmniCreateRawTxOpReturnCommand) Method() string { 11 | return "omni_createrawtx_opreturn" 12 | } 13 | 14 | func (OmniCreateRawTxOpReturnCommand) ID() string { 15 | return "1" 16 | } 17 | 18 | func (cmd OmniCreateRawTxOpReturnCommand) Params() []interface{} { 19 | return []interface{}{cmd.Raw, cmd.Payload} 20 | } 21 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_createrawtx_reference.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type OmniCreateRawTxReferenceResult = string 4 | 5 | type OmniCreateRawTxReferenceCommand struct { 6 | Raw string 7 | Destination string 8 | Amount float64 9 | } 10 | 11 | func (OmniCreateRawTxReferenceCommand) Method() string { 12 | return "omni_createrawtx_reference" 13 | } 14 | 15 | func (OmniCreateRawTxReferenceCommand) ID() string { 16 | return "1" 17 | } 18 | 19 | func (cmd OmniCreateRawTxReferenceCommand) Params() []interface{} { 20 | return []interface{}{cmd.Raw, cmd.Destination, cmd.Amount} 21 | } 22 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_funded_send.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | //OmniFoundedSendResult ... 4 | type OmniFoundedSendResult = string 5 | 6 | //OmniFoundedSendCommand ... 7 | type OmniFoundedSendCommand struct { 8 | From string `json:"fromaddress"` 9 | To string `json:"toaddress"` 10 | ProperID int64 `json:"propertyid"` 11 | Amount string `json:"amount"` 12 | Fee string `json:"feeaddress"` 13 | } 14 | 15 | //Method ... 16 | func (OmniFoundedSendCommand) Method() string { 17 | return "omni_funded_send" 18 | } 19 | 20 | //ID ... 21 | func (OmniFoundedSendCommand) ID() string { 22 | return "1" 23 | } 24 | 25 | //Params ... 26 | func (cmd OmniFoundedSendCommand) Params() []interface{} { 27 | return []interface{}{cmd.From, cmd.To, cmd.ProperID, cmd.Amount, cmd.Fee} 28 | } 29 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_getbalance.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | { 5 | "balance" : "n.nnnnnnnn", (string) the available balance of the address 6 | "reserved" : "n.nnnnnnnn" (string) the amount reserved by sell offers and accepts 7 | "frozen" : "n.nnnnnnnn" (string) the amount frozen by the issuer (applies to managed properties only) 8 | } 9 | */ 10 | 11 | type OmniGetBalanceResult = struct { 12 | Balance string 13 | Reserved string 14 | Frozen string 15 | } 16 | 17 | type OmniGetBalanceCommand struct { 18 | Address string 19 | PropertyID int32 20 | } 21 | 22 | func (OmniGetBalanceCommand) Method() string { 23 | return "omni_getbalance" 24 | } 25 | 26 | func (OmniGetBalanceCommand) ID() string { 27 | return "1" 28 | } 29 | 30 | func (cmd OmniGetBalanceCommand) Params() []interface{} { 31 | return []interface{}{cmd.Address, cmd.PropertyID} 32 | } 33 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_getinfo.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | { 6 | "omnicoreversion_int" : xxxxxxx, // (number) client version as integer 7 | "omnicoreversion" : "x.x.x.x-xxx", // (string) client version 8 | "mastercoreversion" : "x.x.x.x-xxx", // (string) client version (DEPRECIATED) 9 | "bitcoincoreversion" : "x.x.x", // (string) Bitcoin Core version 10 | "commitinfo" : "xxxxxxx", // (string) build commit identifier 11 | "block" : nnnnnn, // (number) index of the last processed block 12 | "blocktime" : nnnnnnnnnn, // (number) timestamp of the last processed block 13 | "blocktransactions" : nnnn, // (number) Omni transactions found in the last processed block 14 | "totaltransactions" : nnnnnnnn, // (number) Omni transactions processed in total 15 | "alerts" : [ // (array of JSON objects) active protocol alert (if any) 16 | { 17 | "alerttype" : n // (number) alert type as integer 18 | "alerttype" : "xxx" // "alertexpiringbyblock", "alertexpiringbyblocktime", "alertexpiringbyclientversion" or "error" 19 | "alertexpiry" : "nnnnnnnnnn" // (string) expiration criteria (can refer to block height, timestamp or client verion) 20 | "alertmessage" : "xxx" // (string) information about the alert 21 | }, 22 | ... 23 | ] 24 | } 25 | */ 26 | 27 | type OmniGetInfoResult struct { 28 | VersionInt int32 `json:"omnicoreversion_int"` 29 | Version string `json:"omnicoreversion"` 30 | BitcoinCoreVersion string `json:"bitcoincoreversion"` 31 | CommitInfo string `json:"commitinfo"` 32 | Block int32 `json:"block"` 33 | BlockTimestamp int32 `json:"blocktime"` 34 | BlockTransaction int32 `json:"blocktransactions"` 35 | TotalTransaction int32 `json:"totaltransactions"` 36 | } 37 | 38 | type OmniGetInfoCommand struct{} 39 | 40 | func (OmniGetInfoCommand) Method() string { 41 | return "omni_getinfo" 42 | } 43 | 44 | func (OmniGetInfoCommand) ID() string { 45 | return "1" 46 | } 47 | 48 | func (OmniGetInfoCommand) Params() []interface{} { 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_gettransaction.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result: 5 | { 6 | "txid": "84504b62edb18d6b9fa7089c5cba09fabaa7f1f46034ad9d49fb5781d7cf1bc6", 7 | "fee": "0.00100000", 8 | "sendingaddress": "1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh", 9 | "referenceaddress": "1PowyXXycpvSEbfY7cFcuDzpVAh6sTNejo", 10 | "ismine": false, 11 | "version": 0, 12 | "type_int": 0, 13 | "type": "Simple Send", 14 | "propertyid": 31, 15 | "divisible": true, 16 | "amount": "97.96000000", 17 | "valid": true, 18 | "blockhash": "000000000000000000190b41bf8c7b5e1c49275ab71b5fe1aef57864bedf2b5b", 19 | "blocktime": 1549012217, 20 | "positioninblock": 38, 21 | "block": 561034, 22 | "confirmations": 2 23 | } 24 | */ 25 | 26 | type OmniGettransactionResult struct { 27 | ID string `json:"txid"` 28 | Fee string `json:"fee"` 29 | From string `json:"sendingaddress"` 30 | To string `json:"referenceaddress"` 31 | Type string `json:"type"` 32 | Amount string `json:"amount"` 33 | BlockHash string `json:"blockhash"` 34 | InvalidReason string `json:"invalidreason"` 35 | Version int32 `json:"version"` 36 | TypeInt int32 `json:"type_int"` 37 | PropertyID int32 `json:"propertyid"` 38 | BlockTimestamp int32 `json:"blocktime"` 39 | PositionInBlock int32 `json:"positioninblock"` 40 | BlockHeight int32 `json:"block"` 41 | Confirmations uint32 `json:"confirmations"` 42 | Mine bool `json:"ismine"` 43 | Divisible bool `json:"divisible"` 44 | Valid bool `json:"valid"` 45 | } 46 | 47 | type OmniGetTransactionCommand struct { 48 | Hash string 49 | } 50 | 51 | func (OmniGetTransactionCommand) Method() string { 52 | return "omni_gettransaction" 53 | } 54 | 55 | func (OmniGetTransactionCommand) ID() string { 56 | return "1" 57 | } 58 | 59 | func (cmd OmniGetTransactionCommand) Params() []interface{} { 60 | return []interface{}{cmd.Hash} 61 | } 62 | -------------------------------------------------------------------------------- /omnilayer/omnijson/omni_listblocktransactions.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | Result 5 | [ (array of string) 6 | "hash", (string) the hash of the transaction 7 | ... 8 | ] 9 | */ 10 | 11 | type OmniListBlockTransactionsResult = []string 12 | 13 | type OmniListBlockTransactionsCommand struct { 14 | Block int64 15 | } 16 | 17 | func (OmniListBlockTransactionsCommand) Method() string { 18 | return "omni_listblocktransactions" 19 | } 20 | 21 | func (OmniListBlockTransactionsCommand) ID() string { 22 | return "1" 23 | } 24 | 25 | func (cmd OmniListBlockTransactionsCommand) Params() []interface{} { 26 | return []interface{}{cmd.Block} 27 | } 28 | -------------------------------------------------------------------------------- /omnilayer/omnijson/sendrawtransaction.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type SendRawTransactionResult = string 4 | 5 | type SendRawTransactionCommand struct { 6 | Hex string 7 | AllowHighFees bool 8 | } 9 | 10 | func (SendRawTransactionCommand) Method() string { 11 | return "sendrawtransaction" 12 | } 13 | 14 | func (SendRawTransactionCommand) ID() string { 15 | return "1" 16 | } 17 | 18 | func (cmd SendRawTransactionCommand) Params() []interface{} { 19 | return []interface{}{cmd.Hex, cmd.AllowHighFees} 20 | } 21 | -------------------------------------------------------------------------------- /omnilayer/omnijson/signrawtransaction.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | /* 4 | { 5 | "hex" : "value", (string) The hex-encoded raw transaction with signature(s) 6 | "complete" : true|false, (boolean) If the transaction has a complete set of signatures 7 | "errors" : [ (json array of objects) Script verification errors (if there are any) 8 | { 9 | "txid" : "hash", (string) The hash of the referenced, previous transaction 10 | "vout" : n, (numeric) The index of the output to spent and used as input 11 | "scriptSig" : "hex", (string) The hex-encoded signature script 12 | "sequence" : n, (numeric) Script sequence number 13 | "error" : "text" (string) Verification or signing error related to the input 14 | } 15 | ,... 16 | ] 17 | } 18 | */ 19 | 20 | type SignRawTransactionResult = struct { 21 | Hex string `json:"hex"` 22 | Complete bool `json:"complete"` 23 | Errors []signRawTransactionError `json:"errors"` 24 | } 25 | 26 | type signRawTransactionError struct { 27 | TxID string `json:"txid"` 28 | ScriptSig string `json:"scriptSig"` 29 | Error string `json:"error"` 30 | Vout uint32 `json:"vout"` 31 | Sequence uint32 `json:"sequence"` 32 | } 33 | 34 | type SignRawTransactionCommand struct { 35 | Hex string 36 | Previous []Previous 37 | Keys []string 38 | Type string 39 | } 40 | 41 | type Previous struct { 42 | TxID string `json:"txid"` 43 | Vout uint32 `json:"vout"` 44 | ScriptPubKey string `json:"scriptPubKey"` 45 | RedeemScript string `json:"redeemScript"` 46 | Value float64 `json:"value"` 47 | } 48 | 49 | func (SignRawTransactionCommand) Method() string { 50 | return "signrawtransaction" 51 | } 52 | 53 | func (SignRawTransactionCommand) ID() string { 54 | return "1" 55 | } 56 | 57 | func (cmd SignRawTransactionCommand) Params() []interface{} { 58 | return []interface{}{cmd.Hex, cmd.Previous, cmd.Keys, cmd.Type} 59 | } 60 | -------------------------------------------------------------------------------- /omnilayer/omnijson/signrawtransactionwithkey.go: -------------------------------------------------------------------------------- 1 | package omnijson 2 | 3 | type SignRawTransactionWithKeyResult = string 4 | 5 | type SignRawTransactionWithKeyCommand struct { 6 | Hex string 7 | Previous []Previous 8 | Keys []string 9 | Type string 10 | } 11 | 12 | func (SignRawTransactionWithKeyCommand) Method() string { 13 | return "signrawtransactionwithkey" 14 | } 15 | 16 | func (SignRawTransactionWithKeyCommand) ID() string { 17 | return "1" 18 | } 19 | 20 | func (cmd SignRawTransactionWithKeyCommand) Params() []interface{} { 21 | return []interface{}{cmd.Hex, cmd.Keys, cmd.Previous, cmd.Type} 22 | } 23 | -------------------------------------------------------------------------------- /omnilayer/rpc.go: -------------------------------------------------------------------------------- 1 | package omnilayer 2 | 3 | import "github.com/cmingxu/wallet-keeper/omnilayer/omnijson" 4 | 5 | func (c *Client) OmniFoundedSend(from, to string, propertyid int64, amount, fee string) (omnijson.OmniFoundedSendResult, error) { 6 | return futureOmniFoundedSend(c.do(omnijson.OmniFoundedSendCommand{ 7 | From: from, 8 | To: to, 9 | ProperID: propertyid, 10 | Amount: amount, 11 | Fee: fee, 12 | })).Receive() 13 | } 14 | 15 | func (c *Client) ListAccounts(minConf int64) (omnijson.ListAccountsResult, error) { 16 | return futureListAccounts(c.do(omnijson.ListAccountsCommand{ 17 | MinConf: minConf, 18 | })).Receive() 19 | } 20 | 21 | func (c *Client) GetAddressesByAccount(account string) (omnijson.GetAddressesByAccountResult, error) { 22 | return futureGetAddressesByAccount(c.do(omnijson.GetAddressesByAccountCommand{ 23 | Account: account, 24 | })).Receive() 25 | } 26 | 27 | func (c *Client) GetAccountAddress(account string) (omnijson.GetAccountAddressResult, error) { 28 | return futureGetAccountAddress(c.do(omnijson.GetAccountAddressCommand{ 29 | Account: account, 30 | })).Receive() 31 | } 32 | 33 | func (c *Client) GetNewAddress(account string) (omnijson.GetNewAddressResult, error) { 34 | return futureGetNewAddress(c.do(omnijson.GetNewAddressCommand{ 35 | Account: account, 36 | })).Receive() 37 | } 38 | 39 | func (c *Client) GetBlockChainInfo() (omnijson.GetBlockChainInfoResult, error) { 40 | return futureGetBlockChainInfo(c.do(omnijson.GetBlockChainInfoCommand{})).Receive() 41 | } 42 | 43 | func (c *Client) OmniListBlockTransactions(block int64) (omnijson.OmniListBlockTransactionsResult, error) { 44 | return futureOmniListBlockTransactions(c.do(omnijson.OmniListBlockTransactionsCommand{ 45 | Block: block, 46 | })).Receive() 47 | } 48 | 49 | func (c *Client) GetInfo() (omnijson.OmniGetInfoResult, error) { 50 | return futureGetInfo(c.do(omnijson.OmniGetInfoCommand{})).Receive() 51 | } 52 | 53 | func (c *Client) OmniGetTransaction(hash string) (omnijson.OmniGettransactionResult, error) { 54 | return futureOmniGetTransaction(c.do(omnijson.OmniGetTransactionCommand{ 55 | Hash: hash, 56 | })).Receive() 57 | } 58 | 59 | func (c *Client) ListUnspent(cmd omnijson.ListUnspentCommand) (omnijson.ListUnspentResult, error) { 60 | return futureListUnspent(c.do(cmd)).Receive() 61 | } 62 | 63 | func (c *Client) OmniCreatePayloadSimpleSend(cmd omnijson.OmniCreatePayloadSimpleSendCommand) (omnijson.OmniCreatePayloadSimpleSendResult, error) { 64 | return futureOmniCreatePayloadSimpleSend(c.do(cmd)).Receive() 65 | } 66 | 67 | func (c *Client) CreateRawTransaction(cmd omnijson.CreateRawTransactionCommand) (omnijson.CreateRawTransactionResult, error) { 68 | return futureCreateRawTransaction(c.do(cmd)).Receive() 69 | } 70 | 71 | func (c *Client) OmniCreateRawTxOpReturn(cmd omnijson.OmniCreateRawTxOpReturnCommand) (omnijson.OmniCreateRawTxOpReturnResult, error) { 72 | return futureOmniCreateRawTxOpReturn(c.do(cmd)).Receive() 73 | } 74 | 75 | func (c *Client) OmniCreateRawTxReference(cmd omnijson.OmniCreateRawTxReferenceCommand) (omnijson.OmniCreateRawTxReferenceResult, error) { 76 | return futureOmniCreateRawTxReference(c.do(cmd)).Receive() 77 | } 78 | 79 | func (c *Client) OmniCreateRawTxChange(cmd omnijson.OmniCreateRawTxChangeCommand) (omnijson.OmniCreateRawTxChangeResult, error) { 80 | return futureOmniCreateRawTxChange(c.do(cmd)).Receive() 81 | } 82 | 83 | func (c *Client) ImportAddress(address string, rescan bool) error { 84 | return futureImportAddress(c.do(omnijson.ImportAddressCommand{Adress: address, Rescan: rescan})).Receive() 85 | } 86 | 87 | func (c *Client) SendRawTransaction(cmd omnijson.SendRawTransactionCommand) (omnijson.SendRawTransactionResult, error) { 88 | return futureSendRawTransaction(c.do(cmd)).Receive() 89 | } 90 | 91 | func (c *Client) SignRawTransaction(cmd omnijson.SignRawTransactionCommand) (omnijson.SignRawTransactionResult, error) { 92 | return futureSignRawTransaction(c.do(cmd)).Receive() 93 | } 94 | 95 | func (c *Client) SignRawTransactionWithKey(cmd omnijson.SignRawTransactionWithKeyCommand) (omnijson.SignRawTransactionWithKeyResult, error) { 96 | return futureSignRawTransactionWithKey(c.do(cmd)).Receive() 97 | } 98 | 99 | func (c *Client) OmniGetBalance(cmd omnijson.OmniGetBalanceCommand) (omnijson.OmniGetBalanceResult, error) { 100 | return futureOmniGetBalance(c.do(cmd)).Receive() 101 | } 102 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./bin/wallet-keeper \ 4 | --log-level debug \ 5 | --log-dir /tmp \ 6 | --env development \ 7 | run \ 8 | --http-listen-addr 0.0.0.0:8080 \ 9 | --backends btc,usdt,eth \ 10 | --rpc-addr ws://107.150.126.20:9546 \ 11 | --eth-wallet-dir /tmp/wallets 12 | --eth-rpc-addr http://192.168.0.101:8545 \ 13 | --eth-account-path /tmp/eth-accounts.json \ 14 | --btc-rpc-addr 192.168.0.101:8332 \ 15 | --btc-rpc-user foo \ 16 | --btc-rpc-pass bar \ 17 | --usdt-rpc-addr localhost:18332 \ 18 | --usdt-rpc-user foo \ 19 | --usdt-rpc-pass bar \ 20 | --usdt-property-id 31 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /scripts/btc_usdt_call.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | docker run --net host \ 4 | -it ruimarinho/bitcoin-core bitcoin-cli \ 5 | --testnet \ 6 | --rpcconnect= \ 7 | --rpcuser=omnicore \ 8 | --rpcpassword=7zmZ*gV6sQK \ 9 | --rpcport=18332 "$@" 10 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT_PATH=$(cd "$(dirname $BASH_SOURCE[0])/.." && pwd) 4 | 5 | cd $ROOT_PATH 6 | VERSION=$(cat ./VERSION) 7 | RELEASE_IMAGE=wallet_keeper:${VERSION} 8 | 9 | docker build -t $RELEASE_IMAGE --no-cache --rm -f ./Dockerfile . 10 | -------------------------------------------------------------------------------- /scripts/build_bitcoind_core_image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ROOT_PATH=$(cd $(dirname $BASH_SOURCE[0])/.. && pwd) 4 | cd $ROOT_PATH 5 | 6 | docker build --no-cache -t cmingxu/bitcoin-core -f Dockerfile.bitcoind-core . 7 | 8 | -------------------------------------------------------------------------------- /scripts/eth_rpc_call.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | docker run --net host \ 4 | -it ethereum/client-go \ 5 | --testnet \ 6 | --rpcconnect=154.8.201.160 \ 7 | -rpcport=18332 "$@" 8 | -------------------------------------------------------------------------------- /scripts/eth_rpc_call_curl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | METHOD=$1 4 | PARAM=$2 5 | 6 | curl -X POST -H 'Content-Type: application/json' \ 7 | --data "{\"jsonrpc\":\"2.0\",\"method\":\"${METHOD}\",\"params\":[${PARAM}],\"id\":1}" \ 8 | http://154.8.201.160:8545/ 9 | -------------------------------------------------------------------------------- /scripts/genauth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if [[ $# != 1 ]];then 4 | echo "./genauth.sh " 5 | else 6 | curl -sSL https://raw.githubusercontent.com/bitcoin/bitcoin/master/share/rpcauth/rpcauth.py | python - $1 7 | fi 8 | 9 | -------------------------------------------------------------------------------- /scripts/rpcauth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2015-2018 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | 6 | from argparse import ArgumentParser 7 | from base64 import urlsafe_b64encode 8 | from binascii import hexlify 9 | from getpass import getpass 10 | from os import urandom 11 | 12 | import hmac 13 | 14 | def generate_salt(size): 15 | """Create size byte hex salt""" 16 | return hexlify(urandom(size)).decode() 17 | 18 | def generate_password(): 19 | """Create 32 byte b64 password""" 20 | return urlsafe_b64encode(urandom(32)).decode('utf-8') 21 | 22 | def password_to_hmac(salt, password): 23 | m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') 24 | return m.hexdigest() 25 | 26 | def main(): 27 | parser = ArgumentParser(description='Create login credentials for a JSON-RPC user') 28 | parser.add_argument('username', help='the username for authentication') 29 | parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?') 30 | args = parser.parse_args() 31 | 32 | if not args.password: 33 | args.password = generate_password() 34 | elif args.password == '-': 35 | args.password = getpass() 36 | 37 | # Create 16 byte hex salt 38 | salt = generate_salt(16) 39 | password_hmac = password_to_hmac(salt, args.password) 40 | 41 | print('String to be appended to bitcoin.conf:') 42 | print('rpcauth={0}:{1}${2}'.format(args.username, salt, password_hmac)) 43 | print('Your password:\n{0}'.format(args.password)) 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /scripts/run_bitcoind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | CONTAINER_NAME=bitcoind-node 4 | #IMAGE_NAME=cmingxu/bitcoin-core 5 | IMAGE_NAME=ruimarinho/bitcoin-core 6 | DAEMON=${1:yes} 7 | 8 | if [[ x${DAEMON} == x"yes" ]]; then 9 | DOCKER_ARGS=-d 10 | else 11 | DOCKER_ARGS= 12 | fi 13 | 14 | BITCOIN_DATA=/bitcoind-data 15 | 16 | 17 | docker rm -f ${CONTAINER_NAME} 2>&1 >/dev/null 18 | 19 | docker run --env=BITCOIN_DATA=${BITCOIN_DATA} \ 20 | -v $(dirname "$(pwd)/..")/bitcoind-data:/bitcoind-data \ 21 | -v $(pwd)/bitcoin.conf:/bitcoin.conf \ 22 | --name=${CONTAINER_NAME} \ 23 | ${DOCKER_ARGS} \ 24 | -p 8333:8333 \ 25 | -p 0.0.0.0:8332:8332 \ 26 | -p 0.0.0.0:18332:18332 \ 27 | ${IMAGE_NAME} -conf=/bitcoin.conf \ 28 | -deprecatedrpc=accounts 29 | -------------------------------------------------------------------------------- /scripts/run_wallet_keeper_docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT_PATH=$(cd "$(dirname $BASH_SOURCE[0])/.." && pwd) 4 | 5 | cd $ROOT_PATH 6 | VERSION=$(cat ./VERSION) 7 | RELEASE_IMAGE=wallet_keeper:${VERSION} 8 | CONTAINER_NAME=wallet_keeper_${VERSION} 9 | 10 | 11 | docker run -it \ 12 | --publish 127.0.0.1:8000:8000/tcp \ 13 | --volume /data/wallet-keeper/eth-accounts.json:/data/eth-accounts.json \ 14 | --name $CONTAINER_NAME $RELEASE_IMAGE 15 | 16 | --------------------------------------------------------------------------------