Field notes
How to monitor a crypto wallet balance with GitHub Actions
by Arya Rahimi · · 7 min read
To monitor a crypto wallet balance with GitHub Actions, run a scheduled workflow that installs glnc, calls glnc balance <address> --json, and gates the job on the result. Because glnc reads from free public RPCs with no API key, there is no secret to store on the runner, which makes the whole pipeline shorter and safer than the usual provider-SDK setup. The full workflow is below, followed by exactly how to fail the job on degraded data or a balance threshold.
The whole setup is four steps:
- Add a workflow file at
.github/workflows/wallet-balance.yml. - Install glnc on the runner with the one-line script.
- Run
glnc balance <address> --json --stricton a cron. - Gate the step on the exit code, or alert on a balance threshold with jq.
The full GitHub Actions workflow
Drop this file at .github/workflows/wallet-balance.yml. It runs on a schedule (and on a manual trigger), installs glnc on the runner, and checks a wallet. The address lives in a repository variable, not a secret, because an address is public. See the install guide for other install paths.
cat .github/workflows/wallet-balance.ymlThat is the whole thing. No actions/setup-* step for a runtime, no provider SDK to install, and no API key to provision. The install script is the same SHA256-verified script (opens in new tab) you would run locally.
For a reproducible pipeline, pin the version and drop the job to zero permissions. Pinning means a new release cannot change what your monitor does mid-week, and an empty permissions block means a read-only check cannot touch your repository even if something downstream goes wrong:
cat .github/workflows/wallet-balance.ymlWhy there is no secret to store
The usual way to read chain data in CI is a hosted provider SDK, which means an API key, which means a GitHub Actions secret (opens in new tab) to create, scope, and rotate. glnc skips that entirely: the core commands run against free public RPC endpoints, so the only inputs are a wallet address and a chain, both of which are public. Fewer secrets on a runner is a smaller attack surface and one less thing to expire silently three months from now.
Gate the job on data quality
A monitor is only useful if it can tell a real change from a degraded read. glnc carries that signal in the JSON envelope. Every balance --json response includes a meta.partial flag that is true if any source (an RPC, a price feed, or the token list) degraded for that call. Pass --strict and a one-shot command exits 3 when the result is partial, which fails the step automatically:
glnc balance "$WALLET" --chain eth --json --strict; echo "exit: $?"The exit codes are stable and scriptable:
0: success.1: a user or input error (a bad address or unsupported chain).2: all network requests failed.3: a partial result, emitted only under--strict.
One subtlety worth knowing: GitHub Actions runs each run: step with bash --noprofile --norc -eo pipefail, so the glnc exit code propagates through the jq pipe and fails the step. If you reuse the same command outside Actions, run it under bash with set -o pipefail so jq does not mask a non-zero exit. If you would rather branch in the workflow than fail the step, drop --strict and test the flag yourself with jq (opens in new tab):
glnc balance "$WALLET" --chain eth --json | jq -e '.meta.partial == false'Alert on a balance threshold
To page yourself when a treasury or hot wallet drops below a floor, let jq -e assert the condition. A false assertion exits non-zero, the step turns red, and GitHub sends its normal failure notification with no extra wiring:
glnc balance "$WALLET" --chain eth --json \
| jq -e '.data.wallets[0].grandTotalUsd >= 1000'If you want a message in a channel instead of (or as well as) a failed run, POST to a webhook from the same step. That webhook URL is the one value that genuinely belongs in an encrypted secret. The Slack balance alert walkthrough covers the payload shape and the honesty checks in detail.
Watch several wallets at once
To monitor a treasury that spans several wallets, run a matrix instead of a single step. Each wallet becomes its own job, so one address timing out does not hide the others, and the Actions summary shows you exactly which one failed:
cat .github/workflows/treasury.ymlOne ENS name per matrix entry, one job per name, and a single install line shared across them. Because there is no key to provision, adding a wallet is a one-line diff rather than a new secret and a new rotation reminder. One caveat with --strict here: an ENS or 0x address auto-detects across all EVM chains, so a single degraded chain trips meta.partial and fails the job. For a strict monitor, scope each job with --chain (as the first workflow does) so a quiet chain you do not care about cannot turn the run red.
Schedule the check with cron
Scheduled workflows fire on a cron expression (opens in new tab), but GitHub runs them on a best-effort basis and can delay them when the platform is busy. Treat the interval as approximate. Always pair schedule with workflow_dispatch so you can trigger a check by hand from the Actions tab, and keep the cadence modest: an hourly or daily balance read is plenty for most monitoring, and it keeps you well clear of public RPC rate limits.
What this does and does not do
This pattern is a lightweight, no-infrastructure monitor: a cron, a one-line install, and one command whose output you can trust because the envelope tells you when it is degraded. It is not a real-time watcher. For second-by-second deltas, glnc balance --watch streams NDJSON locally, and for a condition engine with its own webhook delivery, the alert command is purpose-built. GitHub Actions is the right home when you want the check to live in version control, run on a schedule you can read in a diff, and cost nothing to keep running.
Common questions
Do I need to store an API key or secret to monitor a wallet in GitHub Actions?
No. glnc reads balances from free public RPC endpoints with no account and no API key, so there is no secret to add to the repository or rotate on the runner. The wallet address is public, so it can live in a plain repository variable rather than an encrypted secret.
How do I make the job fail when the on-chain data is degraded?
Pass --strict. In one-shot mode glnc then exits 3 when any source degraded (meta.partial is true), which fails the step. Without --strict, glnc exits 0 and reports the degraded state in meta.partial, so you can also branch on it explicitly with jq.
How often can a GitHub Actions wallet check run?
As often as a cron schedule allows. Scheduled workflows run on a best-effort basis and can be delayed under load, so treat the interval as approximate rather than exact, and add workflow_dispatch so you can also trigger a check by hand.
Can the workflow alert Slack or Discord instead of just failing?
Yes. A failing step already triggers GitHub notifications, but you can also pipe the JSON through jq and POST to a Slack or Discord webhook from the same step. The webhook URL is the only value that belongs in an encrypted secret.
Which chains and assets can it check in CI?
All 9 chains glnc supports: Ethereum, Polygon, Arbitrum, Base, Optimism, zkSync, Linea, Solana, and Bitcoin. The balance command auto-detects the chain from the address format, and --json emits a stable, versioned envelope you can gate on.
Does running this in GitHub Actions cost anything?
On a public repository, GitHub Actions minutes are free, so a scheduled wallet check costs nothing. On a private repository the run counts against your included Actions minutes, but each check is a few seconds, so a daily or hourly monitor uses a negligible slice of the free allotment.