Initial commit
This commit is contained in:
commit
75c6ae73ad
|
@ -0,0 +1,7 @@
|
|||
ZFAUCET_RPCUSER=zcashrpc
|
||||
ZFAUCET_RPCPASSWORD=notsecure
|
||||
ZFAUCET_RPCHOST=192.168.86.46
|
||||
ZFAUCET_RPCPORT=38237
|
||||
ZFAUCET_LISTENPORT=3000
|
||||
ZFAUCET_LISTENADDRESS=127.0.0.1
|
||||
ZFAUCET_FUNDINGADDRESS=
|
|
@ -0,0 +1,2 @@
|
|||
.env
|
||||
zfaucet
|
|
@ -0,0 +1,12 @@
|
|||
FROM golang:1.13 as builder
|
||||
|
||||
ADD . /app/
|
||||
WORKDIR /app/
|
||||
RUN make build
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /app/zfaucet /app/zfaucet
|
||||
COPY ./templates /app/templates
|
||||
ENTRYPOINT ["/app/zfaucet"]
|
|
@ -0,0 +1,26 @@
|
|||
SHELL := /bin/bash
|
||||
|
||||
VERSION := `git describe --always`
|
||||
GITCOMMIT := `git rev-parse HEAD`
|
||||
BRANCH := `git rev-parse --abbrev-ref HEAD`
|
||||
BUILDDATE := `date +%Y-%m-%d`
|
||||
BUILDUSER := `whoami`
|
||||
|
||||
LDFLAGSSTRING :=-X main.Version=$(VERSION)
|
||||
LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
|
||||
LDFLAGSSTRING +=-X main.Branch=$(BRANCH)
|
||||
LDFLAGSSTRING +=-X main.BuildDate=$(BUILDDATE)
|
||||
LDFLAGSSTRING +=-X main.BuildUser=$(BUILDUSER)
|
||||
|
||||
LDFLAGS :=-ldflags "$(LDFLAGSSTRING)"
|
||||
|
||||
.PHONY: all build
|
||||
|
||||
all: build
|
||||
|
||||
# Build binary
|
||||
build:
|
||||
CGO_ENABLED=0 go build $(LDFLAGS)
|
||||
|
||||
test:
|
||||
go test -v ./...
|
|
@ -0,0 +1,36 @@
|
|||
# zfaucet
|
||||
|
||||
ZFaucet is software to request zcash testnet funds.
|
||||
|
||||
## Requirements
|
||||
|
||||
A fully synced zcashd node is required with RPC access.
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is done through environmental variables.
|
||||
|
||||
Copy the template file and edit the values.
|
||||
|
||||
`cp .envtemplate .env`
|
||||
|
||||
## Build a binary
|
||||
|
||||
`make build`
|
||||
|
||||
## Build Docker iamge
|
||||
|
||||
`docker build .`
|
||||
|
||||
## Run binary
|
||||
|
||||
From the direcotry with the `.env` file.
|
||||
`./zfaucet`
|
||||
|
||||
## Run a Docker image
|
||||
|
||||
From the direcotry with the `.env` file.
|
||||
```
|
||||
docker build . -t zfaucet \
|
||||
&& docker run --env-file ./.env --rm -ti -p 3000:3000 zfaucet
|
||||
```
|
|
@ -0,0 +1,14 @@
|
|||
module github.com/doubtingben/zfaucet
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
||||
github.com/gobuffalo/packr v1.30.1
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/lib/pq v1.7.0
|
||||
github.com/onsi/gomega v1.10.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.6.0 // indirect
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible
|
||||
)
|
|
@ -0,0 +1,122 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
|
||||
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
||||
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC3SbsI=
|
||||
github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible h1:V4mkE9qhbDQ92/MLMIhlhMSbz8jNXdagC3xBR5NDwaQ=
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,395 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/ybbus/jsonrpc"
|
||||
)
|
||||
|
||||
const tapAmount = 1.0
|
||||
|
||||
type TapRequest struct {
|
||||
NetworkAddress string
|
||||
WalletAddress string
|
||||
RequestedAt time.Time
|
||||
}
|
||||
type ZfaucetConfig struct {
|
||||
ListenPort string
|
||||
ListenAddress string
|
||||
RPCUser string
|
||||
RPCPassword string
|
||||
RPCHost string
|
||||
RPCPort string
|
||||
FundingAddress string
|
||||
}
|
||||
|
||||
func (zConfig *ZfaucetConfig) checkConfig() error {
|
||||
if zConfig.ListenPort == "" {
|
||||
zConfig.ListenPort = "3000"
|
||||
}
|
||||
if zConfig.ListenAddress == "" {
|
||||
zConfig.ListenPort = "127.0.0.1"
|
||||
}
|
||||
if zConfig.RPCHost == "" {
|
||||
zConfig.ListenPort = "localhost"
|
||||
}
|
||||
if zConfig.ListenPort == "" {
|
||||
zConfig.ListenPort = "3000"
|
||||
}
|
||||
if zConfig.FundingAddress == "" {
|
||||
return fmt.Errorf("ZFAUCET_FUNDINGADDRESS is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zfaucet holds a zfaucet configuration
|
||||
type Zfaucet struct {
|
||||
RPCConnetion jsonrpc.RPCClient
|
||||
CurrentHeight int
|
||||
UpdatedChainInfo time.Time
|
||||
UpdatedWallet time.Time
|
||||
Operations map[string]OperationStatus
|
||||
ZcashdVersion string
|
||||
ZcashNetwork string
|
||||
FundingAddress string
|
||||
TapRequests []*TapRequest
|
||||
ZfaucetHTML string
|
||||
}
|
||||
|
||||
type SendAmount struct {
|
||||
Address string `json:"address"`
|
||||
Amount float32 `json:"amount"`
|
||||
}
|
||||
|
||||
// TODO tag facet transactions, zaddr targets only
|
||||
type SendAmountMemo struct {
|
||||
SendAmount
|
||||
Memo string
|
||||
}
|
||||
|
||||
func (z *Zfaucet) WaitForOperation(opid string) (os OperationStatus, err error) {
|
||||
var opStatus []struct {
|
||||
CreationTime int `json:"creation_time"`
|
||||
ID string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Result struct {
|
||||
TxID string `json:"txid"`
|
||||
}
|
||||
Status string `json:"status"`
|
||||
}
|
||||
var parentList [][]string
|
||||
var opList []string
|
||||
opList = append(opList, opid)
|
||||
parentList = append(parentList, opList)
|
||||
fmt.Printf("opList: %s\n", opList)
|
||||
fmt.Printf("parentList: %s\n", parentList)
|
||||
// Wait for a few seconds for the operational status to become available
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := z.RPCConnetion.CallFor(
|
||||
&opStatus,
|
||||
"z_getoperationresult",
|
||||
parentList,
|
||||
); err != nil {
|
||||
return os, fmt.Errorf("failed to call z_getoperationresult: %s", err)
|
||||
} else {
|
||||
fmt.Printf("op: %s, i: %d, status: %#v\n", opid, i, opStatus)
|
||||
if len(opStatus) > 0 {
|
||||
fmt.Printf("opStatus: %#v\n", opStatus[0])
|
||||
//z.Operations[opid] = OperationStatus{
|
||||
os = OperationStatus{
|
||||
UpdatedAt: time.Now(),
|
||||
TxID: opStatus[0].Result.TxID,
|
||||
Status: opStatus[0].Status,
|
||||
}
|
||||
z.Operations[opid] = os
|
||||
return os, nil
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
return os, errors.New("Timeout waiting for operations status")
|
||||
}
|
||||
|
||||
func (z *Zfaucet) ValidateFundingAddress() (bool, error) {
|
||||
if z.FundingAddress == "" {
|
||||
return false, errors.New("FundingAddressis required")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (z *Zfaucet) ZSendManyFaucet(remoteAddr string, remoteWallet string) (opStatus OperationStatus, err error) {
|
||||
var op *string
|
||||
amountEntry := SendAmount{
|
||||
Address: remoteWallet,
|
||||
Amount: tapAmount,
|
||||
}
|
||||
fmt.Printf("ZSendManyFaucet sending: %#v\n", amountEntry)
|
||||
fmt.Printf("ZSendManyFaucet from funding address: %s\n", z.FundingAddress)
|
||||
// if err != nil {
|
||||
// return opStatus, err
|
||||
// }
|
||||
// Call z_sendmany with a single entry entry list
|
||||
if err := z.RPCConnetion.CallFor(
|
||||
&op,
|
||||
"z_sendmany",
|
||||
z.FundingAddress,
|
||||
[]SendAmount{amountEntry},
|
||||
); err != nil {
|
||||
return opStatus, err
|
||||
}
|
||||
fmt.Printf("ZSendManyFaucet sent to %s: Address: %s %s\n", remoteWallet, remoteAddr, *op)
|
||||
opStatus, err = z.WaitForOperation(*op)
|
||||
if err != nil {
|
||||
return opStatus, err
|
||||
}
|
||||
if opStatus.Status != "success" {
|
||||
return opStatus, fmt.Errorf("Failed to send funds: %s", err)
|
||||
}
|
||||
z.TapRequests = append(z.TapRequests, &TapRequest{
|
||||
NetworkAddress: remoteAddr,
|
||||
WalletAddress: remoteWallet,
|
||||
RequestedAt: time.Now(),
|
||||
})
|
||||
return opStatus, err
|
||||
|
||||
}
|
||||
|
||||
type GetBlockInfo struct {
|
||||
Version int
|
||||
}
|
||||
|
||||
func getBlockchainInfo(rpcClient jsonrpc.RPCClient) (blockChainInfo *GetBlockchainInfo, err error) {
|
||||
if err := rpcClient.CallFor(&blockChainInfo, "getblockchaininfo"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getInfo(rpcClient jsonrpc.RPCClient) (info *GetBlockInfo, err error) {
|
||||
if err := rpcClient.CallFor(&info, "getinfo"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
versionFlag := flag.Bool("version", false, "print version information")
|
||||
flag.Parse()
|
||||
if *versionFlag {
|
||||
fmt.Printf("(version=%s, branch=%s, gitcommit=%s)\n", Version, Branch, GitCommit)
|
||||
fmt.Printf("(go=%s, user=%s, date=%s)\n", GoVersion, BuildUser, BuildDate)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var zConfig ZfaucetConfig
|
||||
err := envconfig.Process("zfaucet", &zConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if err = zConfig.checkConfig(); err != nil {
|
||||
log.Fatalf("Config error: %s", err)
|
||||
}
|
||||
fmt.Printf("zfaucet: %#v\n", zConfig)
|
||||
|
||||
basicAuth := base64.StdEncoding.EncodeToString([]byte(zConfig.RPCUser + ":" + zConfig.RPCPassword))
|
||||
var z Zfaucet
|
||||
z.FundingAddress = zConfig.FundingAddress
|
||||
z.Operations = make(map[string]OperationStatus)
|
||||
z.RPCConnetion = jsonrpc.NewClientWithOpts("http://"+zConfig.RPCHost+":"+zConfig.RPCPort,
|
||||
&jsonrpc.RPCClientOpts{
|
||||
CustomHeaders: map[string]string{
|
||||
"Authorization": "Basic " + basicAuth,
|
||||
}})
|
||||
|
||||
zChainInfo, err := getBlockchainInfo(z.RPCConnetion)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get blockchaininfo: %s", err)
|
||||
}
|
||||
z.CurrentHeight = zChainInfo.Blocks
|
||||
z.ZcashNetwork = zChainInfo.Chain
|
||||
zVersion, err := getInfo(z.RPCConnetion)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to getinfo: %s", err)
|
||||
}
|
||||
z.ZcashdVersion = strconv.Itoa(zVersion.Version)
|
||||
|
||||
box := packr.NewBox("./templates")
|
||||
z.ZfaucetHTML, err = box.FindString("zfaucet.html")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
homeHandler := http.HandlerFunc(z.home)
|
||||
balanceHandler := http.HandlerFunc(z.balance)
|
||||
opsStatusHandler := http.HandlerFunc(z.opsStatus)
|
||||
addressHandler := http.HandlerFunc(z.addresses)
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", homeHandler)
|
||||
mux.Handle("/balance", z.OKMiddleware(balanceHandler))
|
||||
mux.Handle("/addresses", z.OKMiddleware(addressHandler))
|
||||
mux.Handle("/ops/status", z.OKMiddleware(opsStatusHandler))
|
||||
log.Printf("Listening on :%s...\n", zConfig.ListenPort)
|
||||
err = http.ListenAndServe(zConfig.ListenAddress+":"+zConfig.ListenPort, mux)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// OperationStatus describes an rpc response
|
||||
type OperationStatus struct {
|
||||
UpdatedAt time.Time
|
||||
Status string
|
||||
TxID string
|
||||
result interface{}
|
||||
}
|
||||
|
||||
// home is the default request handler
|
||||
func (z *Zfaucet) home(w http.ResponseWriter, r *http.Request) {
|
||||
// tData is the html template data
|
||||
tData := struct {
|
||||
Z *Zfaucet
|
||||
Msg string
|
||||
}{
|
||||
z,
|
||||
"",
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
if err := checkFaucetAddress(r.FormValue("address")); err != nil {
|
||||
tData.Msg = fmt.Sprintf("Invalid address: %s", err)
|
||||
break
|
||||
}
|
||||
opStatus, err := z.ZSendManyFaucet(r.RemoteAddr, r.FormValue("address"))
|
||||
if err != nil {
|
||||
tData.Msg = fmt.Sprintf("Failed to send funds: %s", err)
|
||||
break
|
||||
}
|
||||
tData.Msg = fmt.Sprintf("Successfully submitted operation: %s", opStatus)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
tmpl, err := template.New("name").Parse(z.ZfaucetHTML)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
tmpl.Execute(w, tData)
|
||||
}
|
||||
|
||||
// OKMiddleware determines if a request is allowed before execution
|
||||
func (z *Zfaucet) OKMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Our middleware logic goes here...
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Balance
|
||||
func (z *Zfaucet) balance(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var totalBalance *z_gettotalbalance
|
||||
if err := z.RPCConnetion.CallFor(&totalBalance, "z_gettotalbalance"); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
out, err := json.Marshal(totalBalance)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, string(out))
|
||||
}
|
||||
|
||||
// opsStatus
|
||||
func (z *Zfaucet) opsStatus(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var ops *[]string
|
||||
if err := z.RPCConnetion.CallFor(&ops, "z_listoperationids"); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
out, err := json.Marshal(ops)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, string(out))
|
||||
}
|
||||
|
||||
// addresses
|
||||
func (z *Zfaucet) addresses(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var addresses []WalletAddress
|
||||
var zlist *[]string
|
||||
var taddrs []interface{}
|
||||
// Z addresses
|
||||
if err := z.RPCConnetion.CallFor(&zlist, "z_listaddresses"); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
for _, zaddr := range *zlist {
|
||||
entry := WalletAddress{
|
||||
Address: zaddr,
|
||||
}
|
||||
entry.Notes = append(entry.Notes, "z address")
|
||||
addresses = append(addresses, entry)
|
||||
|
||||
}
|
||||
// T addresses
|
||||
if err := z.RPCConnetion.CallFor(&taddrs, "listaddressgroupings"); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Problem calling listaddressgroupings: %s", err.Error()), 500)
|
||||
return
|
||||
}
|
||||
fmt.Printf("T addresses:\n%#v\n", taddrs)
|
||||
// TODO: fix this mess
|
||||
for _, a := range taddrs {
|
||||
switch aResult := a.(type) {
|
||||
case []interface{}:
|
||||
for _, b := range aResult {
|
||||
switch bResult := b.(type) {
|
||||
case []interface{}:
|
||||
for _, x := range bResult {
|
||||
switch x.(type) {
|
||||
case string:
|
||||
taddr := fmt.Sprintf("%v", x)
|
||||
fmt.Printf("Adding T Address: %s\n", taddr)
|
||||
entry := WalletAddress{
|
||||
Address: taddr,
|
||||
}
|
||||
entry.Notes = append(entry.Notes, "t address")
|
||||
addresses = append(addresses, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out, err := json.Marshal(addresses)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, string(out))
|
||||
}
|
||||
|
||||
// GetBytes returns a byte slice from an interface
|
||||
func GetBytes(key interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<h1><a href="/">zFaucet</a></h1>
|
||||
<div id="links">
|
||||
<ul style="list-style-type: none;">
|
||||
<li style="display: inline-block;"><a href="/balance">balance</a></li>
|
||||
<li style="display: inline-block;"><a href="/addresses">addresses</a></li>
|
||||
<li style="display: inline-block;"><a href="/ops/status">ops</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="header">
|
||||
<ul>
|
||||
<li>Current Height:{{.Z.CurrentHeight}}</li>
|
||||
<li>Zcashd Version:{{.Z.ZcashdVersion}}</li>
|
||||
<li>Zcashd Network:{{.Z.ZcashNetwork}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="main">
|
||||
<form method="POST" action="/">
|
||||
<label>Address</label><input name="address" type="text" value="Testnet receiveing address" />
|
||||
<input type="submit" value="submit" />
|
||||
</form>
|
||||
<div id="msg">
|
||||
{{.Msg}}
|
||||
</div>
|
||||
<div id="tapRequests">
|
||||
<h2>Tap Requests</h2>
|
||||
{{range $k, $v := .Z.TapRequests}}
|
||||
<div>
|
||||
<b>NetworkAddress: </b>{{$v.NetworkAddress}}</br>
|
||||
<b>WalletAddress: </b>{{$v.WalletAddress}}</br>
|
||||
<b>Requested at: </b>{{$v.RequestedAt.Format "Mon, 02 Jan 2006 15:04:05 MST "}}</br>
|
||||
</div>
|
||||
<hr>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="ops">
|
||||
<h2>Operations</h2>
|
||||
{{range $k, $v := .Z.Operations}}
|
||||
<div>
|
||||
<b>Updated at: </b>{{$v.UpdatedAt.Format "Mon, 02 Jan 2006 15:04:05 MST "}}</br>
|
||||
<span class="{{$v.Status}}"><b>opid: </b>{{$k}}</span></br>
|
||||
<b>Status: </b>{{$v.Status}}</br>
|
||||
<b>Transaction ID: </b>{{$v.TxID}}</br>
|
||||
</div>
|
||||
<hr>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
// GetBlockchainInfo return the zcashd rpc `getblockchaininfo` status
|
||||
// https://zcash-rpc.github.io/getblockchaininfo.html
|
||||
type GetBlockchainInfo struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
BestBlockhash string `json:"bestblockhash"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
VerificationProgress float64 `json:"verificationprogress"`
|
||||
SizeOnDisk float64 `json:"size_on_disk"`
|
||||
SoftForks []SoftFork `json:"softforks"`
|
||||
}
|
||||
|
||||
type SoftFork struct {
|
||||
ID string `json:"id"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type z_gettotalbalance struct {
|
||||
Transparent string
|
||||
Private string
|
||||
Total string
|
||||
}
|
||||
|
||||
type WalletAddress struct {
|
||||
Address string
|
||||
Notes []string
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
GitCommit string
|
||||
Branch string
|
||||
BuildUser string
|
||||
BuildDate string
|
||||
GoVersion = runtime.Version()
|
||||
)
|
||||
|
||||
func Info() string {
|
||||
return fmt.Sprintf("(version=%s, branch=%s, gitcommit=%s)", Version, Branch, GitCommit)
|
||||
}
|
||||
|
||||
func BuildContext() string {
|
||||
return fmt.Sprintf("(go=%s, user=%s, date=%s)", GoVersion, BuildUser, BuildDate)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func zSendManyHTTPPost(r *http.Request) (opid string, err error) {
|
||||
fmt.Printf("zSendManyHTTPPost address: %s\n", r.FormValue("address"))
|
||||
switch {
|
||||
case r.FormValue("address") == "":
|
||||
fmt.Println("address blank case")
|
||||
return "", errors.New("Form field value required: address")
|
||||
case isTestnetTransparent(r.FormValue("address")):
|
||||
fmt.Println("Address is a transparent testnet address")
|
||||
|
||||
return r.FormValue("address"), nil
|
||||
|
||||
default:
|
||||
fmt.Println("address default case")
|
||||
return "", errors.New("A valid address is required")
|
||||
}
|
||||
}
|
||||
|
||||
func isTestnetTransparent(addr string) bool {
|
||||
//TODO Check length and encoding
|
||||
matched, _ := regexp.MatchString(`^tm`, addr)
|
||||
return matched
|
||||
}
|
||||
|
||||
func isTestnetSaplingZaddr(addr string) bool {
|
||||
//TODO Check length and encoding
|
||||
matched, _ := regexp.MatchString(`^ztestsapling`, addr)
|
||||
return matched
|
||||
}
|
||||
|
||||
func checkFaucetAddress(checkAddr string) error {
|
||||
switch {
|
||||
case checkAddr == "":
|
||||
fmt.Println("address blank case")
|
||||
return errors.New("Form field value required: address")
|
||||
case isTestnetTransparent(checkAddr):
|
||||
fmt.Println("Address is a testnet transparent address")
|
||||
return nil
|
||||
case isTestnetSaplingZaddr(checkAddr):
|
||||
fmt.Println("Address is a testnet sapling address")
|
||||
return nil
|
||||
default:
|
||||
fmt.Println("address default case")
|
||||
return errors.New("A valid address is required")
|
||||
}
|
||||
}
|
||||
|
||||
func zSendManyFaucet(addr string) (opid string, err error) {
|
||||
return "000-test-opid-string-000", nil
|
||||
}
|
Loading…
Reference in New Issue