fix: Mint drip out of gas — add FAUCET_DRIP_GAS_LIMIT (soft-launch faucet) #474

Closed
opened 2026-07-12 07:09:44 +00:00 by PlasticDigits · 7 comments
PlasticDigits commented 2026-07-12 07:09:44 +00:00 (Migrated from gitlab.com)

Bug report

Users on columbus-5 (https://dex.cl8y.com/mint) report "Automatic gas sometimes fails" / "Actually all the time".

Observed UI copy (wallet in-app browser, Terra Classic MAINNET):

Transaction needed more gas than estimated. Try again — gas usage can vary slightly between blocks.

This is the retail humanization of on-chain out of gas (humanizeTerraTxError.ts). Mint shows Ready to mint / amount 100 / MINT CTA, then fails every attempt. Users still pay network fees for failed txs.

Related: soft-launch faucet shipped in #473; identical failure class previously fixed for fee-discount register in #384.


Current codebase

The dApp does not use LCD/wallet gas simulation for executes. “Automatic gas” means static per-message limits from getGasLimitForTx × effectiveGasPriceUluna(), then an explicit Fee protobuf with preferNoSetFee: true.

Mint execute path

  1. MintPage.tsx → drip(wallet, token)
  2. faucet.ts → executeTerraContract(..., { drip: { token } })
  3. broadcastTerraExecuteContracts → estimateTerraClassicFeeForEntries → getGasLimitForTx(msg) → buildTerraClassicFee
  4. Sign + broadcast; on failure → tryHumanizeTerraTxMessage maps /out of gas/i to the user-facing string above

Root cause

getGasLimitForTx in terraGas.ts has no 'drip' in executeMsg branch. Faucet drip falls through to:

export const BASE_GAS_LIMIT = 200000
// ...
return BASE_GAS_LIMIT

On-chain, faucet execute_drip (smartcontracts/contracts/faucet/src/contract.rs) does allowlist/cooldown storage plus a cross-contract CW20 Mint submessage. That routinely needs > 200k gas (same pattern as fee-discount register measured ~204k → fixed with REGISTER_FEE_DISCOUNT_GAS_LIMIT = 300_000 in #384).

Because the ceiling is fixed and too low, mint failure is deterministic (“all the time”), not intermittent block variance.

What is not the Mint failure

Mechanism Role
gasAdjustment: 1.75 (stationNativeNetwork.ts) LocalTerra Station addNetwork only — not dApp fee math
SWAP_GAS_BUFFER / hybrid swap gas Swap hops only
Mainnet extension fee guard (#429) Off on columbus-5; undershoot would show fee-mismatch copy, not this OOG path
Insufficient LUNC Different humanized errors

Why a new implementation is needed

Soft-launch Mint is live on mainnet. Every drip attempt burns user LUNC and fails. #473 shipped the contract + page but never added a measured gas constant to the static envelope table (playbook AGENTS_SOFT_LAUNCH_FAUCET.md only notes users pay gas). Without a dedicated drip mapping, Mint remains broken regardless of wallet (Keplr, Station in-app browser, simulated).


Constraints / guardrails

  • Keep static envelope architecture (terraClassicFeeEstimate.ts / docs: no LCD simulate-before-broadcast as the primary fix).
  • Preserve effectiveGasPriceUluna() floor (28.325 uluna/gas) — this bug is gas limit, not fee price.
  • Set FAUCET_DRIP_GAS_LIMIT from measured gas_used + ~20–30% margin (same discipline as #384). Prefer slightly over-allocating vs under (users pay more LUNC but tx succeeds).
  • Do not lower swap / hybrid / limit-order constants to “compensate.”
  • Do not change faucet cooldown / allowlist / drip amount as part of this fix.
  • Mainnet faucet address is live (deployments/mainnet-soft-launch/faucet-trace.md).
  • If touching sign path: npm ci + cosmes patch still required.

Relevant files

File Role
frontend-dapp/src/services/terraclassic/terraGas.ts BASE_GAS_LIMIT, getGasLimitForTx — add drip branch
frontend-dapp/src/services/terraclassic/faucet.ts { drip: { token } } execute
frontend-dapp/src/pages/MintPage.tsx Mint UI / error surface
frontend-dapp/src/services/terraclassic/terraBroadcast.ts Fee build + error humanization
frontend-dapp/src/services/terraclassic/terraClassicFeeEstimate.ts Envelope source of truth
frontend-dapp/src/utils/humanizeTerraTxError.ts OOG → retail copy
frontend-dapp/src/utils/constants.ts Gas price floor (unchanged)
smartcontracts/contracts/faucet/src/contract.rs Cross-contract mint workload
frontend-dapp/src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts Pattern for #384 regression tests
frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts Unknown msg → 200k fallback docs
docs/frontend.md Gas architecture table (#384 note)
skills/AGENTS_TERRACLASSIC_GAS.md Agent gas rules
skills/AGENTS_SOFT_LAUNCH_FAUCET.md Soft-launch faucet playbook

  1. Add FAUCET_DRIP_GAS_LIMIT (name bikeshed OK) in terraGas.ts.
  2. Handle 'drip' in executeMsg in getGasLimitForTx before the BASE_GAS_LIMIT fallback.
  3. Benchmark on columbus-5 (or LocalTerra optimized wasm): one successful drip, read gas_used, set constant to measured + margin. Starting guess until measured: 350k–400k (cross-contract mint + storage; register alone needed 300k).
  4. Unit tests mirroring #384 (terraGas.faucet.test.ts or extend existing): getGasLimitForTx({ drip: … }) ≠ 200k and > measured.
  5. Optional: show TerraClassicTxFeeHint on Mint (transparency for “you pay network gas”).
  6. Docs: add drip row to docs/frontend.md gas table + soft-launch faucet skill cross-link.
  7. Optional: make verify-issue-NNN script patterned on scripts/qa/verify-issue-384.sh.

Out of scope for this issue: full getGasLimitForTx audit of other retail msgs (separate follow-up); LCD simulate architecture; enabling mainnet fee guard (#429).


Acceptance criteria

  • getGasLimitForTx({ drip: { token: 'terra1…' } }) returns a dedicated constant > 200_000 and > measured gas_used with documented margin.
  • Successful Mint / drip on columbus-5 from wallet in-app browser (and Keplr / simulated where available).
  • Failed-tx class no longer reproduces for happy-path drip: gas_used < gas_wanted with comfortable headroom.
  • Unit tests cover drip gas mapping (regression against 200k fallback).
  • docs/frontend.md (and soft-launch faucet skill if needed) document the constant.
  • Mint page still respects cooldown / paused / non-allowlisted token contract errors (unchanged).

Test plan (all paths)

Path Expectation
Happy-path drip (mintable token, cooldown clear, faucet not paused) Tx succeeds; balances increase by drip amount
Cooldown active Contract cooldown error — not OOG
Paused faucet Contract paused error — not OOG
Non-allowlisted token TokenNotAllowed (or equivalent) — not OOG
First drip vs repeat after cooldown Both succeed with same gas envelope (margin covers LAST_CLAIM write)
Wallet: Station in-app browser / Keplr / simulated Same dApp envelope; Mint succeeds when funded
Fee display (if hint added) Shows uluna estimate consistent with FAUCET_DRIP_GAS_LIMIT × gas price
Unit: unknown execute msg Still falls back to BASE_GAS_LIMIT (200k)
Unit: { drip: { token } } Uses FAUCET_DRIP_GAS_LIMIT
Regression: register / deregister / swap / hybrid Unchanged limits

Test plan (attack / hack / abuse vectors)

Vector Expectation
Sybil / rapid mint attempts Cooldown still enforced; higher gas limit does not bypass F3 cooldown
Inflated gas limit griefing User overpays own LUNC only; no protocol fund drain
Crafted drip with huge/invalid token address Contract validation fails; gas envelope still sufficient to fail cleanly
Replay / double-submit while pending Existing wallet/sequence handling; no new privilege
Operator pause during mint Clean pause error; no OOG
Removing drip mapping in a future PR Tests / verify script must fail
Wallet rewriting fee lower than envelope Separate #429 concern; document if observed — not fixed here

Verification criteria

# Unit
cd frontend-dapp && npm run test:run -- \
  src/services/terraclassic/__tests__/transactions.test.ts \
  src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts
# plus new faucet gas test file when added

# Live (columbus-5) — after a successful mint
# gas_used < gas_wanted; gas_wanted === FAUCET_DRIP_GAS_LIMIT
terrad query tx <TXHASH> --node https://terra-classic-rpc.publicnode.com:443 \
  --output json | jq '.tx_response.gas_used, .tx_response.gas_wanted'

Manual: open /mint on mainnet → select CORAL (or any mintable) → MINT → success toast / balance update; no “needed more gas than estimated.”

## Bug report Users on columbus-5 (`https://dex.cl8y.com/mint`) report **"Automatic gas sometimes fails"** / **"Actually all the time"**. Observed UI copy (wallet in-app browser, Terra Classic MAINNET): > Transaction needed more gas than estimated. Try again — gas usage can vary slightly between blocks. This is the retail humanization of on-chain **`out of gas`** (`humanizeTerraTxError.ts`). Mint shows **Ready to mint** / amount **100** / **MINT** CTA, then fails every attempt. Users still pay network fees for failed txs. Related: soft-launch faucet shipped in **#473**; identical failure class previously fixed for fee-discount `register` in **#384**. --- ## Current codebase The dApp does **not** use LCD/wallet gas simulation for executes. “Automatic gas” means **static per-message limits** from `getGasLimitForTx` × `effectiveGasPriceUluna()`, then an explicit `Fee` protobuf with `preferNoSetFee: true`. ### Mint execute path 1. `MintPage.tsx` → `drip(wallet, token)` 2. `faucet.ts` → `executeTerraContract(..., { drip: { token } })` 3. `broadcastTerraExecuteContracts` → `estimateTerraClassicFeeForEntries` → `getGasLimitForTx(msg)` → `buildTerraClassicFee` 4. Sign + broadcast; on failure → `tryHumanizeTerraTxMessage` maps `/out of gas/i` to the user-facing string above ### Root cause `getGasLimitForTx` in `terraGas.ts` has **no `'drip' in executeMsg` branch**. Faucet drip falls through to: ```ts export const BASE_GAS_LIMIT = 200000 // ... return BASE_GAS_LIMIT ``` On-chain, faucet `execute_drip` (`smartcontracts/contracts/faucet/src/contract.rs`) does allowlist/cooldown storage **plus** a cross-contract CW20 **`Mint`** submessage. That routinely needs **> 200k** gas (same pattern as fee-discount `register` measured ~204k → fixed with `REGISTER_FEE_DISCOUNT_GAS_LIMIT = 300_000` in #384). Because the ceiling is fixed and too low, mint failure is **deterministic** (“all the time”), not intermittent block variance. ### What is *not* the Mint failure | Mechanism | Role | |-----------|------| | `gasAdjustment: 1.75` (`stationNativeNetwork.ts`) | LocalTerra Station `addNetwork` only — not dApp fee math | | `SWAP_GAS_BUFFER` / hybrid swap gas | Swap hops only | | Mainnet extension fee guard (#429) | Off on columbus-5; undershoot would show fee-mismatch copy, not this OOG path | | Insufficient LUNC | Different humanized errors | --- ## Why a new implementation is needed Soft-launch Mint is live on mainnet. Every drip attempt burns user LUNC and fails. #473 shipped the contract + page but never added a measured gas constant to the static envelope table (playbook `AGENTS_SOFT_LAUNCH_FAUCET.md` only notes users pay gas). Without a dedicated `drip` mapping, Mint remains broken regardless of wallet (Keplr, Station in-app browser, simulated). --- ## Constraints / guardrails - Keep **static envelope** architecture (`terraClassicFeeEstimate.ts` / docs: no LCD simulate-before-broadcast as the primary fix). - Preserve `effectiveGasPriceUluna()` floor (**28.325** uluna/gas) — this bug is **gas limit**, not fee price. - Set `FAUCET_DRIP_GAS_LIMIT` from **measured `gas_used` + ~20–30% margin** (same discipline as #384). Prefer slightly over-allocating vs under (users pay more LUNC but tx succeeds). - Do **not** lower swap / hybrid / limit-order constants to “compensate.” - Do **not** change faucet cooldown / allowlist / drip amount as part of this fix. - Mainnet faucet address is live (`deployments/mainnet-soft-launch/faucet-trace.md`). - If touching sign path: `npm ci` + cosmes patch still required. --- ## Relevant files | File | Role | |------|------| | `frontend-dapp/src/services/terraclassic/terraGas.ts` | `BASE_GAS_LIMIT`, `getGasLimitForTx` — **add `drip` branch** | | `frontend-dapp/src/services/terraclassic/faucet.ts` | `{ drip: { token } }` execute | | `frontend-dapp/src/pages/MintPage.tsx` | Mint UI / error surface | | `frontend-dapp/src/services/terraclassic/terraBroadcast.ts` | Fee build + error humanization | | `frontend-dapp/src/services/terraclassic/terraClassicFeeEstimate.ts` | Envelope source of truth | | `frontend-dapp/src/utils/humanizeTerraTxError.ts` | OOG → retail copy | | `frontend-dapp/src/utils/constants.ts` | Gas price floor (unchanged) | | `smartcontracts/contracts/faucet/src/contract.rs` | Cross-contract mint workload | | `frontend-dapp/src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts` | Pattern for #384 regression tests | | `frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts` | Unknown msg → 200k fallback docs | | `docs/frontend.md` | Gas architecture table (#384 note) | | `skills/AGENTS_TERRACLASSIC_GAS.md` | Agent gas rules | | `skills/AGENTS_SOFT_LAUNCH_FAUCET.md` | Soft-launch faucet playbook | --- ## Recommended direction 1. Add `FAUCET_DRIP_GAS_LIMIT` (name bikeshed OK) in `terraGas.ts`. 2. Handle `'drip' in executeMsg` in `getGasLimitForTx` **before** the `BASE_GAS_LIMIT` fallback. 3. Benchmark on columbus-5 (or LocalTerra optimized wasm): one successful `drip`, read `gas_used`, set constant to measured + margin. Starting guess until measured: **350k–400k** (cross-contract mint + storage; register alone needed 300k). 4. Unit tests mirroring #384 (`terraGas.faucet.test.ts` or extend existing): `getGasLimitForTx({ drip: … })` ≠ 200k and `> measured`. 5. Optional: show `TerraClassicTxFeeHint` on Mint (transparency for “you pay network gas”). 6. Docs: add drip row to `docs/frontend.md` gas table + soft-launch faucet skill cross-link. 7. Optional: `make verify-issue-NNN` script patterned on `scripts/qa/verify-issue-384.sh`. **Out of scope for this issue:** full `getGasLimitForTx` audit of other retail msgs (separate follow-up); LCD simulate architecture; enabling mainnet fee guard (#429). --- ## Acceptance criteria - [ ] `getGasLimitForTx({ drip: { token: 'terra1…' } })` returns a dedicated constant **> 200_000** and **> measured `gas_used`** with documented margin. - [ ] Successful Mint / `drip` on **columbus-5** from wallet in-app browser (and Keplr / simulated where available). - [ ] Failed-tx class no longer reproduces for happy-path drip: `gas_used < gas_wanted` with comfortable headroom. - [ ] Unit tests cover drip gas mapping (regression against 200k fallback). - [ ] `docs/frontend.md` (and soft-launch faucet skill if needed) document the constant. - [ ] Mint page still respects cooldown / paused / non-allowlisted token contract errors (unchanged). --- ## Test plan (all paths) | Path | Expectation | |------|-------------| | Happy-path drip (mintable token, cooldown clear, faucet not paused) | Tx succeeds; balances increase by drip amount | | Cooldown active | Contract cooldown error — **not** OOG | | Paused faucet | Contract paused error — **not** OOG | | Non-allowlisted token | `TokenNotAllowed` (or equivalent) — **not** OOG | | First drip vs repeat after cooldown | Both succeed with same gas envelope (margin covers `LAST_CLAIM` write) | | Wallet: Station in-app browser / Keplr / simulated | Same dApp envelope; Mint succeeds when funded | | Fee display (if hint added) | Shows uluna estimate consistent with `FAUCET_DRIP_GAS_LIMIT` × gas price | | Unit: unknown execute msg | Still falls back to `BASE_GAS_LIMIT` (200k) | | Unit: `{ drip: { token } }` | Uses `FAUCET_DRIP_GAS_LIMIT` | | Regression: `register` / `deregister` / swap / hybrid | Unchanged limits | --- ## Test plan (attack / hack / abuse vectors) | Vector | Expectation | |--------|-------------| | Sybil / rapid mint attempts | Cooldown still enforced; higher gas limit does **not** bypass F3 cooldown | | Inflated gas limit griefing | User overpays own LUNC only; no protocol fund drain | | Crafted `drip` with huge/invalid token address | Contract validation fails; gas envelope still sufficient to fail cleanly | | Replay / double-submit while pending | Existing wallet/sequence handling; no new privilege | | Operator pause during mint | Clean pause error; no OOG | | Removing drip mapping in a future PR | Tests / verify script must fail | | Wallet rewriting fee lower than envelope | Separate #429 concern; document if observed — not fixed here | --- ## Verification criteria ```bash # Unit cd frontend-dapp && npm run test:run -- \ src/services/terraclassic/__tests__/transactions.test.ts \ src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts # plus new faucet gas test file when added # Live (columbus-5) — after a successful mint # gas_used < gas_wanted; gas_wanted === FAUCET_DRIP_GAS_LIMIT terrad query tx <TXHASH> --node https://terra-classic-rpc.publicnode.com:443 \ --output json | jq '.tx_response.gas_used, .tx_response.gas_wanted' ``` Manual: open `/mint` on mainnet → select CORAL (or any mintable) → MINT → success toast / balance update; no “needed more gas than estimated.”
PlasticDigits commented 2026-07-12 07:09:45 +00:00 (Migrated from gitlab.com)

marked as related to #473

marked as related to #473
PlasticDigits commented 2026-07-12 07:09:46 +00:00 (Migrated from gitlab.com)

marked as related to #384

marked as related to #384
PlasticDigits commented 2026-07-12 07:09:54 +00:00 (Migrated from gitlab.com)

marked as related to #475

marked as related to #475
PlasticDigits commented 2026-07-12 09:10:36 +00:00 (Migrated from gitlab.com)

mentioned in commit d3e10ddb7b

mentioned in commit d3e10ddb7bbd2e8ffe0d8d6fbe56cc49906ba7b3
PlasticDigits commented 2026-07-12 09:10:39 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1015

mentioned in merge request !1015
PlasticDigits commented 2026-07-12 09:11:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #475

mentioned in issue #475
PlasticDigits commented 2026-07-12 09:22:48 +00:00 (Migrated from gitlab.com)

mentioned in commit b598b37758

mentioned in commit b598b3775898defea541ddc8379977cc6cd10a3e
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-07-12 09:22:48 +00:00
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#474
No description provided.