bug: Swap Station auto-gas ~4k LUNC on hybrid multi-hop; manual ~200 LUNC succeeds #679

Closed
opened 2026-08-27 00:20:43 +00:00 by PlasticDigits · 15 comments
PlasticDigits commented 2026-08-27 00:20:43 +00:00 (Migrated from gitlab.com)

Summary

Retail report on Swap (/): a cLUNC → USTR quote showed Station auto gas ~4,157.699 LUNC (~$0.21) on the mobile Confirm Transaction screen. The trader set gas manually ~200 LUNC and the tx succeeded.

This is not an out-of-gas miss (#587). The chain has enough headroom at ~7M gas. Station (and/or the dApp envelope Station ignores) is asking for tens of times more LUNC than gas_used requires.

Do not split “honor Station/WC fee,” “stop 15M-per-pool-hop on mixed hybrid router msgs,” and “align Swap Network fee with the submit envelope.” Those are one user journey: the wallet confirm fee for a CW20 hybrid multi-hop swap.

Related: #587 (wrap+≥2hop OOG + Network fee row — closed; native path stays pool-only), #596 (hybrid always-on), #249 (quote-driven hybrid gas), #127 / #208 (preferNoSetFee, Station amino), #429 (mainnet fee-guard off), #235 (Station P0 on columbus-5 only), #519 / #566 (Station mobile WalletConnect), #475 (retail gas inventory), #353 (router hop floors).

Evidence (columbus-5)

Successful follow-up (trader reduced size after the 100k auto-gas scare; same shape):

Field Value
Hash AB8BE4F75E051837BB01C364DEDE6611727E47F0F857AADF04B17C39F360446D
Height 30121174
code 0
Offer 10,000 cLUNC (10000000000 raw) — screenshot of the rejected auto-gas prompt was 100,000 cLUNC
Contract cLUNC terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg send → official router terra1e7s0h9ftxakwca5gxspyt4haeuaqxds6swr08ul3tsepq7el924sprrsrw
Ops 4 terra_swap hops: cLUNC → CL8Y (book-only hybrid) → cUSTC → UST1 → USTR
Hop 1 hybrid pool_input: "0", book_input: "10000000000", max_maker_fills: 8, book_start_hint: 1426
Hops 2–4 no hybrid field (pool-only)
gas_wanted 6,785,500
gas_used 5,026,176
Fee paid 192,199,288 uluna = 192.199 LUNC (6,785,500 × 28.325)

Station auto-gas 4,157.699 LUNC at the same 28.325 uluna/gas is gas_wanted ≈ 146,785,000 — ~29× measured gas_used, ~22× the successful manual limit.

The reporter described the quote as “1 hop / 2 swaps.” The signed msg is 4 hops. Route chrome must show the real path; do not “fix” gas by turning hybrid off or forcing the native hub 2-hop (cLUNC → UST1 → USTR) — that would violate H596.

Current codebase

The dApp does not LCD-simulate CosmWasm execute. It builds an explicit Fee and asks the wallet to honor it. Three layers currently disagree, and Station mobile often replaces the dApp fee with its own simulate/auto-gas.

Layer Behavior today
Quote / submit Swap CW20↔CW20 uses quoteCw20ViaRouteSolve (GET /route/solve, hybrid always-on #596). swapOpsRequireRouter is true when ops.length ≥ 2 → executeMultiHopSwap CW20 send → router execute_swap_operations with per-hop hybrid copied from the indexer (serializeTerraSwap).
Swap Network fee row estimateSwapNetworkFee on SwapPage: for this path cw20DirectPair is false, cw20Hybrid is true only when the Advanced manual book field is filled (multi-hop ignores indexer hybrid). hopCount = indexerOperations.length (here 4) → pool-only gasLimitForRouterExecuteSwapOperations(4) = 3,810,000 → ~107.9 LUNC. That is below live gas_used 5,026,176 — the hint would OOG if a wallet used it as gas_wanted.
Broadcast envelope broadcastTerraExecuteContracts → getGasLimitForTx on the decoded send.msg. If any hop has hybrid, gasLimitForHybridRouterOperations sums gasLimitForHybridParams per hop. Missing hybrid → makersUsed === undefined → HYBRID_SWAP_GAS_LIMIT 15,000,000. Live shape: hop1 book/8 makers = 1,785,500; hops 2–4 = 15M each → 46,785,500 → ~1,325 LUNC. Tests only cover hybrid on every hop (transactions.test.ts 2-hop … hybrid on each hop = 3,058,600). Mixed hybrid + pool hops are untested.
Station extension preferNoSetFee: true + amino (StationController, #208). Post-sign undershoot guard is LocalTerra-only (#429 / SEC-E08).
Station / LuncDash WalletConnect isAtomicWalletConnectPost forces wallet.broadcastTx(unsignedTx, fee) — no split signAmino. Station mobile Confirm Transaction (settings gear next to Tx Fee) is that app’s auto-gas / LCD simulate, not the dApp row. Classic CosmWasm simulate on a 4-hop hybrid book walk routinely returns near block gas (~100M); × adjustment ≈ 147M gas / 4,158 LUNC.
Native wrap path Unchanged and out of scope for the envelope bug: LUNC→USTR is wrap + pool-only BFS (H596-7 / #587). This report is already-wrapped cLUNC → indexer hybrid router.

Numeric classes at MIN_GAS_PRICE_ULUNA 28.325:

Envelope Gas LUNC vs gas_used 5.03M
Swap Network fee (4-hop pool-only) 3,810,000 ~108 under — OOG if honored
Quote-driven mixed (hop1 1.79M + 3×950k floor, no extra margin) ~4,640,000 ~131 under live used
Successful manual 6,785,500 192.20 above used (safe)
Broadcast getGasLimitForTx today 46,785,500 ~1,325 ~9× used
Station auto-gas (screenshot) ~146,785,000 4,157.70 ~29× used
Flat HYBRID_SWAP_GAS_LIMIT × 4 60,000,000 ~1,700 still ≪ Station

Why this is needed

Traders see thousands of LUNC on Station Approve for a routine hub swap. Many will cancel (or drain a large LUNC reserve). The same tx lands at ~192 LUNC when gas is set by hand, and gas_used is ~5.0M (~142 LUNC at 28.325). That is a trust / convert bug on the primary Swap CTA, not a “Classic is expensive” fact.

Even if Station is patched or bypassed, the dApp today would still either OOG (Network fee / pool-only 3.81M) or over-charge ~1,325 LUNC (15M per pool hop). Hint, broadcast, and wallet must share one envelope that sits above measured gas_used with margin and in the low hundreds of LUNC, not thousands.

Constraints / guardrails

  1. Fee denom stays uluna. No USTC / uusd / multi-denom fees. Floor remains effectiveGasPriceUluna() / 28.325.
  2. Do not LCD-simulate as the sole envelope (AGENTS_TERRACLASSIC_GAS). Station’s simulate is the 4k-LUNC bug. LCD sim may tune constants; broadcast must still use named hop/hybrid math that exceeds measured gas_used with wasm/storage headroom.
  3. Do not disable retail hybrid (H596-1–H596-8). No pool-only checkbox. Do not strip indexer hybrid from submit to “make gas cheap.”
  4. Do not attach hybrid / book_input to native wrap+multihop (H596-7 / #587). This issue is CW20 send → router.
  5. Missing hybrid on a hop is pool-only, not 15M. gasLimitForHybridParams(undefined) as a per-hop addend inside a mixed router msg is the 46.8M bug. Unknown hybrid remains a tx-level last resort only when no hop has parseable params (keep the #249 fallback).
  6. If gasUsed is known, new defaults must exceed it with margin. Live floor: 5,026,176 on this 4-hop book+pool shape. The successful 6,785,500 (~192 LUNC) is a proven ceiling class — do not regress below gas_used.
  7. Hint = broadcast = Max/gate. estimateSwapNetworkFee / evaluateSwapNativeGasGate must use the same execute-msg shape as submit (indexer ops + per-hop hybrid), not hopCount + pool-only. #213 Max reserve tracks that envelope when pay is native LUNC (CW20 pay unchanged).
  8. Retail inventory (#475). New mixed-hybrid router fixtures in terraGasRetailInventory.ts. Do not fall through to BASE_GAS_LIMIT / SWAP_GAS_LIMIT (600k) if send.msg cannot be decoded — throw SendHookGasDecodeError.
  9. Lockstep packages/localnet-trading-swarm/src/gas.ts if hop/hybrid floors change.
  10. Do not enable mainnet extension fee-guard here (#429 / SEC-E08). Diagnosing Station overwrite (too high) is the opposite of the LocalTerra undershoot guard. A mainnet “reject if signed fee ≫ expected” could brick Station mobile if the wallet still rewrites upward.
  11. Copy (#489). Keep Network fee (est.) ~X LUNC. Do not add a lecture on Station auto-gas on the primary card. Optional one-line under Trade details if Station still shows a different number after the fee is passed through. Do not rename the AMM Fee row.
  12. Station LocalTerra QA is not the gate (#235). Prove envelopes with Keplr / simulated wallet + unit tests. Station P0 remains columbus-5 (this report).
  13. Out of scope. Changing wrap-mapper bps. Native uluna route/solve. Forcing a shorter cLUNC→USTR path (hub 2-hop) unless a separate solver issue shows the 4-hop book path is not best execution. New factory pairs.

Relevant files

Path Role
frontend-dapp/src/services/terraclassic/terraGas.ts gasLimitForHybridRouterOperations — stop 15M on pool hops
frontend-dapp/src/services/terraclassic/hybridSwapGas.ts gasLimitForHybridParams(undefined) = 15M fallback
frontend-dapp/src/services/terraclassic/swapNetworkFee.ts Hint must encode real ops + hybrid
frontend-dapp/src/pages/SwapPage.tsx estimateSwapNetworkFee hints; swap-network-fee
frontend-dapp/src/services/terraclassic/router.ts executeMultiHopSwap / serializeTerraSwap
frontend-dapp/src/utils/cw20RouteSolveQuote.ts Shared Swap + Trade market quote
frontend-dapp/src/services/terraclassic/terraBroadcast.ts Atomic WC broadcastTx vs split sign
frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts isAtomicWalletConnectPost, preferNoSetFee
frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts Extension shim defaults
frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch Station amino / fee options (update SHA if patched)
frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts Mixed-hybrid 4-hop fixture
frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts Envelope assertions
frontend-dapp/src/services/terraclassic/__tests__/swapNetworkFee.test.ts Hint ↔ broadcast
frontend-dapp/src/utils/swapNativeGasBalanceGate.ts Gate uses hint feeUluna
frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx Same CW20 quote family if market can be multi-hop
docs/frontend.md Gas table: mixed hybrid router
skills/AGENTS_TERRACLASSIC_GAS.md Playbook row + Station WC auto-gas
AGENTS.md make verify-issue-<iid>
  1. Fix mixed-hop gas first (deterministic). In gasLimitForHybridRouterOperations, hops without hybrid (or book_input = 0) use the pool-only router/pair floor for that hop — not HYBRID_SWAP_GAS_LIMIT. Hops with book_input > 0 keep gasLimitForHybridSwap. Take max(router N-hop floor, sum of per-hop pieces) so a 4-hop path cannot sit at 3.81M when hop1 is a book walk. Target class for the live shape: above 5.03M used, well below 15M, ~150–250 LUNC at 28.325 (the manual 6.79M / 192 LUNC is the calibration).
  2. Drive the Swap hint from the same msgs as submit. Pass indexer operations (or a fixture-equivalent execute payload) into estimateSwapNetworkFee / estimateTerraClassicFeeForEntries. Stop using hopCount alone for CW20 multi-hop when hybrid is on the wire. Network fee row, gas gate, and Fee.gasLimit must match.
  3. Station / WC fee pass-through. Confirm whether atomic broadcastTx / Station post receives amino fee.gas + fee.amount. If Station still simulates, investigate: pass fee in the payload Station documents; prefer split signAmino + preferNoSetFee when the mobile session exposes it; do not rely on LCD simulate. Document residual Station-app-only auto-gas as a known wallet limitation with the compact Network fee number as the dApp source of truth.
  4. Inventory + tests for: pool-only N-hop; hybrid on one hop + pool on the rest (this repro); hybrid on every hop (existing 3,058,600); wrap+2hop unchanged (must stay ≪ 15M); 1-hop direct pair hybrid; unknown hybrid still 15M once at tx level, not × N.
  5. Docs / playbook. Record columbus-5 gas_used 5,026,176 vs Station ~147M; mixed-hop rule; Station mobile auto-gas ≠ dApp envelope.

Acceptance criteria

  • G-AUTO-1. getGasLimitForTx on the live 4-hop send hook (hybrid book on hop 1, no hybrid on hops 2–4) is > 5,026,176 and < 15,000,000. Fee at 28.325 is < 400 LUNC (tens-to-low-hundreds class, not 1,325 / 4,158).
  • G-AUTO-2. Same payload: Swap Network fee (est.) LUNC equals buildTerraClassicFee(getGasLimitForTx(…)).amount (hint = broadcast).
  • G-AUTO-3. Hops without hybrid never add HYBRID_SWAP_GAS_LIMIT. gasLimitForHybridParams(undefined) is not summed per empty hop.
  • G-AUTO-4. Existing wrap+1hop / wrap+2hop / unwrap+2hop envelopes unchanged (still ≪ 15M; #587 / #599 fixtures still pass).
  • G-AUTO-5. Hybrid-on-every-hop 2-hop case stays quote-driven (current 3,058,600 class unless constants change with tests).
  • G-AUTO-6. Direct pair swap + hybrid with book_input = 0 stays pool-only 840k; book_input > 0 stays gasLimitForHybridSwap (not 15M when max_maker_fills is set).
  • G-AUTO-7. Retail inventory includes send_4hop_hybrid_first_pool_rest (or equivalent name) in RETAIL_COMBINED_ENVELOPE_FIXTURES / shape fixtures.
  • G-AUTO-8. Station extension (columbus-5): confirm screen Tx Fee matches the dApp envelope within rounding when preferNoSetFee is honored. Station WalletConnect / mobile post: either the same match, or a documented residual + dApp row still shows the honest ~200 LUNC class (no silent 4k).
  • G-AUTO-9. Route row still shows the execution path (4 hops if that is what is signed). No hybrid opt-out.
  • G-AUTO-10. make verify-issue-<iid> + docs/skill row. Swarm gas.ts lockstep if floors change.

Test plan (all paths)

Unit (Vitest, no chain)

  1. Mixed hybrid router (this repro). send → execute_swap_operations with 4 ops, hop1 { pool_input: 0, book_input: >0, max_maker_fills: 8 }, hops 2–4 omit hybrid. Assert gas ∈ (5_026_176, 15_000_000) and fee LUNC < 400. Assert not 46_785_500.
  2. Pool-only 2-hop / 4-hop. Unchanged router floors (1.91M / 3.81M) when no hop has hybrid.
  3. Hybrid on each hop (2-hop). Existing 3,058,600 (or updated constant + comment).
  4. Hybrid first hop + one pool hop (2-hop). Sum is quote-driven hop1 + pool hop; not 15M + hop1.
  5. Unknown hybrid tx-level. execute_swap_operations with hybrid: {} / unparseable on all hops still hits the single 15M fallback (or documented equivalent) — do not silently grant 600k.
  6. estimateSwapNetworkFee with indexer-equivalent ops matches estimateTerraClassicFeeForEntries of the submit send msg (4-hop mixed + 2-hop pool-only + wrap+2hop).
  7. Swap page. Quote with indexerOperations.length === 4 and hop1 hybrid: swap-network-fee LUNC matches the envelope (not ~108). AMM Fee row still bps.
  8. Decode failure. Malformed send.msg throws SendHookGasDecodeError.
  9. Wrap / unwrap combos. #587 / #599 / #475 fixtures still pass; cw20Hybrid: true must not jump wrap+2hop to 15M (existing test).
  10. Trade market. Single-pair hybrid still uses estimateMarketPairSwapSequenceUlunaFeesTotal; multi-hop market (if reachable) shares the mixed-hop helper — no second formula.
  11. Atomic WC vs split. Existing isAtomicWalletConnectPost tests still treat Station/LuncDash WC as atomic unless the implementation intentionally moves them to split sign (then update tests + #519/#566).
  12. Swarm lockstep if gas.ts constants change.

LocalTerra (Keplr or simulated wallet — not Station)

  1. Seed a ≥2 hop CW20 path with hybrid on one hop only. Broadcast with the new envelope: gas_used < gas_wanted, fee LUNC in the accepted class, no OOG toast.
  2. Pool-only 2-hop and wrap+2hop happy paths still land (e2e/wrap-swap.spec.ts E7/E8 as applicable).
  3. Optional: indexer-shaped 4-hop stand-in if LocalTerra graph can express cLUNC→CL8Y book→…→USTR; otherwise document the 2-hop mixed stand-in and keep columbus-5 hash as the 4-hop anchor.

columbus-5 (Station P0)

  1. Repeat cLUNC → USTR on Swap with Station extension and Station mobile / WC. Screenshot: wallet Tx Fee vs dApp Network fee. Must not show ~4,157 LUNC when the dApp envelope is ~200 LUNC class. If Station app still auto-gasses after fee pass-through, record app version + residual in the issue and keep G-AUTO-8 residual clause.
  2. Confirm a successful hash: gas_used < gas_wanted, fee paid ≈ envelope (or manual override no longer required).

Test plan (attack, hack, and abuse)

  1. Indexer spoof — long route + fake hybrid (#450 / SEC-I02). A compromised route/solve can attach hybrid on one hop and 4+ pool hops to inflate wallet gas (grief / drain LUNC reserve) or display a short path while submitting a long one. Submit path must stay ops-aligned (reconcileSwapRouteIntermediateTokens). Gas must not jump to 15M×N from a single hybrid flag. Cap / monotonicity: more hops may cost more, but not 15M per extra hop.
  2. max_maker_fills grief. Huge max_maker_fills on hop 1 must remain clamped by maxMakerFillsForSubmit / on-chain MAX_MAKER_FILLS_HARD_CAP (100) and HYBRID_SWAP_GAS_LIMIT per book hop, not unbounded.
  3. Missing / malformed send.msg. Must throw (no 600k fallback that OOGs, no 15M×N). Attacker-controlled inner JSON must not select place_limit_order / unwrap / invoice hooks by accident — keep exact-key parse order in getGasLimitForTx.
  4. Fee overwrite both directions. Station high auto-gas (this bug) vs Station/Keplr low rewrite (#127). Do not add a mainnet “must match exactly” guard that lets a wallet force a 4k-LUNC fee through, or that rejects a honest ~200 LUNC envelope. Do not re-enable SEC-E08 on columbus-5 in this issue.
  5. Atomic WC replay / double post. Changing Station WC to split sign must keep #499 sequence refresh and #208 single-popup lock (withTerraWalletSignLock). No second silent broadcastTx after user approval.
  6. Hybrid opt-out smuggling. Gas-fix PRs must not add pool_only=true on official Swap/Trade (H596-4) or drop hybrid from submit while the quote assumed a book fill (H596-6) — that is a price-integrity / sandwich-adjacent miss, not a fee tweak.
  7. Native wrap confusion. Do not apply the mixed-hop 15M bugfix by attaching hybrid to executeNativeSwap. Keep wrap+N-hop on #587 constants so a crafted “needsWrap + cw20Hybrid” hint cannot request 15M×hops.
  8. Dust / huge offer. Envelope is hop/hybrid-shaped, not amount-linear. 100k vs 10k cLUNC must not by itself justify 4k LUNC gas. If a future LCD sim is used for tuning only, reject using amount as a gas multiplier.

Verification criteria

make verify-issue-<iid>
# expected to include:
#   frontend Vitest: terraGas / swapNetworkFee / transactions / SwapPage network-fee
#   make verify-issue-475   # inventory still complete
#   make verify-issue-587   # wrap+multihop unchanged
#   make verify-issue-596   # no hybrid opt-out
# optional:
#   VERIFY_ISSUE_<iid>_CHAIN=1  # LocalTerra mixed-hop tx, 1 worker

Close when:

  1. Unit assertions prove mixed-hop gas is above live gas_used 5,026,176 and below 15M, and hint equals broadcast.
  2. #587 / #599 / #475 / #596 verifies still pass.
  3. A LocalTerra or columbus-5 hash shows gas_used < gas_wanted without a manual Station gas override, or an operator comment records Station-app residual with the dApp row in the ~200 LUNC class and extension/Keplr matching the envelope.
  4. Docs + AGENTS_TERRACLASSIC_GAS describe mixed hybrid router hops and Station auto-gas ≠ LCD-sim envelope.

Notes

  • Reporter wallet / chat handles omitted on purpose.
  • Do not paste internal chat logs into this issue or MRs.
## Summary Retail report on **Swap** (`/`): a **cLUNC → USTR** quote showed Station **auto gas ~4,157.699 LUNC** (~$0.21) on the mobile **Confirm Transaction** screen. The trader set gas **manually ~200 LUNC** and the tx succeeded. This is **not** an out-of-gas miss (#587). The chain has enough headroom at ~7M gas. Station (and/or the dApp envelope Station ignores) is asking for **tens of times** more LUNC than `gas_used` requires. **Do not split** “honor Station/WC fee,” “stop 15M-per-pool-hop on mixed hybrid router msgs,” and “align Swap Network fee with the submit envelope.” Those are one user journey: the wallet confirm fee for a CW20 hybrid multi-hop swap. Related: [#587](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/587) (wrap+≥2hop OOG + Network fee row — **closed**; native path stays pool-only), [#596](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/596) (hybrid always-on), [#249](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/249) (quote-driven hybrid gas), [#127](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/127) / [#208](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/208) (`preferNoSetFee`, Station amino), [#429](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/429) (mainnet fee-guard **off**), [#235](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/235) (Station P0 on columbus-5 only), [#519](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/519) / [#566](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/566) (Station mobile WalletConnect), [#475](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/475) (retail gas inventory), [#353](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/353) (router hop floors). ## Evidence (columbus-5) Successful follow-up (trader reduced size after the 100k auto-gas scare; same shape): | Field | Value | |-------|--------| | Hash | `AB8BE4F75E051837BB01C364DEDE6611727E47F0F857AADF04B17C39F360446D` | | Height | `30121174` | | `code` | `0` | | Offer | **10,000 cLUNC** (`10000000000` raw) — screenshot of the rejected auto-gas prompt was **100,000 cLUNC** | | Contract | cLUNC `terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg` `send` → official router `terra1e7s0h9ftxakwca5gxspyt4haeuaqxds6swr08ul3tsepq7el924sprrsrw` | | Ops | **4** `terra_swap` hops: **cLUNC → CL8Y (book-only hybrid) → cUSTC → UST1 → USTR** | | Hop 1 hybrid | `pool_input: "0"`, `book_input: "10000000000"`, `max_maker_fills: 8`, `book_start_hint: 1426` | | Hops 2–4 | **no** `hybrid` field (pool-only) | | `gas_wanted` | **6,785,500** | | `gas_used` | **5,026,176** | | Fee paid | **192,199,288 uluna = 192.199 LUNC** (`6,785,500 × 28.325`) | Station auto-gas **4,157.699 LUNC** at the same 28.325 uluna/gas is **`gas_wanted` ≈ 146,785,000** — ~29× measured `gas_used`, ~22× the successful manual limit. The reporter described the quote as “1 hop / 2 swaps.” The signed msg is **4 hops**. Route chrome must show the real path; do **not** “fix” gas by turning hybrid off or forcing the native hub 2-hop (`cLUNC → UST1 → USTR`) — that would violate **H596**. ## Current codebase The dApp **does not LCD-simulate** CosmWasm execute. It builds an explicit `Fee` and asks the wallet to honor it. Three layers currently **disagree**, and Station mobile often **replaces** the dApp fee with its own simulate/auto-gas. | Layer | Behavior today | |-------|----------------| | **Quote / submit** | Swap CW20↔CW20 uses [`quoteCw20ViaRouteSolve`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/cw20RouteSolveQuote.ts) (`GET /route/solve`, hybrid always-on **#596**). `swapOpsRequireRouter` is true when `ops.length ≥ 2` → [`executeMultiHopSwap`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/router.ts) CW20 `send` → router `execute_swap_operations` with **per-hop** `hybrid` copied from the indexer (`serializeTerraSwap`). | | **Swap Network fee row** | [`estimateSwapNetworkFee`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/swapNetworkFee.ts) on [`SwapPage`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/SwapPage.tsx): for this path `cw20DirectPair` is false, `cw20Hybrid` is true only when the Advanced **manual book** field is filled (multi-hop **ignores** indexer hybrid). `hopCount` = `indexerOperations.length` (here **4**) → pool-only [`gasLimitForRouterExecuteSwapOperations(4)`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGas.ts) = **3,810,000** → **~107.9 LUNC**. That is **below** live `gas_used` **5,026,176** — the hint would **OOG** if a wallet used it as `gas_wanted`. | | **Broadcast envelope** | [`broadcastTerraExecuteContracts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraBroadcast.ts) → [`getGasLimitForTx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGas.ts) on the decoded `send.msg`. If **any** hop has `hybrid`, [`gasLimitForHybridRouterOperations`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGas.ts) **sums** [`gasLimitForHybridParams`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/hybridSwapGas.ts) **per hop**. Missing hybrid → `makersUsed === undefined` → **`HYBRID_SWAP_GAS_LIMIT` 15,000,000**. Live shape: hop1 book/8 makers = **1,785,500**; hops 2–4 = **15M each** → **46,785,500** → **~1,325 LUNC**. Tests only cover hybrid on **every** hop ([`transactions.test.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts) `2-hop … hybrid on each hop` = 3,058,600). Mixed hybrid + pool hops are **untested**. | | **Station extension** | [`preferNoSetFee: true`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts) + amino (`StationController`, **#208**). Post-sign undershoot guard is **LocalTerra-only** (**#429** / SEC-E08). | | **Station / LuncDash WalletConnect** | [`isAtomicWalletConnectPost`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts) forces [`wallet.broadcastTx(unsignedTx, fee)`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraBroadcast.ts) — **no** split `signAmino`. Station mobile **Confirm Transaction** (settings gear next to **Tx Fee**) is that app’s **auto-gas / LCD simulate**, not the dApp row. Classic CosmWasm simulate on a 4-hop hybrid book walk routinely returns near **block gas** (~100M); × adjustment ≈ **147M gas / 4,158 LUNC**. | | **Native wrap path** | Unchanged and **out of scope** for the envelope bug: LUNC→USTR is wrap + pool-only BFS (`H596-7` / **#587**). This report is **already-wrapped cLUNC** → indexer hybrid router. | Numeric classes at `MIN_GAS_PRICE_ULUNA` **28.325**: | Envelope | Gas | LUNC | vs `gas_used` 5.03M | |----------|-----|------|---------------------| | Swap Network fee (4-hop pool-only) | 3,810,000 | ~108 | **under** — OOG if honored | | Quote-driven mixed (hop1 1.79M + 3×950k floor, no extra margin) | ~4,640,000 | ~131 | **under** live used | | Successful manual | 6,785,500 | 192.20 | **above** used (safe) | | Broadcast `getGasLimitForTx` today | 46,785,500 | ~1,325 | ~9× used | | Station auto-gas (screenshot) | ~146,785,000 | 4,157.70 | ~29× used | | Flat `HYBRID_SWAP_GAS_LIMIT` × 4 | 60,000,000 | ~1,700 | still ≪ Station | ## Why this is needed Traders see **thousands of LUNC** on Station Approve for a routine hub swap. Many will cancel (or drain a large LUNC reserve). The same tx lands at **~192 LUNC** when gas is set by hand, and **`gas_used` is ~5.0M** (~142 LUNC at 28.325). That is a **trust / convert** bug on the primary Swap CTA, not a “Classic is expensive” fact. Even if Station is patched or bypassed, the dApp today would still either **OOG** (Network fee / pool-only 3.81M) or **over-charge ~1,325 LUNC** (15M per pool hop). Hint, broadcast, and wallet must share one envelope that sits **above measured `gas_used` with margin** and **in the low hundreds of LUNC**, not thousands. ## Constraints / guardrails 1. **Fee denom stays `uluna`.** No USTC / `uusd` / multi-denom fees. Floor remains `effectiveGasPriceUluna()` / **28.325**. 2. **Do not LCD-simulate as the sole envelope** ([`AGENTS_TERRACLASSIC_GAS`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_TERRACLASSIC_GAS.md)). Station’s simulate **is** the 4k-LUNC bug. LCD sim may **tune** constants; broadcast must still use named hop/hybrid math that exceeds measured `gas_used` with wasm/storage headroom. 3. **Do not disable retail hybrid** (`H596-1`–`H596-8`). No pool-only checkbox. Do not strip indexer `hybrid` from submit to “make gas cheap.” 4. **Do not attach hybrid / `book_input` to native wrap+multihop** (`H596-7` / **#587**). This issue is CW20 `send` → router. 5. **Missing hybrid on a hop is pool-only, not 15M.** `gasLimitForHybridParams(undefined)` as a **per-hop** addend inside a mixed router msg is the 46.8M bug. Unknown hybrid remains a **tx-level** last resort only when **no** hop has parseable params (keep the #249 fallback). 6. **If `gasUsed` is known, new defaults must exceed it with margin.** Live floor: **5,026,176** on this 4-hop book+pool shape. The successful **6,785,500** (~192 LUNC) is a proven ceiling class — do not regress below `gas_used`. 7. **Hint = broadcast = Max/gate.** [`estimateSwapNetworkFee`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/swapNetworkFee.ts) / [`evaluateSwapNativeGasGate`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/swapNativeGasBalanceGate.ts) must use the **same** execute-msg shape as submit (indexer ops + per-hop hybrid), not `hopCount` + pool-only. `#213` Max reserve tracks that envelope when pay is native LUNC (CW20 pay unchanged). 8. **Retail inventory (#475).** New mixed-hybrid router fixtures in [`terraGasRetailInventory.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts). Do not fall through to `BASE_GAS_LIMIT` / `SWAP_GAS_LIMIT` (600k) if `send.msg` cannot be decoded — throw `SendHookGasDecodeError`. 9. **Lockstep** [`packages/localnet-trading-swarm/src/gas.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/packages/localnet-trading-swarm/src/gas.ts) if hop/hybrid floors change. 10. **Do not enable mainnet extension fee-guard** here (**#429** / SEC-E08). Diagnosing Station **overwrite** (too high) is the opposite of the LocalTerra **undershoot** guard. A mainnet “reject if signed fee ≫ expected” could brick Station mobile if the wallet still rewrites upward. 11. **Copy (#489).** Keep **Network fee (est.) ~X LUNC**. Do not add a lecture on Station auto-gas on the primary card. Optional one-line under Trade details if Station still shows a different number after the fee is passed through. Do not rename the AMM **Fee** row. 12. **Station LocalTerra QA is not the gate** (**#235**). Prove envelopes with Keplr / simulated wallet + unit tests. Station P0 remains **columbus-5** (this report). 13. **Out of scope.** Changing wrap-mapper bps. Native `uluna` `route/solve`. Forcing a shorter cLUNC→USTR path (hub 2-hop) unless a **separate** solver issue shows the 4-hop book path is not best execution. New factory pairs. ## Relevant files | Path | Role | |------|------| | [`frontend-dapp/src/services/terraclassic/terraGas.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGas.ts) | `gasLimitForHybridRouterOperations` — stop 15M on pool hops | | [`frontend-dapp/src/services/terraclassic/hybridSwapGas.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/hybridSwapGas.ts) | `gasLimitForHybridParams(undefined)` = 15M fallback | | [`frontend-dapp/src/services/terraclassic/swapNetworkFee.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/swapNetworkFee.ts) | Hint must encode real ops + hybrid | | [`frontend-dapp/src/pages/SwapPage.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/SwapPage.tsx) | `estimateSwapNetworkFee` hints; `swap-network-fee` | | [`frontend-dapp/src/services/terraclassic/router.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/router.ts) | `executeMultiHopSwap` / `serializeTerraSwap` | | [`frontend-dapp/src/utils/cw20RouteSolveQuote.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/cw20RouteSolveQuote.ts) | Shared Swap + Trade market quote | | [`frontend-dapp/src/services/terraclassic/terraBroadcast.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraBroadcast.ts) | Atomic WC `broadcastTx` vs split sign | | [`frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts) | `isAtomicWalletConnectPost`, `preferNoSetFee` | | [`frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts) | Extension shim defaults | | [`frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch) | Station amino / fee options (update SHA if patched) | | [`frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts) | Mixed-hybrid 4-hop fixture | | [`frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts) | Envelope assertions | | [`frontend-dapp/src/services/terraclassic/__tests__/swapNetworkFee.test.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/__tests__/swapNetworkFee.test.ts) | Hint ↔ broadcast | | [`frontend-dapp/src/utils/swapNativeGasBalanceGate.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/swapNativeGasBalanceGate.ts) | Gate uses hint `feeUluna` | | [`frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx) | Same CW20 quote family if market can be multi-hop | | [`docs/frontend.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md) | Gas table: mixed hybrid router | | [`skills/AGENTS_TERRACLASSIC_GAS.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_TERRACLASSIC_GAS.md) | Playbook row + Station WC auto-gas | | [`AGENTS.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/AGENTS.md) | `make verify-issue-<iid>` | ## Recommended direction 1. **Fix mixed-hop gas first (deterministic).** In `gasLimitForHybridRouterOperations`, hops **without** `hybrid` (or `book_input = 0`) use the **pool-only** router/pair floor for that hop — **not** `HYBRID_SWAP_GAS_LIMIT`. Hops with `book_input > 0` keep `gasLimitForHybridSwap`. Take `max(router N-hop floor, sum of per-hop pieces)` so a 4-hop path cannot sit at 3.81M when hop1 is a book walk. Target class for the live shape: **above 5.03M used**, **well below 15M**, **~150–250 LUNC** at 28.325 (the manual **6.79M / 192 LUNC** is the calibration). 2. **Drive the Swap hint from the same msgs as submit.** Pass indexer `operations` (or a fixture-equivalent execute payload) into `estimateSwapNetworkFee` / `estimateTerraClassicFeeForEntries`. Stop using `hopCount` alone for CW20 multi-hop when hybrid is on the wire. Network fee row, gas gate, and `Fee.gasLimit` must match. 3. **Station / WC fee pass-through.** Confirm whether atomic `broadcastTx` / Station `post` receives amino `fee.gas` + `fee.amount`. If Station still simulates, investigate: pass fee in the payload Station documents; prefer split `signAmino` + `preferNoSetFee` when the mobile session exposes it; do **not** rely on LCD simulate. Document residual Station-app-only auto-gas as a known wallet limitation with the compact Network fee number as the dApp source of truth. 4. **Inventory + tests** for: pool-only N-hop; hybrid on **one** hop + pool on the rest (this repro); hybrid on every hop (existing 3,058,600); wrap+2hop unchanged (must stay ≪ 15M); 1-hop direct pair hybrid; unknown hybrid still 15M **once** at tx level, not × N. 5. **Docs / playbook.** Record columbus-5 `gas_used` 5,026,176 vs Station ~147M; mixed-hop rule; Station mobile auto-gas ≠ dApp envelope. ## Acceptance criteria - [ ] **G-AUTO-1.** `getGasLimitForTx` on the live 4-hop send hook (hybrid book on hop 1, no hybrid on hops 2–4) is **> 5,026,176** and **< 15,000,000**. Fee at 28.325 is **< 400 LUNC** (tens-to-low-hundreds class, not 1,325 / 4,158). - [ ] **G-AUTO-2.** Same payload: Swap **Network fee (est.)** LUNC equals `buildTerraClassicFee(getGasLimitForTx(…)).amount` (hint = broadcast). - [ ] **G-AUTO-3.** Hops without `hybrid` never add `HYBRID_SWAP_GAS_LIMIT`. `gasLimitForHybridParams(undefined)` is not summed per empty hop. - [ ] **G-AUTO-4.** Existing wrap+1hop / wrap+2hop / unwrap+2hop envelopes **unchanged** (still ≪ 15M; #587 / #599 fixtures still pass). - [ ] **G-AUTO-5.** Hybrid-on-every-hop 2-hop case stays quote-driven (current **3,058,600** class unless constants change with tests). - [ ] **G-AUTO-6.** Direct pair `swap` + hybrid with `book_input = 0` stays pool-only 840k; `book_input > 0` stays `gasLimitForHybridSwap` (not 15M when `max_maker_fills` is set). - [ ] **G-AUTO-7.** Retail inventory includes `send_4hop_hybrid_first_pool_rest` (or equivalent name) in `RETAIL_COMBINED_ENVELOPE_FIXTURES` / shape fixtures. - [ ] **G-AUTO-8.** Station extension (columbus-5): confirm screen **Tx Fee** matches the dApp envelope within rounding when `preferNoSetFee` is honored. Station **WalletConnect / mobile `post`**: either the same match, or a documented residual + dApp row still shows the honest ~200 LUNC class (no silent 4k). - [ ] **G-AUTO-9.** Route row still shows the **execution** path (4 hops if that is what is signed). No hybrid opt-out. - [ ] **G-AUTO-10.** `make verify-issue-<iid>` + docs/skill row. Swarm `gas.ts` lockstep if floors change. ## Test plan (all paths) **Unit (Vitest, no chain)** 1. **Mixed hybrid router (this repro).** `send` → `execute_swap_operations` with 4 ops, hop1 `{ pool_input: 0, book_input: >0, max_maker_fills: 8 }`, hops 2–4 omit `hybrid`. Assert gas ∈ (5_026_176, 15_000_000) and fee LUNC &lt; 400. Assert **not** 46_785_500. 2. **Pool-only 2-hop / 4-hop.** Unchanged router floors (1.91M / 3.81M) when **no** hop has hybrid. 3. **Hybrid on each hop (2-hop).** Existing 3,058,600 (or updated constant + comment). 4. **Hybrid first hop + one pool hop (2-hop).** Sum is quote-driven hop1 + pool hop; **not** 15M + hop1. 5. **Unknown hybrid tx-level.** `execute_swap_operations` with `hybrid: {}` / unparseable on **all** hops still hits the single 15M fallback (or documented equivalent) — do not silently grant 600k. 6. **`estimateSwapNetworkFee`** with indexer-equivalent ops matches `estimateTerraClassicFeeForEntries` of the submit send msg (4-hop mixed + 2-hop pool-only + wrap+2hop). 7. **Swap page.** Quote with `indexerOperations.length === 4` and hop1 hybrid: `swap-network-fee` LUNC matches the envelope (not ~108). AMM **Fee** row still bps. 8. **Decode failure.** Malformed `send.msg` throws `SendHookGasDecodeError`. 9. **Wrap / unwrap combos.** #587 / #599 / #475 fixtures still pass; `cw20Hybrid: true` must not jump wrap+2hop to 15M (existing test). 10. **Trade market.** Single-pair hybrid still uses `estimateMarketPairSwapSequenceUlunaFeesTotal`; multi-hop market (if reachable) shares the mixed-hop helper — no second formula. 11. **Atomic WC vs split.** Existing `isAtomicWalletConnectPost` tests still treat Station/LuncDash WC as atomic unless the implementation **intentionally** moves them to split sign (then update tests + #519/#566). 12. **Swarm lockstep** if `gas.ts` constants change. **LocalTerra (Keplr or simulated wallet — not Station)** 13. Seed a **≥2 hop** CW20 path with hybrid on **one** hop only. Broadcast with the new envelope: `gas_used < gas_wanted`, fee LUNC in the accepted class, no OOG toast. 14. Pool-only 2-hop and wrap+2hop happy paths still land (`e2e/wrap-swap.spec.ts` E7/E8 as applicable). 15. Optional: indexer-shaped 4-hop stand-in if LocalTerra graph can express cLUNC→CL8Y book→…→USTR; otherwise document the 2-hop mixed stand-in and keep columbus-5 hash as the 4-hop anchor. **columbus-5 (Station P0)** 16. Repeat **cLUNC → USTR** on Swap with Station **extension** and Station **mobile / WC**. Screenshot: wallet Tx Fee vs dApp Network fee. Must not show ~4,157 LUNC when the dApp envelope is ~200 LUNC class. If Station app still auto-gasses after fee pass-through, record app version + residual in the issue and keep G-AUTO-8 residual clause. 17. Confirm a successful hash: `gas_used < gas_wanted`, fee paid ≈ envelope (or manual override no longer required). ## Test plan (attack, hack, and abuse) 1. **Indexer spoof — long route + fake hybrid (#450 / SEC-I02).** A compromised `route/solve` can attach `hybrid` on one hop and 4+ pool hops to inflate **wallet** gas (grief / drain LUNC reserve) or display a short path while submitting a long one. Submit path must stay ops-aligned (`reconcileSwapRouteIntermediateTokens`). Gas must **not** jump to 15M×N from a single hybrid flag. Cap / monotonicity: more hops may cost more, but not 15M per extra hop. 2. **`max_maker_fills` grief.** Huge `max_maker_fills` on hop 1 must remain clamped by `maxMakerFillsForSubmit` / on-chain `MAX_MAKER_FILLS_HARD_CAP` (100) and `HYBRID_SWAP_GAS_LIMIT` **per book hop**, not unbounded. 3. **Missing / malformed `send.msg`.** Must throw (no 600k fallback that OOGs, no 15M×N). Attacker-controlled inner JSON must not select `place_limit_order` / `unwrap` / invoice hooks by accident — keep exact-key parse order in `getGasLimitForTx`. 4. **Fee overwrite both directions.** Station **high** auto-gas (this bug) vs Station/Keplr **low** rewrite (#127). Do not add a mainnet “must match exactly” guard that lets a wallet force a 4k-LUNC fee through, or that rejects a honest ~200 LUNC envelope. Do not re-enable SEC-E08 on columbus-5 in this issue. 5. **Atomic WC replay / double `post`.** Changing Station WC to split sign must keep `#499` sequence refresh and `#208` single-popup lock (`withTerraWalletSignLock`). No second silent `broadcastTx` after user approval. 6. **Hybrid opt-out smuggling.** Gas-fix PRs must not add `pool_only=true` on official Swap/Trade (`H596-4`) or drop `hybrid` from submit while the quote assumed a book fill (`H596-6`) — that is a price-integrity / sandwich-adjacent miss, not a fee tweak. 7. **Native wrap confusion.** Do not apply the mixed-hop 15M bugfix by attaching hybrid to `executeNativeSwap`. Keep wrap+N-hop on #587 constants so a crafted “needsWrap + cw20Hybrid” hint cannot request 15M×hops. 8. **Dust / huge offer.** Envelope is hop/hybrid-shaped, not amount-linear. 100k vs 10k cLUNC must not by itself justify 4k LUNC gas. If a future LCD sim is used for tuning only, reject using `amount` as a gas multiplier. ## Verification criteria ```bash make verify-issue-<iid> # expected to include: # frontend Vitest: terraGas / swapNetworkFee / transactions / SwapPage network-fee # make verify-issue-475 # inventory still complete # make verify-issue-587 # wrap+multihop unchanged # make verify-issue-596 # no hybrid opt-out # optional: # VERIFY_ISSUE_<iid>_CHAIN=1 # LocalTerra mixed-hop tx, 1 worker ``` Close when: 1. Unit assertions prove mixed-hop gas is **above live `gas_used` 5,026,176** and **below 15M**, and hint **equals** broadcast. 2. #587 / #599 / #475 / #596 verifies still pass. 3. A LocalTerra or columbus-5 hash shows `gas_used < gas_wanted` without a **manual** Station gas override, **or** an operator comment records Station-app residual with the dApp row in the ~200 LUNC class and extension/Keplr matching the envelope. 4. Docs + `AGENTS_TERRACLASSIC_GAS` describe mixed hybrid router hops and Station auto-gas ≠ LCD-sim envelope. ## Notes - Reporter wallet / chat handles omitted on purpose. - Do not paste internal chat logs into this issue or MRs.
PlasticDigits commented 2026-08-27 00:20:45 +00:00 (Migrated from gitlab.com)

marked as related to #587

marked as related to #587
PlasticDigits commented 2026-08-27 00:20:45 +00:00 (Migrated from gitlab.com)

marked as related to #596

marked as related to #596
PlasticDigits commented 2026-08-27 00:20:46 +00:00 (Migrated from gitlab.com)

marked as related to #249

marked as related to #249
PlasticDigits commented 2026-08-27 00:20:47 +00:00 (Migrated from gitlab.com)

marked as related to #127

marked as related to #127
PlasticDigits commented 2026-08-27 00:20:48 +00:00 (Migrated from gitlab.com)

marked as related to #429

marked as related to #429
PlasticDigits commented 2026-08-27 00:20:48 +00:00 (Migrated from gitlab.com)

marked as related to #475

marked as related to #475
PlasticDigits commented 2026-08-27 00:20:49 +00:00 (Migrated from gitlab.com)

marked as related to #353

marked as related to #353
PlasticDigits commented 2026-08-27 00:37:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1182

mentioned in merge request !1182
PlasticDigits commented 2026-08-27 00:37:34 +00:00 (Migrated from gitlab.com)

Implementation in !1182.

Completed (unit + docs; make verify-issue-679 passed + retest)

  • G-AUTO-1. 4-hop mixed envelope is 6,785,500 (> 5,026,176, < 15M; ~192 LUNC). Not 46,785,500.
  • G-AUTO-2. estimateSwapNetworkFee with indexer ops equals broadcast getGasLimitForTx send hook.
  • G-AUTO-3. Hops without hybrid use the 950k pool floor. gasLimitForHybridParams(undefined) is not summed per empty hop.
  • G-AUTO-4. Wrap+1hop / wrap+2hop / unwrap+2hop unchanged (make verify-issue-587).
  • G-AUTO-5. Hybrid-on-every-hop 2-hop stays 3,058,600.
  • G-AUTO-6. Direct pair book_input = 0 stays 840k; book_input > 0 stays quote-driven.
  • G-AUTO-7. Inventory send_4hop_hybrid_first_pool_rest.
  • G-AUTO-9. Route still shows the execution path. No hybrid opt-out (make verify-issue-596).
  • G-AUTO-10. make verify-issue-679 + docs/skill + swarm gas.ts lockstep.

Not done (needs operator / wallet)

  • G-AUTO-8. Station extension columbus-5 confirm-screen match, and Station mobile/WC residual screenshot. Residual is documented: atomic WC stays; dApp Network fee is the source of truth; do not enable mainnet SEC-E08.
  • LocalTerra mixed-hop live tx (VERIFY_ISSUE_679_CHAIN=1) — optional; 4-hop anchor remains hash AB8BE4F7….
  • columbus-5 successful hash without a manual Station gas override.
Implementation in !1182. **Completed (unit + docs; `make verify-issue-679` passed + retest)** - [x] **G-AUTO-1.** 4-hop mixed envelope is **6,785,500** (> 5,026,176, < 15M; ~192 LUNC). Not 46,785,500. - [x] **G-AUTO-2.** `estimateSwapNetworkFee` with indexer ops equals broadcast `getGasLimitForTx` send hook. - [x] **G-AUTO-3.** Hops without `hybrid` use the 950k pool floor. `gasLimitForHybridParams(undefined)` is not summed per empty hop. - [x] **G-AUTO-4.** Wrap+1hop / wrap+2hop / unwrap+2hop unchanged (`make verify-issue-587`). - [x] **G-AUTO-5.** Hybrid-on-every-hop 2-hop stays **3,058,600**. - [x] **G-AUTO-6.** Direct pair `book_input = 0` stays 840k; `book_input > 0` stays quote-driven. - [x] **G-AUTO-7.** Inventory `send_4hop_hybrid_first_pool_rest`. - [x] **G-AUTO-9.** Route still shows the execution path. No hybrid opt-out (`make verify-issue-596`). - [x] **G-AUTO-10.** `make verify-issue-679` + docs/skill + swarm `gas.ts` lockstep. **Not done (needs operator / wallet)** - [ ] **G-AUTO-8.** Station extension columbus-5 confirm-screen match, and Station mobile/WC residual screenshot. Residual is documented: atomic WC stays; dApp Network fee is the source of truth; do not enable mainnet SEC-E08. - [ ] LocalTerra mixed-hop live tx (`VERIFY_ISSUE_679_CHAIN=1`) — optional; 4-hop anchor remains hash `AB8BE4F7…`. - [ ] columbus-5 successful hash **without** a manual Station gas override.
PlasticDigits commented 2026-08-27 04:52:42 +00:00 (Migrated from gitlab.com)

mentioned in commit 1fbee5b4d5

mentioned in commit 1fbee5b4d529875467ae6a2467f73ddf813943ef
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-27 04:52:42 +00:00
PlasticDigits commented 2026-08-27 05:29:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #686

mentioned in issue #686
PlasticDigits commented 2026-08-27 05:29:55 +00:00 (Migrated from gitlab.com)

marked as related to #686

marked as related to #686
PlasticDigits commented 2026-08-27 05:30:26 +00:00 (Migrated from gitlab.com)

Merged via !1182 (1fbee5b4) onto main 36d64528.

make verify-issue-679 PASS (9/9, including children #475 / #587 / #596). Mixed 4-hop envelope stays 6,785,500 gas (~192 LUNC). Residual: Station mobile / WalletConnect may still replace the dApp fee with auto-gas — the Swap Network fee (est.) row is the source of truth; do not turn hybrid off (#596). Optional VERIFY_ISSUE_679_CHAIN=1 not run (LocalTerra down). Duplicate ticket #681 should stay closed. Post-merge ops: #686.

Merged via !1182 (`1fbee5b4`) onto `main` `36d64528`. `make verify-issue-679` **PASS** (9/9, including children #475 / #587 / #596). Mixed 4-hop envelope stays 6,785,500 gas (~192 LUNC). **Residual:** Station mobile / WalletConnect may still replace the dApp fee with auto-gas — the Swap **Network fee (est.)** row is the source of truth; do not turn hybrid off (#596). Optional `VERIFY_ISSUE_679_CHAIN=1` not run (LocalTerra down). Duplicate ticket #681 should stay closed. Post-merge ops: #686.
PlasticDigits commented 2026-08-27 05:30:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #681

mentioned in issue #681
PlasticDigits commented 2026-08-27 11:52:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #690

mentioned in issue #690
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#679
No description provided.