feat(create-token): SKU initialization fields and retail percent tax inputs #605

Closed
opened 2026-08-23 06:48:27 +00:00 by PlasticDigits · 36 comments
PlasticDigits commented 2026-08-23 06:48:27 +00:00 (Migrated from gitlab.com)

Create Token #593 · template #592 · invoices #595 · identity/wallet defaults (sibling issue, same Create Token surface). Do not wait on #603 migrate-adopt.

Live routes: /token/create, /token/:addr/manage. There is no /token/migrate page.


Current codebase

Paid SKUs are checkboxes on create. Only Minting reveals an extra field (optional raw mint cap). Every other SKU is a boolean in features[] with no init payload, even though the launcher and token already accept most of those fields.

SKU (retail label) JSON id On-chain init already exists? UI today
Wallet-to-wallet tax transfer_tax CreateTokenMsg.transfer_bps Checkbox only. Manage has a bps field once unlocked.
Split treasury split_router CreateTokenMsg.sinks (SinkKind + bps, max 4, must sum 10000) Checkbox only. Manage has no sink editor.
Auto liquidity auto_v2_lp Launcher autolp_threshold, autolp_lp_recipient; token autolp Checkbox only. Launcher computes then discards AutoLP instantiate (let _ = (code_id, autolp_init)). Manage shows Skim only if cfg.autolp is bound.
Extra exemptions exemption_directory No instantiate list. Settings batch add_exempt / remove_exempt after create. Checkbox only. Manage: single “Add exemption” when unlocked.
Change rates later variable_rates Immutable max_buy_bps / max_sell_bps / max_transfer_bps (combined ≤ 2500, C593-13) Checkbox only. Caps auto-filled by instantiateTaxCaps (slack split). User cannot set max %.
Launch guards launch_guards LaunchGuardsConfig { max_wallet, cooldown_blocks, trading_enabled }. If SKU on and field omitted, contract defaults trading_enabled: true, cooldown 0, no max wallet. Checkbox only. Manage has no launch-guard editor.
Minting mint_control mint: { minter, cap } Optional raw cap. Create-only (C593-5 / T592-6).

Tax inputs are labeled Buy tax (bps) / Sell tax (bps) (and manage Buy bps / Sell bps). parseTaxBps requires a whole number of basis points, max 2500. Most retail users do not know bps; 100 is read as “100%” and then either bricks trading or hits the 25% cap error.

CreateTokenHookArgs has transferBps? but create page never sets it, and has no sinks / launch_guards / autolp_* / initial_exempt / max-% fields.

On-chain money is still u16 bps. This issue is display + parse + wire-up, not a denom change.


Why this is needed

Paying 50 UST1 per SKU with no way to set the SKU’s initial parameters means:

  • Transfer tax instantiates at transfer_bps = 0 until a second 50 UST1 settings batch.
  • Split treasury SKU on with empty sinks → tax still 100% to treasury (SKU paid, no split).
  • AutoLP SKU charged while the sister contract is never instantiated (user paid for a no-op).
  • Launch guards SKU on with default trading enabled — the opposite of a launch lock.
  • Variable-rates slack is an implementation detail, not a user-chosen ceiling.

BPS labels cause dangerous mis-entry (10 bps vs 10%). Percent with 2 decimal places maps 1:1 to bps (2.50% = 250 bps).


Constraints / guardrails

  1. On-chain unit stays bps. UI percent → bps = round(percent × 100) with exactly 2 decimal places (0.01% = 1 bp). Reject 3+ fraction digits (2.501). Reject > 25.00 per leg when that would exceed MAX_TAX_BPS / combined cap.
  2. Combined cap unchanged (C593-13 / T592): max_buy + max_sell + max_transfer ≤ 2500. Current buy+sell+transfer likewise. Never default each max to 2500.
  3. SKU payload only when that SKU is selected. Unchecking a SKU drops its fields from the hook (do not send transfer_bps / sinks / launch_guards for unpaid SKUs). Contract already errors if SKU data is present without the feature.
  4. Do not mix Enable Feature into Save (T592-4). Create inits go in create_token. Post-create changes stay 50 UST1 settings batch (or AutoLP sister UpdateConfig after bind).
  5. AutoLP: do not silently discard user inputs. Either (a) finish the stubbed launcher reply instantiate when autolp_code_id is set (pair may be None at create — pair usually does not exist yet), bind cfg.autolp on the token, or (b) refuse the AutoLP SKU at create with copy to unlock + bind on Manage. Fail closed if AutoLP SKU is selected and autolp_code_id is unset (do not take 50 UST1 for a no-op). SkimToLp stays permissionless and is never called from Transfer/Send (T592-10). Pair must be factory-listed before skim is useful; do not invent pair FoT math (H-01 / T592-1).
  6. Exemptions: add optional initial_exempt: Vec<String> on token InstantiateMsg + launcher CreateTokenMsg, only if exemption_directory is in features. Cap length (recommend ≤ 20). addr_validate each. Reject protocol addresses (self token, factory, router, AutoLP) — those are PROTOCOL_EXEMPT and cannot be removed (T592-9). Manager cannot remove protocol entries later.
  7. Launch guards: when SKU selected, require explicit trading_enabled (do not silently default true). Recommend UI default trading_enabled: false (anti-snipe). max_wallet optional human amount → raw via decimals. cooldown_blocks ≥ 0 integer. T592-11: trading_enabled=false blocks both buy and sell; sell to a listed pair still bypasses max_wallet.
  8. Split sinks: 1–4 rows; kinds Treasury / Burn / AutoLp / Wallet; Wallet requires bech32; shares are percents summing to 100.00% → 10000 bps. AutoLp sink does not instantiate AutoLP by itself (needs AutoLP SKU + bind).
  9. Variable rates: when SKU on, show Max buy % / Max sell % / Max transfer % (2 dp), each ≥ the corresponding current rate, combined max ≤ 25.00%. Caps are immutable after instantiate — copy must say so. When SKU off, hide max fields; encoder uses current rates as caps (instantiateTaxCaps locked path).
  10. Mint cap: if Minting SKU on, cap is human (not raw), optional; convert with decimals. Minter defaults to manager. Still create-only.
  11. Manage page uses the same percent parser for buy/sell/transfer and must grow editors for sinks, launch guards, AutoLP bind (pair / threshold / recipient) when those SKUs are on. Placeholders show current on-chain value in percent, not bps.
  12. PayWithAnyToken still builds the hook — do not fork Swap/router on these pages (C593-3). Payee from env/token, never ?payee=.
  13. Retail labels stay (Minting, Wallet-to-wallet tax, …). No raw SKU JSON in headings. Copy: tax is not the DEX swap fee.
  14. New token wasm may be required for initial_exempt (and AutoLP bind). Same ops as the identity issue: do not migrate every 11611 token; rotate launcher token_code_id after #589 GO.

