Command trade prep
Template: command-trade-prep ยท category: relayers ยท risk: money_moving
A deposit into a watched address fires a project-owned node script that
quotes a trade (amount, min_out, deadline, freshness window); rflow then
signs, simulates, rechecks and sends it. The quote script never signs or
sends โ it has no private key and no RPC handle. Your logic decides what to
trade; rflow owns moving the money. Runnable sibling:
examples/command-trade-prep.
When to use it
- keeper/trading flows where sizing lives in your own code or a pricing API
- any event-driven send whose parameters must be computed, not hardcoded
- the money-moving counterpart of
command-decision
Generate it
rflow new --template command-trade-prep
# or into an existing project (needs a signer; adds the `trader` relayer):
rflow add workflow command-trade-prepInputs
| key | type | default |
|---|---|---|
project_name | string | command-trade-prep |
network / chain_id / rpc_env / rpc_url | network / chain_id / env_var / string | ethereum / 1 / ETH_RPC / a public RPC |
token_name / token_address / token_decimals | contract / address / int | USDC / USDC mainnet / 6 |
watched | address | zero placeholder โ replace it |
trade_target | address | zero placeholder โ replace it |
min_deposit | token_amount | 100 (whole tokens) |
confirmations | int | 12 (blocks a deposit must be deep before it counts) |
quote_ttl | int | 120 seconds |
gas_max_cost | string | 0.02 ether |
Generated YAML (the shape)
# recipe: partial
trigger:
event: # deposits >= min_deposit INTO the watched address
contract: USDC
name: Transfer
where: "${{ trigger.args.value >= wei('100', 6) and lower(trigger.args.to) == lower(constants.watched) }}"
confirmations: 12 # a reorged deposit can never trigger a trade
steps:
- id: gate # rflow reads the relayer's balance (the command never does)
read: { function: "balanceOf(address)", args: ["${{ relayers.trader.address }}"] }
- id: quote # your script quotes: amount, min_out, deadline, valid_until
command: { run: "node ./scripts/quote.js", timeout: 10s, output: json, ... }
- id: trade # rflow signs + simulates + rechecks + sends
if: "${{ steps.quote.output.should_send == true }}"
send_transaction:
function: "transfer(address,uint256)"
args: ["${{ constants.trade_target }}", "${{ steps.quote.output.amount }}"]
simulate: true
assert_sim: ["${{ sim.ok }}"]
gas: { limit_from_simulation: true, multiplier: 1.2, max_cost: "0.02 ether" }
recheck: "${{ now() < steps.quote.output.valid_until }}"
wait_for: confirmed
on_failure: dead_letterThe 'trade' is modelled as an ERC20 transfer so the template is
self-contained; in a real deployment the same shape drives e.g.
Router.swapExactTokensForTokens(...) with the quoted min_out/deadline/path
as args.
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. node must be on PATH.
Safety defaults (all generated)
- pre-flight simulation +
assert_sim: ["${{ sim.ok }}"] - gas cap:
limit_from_simulation+max_costโ a fat-fingered price never drains gas recheck: now() < valid_untilโ a run parked/restarted past the quote's freshness window is dropped, never broadcast stale- the trigger waits
confirmations: 12before firing โ a reorg cannot orphan the deposit AFTER the trade is sent confirmations: 12on the network,wait_for: confirmedon_failure: dead_letter
This path is fully automated (no approval gate โ that would defeat a
trading keeper). If your flow can wait for a human, add an approval: block
to the trade step exactly as treasury-sweep-approval
does.
Run it locally
docker compose up -d
rflow validate
# rehearse without touching a chain (`to` in the fixture is your watched address):
rflow test command-trade-prep --fixture fixtures/deposit-event.json
rflow startProduction checklist
- replace the
watchedandtrade_targetzero placeholders - replace the raw dev mnemonic with a production signer
- make
quote.jscall your real pricing source and enforce YOUR slippage - size
gas_max_costandmin_depositfor the network you deploy on - add a spend budget / rate limit around the workflow when budgets exist in
your project, and consider
concurrency:if quotes must not overlap
Common modifications
- drive a router/DEX call instead of
transfer(use the quotedmin_outanddeadlineas args) - add an
approval:gate for semi-automated desks notify:on every executed trade for a human audit trail- tighten
quote_ttlfor fast markets โ the recheck makes staleness a no-op