test: tax-aware localnet swarm workers (exclude gems from extra-debit) #621

Closed
opened 2026-08-24 05:54:08 +00:00 by PlasticDigits · 19 comments
PlasticDigits commented 2026-08-24 05:54:08 +00:00 (Migrated from gitlab.com)

Parent / siblings

Depends on the LocalTerra community-tax seed + funding + indexer env issue (deploy must create a registered tax/EMBER pair and Transfer-fund it). Parent product: #592 T592-2/3/13, #607, #119, #293.

Do not reopen swarm liquidity (#293) unless OE-1 gem hub checks regress.

Current codebase

Two localnet-only bot layers enumerate all factory pairs and treat every CW20 Send as 1:1:

TypeScript @cl8y-dex/localnet-trading-swarm:

  • factoryTokens.ts — paginate pairs, unique CW20s for funding + graph.
  • funding.ts — Mint every CW20 (sibling issue adds Transfer for tax).
  • actions.ts — pair_swap / hybrid_swap / limit_order / LP use pickOfferAmount (slice of reserves) as the Send.amount. Pair-direct swap leaves trader unset. router_multihop Sends to the router with execute_swap_operations and no tax headroom.
  • profiles.json — five Poisson ~20s profiles; no tax worker.
  • Guard: LCD network must be localterra (validateLocalnet.ts).

Python scripts/bots/swarm.py + launch-swarm.sh:

  • 25 swap + 5 limit + 3 LP workers, all --from test1.
  • _iter_factory_pairs → swap_cw20_send / place_limit_cw20_send / provide_liquidity with reserve-fraction amounts.
  • Bootstrap deepen is gem hubs only (EMBER/CORAL, TOPAZ/ONYX, ONYX/CORAL).

Neither layer queries TaxPreview, sizes amount + sell_bps, or sets official-router Swap.trader. After the seed issue lands, random walks will pick the tax pair and revert (underfunded extra-debit) or mis-account buys.

Why this is needed

The swarm is how QA generates tape, book, LP churn, and indexer load for “most CW20s.” A listed tax token is not an honest mintable gem: sell extra-debits the trader; official-router hops extra-debit authenticated trader (T592-13); place-limit Send stays 1:1 (T592-7). Dumping the pair into the naive random walk either fails loudly or silently skips after errors, so the tax market never gets volume. Dedicated workers are the only way to exercise the same action set (swap / router / hybrid / limit / LP) correctly.

Constraints / guardrails

  • LocalTerra only. Existing network / VITE_NETWORK=local guards stay.
  • Default gem workers exclude tax-token addresses from random offer/route/LP picks (same idea as wrap Mint skip).
  • Dedicated tax workers (1–2 TS profiles and/or Python --worker tax) must:
    • Size sells with TaxPreview (or amount * (1 + sell_bps/10000) fail-closed if preview missing).
    • Require balance ≥ debit before broadcast.
    • Pair-direct sell: extra-debit from; do not spoof trader.
    • Official-router hops: set authenticated Swap.trader only when Send is to config.router (T592-13). Pair-direct must ignore spoofed trader.
    • Buys: expect outbound split (user credit < pair debit).
    • Provide/withdraw and place-limit: 1:1 (no extra-debit).
  • Do not turn hybrid off to “make tax apply” (#596 / H-01).
  • Do not add the tax pair to OE-1 reciprocal pool_only symmetry checks until quotes are net-of-tax (#293). Hub gems stay the OE-1 target.
  • Shared test1 (Python) + five TS wallets: extra-debit + gas must not drain below #372 soak floor. Prefer Transfer top-up from deploy funding, not Mint.
  • Gas constants stay aligned with dApp SWAP_GAS_BUFFER (#115). Router+tax may need the same hop padding, not a new FoT gas model.

Relevant files

Path Role
packages/localnet-trading-swarm/src/actions.ts Swap/router/hybrid/limit/LP messages
packages/localnet-trading-swarm/src/factoryTokens.ts Pair/CW20 enumeration
packages/localnet-trading-swarm/src/funding.ts Top-up (Transfer after sibling)
packages/localnet-trading-swarm/src/profiles.json Weights; add tax profile or filter
packages/localnet-trading-swarm/src/liquidityGuards.ts Reserve floors
packages/localnet-trading-swarm/README.md Invariants
scripts/bots/swarm.py Python swap/limit/LP
scripts/bots/launch-swarm.sh Worker matrix
scripts/bots/bootstrap-swarm-liquidity.py Hub deepen
skills/AGENTS_LOCALNET_TRADING_SWARM.md Swarm playbook
skills/AGENTS_COMMUNITY_TAX_ROUTER.md T592-13
  1. Shared detector: env VITE_TOKEN_COMMUNITY_TAX_ADDRESS and/or LCD GetConfig / GetLauncherOrigin on a CW20.
  2. TS: filter tax tokens out of randomCw20PairEndpoints / randomLiquidPair for existing profiles. Add profile tax_listed (or env SWARM_TAX_WORKERS=1) that only acts on the tax/EMBER pair + one official-router ≥2hop that includes the tax token.
  3. Before tax Send swap: tax_preview { from, to: pair or router, amount, send_msg }; use preview debit for balance gate; Send.amount stays the economic swap amount (pair still credits amount).
  4. Python: same exclude in _collect_pair_metas for swap/lp workers; add --worker tax started from launch-swarm.sh (one process is enough). Limit workers may include the tax token (1:1 place).
  5. Structured logs: tax_debit, tax_credit, bps, path=pair|router so QA can grep extra-debit vs 1:1.
  6. Unit tests without chain: preview math, exclude filter, spoofed-trader not set on pair-direct.

Acceptance criteria

  • After seed deploy, make swarm-local / make swarm-launch start without Mint errors and without a storm of insufficient-funds on the tax pair.
  • Gem/OE-1 workers never offer the tax token.
  • Tax workers produce at least: pair-direct sell (extra-debit), pair-direct buy (outbound split), provide 1:1, place-limit 1:1, one official-router ≥2hop with trader set.
  • Hybrid on the tax pair is either implemented with net quote awareness or explicitly skipped with a log tax_hybrid_skip (do not broadcast a 1:1-sized hybrid sell).
  • --dry-run still validates localterra + factory including the tax pair.
  • npm run test:run in the TS package and make test-swarm-liquidity stay green.
  • README + AGENTS_LOCALNET_TRADING_SWARM.md document exclude + tax workers.

Test plan (all paths)

  1. Seed deploy + TS --dry-run — tax pair visible, no broadcast.
  2. TS live 2–3 minutes — logs include tax_listed actions; gem profiles have no tax offer token.
  3. Python launch-swarm.sh — tax worker txs included; swap workers skip tax pair; limit worker may place on tax/EMBER.
  4. Sell: user debit == TaxPreview.debit; pair credit == amount.
  5. Buy: user tax-token credit < pair debit; sink gets the remainder.
  6. Router ≥2hop: extra-debit hits trader wallet, not the router’s CW20 inventory (T592-13).
  7. Provide: pair CW20 credit == declared; TransferFrom 1:1.
  8. Limit place: user debit == pair(+treasury fee) credit (L1).
  9. Thin tax reserves below floor — tax worker skips like gems (MIN_RESERVE_PER_SIDE).
  10. SWARM_TAX_WORKERS=0 — exclude only; no tax volume (escape hatch).

Test plan (attack / hack / abuse)

Vector Expect
Pair-direct Send+Swap with spoofed trader (another wallet) Token ignores spoof; extra-debit from only. Bots must not set trader on pair-direct
Router hop with missing trader Fail-closed on option-2 wasm; worker must set trader
Size Send.amount = wallet balance (no tax headroom) Preview gate refuses; no revert storm
Point swarm at public LCD Existing localterra guard exits
Treat park/limit debit as taxed Place stays 1:1; do not add tax to limit Send
OE-1 EMBER→CORAL vs CORAL→EMBER includes tax hops Must not; tax pair excluded from hub symmetry
Drain test1 via 500 bps extra-debit all day Funding Transfer + #372 floor; preflight preflight-test1-uluna.sh still warns
Hybrid book+pool sell sized as 1:1 Skip or size both legs + tax; never underfund

Verification

# after seed deploy
make swarm-local          # or ./scripts/localnet-trading-swarm.sh -- --stats
make swarm-launch         # short soak then make swarm-stop
# logs: tax sell extra-debit, buy split, router trader, no Mint
cd packages/localnet-trading-swarm && npm run test:run
make test-swarm-liquidity
make verify-issue-293     # gem hub OE-1 still pool_only
## Parent / siblings Depends on the LocalTerra **community-tax seed + funding + indexer env** issue (deploy must create a registered tax/EMBER pair and Transfer-fund it). Parent product: [#592](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/592) **T592-2/3/13**, [#607](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/607), [#119](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/119), [#293](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/293). Do **not** reopen swarm liquidity (#293) unless OE-1 gem hub checks regress. ## Current codebase Two localnet-only bot layers enumerate **all factory pairs** and treat every CW20 `Send` as 1:1: **TypeScript** `@cl8y-dex/localnet-trading-swarm`: - `factoryTokens.ts` — paginate `pairs`, unique CW20s for funding + graph. - `funding.ts` — `Mint` every CW20 (sibling issue adds Transfer for tax). - `actions.ts` — `pair_swap` / `hybrid_swap` / `limit_order` / LP use `pickOfferAmount` (slice of reserves) as the `Send.amount`. Pair-direct `swap` leaves `trader` unset. `router_multihop` `Send`s to the router with `execute_swap_operations` and no tax headroom. - `profiles.json` — five Poisson ~20s profiles; no tax worker. - Guard: LCD network must be `localterra` (`validateLocalnet.ts`). **Python** `scripts/bots/swarm.py` + `launch-swarm.sh`: - 25 swap + 5 limit + 3 LP workers, all `--from test1`. - `_iter_factory_pairs` → `swap_cw20_send` / `place_limit_cw20_send` / `provide_liquidity` with reserve-fraction amounts. - Bootstrap deepen is gem hubs only (`EMBER/CORAL`, `TOPAZ/ONYX`, `ONYX/CORAL`). Neither layer queries `TaxPreview`, sizes `amount + sell_bps`, or sets official-router `Swap.trader`. After the seed issue lands, random walks **will** pick the tax pair and revert (underfunded extra-debit) or mis-account buys. ## Why this is needed The swarm is how QA generates tape, book, LP churn, and indexer load for “most CW20s.” A listed tax token is not an honest mintable gem: sell extra-debits the trader; official-router hops extra-debit authenticated `trader` (**T592-13**); place-limit `Send` stays 1:1 (**T592-7**). Dumping the pair into the naive random walk either fails loudly or silently skips after errors, so the tax market never gets volume. Dedicated workers are the only way to exercise the same action set (swap / router / hybrid / limit / LP) **correctly**. ## Constraints / guardrails - LocalTerra only. Existing network / `VITE_NETWORK=local` guards stay. - Default gem workers **exclude** tax-token addresses from random offer/route/LP picks (same idea as wrap Mint skip). - Dedicated tax workers (1–2 TS profiles and/or Python `--worker tax`) must: - Size sells with `TaxPreview` (or `amount * (1 + sell_bps/10000)` fail-closed if preview missing). - Require `balance ≥ debit` before broadcast. - Pair-direct sell: extra-debit `from`; do not spoof `trader`. - Official-router hops: set authenticated `Swap.trader` only when `Send` is to `config.router` (**T592-13**). Pair-direct must ignore spoofed `trader`. - Buys: expect outbound split (user credit &lt; pair debit). - Provide/withdraw and place-limit: 1:1 (no extra-debit). - Do **not** turn hybrid off to “make tax apply” (#596 / **H-01**). - Do **not** add the tax pair to OE-1 reciprocal `pool_only` symmetry checks until quotes are net-of-tax (#293). Hub gems stay the OE-1 target. - Shared `test1` (Python) + five TS wallets: extra-debit + gas must not drain below #372 soak floor. Prefer Transfer top-up from deploy funding, not Mint. - Gas constants stay aligned with dApp `SWAP_GAS_BUFFER` (#115). Router+tax may need the same hop padding, not a new FoT gas model. ## Relevant files | Path | Role | |------|------| | `packages/localnet-trading-swarm/src/actions.ts` | Swap/router/hybrid/limit/LP messages | | `packages/localnet-trading-swarm/src/factoryTokens.ts` | Pair/CW20 enumeration | | `packages/localnet-trading-swarm/src/funding.ts` | Top-up (Transfer after sibling) | | `packages/localnet-trading-swarm/src/profiles.json` | Weights; add tax profile or filter | | `packages/localnet-trading-swarm/src/liquidityGuards.ts` | Reserve floors | | `packages/localnet-trading-swarm/README.md` | Invariants | | `scripts/bots/swarm.py` | Python swap/limit/LP | | `scripts/bots/launch-swarm.sh` | Worker matrix | | `scripts/bots/bootstrap-swarm-liquidity.py` | Hub deepen | | `skills/AGENTS_LOCALNET_TRADING_SWARM.md` | Swarm playbook | | `skills/AGENTS_COMMUNITY_TAX_ROUTER.md` | T592-13 | ## Recommended direction 1. Shared detector: env `VITE_TOKEN_COMMUNITY_TAX_ADDRESS` and/or LCD `GetConfig` / `GetLauncherOrigin` on a CW20. 2. TS: filter tax tokens out of `randomCw20PairEndpoints` / `randomLiquidPair` for existing profiles. Add profile `tax_listed` (or env `SWARM_TAX_WORKERS=1`) that only acts on the tax/EMBER pair + one official-router ≥2hop that includes the tax token. 3. Before tax `Send` swap: `tax_preview { from, to: pair or router, amount, send_msg }`; use preview `debit` for balance gate; `Send.amount` stays the **economic** swap amount (pair still credits `amount`). 4. Python: same exclude in `_collect_pair_metas` for swap/lp workers; add `--worker tax` started from `launch-swarm.sh` (one process is enough). Limit workers **may** include the tax token (1:1 place). 5. Structured logs: `tax_debit`, `tax_credit`, `bps`, `path=pair|router` so QA can grep extra-debit vs 1:1. 6. Unit tests without chain: preview math, exclude filter, spoofed-`trader` not set on pair-direct. ## Acceptance criteria - [ ] After seed deploy, `make swarm-local` / `make swarm-launch` start without Mint errors and without a storm of insufficient-funds on the tax pair. - [ ] Gem/OE-1 workers never offer the tax token. - [ ] Tax workers produce at least: pair-direct sell (extra-debit), pair-direct buy (outbound split), provide 1:1, place-limit 1:1, one official-router ≥2hop with `trader` set. - [ ] Hybrid on the tax pair is either implemented with net quote awareness or explicitly skipped with a log `tax_hybrid_skip` (do not broadcast a 1:1-sized hybrid sell). - [ ] `--dry-run` still validates localterra + factory including the tax pair. - [ ] `npm run test:run` in the TS package and `make test-swarm-liquidity` stay green. - [ ] README + `AGENTS_LOCALNET_TRADING_SWARM.md` document exclude + tax workers. ## Test plan (all paths) 1. Seed deploy + TS `--dry-run` — tax pair visible, no broadcast. 2. TS live 2–3 minutes — logs include `tax_listed` actions; gem profiles have no tax offer token. 3. Python `launch-swarm.sh` — tax worker txs included; swap workers skip tax pair; limit worker may place on tax/EMBER. 4. Sell: user debit == `TaxPreview.debit`; pair credit == `amount`. 5. Buy: user tax-token credit &lt; pair debit; sink gets the remainder. 6. Router ≥2hop: extra-debit hits `trader` wallet, not the router’s CW20 inventory (**T592-13**). 7. Provide: pair CW20 credit == declared; `TransferFrom` 1:1. 8. Limit place: user debit == pair(+treasury fee) credit (**L1**). 9. Thin tax reserves below floor — tax worker skips like gems (`MIN_RESERVE_PER_SIDE`). 10. `SWARM_TAX_WORKERS=0` — exclude only; no tax volume (escape hatch). ## Test plan (attack / hack / abuse) | Vector | Expect | |--------|--------| | Pair-direct `Send+Swap` with spoofed `trader` (another wallet) | Token ignores spoof; extra-debit `from` only. Bots must not set `trader` on pair-direct | | Router hop with **missing** `trader` | Fail-closed on option-2 wasm; worker must set `trader` | | Size `Send.amount` = wallet balance (no tax headroom) | Preview gate refuses; no revert storm | | Point swarm at public LCD | Existing localterra guard exits | | Treat park/limit debit as taxed | Place stays 1:1; do not add tax to limit `Send` | | OE-1 `EMBER→CORAL` vs `CORAL→EMBER` includes tax hops | Must not; tax pair excluded from hub symmetry | | Drain `test1` via 500 bps extra-debit all day | Funding Transfer + #372 floor; preflight `preflight-test1-uluna.sh` still warns | | Hybrid book+pool sell sized as 1:1 | Skip or size both legs + tax; never underfund | ## Verification ```bash # after seed deploy make swarm-local # or ./scripts/localnet-trading-swarm.sh -- --stats make swarm-launch # short soak then make swarm-stop # logs: tax sell extra-debit, buy split, router trader, no Mint cd packages/localnet-trading-swarm && npm run test:run make test-swarm-liquidity make verify-issue-293 # gem hub OE-1 still pool_only ```
PlasticDigits commented 2026-08-24 05:54:09 +00:00 (Migrated from gitlab.com)

marked as related to #620

marked as related to #620
PlasticDigits commented 2026-08-24 05:54:09 +00:00 (Migrated from gitlab.com)

marked as related to #119

marked as related to #119
PlasticDigits commented 2026-08-24 05:54:10 +00:00 (Migrated from gitlab.com)

marked as related to #293

marked as related to #293
PlasticDigits commented 2026-08-24 05:54:11 +00:00 (Migrated from gitlab.com)

marked as related to #592

marked as related to #592
PlasticDigits commented 2026-08-24 05:54:11 +00:00 (Migrated from gitlab.com)

marked as related to #607

marked as related to #607
PlasticDigits commented 2026-08-24 05:54:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #620

mentioned in issue #620
PlasticDigits commented 2026-08-24 05:54:34 +00:00 (Migrated from gitlab.com)

marked as related to #622

marked as related to #622
PlasticDigits commented 2026-08-24 05:54:35 +00:00 (Migrated from gitlab.com)

marked as related to #623

marked as related to #623
PlasticDigits commented 2026-08-24 06:19:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #624

mentioned in issue #624
PlasticDigits commented 2026-08-24 06:31:31 +00:00 (Migrated from gitlab.com)

mentioned in commit 68db92b778

mentioned in commit 68db92b778c583a90873d60e2bc5e4e4139d71aa
PlasticDigits commented 2026-08-24 06:31:43 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1133

mentioned in merge request !1133
PlasticDigits commented 2026-08-24 06:58:00 +00:00 (Migrated from gitlab.com)

mentioned in commit 3ca5f62887

mentioned in commit 3ca5f62887785f8b7adb311db47103ab4b74e7a1
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-24 06:58:01 +00:00
PlasticDigits commented 2026-08-24 06:59:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 77df08f4c2

mentioned in commit 77df08f4c242c1785794eed4f7bd073fed0ceebf
PlasticDigits commented 2026-08-24 07:00:29 +00:00 (Migrated from gitlab.com)

mentioned in commit 5f683cfe9f

mentioned in commit 5f683cfe9f4c69438d1f3c7760a8e61ffb22e800
PlasticDigits commented 2026-08-24 07:02:06 +00:00 (Migrated from gitlab.com)

Merge note (!1133)

Merged to main as !1133 (3ca5f628). Did not wait for CI; GitLab jobs failed with ci_quota_exceeded (not a code defect).

Sanity on this tip

make verify-issue-621 9/9 on merged main (01cd541b): docs, TS/Python static + units (50 TS / 20 Python), and live --dry-run against the running LocalTerra. Dry-run emitted kind: swarm_tax with taxWorkers: true and saw the #620 seed pair terra1zmh0…cp2t8.

Remaining (do not reopen this ticket)

Acceptance that still needs a clean #620 seed deploy (this VM’s factory also lists leftover ephemeral tax pairs from #623):

  • Live 2–3 min TS soak: tax_listed sell extra-debit / buy split / router trader / provide 1:1 / limit 1:1 / tax_hybrid_skip
  • make swarm-launch then make swarm-stop — tax-0 txs; gem swap/LP skip tax; SWARM_TAX_WORKERS=0 exclude-only
  • make verify-issue-293 — gem hub OE-1 still pool_only (do not add tax/EMBER)

Tracked on the new post-merge issue for !1133–!1135.

## Merge note (!1133) Merged to `main` as [!1133](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/415) (`3ca5f628`). Did not wait for CI; GitLab jobs failed with `ci_quota_exceeded` (not a code defect). ### Sanity on this tip `make verify-issue-621` **9/9** on merged `main` (`01cd541b`): docs, TS/Python static + units (50 TS / 20 Python), and live `--dry-run` against the running LocalTerra. Dry-run emitted `kind: swarm_tax` with `taxWorkers: true` and saw the #620 seed pair `terra1zmh0…cp2t8`. ### Remaining (do not reopen this ticket) Acceptance that still needs a **clean #620 seed deploy** (this VM’s factory also lists leftover ephemeral tax pairs from #623): - Live 2–3 min TS soak: `tax_listed` sell extra-debit / buy split / router `trader` / provide 1:1 / limit 1:1 / `tax_hybrid_skip` - `make swarm-launch` then `make swarm-stop` — `tax-0` txs; gem swap/LP skip tax; `SWARM_TAX_WORKERS=0` exclude-only - `make verify-issue-293` — gem hub OE-1 still `pool_only` (do not add tax/EMBER) Tracked on the new post-merge issue for !1133–!1135.
PlasticDigits commented 2026-08-24 07:02:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #622

mentioned in issue #622
PlasticDigits commented 2026-08-24 07:02:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #623

mentioned in issue #623
PlasticDigits commented 2026-08-24 07:02:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #625

mentioned in issue #625
PlasticDigits commented 2026-08-24 07:02:37 +00:00 (Migrated from gitlab.com)

marked as related to #625

marked as related to #625
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#621
No description provided.