Relevant files

Path Role
CreateTokenPage.tsx SKU-gated fields + percent tax
ManageTokenPage.tsx Same percent UX + missing editors
communityTaxSku.ts Replace/extend parseTaxBps with parseTaxPercent
communityTaxInvoice.ts Hook fields: sinks, launch_guards, transferBps, autolp, exempt, max_*
communityTaxToken.ts Free-create msg must not include paid SKU payloads (C593-12)
community-tax-token/src/msg.rs InstantiateMsg, Sink, LaunchGuardsConfig, MAX_SINKS
community-tax-token/src/contract.rs Instantiate sinks / guards / new initial_exempt
community-tax-token/src/invoice.rs validate_sinks (sum 10000)
community-token-launcher/src/msg.rs CreateTokenMsg
community-token-launcher/src/contract.rs AutoLP reply stub (~L150–167, reply)
community-tax-autolp/src/msg.rs Sister init
docs/frontend.md · docs/contracts-terraclassic.md Invariants
Skills AGENTS_FRONTEND_CREATE_TOKEN.md, AGENTS_COMMUNITY_TAX_CW20.md

  1. parseTaxPercent(raw) → { ok, bps } or error. Accept 0, 0.00, 2.5, 2.50, 25, 25.00. Reject 2.501, 25.01 (if over cap), abc, 10% trailing junk (or strip a single trailing % if you want — pick one and test it). Empty → 0.
  2. Labels: Buy tax (%), Sell tax (%), Wallet-to-wallet tax (%). Helper: “Up to 25.00% combined.” Never show “bps” on retail fields (devs can keep bps in hook JSON / tests).
  3. SKU panels: uncheck hides + clears draft so stale sinks cannot leak into a later paid hook.
  4. Wire buildCreateTokenHook / free-create: transfer_bps, sinks, launch_guards, initial_exempt, autolp_threshold, autolp_lp_recipient, explicit max_*_bps.
  5. AutoLP: complete launcher reply instantiate or hard-block the SKU; never charge for a discarded init.
  6. Manage: percent fields; sink editor; launch-guard editor; AutoLP bind (listed pair + threshold + recipient) via settings batch + sister UpdateConfig as already designed.

Acceptance criteria

  • Create + Manage tax fields are percent, 2 dp, converted to bps in the hook. Combined cap 25.00% still enforced.
  • Selecting Wallet-to-wallet tax shows validated transfer %; hook includes transfer_bps. Unchecked → omitted.
  • Selecting Split treasury shows 1–4 sink rows (kind, optional wallet, %); percents sum 100.00%; hook sinks with bps sum 10000. Unchecked → omitted.
  • Selecting Auto liquidity shows threshold (human) + LP recipient (default connected wallet). Create either instantiates+binds AutoLP or refuses the SKU; it must not take 50 UST1 and drop fields.
  • Selecting Extra exemptions shows a validated address list (cap); instantiate writes MANAGER_EXEMPT. Protocol addrs rejected.
  • Selecting Change rates later shows max buy/sell/(transfer) % ≥ current, combined ≤ 25.00%; copy that caps cannot rise later without this SKU (and cannot exceed instantiate max even with SKU).
  • Selecting Launch guards shows max wallet (optional human), cooldown blocks, trading on/off (default off). Hook includes launch_guards. Default-on trading without a control is a fail.
  • Minting cap is human-scale when that SKU is on.
  • Free create (0 SKU) still cannot include paid payloads (C593-12).
  • Manage Save still 50 UST1 flat to the token; Enable Feature still 50 UST1 to launcher; no SKU+settings mix.
  • Docs/skills + make verify-issue-593 green + new make verify-issue-<iid>.

Test plan (all paths)

ID Path Expect
P1 Percent 0 / 0.00 / empty 0 bps
P2 2.5 / 2.50 250 bps
P3 25.00 buy, 0 sell, 0 transfer OK (2500); 25.01 fail
P4 2.501, 10.1.0, abc, 1e2 Reject
P5 Buy 10.00% + sell 10.00% + transfer 10.00% Combined 30% → reject (cap 25%)
P6 transfer_tax on, transfer 1.00% Hook transfer_bps: 100, feature present
P7 transfer_tax off, leftover draft 1% Not in hook
P8 Two sinks 70.00% + 30.00% bps 7000+3000; instantiate OK
P9 Sinks 50+50+1 or sum 99.99% Reject
P10 Wallet sink missing addr / bad bech32 Reject
P11 5 sinks Reject (MAX_SINKS)
P12 AutoLP SKU + autolp_code_id set Sister instantiated in create tx or documented Manage-only path; GetConfig.autolp set if (a)
P13 AutoLP SKU + code_id unset Create blocked; no invoice
P14 Exempt list 1 valid EOA Instantiate get_exemptions.manager contains it
P15 Exempt = factory / token / router Reject
P16 Variable rates: max buy 10%, current buy 5% max_buy_bps 1000, buy_bps 500
P17 Variable rates: max < current Reject
P18 Launch guards: trading off, cooldown 10, max wallet human Stored; buys/sells revert until enabled (T592-11)
P19 Manage percent placeholder from buy_bps: 100 Shows 1.00 not 100
P20 Manage sinks / guards / AutoLP bind round-trip settings batch LCD config matches; invoice 50 UST1 once
P21 0-SKU free create No features, no SKU fields; launcher CreateToken execute
P22 Paid create invoice amount Still N × 50 UST1

Attack / abuse test plan

