├── .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 | [](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 |
--------------------------------------------------------------------------------