feat: V3 Grid keeper package — 2x-gas crank + tip to LUNC (from #546) #618
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#618
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add a separate repo package (
grid-keeper/) that permissionlessly cranks V3 Grid vaults: callRebalance { claim_tip: true }when the claimable 5% fee tip is worth at least 2× the estimated LUNC gas, then autoswap the tip to LUNC.This is the operator half of #546 option E. It must not live inside
indexer/(no signing keys in the indexer process) and must not be a CosmWasm contract.Blocked by #617 (message shapes, fee pot query, events).
Current codebase
PlaceLimitOrderBatch/PlaceLimitOrderLadder. Fills pay the order owner. No auto-flip.limit_order_placementsrow peraction=place_limit_order. Fills inlimit_fills.lifecycle_status=activeis not proof the row is still inORDERS(#530). Bots must use LCDOrderStatus(L21).GET .../limit-book/insert-hints(#267). Advisory only (L14).GET /api/v1/hub-prices(#556) — useful to value a CW20 tip in USD/LUNC terms, not a signing path.GET/POST /api/v1/route/solvefor hybrid quotes when swapping tip → LUNC (#596).terraGas.ts: batch place400_000 + 180_000×N; columbus-5 floor 28.325 uluna/gas.CleanLimitBookfuture watcher is docs only (docs/limit-orders.md§ Permissionless limit book clean). No in-repo crank binary.grid-keepercrate, no vault poll loop, no tip→LUNC swap bot, no authz helper. Simulated Wallet is LocalTerra-only and must not be the production signer.Community-tax AutoLP has a permissionless
SkimToLpbut no keeper package. Do not bolt this loop onto the indexer or the frontend.Why this is needed
Rebalanceis permissionless but nobody calls it unless a keeper (or a searcher) does. Without a crank, the grid dies after the first fill — the same failure mode as today’s ladder.CancelLimitOrders. The official keeper must be able toRebalanceonly. Withdraw stays beneficiary-only on the vault.tip_value_in_lunc >= 2 × gas_fee_lunc, then convert the tip to LUNC.indexer/and out of CosmWasm optimizer images.Constraints / guardrails
grid-keeper/(Rust recommended, sibling ofindexer/). Not a workspace member ofsmartcontracts/. Not compiled into the indexer binary.authzMsgExecuteContracton user wallets or the pair. It only executes vaultRebalance+ its own CW20/router swap.OrderStatus+ vault queries, not indexerlifecycle_status=active(L21, #530). Indexer may be used as a hint to find candidate vaults.Rebalance { claim_tip: true }only when estimated claimable tip (5% of that vault’sunclaimed_grid_fees, valued to LUNC) ≥ 2× estimated LUNC gas for that tx (including a small safety margin). Ifclaim_tipwould be 0, do not submit (leave unpaid cranks to searchers; no retail Rebalance button).Rebalanceremains always allowed when there is work. This package is policy, not a vault gate. Do not add a vaultmin_tipthat blocks flips — that would leave the book one-sided until 5% ≥ 2× gas (first fill often has no realized fee yet).Rebalance. Use official router + always-on hybrid quote path. Prefer cLUNC/wrap routes already used by the dApp; nativeulunais the destination for gas. Fail closed on spread / slippage; do not leave the keeper stuck in an obscure CW20 if the swap errors (retry; do not push through withmax_spreadnone).UpdateLimitOrderPrice. Keeper allowlist of execute msgs: vaultRebalance, then keeper-owned CW20increase_allowance/send/ router swap only.indexer/(or this package) into a root Dockercargo. Host compile; if Docker is unavoidable:--user $(id -u):$(id -g)+CARGO_HOME/CARGO_TARGET_DIRoff the bind mount (AGENTS.md).make setup-cloud-localterrawhen measuring gas or running the swap path./poolcopy.Relevant files
Rebalance, fee-pot query, vault list / factory index, wasm attrsdocs/limit-orders.mdOrderStatus, batch gasskills/AGENTS_ORDER_STATUS_QUERY.mdUnknownindexer/src/api/limit_book_lcd.rsindexer/src/api/hub_prices.rsindexer/src/api/route_solver.rsfrontend-dapp/src/services/terraclassic/terraGas.tseffectiveGasPriceUluna()skills/AGENTS_TERRACLASSIC_GAS.mdskills/AGENTS_HYBRID_QUOTING.mdscripts/lib/localterra-host-curl.shdocs/localterra-sdk53.mdRecommended direction
grid-keeper/with its ownCargo.toml,.env.example(KEEPER_MNEMONIC/ keyring, LCD, vault-factory, router, indexer URL, gas price,TIP_TO_GAS_RATIO=2.0, poll interval). Document Coolify / systemd / tmux (grid-keeper) — notindexer-dev.AllVaults(paginated) + optional wasm event stream. For each vault, query on-chain: paused?, beneficiary blacklisted?,unclaimed_grid_fees, idle work flag / live order ids,OrderStatusper id.needs_rebalance).value_lunc(unclaimed_grid_fees * 5%) >= 2 * estimate_gas_lunc(Rebalance).Rebalancegas_used(vault issue appendix) × 1.3, × chain gas price (LocalTerra deploy price vs columbus-5 28.325). Do not use a flat 15M.hub-pricesand/orroute/solve(same as dApp). Stale oracle → skip vault (fail closed), do not crank on a guessed FX.Rebalance { claim_tip: true }from the keeper wallet. Parse tip-paid attrs. Then swap received CW20s → LUNC with slippage cap. Persist last-crank height to avoid double-submit on the same vault in one block.needs_rebalance=false, do not send a tx (rely on vault cheap no-op only as a safety net)..envfor QA), assert crank + LUNC balance up.make verify-issue-<iid>greps the package README for the 2× rule, LCDOrderStatus, and “not indexer lifecycle”; runscargo testingrid-keeper/.Acceptance criteria
grid-keeper/exists; not part ofindexerorsmartcontractsworkspace. README states key threat model.CancelLimitOrder*or vaultWithdraw.OrderStatus. Indexerlifecycle_status=activeis never sufficient.gas_usedvs estimate.make verify-issue-<iid>.Test plan (all paths)
needs_rebalance=falseRebalance { claim_tip: true }active, LCDUnknownafter fillneeds_rebalance(LCD wins)active, LCDActiveRebalanceis no-op or vault rejects double-tip on same potTIP_TO_GAS_RATIO=0)LocalTerra
Test plan — attack, hack, and abuse vectors
Rebalance+ steal future tips and the keeper’s LUNC/CW20, not vault inventory. Document blast radius.GenericAuthorizationon the pair or user. Tests/docs forbid it.lifecycle_statuswould double-place or miss fills. Suite forces LCD.hint_aftermust not be required; vault/pair L14 fallback.max_spread = None(same spirit as AutoLP M610-3)..envgitignored; verify script greps that secrets are not in-tree.Verification criteria
OrderStatus.make verify-issue-<iid>+cargo testingrid-keeper/(host, not root Docker bind-mount)./v3UI (users deposit/withdraw without the keeper), but production “set and forget” needs this package running.Out of scope
/v3APR UI.authzon user keys.x/feegrantfrom users (not required).marked as related to #546
marked as related to #617
mentioned in issue #619
mentioned in issue #546
mentioned in issue #617
mentioned in issue #650
marked as related to #650
mentioned in issue #717
marked as related to #717