Token deposit relay
Template: token-deposit-relay ยท category: relayers ยท risk: money_moving
An ERC20 Transfer into a watched deposit address (at or above a minimum
amount) triggers a relayed payout of the same amount from the payout
wallet โ the classic exchange/treasury deposit-echo pattern. Runnable sibling:
examples/token-transfer-relay.
When to use it
- credit or forward user deposits to a downstream address automatically
- bridge-style "mirror this deposit elsewhere" flows on a single chain
- any event-driven send where the amount comes straight from the trigger
Generate it
rflow new --template token-deposit-relay
# or into an existing project (needs a signer; adds the `payout` relayer):
rflow add workflow token-deposit-relayWhat the generated YAML does
- Trigger โ
Transferevents on the token, filtered inwhere:to deposits>= min_depositwhole tokens whosetois the watcheddeposit_address. The network'sconfirmations: 12means a reorged deposit never pays out. gateโ readsbalanceOf(payout relayer)and asserts the wallet can cover the deposit (a short wallet fails the run into the dead-letter queue instead of firing a doomed send).payoutโtransfer(payout_address, trigger.args.value)through the embedded relayer: simulated first (assert_sim: sim.ok), gas-capped, then held untilconfirmed. The simulation is also the drained-wallet guard โ a transfer the wallet cannot cover reverts ineth_calland dead-letters instead of broadcasting.
Inputs
| key | type | default |
|---|---|---|
project_name | string | token-deposit-relay |
network / chain_id / rpc_env / rpc_url | network / chain_id / env_var / string | ethereum / 1 / ETH_RPC / a public RPC |
confirmations | int | 12 |
token_address / token_decimals | address / int | USDC mainnet / 6 |
deposit_address | address | zero placeholder โ replace it |
payout_address | address | zero placeholder โ replace it |
min_deposit | token_amount | 100 |
gas_max_price | string | 100 gwei |
Required env vars
DATABASE_URL, the RPC env var, RAW_DANGEROUS_MNEMONIC. rflow new fills
.env with a freshly generated DEV-ONLY mnemonic โ swap in a production
signer before real funds ride on this config.
Safety defaults (all generated)
- inventory gate (
read+assert) before any send - pre-flight simulation +
assert_sim: ["${{ sim.ok }}"]โ this is the drained-wallet guard: an uncoverable transfer reverts in simulation instead of broadcasting (arecheckwould only re-test journaled constants the gate already passed, so the template ships none) - gas cap:
limit_from_simulation+max_price - network
confirmations: 12,wait_for: confirmed concurrency.on_conflict: queueโ payouts are serialized, never racing the same wallet balanceon_failure: dead_letter
Run it locally
docker compose up -d # postgres on :5448
rflow validate # should be green out of the box
rflow test token-deposit-relay --fixture fixtures/deposit-event.json # dry-run rehearsal
rflow start # go liveThe generated fixtures/deposit-event.json is a 250-token deposit into your
watched address โ edit value to rehearse the min_deposit boundary.
Production checklist
- replace the
deposit_addressandpayout_addressplaceholders - replace the raw dev mnemonic with a production signer
- fund the
payoutrelayer with tokens AND gas - dry-run the fixture, then watch the first live deposit in
rflow runs ls - consider a budget on the workflow if you run budgets
Common modifications
- Alert instead of failing when inventory is short: drop the
assertongateand add anotifystep behind anif:. - Fee-taking relay: pay out
trigger.args.value * 99 / 100instead of the full amount. - Multiple deposit addresses: move
deposit_addressinto alist:and match withcontains(...)in the triggerwhere:. - Idle-deposit sweep: pair it with
treasury-sweep-approvalto move accumulated balances to cold storage behind an approval.