ID Vector Expect
A1 UI 10% encoded as 10 bps (off-by-100) Tests must assert 2.50 → 250 bps; snapshot hook
A2 UI 10% intended as 10 bps by power user Retail is percent-only; no hidden bps mode
A3 Hook transfer_bps without transfer_tax SKU Instantiate / launcher reject; invoice reverted
A4 Sinks sum 10000 but SKU off Reject or ignore only if contract already ignores — prefer reject
A5 AutoLP SKU, discarded sister, 50 UST1 kept Fail — must not keep fee for no-op (T592-4 spirit)
A6 SkimToLp from token transfer Still forbidden (T592-10)
A7 AutoLP pair = unlisted / other token Bind / skim fail closed; no FoT
A8 Initial exempt = pair then RegisterListedPair Protocol exempt wins; manager cannot remove protocol (T592-9)
A9 Exempt directory used as blacklist Template has no blacklist (T592-8); only skip-tax. Do not add pause/blacklist APIs
A10 Variable rates SKU off, hook max_buy_bps: 2500 with buy_bps: 0 Caps must follow locked instantiateTaxCaps (max = current), not a hidden 25% headroom
A11 Launch guards SKU, omit config, trading defaults on Fail this issue — require explicit flag; prefer default off
A12 max_wallet in human vs raw mismatch (6 vs 18 dp) Convert with create decimals; fuzz 6 and 18
A13 Combined max 2500+2500+2500 via crafted JSON Contract CombinedTaxCap (C593-13)
A14 Settings batch + Enable Feature same hook Reject (T592-4)
A15 ?payee= / ?sink= query Ignored (C593-10)
A16 Sink Wallet = victim EOA, 100% Allowed (manager chooses treasury split); helper not-connected; no wasm-admin change
A17 Percent parser overflow / 999999999.99 Reject before u16 wrap
A18 Floating 0.1 + 0.2 percent sum Use integer bps (×100) so 50.00+50.00 = 10000 exactly

Verification criteria

