├── .gitignore ├── .goreleaser.yml ├── go.mod ├── .github └── workflows │ ├── test.yml │ ├── semantic-pull-request.yml │ ├── release.yml │ └── release-docker.yml ├── entrypoint.sh ├── Dockerfile ├── Makefile ├── internal └── environ │ ├── env.go │ └── env_test.go ├── cmd └── faucet │ ├── main.go │ └── config.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - main: ./cmd/faucet 3 | goarch: 4 | - amd64 5 | - arm64 6 | ignore: 7 | - goos: darwin 8 | goarch: arm64 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tendermint/faucet 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/sirupsen/logrus v1.8.1 7 | github.com/tendermint/starport v0.19.5 8 | ) 9 | 10 | replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 11 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-go@v2 11 | with: 12 | go-version: 1.16 13 | - run: make test -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if pidof $CLI_NAME; then 4 | export PATH=/proc/$(pidof $CLI_NAME)/root/usr/local/sbin:/proc/$(pidof $CLI_NAME)/root/usr/local/bin:/proc/$(pidof $CLI_NAME)/root/usr/sbin:/proc/$(pidof $CLI_NAME)/root/usr/bin:/proc/$(pidof $CLI_NAME)/root/sbin:/proc/$(pidof $CLI_NAME)/root/bin:$PATH 5 | fi 6 | 7 | exec "$@" 8 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: "Semantic PR" 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | - edited 7 | - synchronize 8 | jobs: 9 | main: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: amannn/action-semantic-pull-request@v1.2.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | WORKDIR /src/app/ 3 | RUN apk add git 4 | COPY go.mod go.sum* ./ 5 | RUN go mod download 6 | COPY . . 7 | RUN CGO_ENABLED=0 go build -o=/usr/local/bin/faucet ./cmd/faucet 8 | 9 | FROM alpine 10 | COPY --from=builder /usr/local/bin/faucet /usr/local/bin/faucet 11 | COPY entrypoint.sh /entrypoint.sh 12 | ENTRYPOINT ["/entrypoint.sh"] 13 | CMD [ "faucet" ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILDDIR ?= $(CURDIR)/build 2 | 3 | all: install 4 | 5 | $(BUILDDIR)/: 6 | mkdir -p $@ 7 | 8 | BUILD_TARGETS := build install 9 | build: $(BUILDDIR)/ 10 | build: BUILD_ARGS=-o=$(BUILDDIR) 11 | 12 | $(BUILD_TARGETS): 13 | go $@ -mod=readonly $(BUILD_FLAGS) $(BUILD_ARGS) ./... 14 | 15 | test: 16 | go test ./... 17 | 18 | clean: 19 | rm -rf $(BUILDDIR)/ 20 | 21 | .PHONY: all $(BUILD_TARGETS) clean 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.16 21 | 22 | - name: Run GoReleaser 23 | uses: goreleaser/goreleaser-action@v2 24 | with: 25 | version: latest 26 | args: release --rm-dist 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /internal/environ/env.go: -------------------------------------------------------------------------------- 1 | package environ 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | func GetString(key, fallback string) string { 9 | if value, ok := os.LookupEnv(key); ok { 10 | return value 11 | } 12 | 13 | return fallback 14 | } 15 | 16 | func GetInt(key string, fallback int) int { 17 | if value, ok := os.LookupEnv(key); ok { 18 | if i, err := strconv.Atoi(value); err == nil { 19 | return i 20 | } 21 | } 22 | 23 | return fallback 24 | } 25 | 26 | func GetUint64(key string, fallback uint64) uint64 { 27 | if value, ok := os.LookupEnv(key); ok { 28 | if i, err := strconv.ParseUint(value, 10, 64); err == nil { 29 | return i 30 | } 31 | } 32 | 33 | return fallback 34 | } 35 | 36 | func GetBool(key string, fallback bool) bool { 37 | if value, ok := os.LookupEnv(key); ok { 38 | return value == "true" 39 | } 40 | 41 | return fallback 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/release-docker.yml: -------------------------------------------------------------------------------- 1 | name: Build docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set up Docker Buildx 13 | uses: docker/setup-buildx-action@v1 14 | 15 | - name: Login to GitHub Container Registry 16 | uses: docker/login-action@v1 17 | with: 18 | registry: ghcr.io 19 | username: ${{ github.repository_owner }} 20 | password: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Get version 23 | id: get_version 24 | uses: battila7/get-version-action@v2 25 | 26 | - name: Build and push 27 | id: docker_build 28 | uses: docker/build-push-action@v2 29 | with: 30 | push: true 31 | platforms: linux/amd64,linux/arm64 32 | tags: ghcr.io/tendermint/faucet:latest,ghcr.io/tendermint/faucet:${{ steps.get_version.outputs.version-without-v }} 33 | -------------------------------------------------------------------------------- /internal/environ/env_test.go: -------------------------------------------------------------------------------- 1 | package environ_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/tendermint/faucet/internal/environ" 8 | ) 9 | 10 | func TestEnviron(t *testing.T) { 11 | if os.Getenv("integer") != "" { 12 | t.Fatalf("wrong initialization") 13 | } 14 | 15 | if os.Getenv("unsigned") != "" { 16 | t.Fatalf("wrong initialization") 17 | } 18 | 19 | if os.Getenv("string") != "" { 20 | t.Fatalf("wrong initialization") 21 | } 22 | 23 | if environ.GetInt("integer", -1) != -1 { 24 | t.Fatalf("wanted -1") 25 | } 26 | 27 | if environ.GetUint64("unsigned", 10) != 10 { 28 | t.Fatalf("wanted 10") 29 | } 30 | 31 | if environ.GetString("string", "example") != "example" { 32 | t.Fatalf("wanted example") 33 | } 34 | 35 | integer, unsigned, str := "-1", "10", "example" 36 | 37 | if err := os.Setenv("integer", integer); err != nil { 38 | t.Fatalf("unexpected error: %v", err) 39 | } 40 | 41 | if err := os.Setenv("unsigned", unsigned); err != nil { 42 | t.Fatalf("unexpected error: %v", err) 43 | } 44 | 45 | if err := os.Setenv("string", str); err != nil { 46 | t.Fatalf("unexpected error: %v", err) 47 | } 48 | 49 | if environ.GetInt("integer", -5) != -1 { 50 | t.Fatalf("wanted -1") 51 | } 52 | 53 | if environ.GetUint64("unsigned", 15) != 10 { 54 | t.Fatalf("wanted 10") 55 | } 56 | 57 | if environ.GetString("string", "invalid") != "example" { 58 | t.Fatalf("wanted example") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd/faucet/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "net/http" 8 | "strings" 9 | 10 | log "github.com/sirupsen/logrus" 11 | 12 | "github.com/tendermint/starport/starport/pkg/chaincmd" 13 | chaincmdrunner "github.com/tendermint/starport/starport/pkg/chaincmd/runner" 14 | "github.com/tendermint/starport/starport/pkg/cosmosfaucet" 15 | "github.com/tendermint/starport/starport/pkg/cosmosver" 16 | ) 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | configKeyringBackend, err := chaincmd.KeyringBackendFromString(keyringBackend) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | ccoptions := []chaincmd.Option{ 27 | chaincmd.WithKeyringPassword(keyringPassword), 28 | chaincmd.WithKeyringBackend(configKeyringBackend), 29 | chaincmd.WithAutoChainIDDetection(), 30 | chaincmd.WithNodeAddress(nodeAddress), 31 | } 32 | 33 | if home != "" { 34 | ccoptions = append(ccoptions, chaincmd.WithHome(home)) 35 | } 36 | 37 | if legacySendCmd { 38 | ccoptions = append(ccoptions, chaincmd.WithLegacySendCommand()) 39 | } 40 | 41 | switch sdkVersion { 42 | case "stargate-44": 43 | ccoptions = append(ccoptions, 44 | chaincmd.WithVersion(cosmosver.StargateFortyFourVersion), 45 | ) 46 | case "stargate-40": 47 | ccoptions = append(ccoptions, 48 | chaincmd.WithVersion(cosmosver.StargateFortyVersion), 49 | ) 50 | case "launchpad": 51 | ccoptions = append(ccoptions, 52 | chaincmd.WithVersion(cosmosver.MaxLaunchpadVersion), 53 | chaincmd.WithLaunchpadCLI(appCli), 54 | ) 55 | if home != "" { 56 | ccoptions = append(ccoptions, chaincmd.WithLaunchpadCLIHome(home)) 57 | } 58 | default: 59 | ccoptions = append(ccoptions, 60 | chaincmd.WithVersion(cosmosver.Latest), 61 | ) 62 | } 63 | 64 | cr, err := chaincmdrunner.New(context.Background(), chaincmd.New(appCli, ccoptions...)) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | 69 | coins := strings.Split(defaultDenoms, denomSeparator) 70 | 71 | faucetOptions := make([]cosmosfaucet.Option, len(coins)) 72 | for i, coin := range coins { 73 | faucetOptions[i] = cosmosfaucet.Coin(creditAmount, maxCredit, coin) 74 | } 75 | 76 | faucetOptions = append(faucetOptions, cosmosfaucet.Account(keyName, keyMnemonic, coinType)) 77 | 78 | faucet, err := cosmosfaucet.New(context.Background(), cr, faucetOptions...) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | http.HandleFunc("/", faucet.ServeHTTP) 84 | log.Infof("listening on :%d", port) 85 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) 86 | } 87 | -------------------------------------------------------------------------------- /cmd/faucet/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/tendermint/starport/starport/pkg/cosmosfaucet" 7 | 8 | "github.com/tendermint/faucet/internal/environ" 9 | ) 10 | 11 | const ( 12 | denomSeparator = "," 13 | ) 14 | 15 | var ( 16 | port int 17 | keyringBackend string 18 | sdkVersion string 19 | keyName string 20 | keyMnemonic string 21 | keyringPassword string 22 | appCli string 23 | defaultDenoms string 24 | creditAmount uint64 25 | maxCredit uint64 26 | nodeAddress string 27 | legacySendCmd bool 28 | coinType string 29 | home string 30 | ) 31 | 32 | func init() { 33 | flag.IntVar(&port, "port", 34 | environ.GetInt("PORT", 8000), 35 | "tcp port where faucet will be listening for requests", 36 | ) 37 | flag.StringVar(&keyringBackend, "keyring-backend", 38 | environ.GetString("KEYRING_BACKEND", ""), 39 | "keyring backend to be used", 40 | ) 41 | flag.StringVar(&sdkVersion, "sdk-version", 42 | environ.GetString("SDK_VERSION", "latest"), 43 | "version of sdk (launchpad, stargate-40, stargate-44 or latest)", 44 | ) 45 | flag.StringVar(&keyName, "account-name", 46 | environ.GetString("ACCOUNT_NAME", cosmosfaucet.DefaultAccountName), 47 | "name of the account to be used by the faucet", 48 | ) 49 | flag.StringVar(&keyMnemonic, "mnemonic", 50 | environ.GetString("MNEMONIC", ""), 51 | "mnemonic for restoring an account", 52 | ) 53 | flag.StringVar(&keyringPassword, "keyring-password", 54 | environ.GetString("KEYRING_PASSWORD", ""), 55 | "password for accessing keyring", 56 | ) 57 | flag.StringVar(&appCli, "cli-name", 58 | environ.GetString("CLI_NAME", "gaiad"), 59 | "name of the cli executable", 60 | ) 61 | flag.StringVar(&defaultDenoms, "denoms", 62 | environ.GetString("DENOMS", cosmosfaucet.DefaultDenom), 63 | "denomination of the coins sent by default (comma separated)", 64 | ) 65 | flag.Uint64Var(&creditAmount, 66 | "credit-amount", 67 | environ.GetUint64("CREDIT_AMOUNT", cosmosfaucet.DefaultAmount), 68 | "amount to credit in each request", 69 | ) 70 | flag.Uint64Var(&maxCredit, 71 | "max-credit", environ.GetUint64("MAX_CREDIT", cosmosfaucet.DefaultMaxAmount), 72 | "maximum credit per account", 73 | ) 74 | flag.StringVar(&nodeAddress, "node", 75 | environ.GetString("NODE", ""), 76 | "address of tendermint RPC endpoint for this chain", 77 | ) 78 | flag.BoolVar(&legacySendCmd, "legacy-send", 79 | environ.GetBool("LEGACY_SEND", false), 80 | "whether to use legacy send command", 81 | ) 82 | flag.StringVar(&coinType, "coin-type", 83 | environ.GetString("COIN_TYPE", "118"), 84 | "registered coin type number for HD derivation (BIP-0044), defaults from (satoshilabs/SLIP-0044)", 85 | ) 86 | flag.StringVar(&home, "home", 87 | environ.GetString("HOME", ""), 88 | "replaces the default home used by the chain", 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # faucet 2 | 3 | A faucet that uses [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) executable binaries only. 4 | 5 | The main purpose of this `faucet` is to avoid using RPC or API endpoints, and use the CLI binary instead, more 6 | specifically, the commands: 7 | 8 | ```bash 9 | $ {app}d tx bank send 10 | ``` 11 | 12 | and: 13 | 14 | ```bash 15 | $ {app}d query txs 16 | ``` 17 | 18 | Since the faucet only uses the CLI binary, it is compatible with practically any blockchain built with 19 | [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) even if different types of keys are used (such as in 20 | [ethermint](https://github.com/cosmos/ethermint) for example). 21 | 22 | ## Installation 23 | 24 | ### Using cURL 25 | 26 | ```bash 27 | $ curl https://get.starport.network/faucet! | bash 28 | ``` 29 | 30 | ### Use docker image 31 | 32 | Use docker image `ghcr.io/tendermint/faucet`. You can use it in a Kubernetes pod with 33 | [shareProcessNamespace](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/#configure-a-pod) 34 | or mount the chain binary using docker: 35 | 36 | ```bash 37 | $ docker run -it -v ~/go/bin/gaiad:/usr/local/bin/gaiad ghcr.io/tendermint/faucet 38 | ``` 39 | 40 | ### From Source 41 | 42 | You can build the faucet with: 43 | 44 | ```bash 45 | $ make build 46 | ``` 47 | 48 | The executable binary will be avaialable in the `./build/` directory. To install it to `$GOPATH/bin`, use instead: 49 | 50 | ```bash 51 | $ make install 52 | ``` 53 | 54 | ## Usage 55 | 56 | ### Configuration 57 | 58 | You can configure the faucet either using command line flags or environment variables. The following table 59 | shows the available configuration options and respective defaults: 60 | 61 | | flag | env | description | default | 62 | |---------------------|-------------------|-------------------------------------------------------------- |-----------| 63 | | port | PORT | tcp port where faucet will be listening for requests | 8000 | 64 | | account-name | ACCOUNT_NAME | name of the account to be used by the faucet | faucet | 65 | | mnemonic | MNEMONIC | mnemonic for restoring an account | | 66 | | keyring-password | KEYRING_PASSWORD | password for accessing keyring | | 67 | | cli-name | DENOMS | denomination of the coins sent by default (comma separated) | uatom | 68 | | credit-amount | CREDIT_AMOUNT | amount to credit in each request | 10000000 | 69 | | max-credit | MAX_CREDIT | maximum credit per account | 100000000 | 70 | | sdk-version | SDK_VERSION | version of sdk (launchpad or stargate) | stargate | 71 | | node | NODE | address of tendermint RPC endpoint for this chain | | 72 | | keyring-backend | KEYRING_BACKEND | keyring backend to be used | | 73 | | legacy-send | LEGACY_SEND | whether to use legacy send command | false | 74 | | coin-type | COIN_TYPE | registered coin type number for HD derivation (BIP-0044) | 118 | 75 | | home | HOME | replaces the default home used by the chain | | 76 | | | | | | 77 | 78 | ### [gaia](https://github.com/cosmos/gaia) example 79 | 80 | This faucet options default to work with [gaia](https://github.com/cosmos/gaia). So you can start the faucet with just: 81 | 82 | ```bash 83 | $ faucet --keyring-password 12345678 84 | INFO[0000] listening on :8000 85 | ``` 86 | 87 | or, with environment variables: 88 | 89 | ```bash 90 | $ export KEYRING_PASSWORD=12345678 91 | $ faucet 92 | INFO[0000] listening on :8000 93 | ``` 94 | 95 | ### [ethermint](https://github.com/cosmos/ethermint) example 96 | 97 | Start the faucet with: 98 | 99 | ```bash 100 | $ faucet --cli-name ethermintcli --denoms ueth --keyring-password 12345678 --sdk-version launchpad 101 | INFO[0000] listening on :8000 102 | ``` 103 | 104 | or, with environment variables: 105 | 106 | ```bash 107 | $ export CLI_NAME=ethermintcli 108 | $ export SDK_VERSION=launchpad 109 | $ export DENOMS=ueth 110 | $ export KEYRING_PASSWORD=12345678 111 | $ faucet 112 | INFO[0000] listening on :8000 113 | ``` 114 | 115 | ### [wasmd](https://github.com/CosmWasm/wasmd) example 116 | 117 | Start the faucet with: 118 | 119 | ```bash 120 | $ faucet --cli-name wasmcli --denoms ucosm --keyring-password 12345678 121 | INFO[0000] listening on :8000 122 | ``` 123 | 124 | or, with environment variables: 125 | 126 | ```bash 127 | $ export CLI_NAME=wasmcli 128 | $ export DENOMS=ucosm 129 | $ export KEYRING_PASSWORD=12345678 130 | $ faucet 131 | INFO[0000] listening on :8000 132 | ``` 133 | 134 | ### Request tokens 135 | 136 | You can request tokens by sending a `POST` request to the faucet, with a key address in a `JSON`: 137 | 138 | ```bash 139 | $ curl -X POST -d '{"address": "cosmos1kd63kkhtswlh5vcx5nd26fjmr9av74yd4sf8ve"}' http://localhost:8000 140 | {"transfers":[{"coin":"10000000uatom","status":"ok"}]} 141 | ``` 142 | 143 | For requesting specific coins, use: 144 | 145 | ```bash 146 | $ curl -X POST -d '{"address": "cosmos1kd63kkhtswlh5vcx5nd26fjmr9av74yd4sf8ve", "coins": ["10uatom", "20ueth"]}' http://localhost:8000 147 | {"transfers":[{"coin":"10uatom","status":"ok"}, {"coin":"20ueth","status":"ok"}]} 148 | ``` 149 | --------------------------------------------------------------------------------