make verify-issue-593
make verify-issue-592
# after impl:
make verify-issue-<this-iid>
cd smartcontracts && cargo test -p cl8y-community-tax-token -p cl8y-community-token-launcher -p cl8y-community-tax-autolp
bash scripts/with-node.sh --cwd frontend-dapp -- npm test -- --run \
  src/pages/CreateTokenPage.test.tsx \
  src/pages/ManageTokenPage.test.tsx \
  src/utils/communityTaxSku.test.ts \
  src/utils/communityTaxInvoice.test.ts
  • Vitest: percent ↔ bps matrix; SKU field presence/absence; sink sum; combined cap.
  • Multitest: transfer_bps / sinks / launch_guards / initial_exempt at instantiate; AutoLP no-op SKU cannot keep fee.
  • docs/frontend.md + skills: percent UX, SKU init table, AutoLP create rule.
  • No pair/router FoT math. No 8654 whitelist. No migrate importer (#603).
## Parent / related Create Token [#593](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/593) · template [#592](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/592) · invoices [#595](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/595) · identity/wallet defaults (sibling issue, same Create Token surface). Do **not** wait on [#603](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/603) migrate-adopt. Live routes: **`/token/create`**, **`/token/:addr/manage`**. There is no `/token/migrate` page. --- ## Current codebase Paid SKUs are checkboxes on create. **Only Minting** reveals an extra field (optional **raw** mint cap). Every other SKU is a boolean in `features[]` with **no init payload**, even though the launcher and token already accept most of those fields. | SKU (retail label) | JSON id | On-chain init already exists? | UI today | |--------------------|---------|-------------------------------|----------| | Wallet-to-wallet tax | `transfer_tax` | `CreateTokenMsg.transfer_bps` | Checkbox only. Manage has a **bps** field once unlocked. | | Split treasury | `split_router` | `CreateTokenMsg.sinks` (`SinkKind` + `bps`, max 4, must sum **10000**) | Checkbox only. Manage has **no** sink editor. | | Auto liquidity | `auto_v2_lp` | Launcher `autolp_threshold`, `autolp_lp_recipient`; token `autolp` | Checkbox only. Launcher **computes then discards** AutoLP instantiate (`let _ = (code_id, autolp_init)`). Manage shows Skim only if `cfg.autolp` is bound. | | Extra exemptions | `exemption_directory` | **No** instantiate list. Settings batch `add_exempt` / `remove_exempt` after create. | Checkbox only. Manage: single “Add exemption” when unlocked. | | Change rates later | `variable_rates` | Immutable `max_buy_bps` / `max_sell_bps` / `max_transfer_bps` (combined ≤ **2500**, **C593-13**) | Checkbox only. Caps auto-filled by `instantiateTaxCaps` (slack split). User cannot set max %. | | Launch guards | `launch_guards` | `LaunchGuardsConfig { max_wallet, cooldown_blocks, trading_enabled }`. If SKU on and field omitted, contract defaults **`trading_enabled: true`**, cooldown 0, no max wallet. | Checkbox only. Manage has **no** launch-guard editor. | | Minting | `mint_control` | `mint: { minter, cap }` | Optional **raw** cap. Create-only (**C593-5** / **T592-6**). | Tax inputs are labeled **Buy tax (bps)** / **Sell tax (bps)** (and manage **Buy bps** / **Sell bps**). [`parseTaxBps`](frontend-dapp/src/utils/communityTaxSku.ts) requires a **whole number** of basis points, max 2500. Most retail users do not know bps; `100` is read as “100%” and then either bricks trading or hits the 25% cap error. [`CreateTokenHookArgs`](frontend-dapp/src/utils/communityTaxInvoice.ts) has `transferBps?` but create page never sets it, and has **no** `sinks` / `launch_guards` / `autolp_*` / `initial_exempt` / max-% fields. On-chain money is still **`u16` bps**. This issue is display + parse + wire-up, not a denom change. --- ## Why this is needed Paying 50 UST1 per SKU with no way to set the SKU’s initial parameters means: - Transfer tax instantiates at `transfer_bps = 0` until a **second** 50 UST1 settings batch. - Split treasury SKU on with empty sinks → tax still 100% to treasury (SKU paid, no split). - AutoLP SKU charged while the sister contract is **never instantiated** (user paid for a no-op). - Launch guards SKU on with default **trading enabled** — the opposite of a launch lock. - Variable-rates slack is an implementation detail, not a user-chosen ceiling. BPS labels cause dangerous mis-entry (10 bps vs 10%). Percent with **2 decimal places** maps 1:1 to bps (`2.50%` = 250 bps). --- ## Constraints / guardrails 1. **On-chain unit stays bps.** UI percent → `bps = round(percent × 100)` with **exactly 2 decimal places** (0.01% = 1 bp). Reject 3+ fraction digits (`2.501`). Reject `> 25.00` per leg when that would exceed `MAX_TAX_BPS` / combined cap. 2. **Combined cap unchanged (C593-13 / T592):** `max_buy + max_sell + max_transfer ≤ 2500`. Current buy+sell+transfer likewise. Never default each max to 2500. 3. **SKU payload only when that SKU is selected.** Unchecking a SKU **drops** its fields from the hook (do not send `transfer_bps` / `sinks` / `launch_guards` for unpaid SKUs). Contract already errors if SKU data is present without the feature. 4. **Do not mix Enable Feature into Save** (**T592-4**). Create inits go in `create_token`. Post-create changes stay 50 UST1 settings batch (or AutoLP sister `UpdateConfig` after bind). 5. **AutoLP:** do **not** silently discard user inputs. Either (a) finish the stubbed launcher **reply** instantiate when `autolp_code_id` is set (pair may be `None` at create — pair usually does not exist yet), bind `cfg.autolp` on the token, or (b) refuse the AutoLP SKU at create with copy to unlock + bind on Manage. **Fail closed** if AutoLP SKU is selected and `autolp_code_id` is unset (do not take 50 UST1 for a no-op). `SkimToLp` stays permissionless and is **never** called from `Transfer`/`Send` (**T592-10**). Pair must be factory-listed before skim is useful; do not invent pair FoT math (**H-01** / **T592-1**). 6. **Exemptions:** add optional `initial_exempt: Vec<String>` on token `InstantiateMsg` + launcher `CreateTokenMsg`, **only** if `exemption_directory` is in `features`. Cap length (recommend ≤ 20). `addr_validate` each. Reject protocol addresses (self token, factory, router, AutoLP) — those are `PROTOCOL_EXEMPT` and cannot be removed (**T592-9**). Manager cannot remove protocol entries later. 7. **Launch guards:** when SKU selected, **require explicit** `trading_enabled` (do not silently default true). Recommend UI default **`trading_enabled: false`** (anti-snipe). `max_wallet` optional human amount → raw via decimals. `cooldown_blocks` ≥ 0 integer. **T592-11:** `trading_enabled=false` blocks **both** buy and sell; sell to a listed pair still bypasses `max_wallet`. 8. **Split sinks:** 1–4 rows; kinds Treasury / Burn / AutoLp / Wallet; Wallet requires bech32; **shares are percents summing to 100.00%** → 10000 bps. AutoLp sink does not instantiate AutoLP by itself (needs AutoLP SKU + bind). 9. **Variable rates:** when SKU on, show **Max buy % / Max sell % / Max transfer %** (2 dp), each ≥ the corresponding current rate, combined max ≤ 25.00%. Caps are **immutable** after instantiate — copy must say so. When SKU off, hide max fields; encoder uses current rates as caps (`instantiateTaxCaps` locked path). 10. **Mint cap:** if Minting SKU on, cap is **human** (not raw), optional; convert with decimals. Minter defaults to manager. Still create-only. 11. **Manage page** uses the **same percent parser** for buy/sell/transfer and must grow editors for sinks, launch guards, AutoLP bind (pair / threshold / recipient) when those SKUs are on. Placeholders show current on-chain value in **percent**, not bps. 12. **PayWithAnyToken** still builds the hook — do not fork Swap/router on these pages (**C593-3**). Payee from env/token, never `?payee=`. 13. Retail labels stay (**Minting**, **Wallet-to-wallet tax**, …). No raw SKU JSON in headings. Copy: tax is not the DEX swap fee. 14. New token wasm may be required for `initial_exempt` (and AutoLP bind). Same ops as the identity issue: do not migrate every 11611 token; rotate launcher `token_code_id` after #589 GO. --- ## Relevant files | Path | Role | |------|------| | [`CreateTokenPage.tsx`](frontend-dapp/src/pages/CreateTokenPage.tsx) | SKU-gated fields + percent tax | | [`ManageTokenPage.tsx`](frontend-dapp/src/pages/ManageTokenPage.tsx) | Same percent UX + missing editors | | [`communityTaxSku.ts`](frontend-dapp/src/utils/communityTaxSku.ts) | Replace/extend `parseTaxBps` with `parseTaxPercent` | | [`communityTaxInvoice.ts`](frontend-dapp/src/utils/communityTaxInvoice.ts) | Hook fields: sinks, launch_guards, transferBps, autolp, exempt, max_* | | [`communityTaxToken.ts`](frontend-dapp/src/services/terraclassic/communityTaxToken.ts) | Free-create msg must not include paid SKU payloads (**C593-12**) | | [`community-tax-token/src/msg.rs`](smartcontracts/contracts/community-tax-token/src/msg.rs) | `InstantiateMsg`, `Sink`, `LaunchGuardsConfig`, `MAX_SINKS` | | [`community-tax-token/src/contract.rs`](smartcontracts/contracts/community-tax-token/src/contract.rs) | Instantiate sinks / guards / new initial_exempt | | [`community-tax-token/src/invoice.rs`](smartcontracts/contracts/community-tax-token/src/invoice.rs) | `validate_sinks` (sum 10000) | | [`community-token-launcher/src/msg.rs`](smartcontracts/contracts/community-token-launcher/src/msg.rs) | `CreateTokenMsg` | | [`community-token-launcher/src/contract.rs`](smartcontracts/contracts/community-token-launcher/src/contract.rs) | AutoLP reply stub (~L150–167, `reply`) | | [`community-tax-autolp/src/msg.rs`](smartcontracts/contracts/community-tax-autolp/src/msg.rs) | Sister init | | [`docs/frontend.md`](docs/frontend.md) · [`docs/contracts-terraclassic.md`](docs/contracts-terraclassic.md) | Invariants | | Skills | [`AGENTS_FRONTEND_CREATE_TOKEN.md`](skills/AGENTS_FRONTEND_CREATE_TOKEN.md), [`AGENTS_COMMUNITY_TAX_CW20.md`](skills/AGENTS_COMMUNITY_TAX_CW20.md) | --- ## Recommended direction 1. **`parseTaxPercent(raw)`** → `{ ok, bps }` or error. Accept `0`, `0.00`, `2.5`, `2.50`, `25`, `25.00`. Reject `2.501`, `25.01` (if over cap), `abc`, `10%` trailing junk (or strip a single trailing `%` if you want — pick one and test it). Empty → 0. 2. Labels: **Buy tax (%)**, **Sell tax (%)**, **Wallet-to-wallet tax (%)**. Helper: “Up to 25.00% combined.” Never show “bps” on retail fields (devs can keep bps in hook JSON / tests). 3. SKU panels: uncheck hides + clears draft so stale sinks cannot leak into a later paid hook. 4. Wire `buildCreateTokenHook` / free-create: `transfer_bps`, `sinks`, `launch_guards`, `initial_exempt`, `autolp_threshold`, `autolp_lp_recipient`, explicit `max_*_bps`. 5. AutoLP: complete launcher reply instantiate **or** hard-block the SKU; never charge for a discarded init. 6. Manage: percent fields; sink editor; launch-guard editor; AutoLP bind (listed pair + threshold + recipient) via settings batch + sister `UpdateConfig` as already designed. --- ## Acceptance criteria - [ ] Create + Manage tax fields are **percent, 2 dp**, converted to bps in the hook. Combined cap 25.00% still enforced. - [ ] Selecting **Wallet-to-wallet tax** shows validated transfer %; hook includes `transfer_bps`. Unchecked → omitted. - [ ] Selecting **Split treasury** shows 1–4 sink rows (kind, optional wallet, %); percents sum 100.00%; hook `sinks` with bps sum 10000. Unchecked → omitted. - [ ] Selecting **Auto liquidity** shows threshold (human) + LP recipient (default connected wallet). Create either instantiates+binds AutoLP or **refuses** the SKU; it must not take 50 UST1 and drop fields. - [ ] Selecting **Extra exemptions** shows a validated address list (cap); instantiate writes `MANAGER_EXEMPT`. Protocol addrs rejected. - [ ] Selecting **Change rates later** shows max buy/sell/(transfer) % ≥ current, combined ≤ 25.00%; copy that caps cannot rise later without this SKU (and cannot exceed instantiate max even with SKU). - [ ] Selecting **Launch guards** shows max wallet (optional human), cooldown blocks, trading on/off (default **off**). Hook includes `launch_guards`. Default-on trading without a control is a fail. - [ ] Minting cap is human-scale when that SKU is on. - [ ] Free create (0 SKU) still cannot include paid payloads (**C593-12**). - [ ] Manage Save still **50 UST1 flat** to the token; Enable Feature still 50 UST1 to launcher; no SKU+settings mix. - [ ] Docs/skills + `make verify-issue-593` green + new `make verify-issue-<iid>`. --- ## Test plan (all paths) | ID | Path | Expect | |----|------|--------| | **P1** | Percent `0` / `0.00` / empty | 0 bps | | **P2** | `2.5` / `2.50` | 250 bps | | **P3** | `25.00` buy, 0 sell, 0 transfer | OK (2500); `25.01` fail | | **P4** | `2.501`, `10.1.0`, `abc`, `1e2` | Reject | | **P5** | Buy 10.00% + sell 10.00% + transfer 10.00% | Combined 30% → reject (cap 25%) | | **P6** | `transfer_tax` on, transfer 1.00% | Hook `transfer_bps: 100`, feature present | | **P7** | `transfer_tax` off, leftover draft 1% | Not in hook | | **P8** | Two sinks 70.00% + 30.00% | `bps` 7000+3000; instantiate OK | | **P9** | Sinks 50+50+1 or sum 99.99% | Reject | | **P10** | Wallet sink missing addr / bad bech32 | Reject | | **P11** | 5 sinks | Reject (`MAX_SINKS`) | | **P12** | AutoLP SKU + `autolp_code_id` set | Sister instantiated in create tx **or** documented Manage-only path; `GetConfig.autolp` set if (a) | | **P13** | AutoLP SKU + code_id unset | Create blocked; no invoice | | **P14** | Exempt list 1 valid EOA | Instantiate `get_exemptions.manager` contains it | | **P15** | Exempt = factory / token / router | Reject | | **P16** | Variable rates: max buy 10%, current buy 5% | `max_buy_bps` 1000, `buy_bps` 500 | | **P17** | Variable rates: max &lt; current | Reject | | **P18** | Launch guards: trading off, cooldown 10, max wallet human | Stored; buys/sells revert until enabled (**T592-11**) | | **P19** | Manage percent placeholder from `buy_bps: 100` | Shows `1.00` not `100` | | **P20** | Manage sinks / guards / AutoLP bind round-trip settings batch | LCD config matches; invoice 50 UST1 once | | **P21** | 0-SKU free create | No `features`, no SKU fields; launcher `CreateToken` execute | | **P22** | Paid create invoice amount | Still `N × 50` UST1 | --- ## Attack / abuse test plan | ID | Vector | Expect | |----|--------|--------| | **A1** | UI 10% encoded as 10 bps (off-by-100) | Tests must assert `2.50` → **250** bps; snapshot hook | | **A2** | UI 10% intended as 10 bps by power user | Retail is percent-only; no hidden bps mode | | **A3** | Hook `transfer_bps` without `transfer_tax` SKU | Instantiate / launcher reject; invoice reverted | | **A4** | Sinks sum 10000 but SKU off | Reject or ignore only if contract already ignores — **prefer reject** | | **A5** | AutoLP SKU, discarded sister, 50 UST1 kept | **Fail** — must not keep fee for no-op (**T592-4** spirit) | | **A6** | `SkimToLp` from token transfer | Still forbidden (**T592-10**) | | **A7** | AutoLP pair = unlisted / other token | Bind / skim fail closed; no FoT | | **A8** | Initial exempt = pair then `RegisterListedPair` | Protocol exempt wins; manager cannot remove protocol (**T592-9**) | | **A9** | Exempt directory used as blacklist | Template has **no** blacklist (**T592-8**); only skip-tax. Do not add pause/blacklist APIs | | **A10** | Variable rates SKU off, hook `max_buy_bps: 2500` with `buy_bps: 0` | Caps must follow locked `instantiateTaxCaps` (max = current), not a hidden 25% headroom | | **A11** | Launch guards SKU, omit config, trading defaults **on** | **Fail** this issue — require explicit flag; prefer default off | | **A12** | `max_wallet` in human vs raw mismatch (6 vs 18 dp) | Convert with create decimals; fuzz 6 and 18 | | **A13** | Combined max 2500+2500+2500 via crafted JSON | Contract `CombinedTaxCap` (**C593-13**) | | **A14** | Settings batch + Enable Feature same hook | Reject (**T592-4**) | | **A15** | `?payee=` / `?sink=` query | Ignored (**C593-10**) | | **A16** | Sink Wallet = victim EOA, 100% | Allowed (manager chooses treasury split); helper not-connected; no wasm-admin change | | **A17** | Percent parser overflow / `999999999.99` | Reject before `u16` wrap | | **A18** | Floating `0.1 + 0.2` percent sum | Use integer bps (×100) so 50.00+50.00 = 10000 exactly | --- ## Verification criteria ```bash make verify-issue-593 make verify-issue-592 # after impl: make verify-issue-<this-iid> cd smartcontracts && cargo test -p cl8y-community-tax-token -p cl8y-community-token-launcher -p cl8y-community-tax-autolp bash scripts/with-node.sh --cwd frontend-dapp -- npm test -- --run \ src/pages/CreateTokenPage.test.tsx \ src/pages/ManageTokenPage.test.tsx \ src/utils/communityTaxSku.test.ts \ src/utils/communityTaxInvoice.test.ts ``` - [ ] Vitest: percent ↔ bps matrix; SKU field presence/absence; sink sum; combined cap. - [ ] Multitest: transfer_bps / sinks / launch_guards / initial_exempt at instantiate; AutoLP no-op SKU cannot keep fee. - [ ] `docs/frontend.md` + skills: percent UX, SKU init table, AutoLP create rule. - [ ] No pair/router FoT math. No 8654 whitelist. No migrate importer (#603).
PlasticDigits commented 2026-08-23 06:48:28 +00:00 (Migrated from gitlab.com)

marked as related to #592

marked as related to #592
PlasticDigits commented 2026-08-23 06:48:29 +00:00 (Migrated from gitlab.com)

marked as related to #593

marked as related to #593
PlasticDigits commented 2026-08-23 06:48:29 +00:00 (Migrated from gitlab.com)

marked as related to #595

marked as related to #595
PlasticDigits commented 2026-08-23 06:48:30 +00:00 (Migrated from gitlab.com)

marked as related to #601

marked as related to #601
PlasticDigits commented 2026-08-23 06:48:31 +00:00 (Migrated from gitlab.com)

marked as related to #604

marked as related to #604
PlasticDigits commented 2026-08-23 06:48:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #604

mentioned in issue #604
PlasticDigits commented 2026-08-23 06:48:40 +00:00 (Migrated from gitlab.com)

Depends on identity/wallet defaults in #604 for shared create-form validators (decimals, name/symbol, connected-wallet treasury/manager helpers). Percent tax fields should reuse the same page; do not fork a second wizard.

Depends on identity/wallet defaults in #604 for shared create-form validators (decimals, name/symbol, connected-wallet treasury/manager helpers). Percent tax fields should reuse the same page; do not fork a second wizard.
PlasticDigits commented 2026-08-23 11:46:36 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1121

mentioned in merge request !1121
PlasticDigits commented 2026-08-23 11:49:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #606

mentioned in issue #606
PlasticDigits commented 2026-08-23 11:49:47 +00:00 (Migrated from gitlab.com)

marked as related to #606

marked as related to #606
PlasticDigits commented 2026-08-23 11:49:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #608

mentioned in issue #608
PlasticDigits commented 2026-08-23 11:49:54 +00:00 (Migrated from gitlab.com)

marked as related to #608

marked as related to #608
PlasticDigits commented 2026-08-23 11:49:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #609

mentioned in issue #609
PlasticDigits commented 2026-08-23 11:49:57 +00:00 (Migrated from gitlab.com)

marked as related to #609

marked as related to #609
PlasticDigits commented 2026-08-23 11:50:00 +00:00 (Migrated from gitlab.com)

marked as related to #610

marked as related to #610
PlasticDigits commented 2026-08-23 11:50:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #610

mentioned in issue #610
PlasticDigits commented 2026-08-23 11:50:07 +00:00 (Migrated from gitlab.com)

Audit INTERNAL_KIMIK3_1787468843 notes (approved 2026-08-23)

Do not open a second AutoLP / VariableRates ticket. Fold these into this issue.

H-1 — AutoV2Lp: documented “bind later” has no bind API

Same-tx AutoLP instantiate is out of v1 (AGENTS_COMMUNITY_TAX_CW20.md, docs/contracts-terraclassic.md, REGISTRY). The audit’s new fact: there is no later execute that sets cfg.autolp. apply_autolp_settings errors AutoLP contract not bound; enable AutoLp via launcher forever.

Launcher create_token still builds AutolpInit then let _ = (code_id, autolp_init). Mainnet launcher has autolp_code_id=11613, so the SKU is charged for vapor.

This issue must either refuse auto_v2_lp at create until a bind path exists, or add instantiate+bind (same tx or a later manager/launcher bind) before charging 50 UST1. PoC: poc_autov2lp_paid_but_never_bound (invert when closed).

AutoLP hardening (factory-listed pair + token as one side + skim floor) is a sibling issue, gated on this bind.

M-1 — VariableRates: implement only if needed, else remove

require_variable_or_free_profile is a no-op. Free-profile tokens can raise buy/sell to instantiate max_* via a 50 UST1 settings batch without the SKU. Frontend copy “Adjust buy/sell after launch (still capped)” is true without paying. instantiateTaxCaps only widens caps when the checkbox is on — the user could have typed those max_* anyway.

Direction: implement a real gate only if product still wants a paid “change rates later” SKU (e.g. require the SKU to raise above instantiate rates, or to widen caps after create). Otherwise remove the SKU from the dApp catalog and stop charging 50 UST1 for theater. Do not leave the no-op.

PoC: poc_variable_rates_sku_is_theater.

L-3 (missing Manage Token launch-guard / sink / AutoLP editors) was already in this issue’s body.

## Audit INTERNAL_KIMIK3_1787468843 notes (approved 2026-08-23) Do **not** open a second AutoLP / VariableRates ticket. Fold these into this issue. ### H-1 — AutoV2Lp: documented “bind later” has no bind API Same-tx AutoLP instantiate is **out of v1** (`AGENTS_COMMUNITY_TAX_CW20.md`, `docs/contracts-terraclassic.md`, REGISTRY). The audit’s new fact: there is **no later execute** that sets `cfg.autolp`. `apply_autolp_settings` errors `AutoLP contract not bound; enable AutoLp via launcher` forever. Launcher `create_token` still builds `AutolpInit` then `let _ = (code_id, autolp_init)`. Mainnet launcher has `autolp_code_id=11613`, so the SKU is charged for vapor. **This issue must either** refuse `auto_v2_lp` at create until a bind path exists, **or** add instantiate+bind (same tx or a later manager/launcher bind) before charging 50 UST1. PoC: `poc_autov2lp_paid_but_never_bound` (invert when closed). AutoLP **hardening** (factory-listed pair + token as one side + skim floor) is a **sibling** issue, gated on this bind. ### M-1 — VariableRates: implement only if needed, else remove `require_variable_or_free_profile` is a no-op. Free-profile tokens can raise buy/sell to instantiate `max_*` via a 50 UST1 settings batch without the SKU. Frontend copy “Adjust buy/sell after launch (still capped)” is true **without** paying. `instantiateTaxCaps` only widens caps when the checkbox is on — the user could have typed those `max_*` anyway. **Direction:** implement a real gate **only if** product still wants a paid “change rates later” SKU (e.g. require the SKU to raise above instantiate rates, or to widen caps after create). Otherwise **remove** the SKU from the dApp catalog and stop charging 50 UST1 for theater. Do not leave the no-op. PoC: `poc_variable_rates_sku_is_theater`. L-3 (missing Manage Token launch-guard / sink / AutoLP editors) was already in this issue’s body.
PlasticDigits commented 2026-08-23 11:51:56 +00:00 (Migrated from gitlab.com)

mentioned in commit fe47dd8c60

mentioned in commit fe47dd8c60132caf0241258a1bb67e9845eb8833
PlasticDigits commented 2026-08-23 11:52:15 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1122

mentioned in merge request !1122
PlasticDigits commented 2026-08-23 11:52:36 +00:00 (Migrated from gitlab.com)

Implemented in !1122 (depends on #604 identity/wallet helpers on the same wizard).

Acceptance

  • Create + Manage tax fields are percent, 2 dp, converted to bps. Combined cap 25.00%
  • Wallet-to-wallet tax shows transfer %; hook includes transfer_bps. Unchecked → omitted
  • Split treasury shows 1–4 sink rows; percents sum 100.00%; hook sinks bps sum 10000. Unchecked → omitted
  • Auto liquidity shows threshold + LP recipient. Create instantiates+binds when autolp_code_id is set; unset refuses the SKU (no 50 UST1 no-op)
  • Extra exemptions: validated list (cap 20); instantiate writes manager exempt. Protocol addrs rejected
  • Change rates later: max buy/sell/transfer % ≥ current, combined ≤ 25.00%; immutable-cap copy
  • Launch guards: max wallet (human), cooldown, trading on/off default off. Hook includes launch_guards. Silent default-on is a fail (contract requires explicit config)
  • Minting cap is human-scale when that SKU is on
  • Free create (0 SKU) cannot include paid payloads (C593-12)
  • Manage Save still 50 UST1 flat to the token; Enable Feature still 50 UST1 to launcher
  • Docs/skills + make verify-issue-593 green + make verify-issue-605

Verification

  • Vitest: percent ↔ bps matrix; SKU field presence/absence; sink sum; combined cap
  • Multitest: transfer_bps / sinks / launch_guards / initial_exempt; AutoLP SKU without code_id cannot keep fee
  • Docs + skills: percent UX, SKU init, AutoLP create rule
  • No pair/router FoT math. No 8654 whitelist. No migrate importer (#603)

Not in this ticket

  • P20 live LCD Manage sinks/guards/AutoLP settings-batch round-trip (UI editors are present; no chain smoke)
  • Columbus-5 store of new token wasm / launcher rotate / #589 GO (same ops as #604)
  • Browser E2E of the new SKU panels
Implemented in !1122 (depends on #604 identity/wallet helpers on the same wizard). ## Acceptance - [x] Create + Manage tax fields are percent, 2 dp, converted to bps. Combined cap 25.00% - [x] Wallet-to-wallet tax shows transfer %; hook includes `transfer_bps`. Unchecked → omitted - [x] Split treasury shows 1–4 sink rows; percents sum 100.00%; hook sinks bps sum 10000. Unchecked → omitted - [x] Auto liquidity shows threshold + LP recipient. Create instantiates+binds when `autolp_code_id` is set; unset refuses the SKU (no 50 UST1 no-op) - [x] Extra exemptions: validated list (cap 20); instantiate writes manager exempt. Protocol addrs rejected - [x] Change rates later: max buy/sell/transfer % ≥ current, combined ≤ 25.00%; immutable-cap copy - [x] Launch guards: max wallet (human), cooldown, trading on/off default **off**. Hook includes `launch_guards`. Silent default-on is a fail (contract requires explicit config) - [x] Minting cap is human-scale when that SKU is on - [x] Free create (0 SKU) cannot include paid payloads (C593-12) - [x] Manage Save still 50 UST1 flat to the token; Enable Feature still 50 UST1 to launcher - [x] Docs/skills + `make verify-issue-593` green + `make verify-issue-605` ## Verification - [x] Vitest: percent ↔ bps matrix; SKU field presence/absence; sink sum; combined cap - [x] Multitest: transfer_bps / sinks / launch_guards / initial_exempt; AutoLP SKU without code_id cannot keep fee - [x] Docs + skills: percent UX, SKU init, AutoLP create rule - [x] No pair/router FoT math. No 8654 whitelist. No migrate importer (#603) ## Not in this ticket - P20 live LCD Manage sinks/guards/AutoLP settings-batch round-trip (UI editors are present; no chain smoke) - Columbus-5 store of new token wasm / launcher rotate / #589 GO (same ops as #604) - Browser E2E of the new SKU panels
PlasticDigits commented 2026-08-23 12:20:37 +00:00 (Migrated from gitlab.com)

mentioned in commit 75a0536d58

mentioned in commit 75a0536d58cc00501934bf189a56cd39e533f227
PlasticDigits commented 2026-08-23 12:20:40 +00:00 (Migrated from gitlab.com)

Follow-up on the 2026-08-23 audit note, now in !1122 (75a0536d):

  • H-1 — AutoLP instantiate+bind (or refuse if autolp_code_id unset). No 50 UST1 for a discarded sister.
  • M-1 — Implemented the paid gate (did not remove the SKU). Instantiate max_* must equal current rates without variable_rates. Settings buy_bps / sell_bps require the SKU. require_variable_or_free_profile no-op removed. Manage buy/sell locked until unlock. Invariant C605-4.

Sibling findings stay on their tickets: #606 (C-1/H-2/L-1), #608 (H-3/H-4), #609 (M-5), #610 (M-2/M-3).

Follow-up on the 2026-08-23 audit note, now in !1122 (`75a0536d`): - [x] **H-1** — AutoLP instantiate+bind (or refuse if `autolp_code_id` unset). No 50 UST1 for a discarded sister. - [x] **M-1** — Implemented the paid gate (did not remove the SKU). Instantiate `max_*` must equal current rates without `variable_rates`. Settings `buy_bps` / `sell_bps` require the SKU. `require_variable_or_free_profile` no-op removed. Manage buy/sell locked until unlock. Invariant **C605-4**. Sibling findings stay on their tickets: #606 (C-1/H-2/L-1), #608 (H-3/H-4), #609 (M-5), #610 (M-2/M-3).
PlasticDigits commented 2026-08-23 12:32:53 +00:00 (Migrated from gitlab.com)

mentioned in commit 715d9555bd

mentioned in commit 715d9555bd01f1007f37a459bd255e0c9916afbb
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-23 12:32:53 +00:00
PlasticDigits commented 2026-08-23 12:34:08 +00:00 (Migrated from gitlab.com)

mentioned in commit 33e4153f87

mentioned in commit 33e4153f87ef075614781734950f0cfa211695c2
PlasticDigits commented 2026-08-23 12:35:52 +00:00 (Migrated from gitlab.com)

mentioned in commit 784ac9e1cd

mentioned in commit 784ac9e1cd4e97419b74e3c3e041a78a5b097318
PlasticDigits commented 2026-08-23 12:35:52 +00:00 (Migrated from gitlab.com)

mentioned in commit 709a2694d5

mentioned in commit 709a2694d562d32dd2d6cff7dbfc8c3b307010dc
PlasticDigits commented 2026-08-23 12:40:21 +00:00 (Migrated from gitlab.com)

mentioned in commit fe2a71a5d6

mentioned in commit fe2a71a5d6d8db8a4e66a44b3c7a17eef96f9a13
PlasticDigits commented 2026-08-23 12:41:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #611

mentioned in issue #611
PlasticDigits commented 2026-08-23 12:41:00 +00:00 (Migrated from gitlab.com)

marked as related to #611

marked as related to #611
PlasticDigits commented 2026-08-23 12:41:12 +00:00 (Migrated from gitlab.com)

Merge !1122 — in-repo #605 / H-1 / M-1 passed (make verify-issue-605 green): percent taxes, SKU init payloads, AutoLP instantiate+bind or refuse, VariableRates gate.

Merge integration (fixed on !1123/!1124, now on main): audit_poc fixtures still used max_* headroom and expected AutoLP never-bound; those PoCs were inverted to match this MR. LaunchGuards tests that assumed instantiate trading_enabled=true now call enable_launch_guards.

Ops leftover: LocalTerra store + Manage settings-batch LCD round-trip, and new wasm rotate — #611. AutoLP pair/skim stays #610. EnableFeature/SKU dedupe stays #606.

**Merge !1122** — in-repo #605 / H-1 / M-1 passed (`make verify-issue-605` green): percent taxes, SKU init payloads, AutoLP instantiate+bind or refuse, VariableRates gate. **Merge integration (fixed on !1123/!1124, now on main):** `audit_poc` fixtures still used max_* headroom and expected AutoLP never-bound; those PoCs were inverted to match this MR. LaunchGuards tests that assumed instantiate `trading_enabled=true` now call `enable_launch_guards`. **Ops leftover:** LocalTerra store + Manage settings-batch LCD round-trip, and new wasm rotate — #611. AutoLP pair/skim stays #610. EnableFeature/SKU dedupe stays #606.
PlasticDigits commented 2026-08-24 03:01:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #616

mentioned in issue #616
PlasticDigits commented 2026-08-24 04:02:55 +00:00 (Migrated from gitlab.com)

mentioned in commit 0e70c6b7fa

mentioned in commit 0e70c6b7fa86b892ccb36ac6fe4d8df91030d082
PlasticDigits commented 2026-08-24 05:53:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #620

mentioned in issue #620
PlasticDigits commented 2026-08-24 11:45:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #603

mentioned in issue #603
PlasticDigits commented 2026-08-26 04:19:23 +00:00 (Migrated from gitlab.com)

mentioned in issue #669

mentioned in issue #669
PlasticDigits commented 2026-08-26 04:19:25 +00:00 (Migrated from gitlab.com)

marked as related to #669

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