feat(community-tax): autoregister CL8Y pairs on create + Manage catch-up + manager tax skip #633
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#633
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent / related
Template #592 (T592-1, T592-7, T592-9). Create/Manage #593. ExemptionDirectory #609 (E609-1–E609-7). AutoLP factory pair #610 (M610-1). Migrate-adopt #626 (M626-10 — do not register Terraport/GDEX). LocalTerra seed already registers QATax/EMBER (#620 L620-4). Router hops #607. H-01: no pair/router FoT math.
Product (2026-08-25): every CL8Y factory pair that holds a community-tax CW20 must be
RegisterListedPair'd. The manager wallet must not pay buy / sell / transfer tax when they add LP or trade. Catch-up lives on Manage: if a managed tax token still has no registered factory pair, show a highly visible alert and one button that registers the highest-LP unregistered factory pair. Hide the alert when registration is not needed.This ticket is one product change. Shipping autoregister without the manager tax skip taxes the manager on the newly listed markets. Do not split those workstreams across merges.
Current codebase
Buy/sell tax is per pair address, not per token.
LISTED_PAIRSis written only byExecuteMsg::RegisterListedPair { pair }on the token (pair_registry.rs). The execute is permissionless and add-only. It verifies:Pair {}contract_addrmatches the argument.config.factoryPair { asset_infos }returns the same address.On success it writes
LISTED_PAIRS+PROTOCOL_EXEMPT. There is no unregister. Terraport/GDEX addrs fail the factory lookup (T592-9 / M626-10).Nothing in the retail path calls it:
CreateToken/ adopt migrateMANAGER_EXEMPT./createCreatePairPage.tsxCreatePaironly.createPairreturns a tx hash. No follow-up.execute_create_pairManageTokenPage.tsx/create. No register alert/button.registerListedPairexists but is unused.pair.rsRegisterListedPair, then storescfg.pair. Does not execute register on the token. SkimSend+Swap+ provide assumes the pair is already listed for retail tax; AutoLP itself is protocol-exempt so its own pull stays 1:1.deploy-community-tax-local.sh/#601smokeCreatePair(ops-only).IsProtocolExemptprotocolis true for registered pairs (and factory/router/AutoLP/self).manageris onlyMANAGER_EXEMPT, notconfig.manager.Classification (
tax.rs):Send+Swap→ sell extra-debit. Pair → non-exempt → buy split.TransferFromto a registered pair → Honest.Transfer/TransferFromto that pair is Transfer FoT. Pairprovide_liquiditycreditsRESERVESfrom the declared amount (pair/src/contract.rs~1711–1746). That is P2 / H-01 reserve desync (High when TransferTax is on). Crate fixture comment already says “List before seed so TransferTax SKU does not tax the pair fund.”Manager tax skip today is paid SKU only (E609-5).
config.manageris instantiate/adopt-only (SettingsBatchcannot rotate it). Extra directory wallets stay onexemption_directory+ 50 UST1. Launch guards still use economic kind (E609-2).Indexer
GET /api/v1/tokens/{addr}/pairslists factory-ingested pairs without reserves /listed/ USD TVL. Highest-LP must come from LCDPool(getPool) plus optional hub USD. CatalogGET /api/v1/community-tokens?manager=powersMyCommunityTokensPage.tsx.Swap/Trade Max uses token-level
sell_bpsandIsProtocolExempt.manager— it does not check whether the hop pair is registered.Why this is needed
Product is not “some pairs tax-off forever.” Managers create (and after migrate, catch up) CL8Y markets and expect:
Today a manager who follows Create Token → Create Pair gets a tax-off pool. Retail quotes still say “listed-pair swap.” If they later enable TransferTax and provide before registering, inbound FoT desyncs reserves. AutoLP can be bound to a factory pair that is still unregistered, so skim LP works but retail buy/sell on that market never starts. Adopted tokens with existing CL8Y pairs stay unregistered until someone runs terrad.
Constraints / guardrails
RegisterListedPairTerraport / GDEX / non-factory addrs (M626-10). FactoryPairlookup stays the gate (T592-9).already: true.register_listed_pairon both assets. Honest CW20s reject the unknown variant and would revertCreatePair. Gate on cw2 ∈ community-tax token crate name(s) (11611 / 11619 / LocalTerra store), or an equivalentGetConfigprobe that honest templates do not implement.config.managerlikeMANAGER_EXEMPTfor Buy / Sell / Transfer (and official-router hoptrader). Do not make the manager protocol-exempt (that would drop them as a cooldown subject — E609-2 / T592-11 stay on). Extra wallets still requireexemption_directory.remove_exemptto “opt into tax” in a way that breaks the product rule — skip isaddr == config.manager || MANAGER_EXEMPT. Protocol entries still cannot be removed.code_idis the community-tax pin (VITE_COMMUNITY_TAX_CODE_ID/ LocalTerra tax store). 6036 / 10184 / 8266 / 8654 must not see it.GetConfig.manager. No?payee=/?manager=/?pair=./poolfor that pair).LISTED_PAIRS/VITE_*/RegisterListedPairon the alert. Something like: this market is not collecting buy/sell tax yet; register the largest CL8Y pool.Relevant files
smartcontracts/contracts/community-tax-token/src/pair_registry.rs,tax.rs,contract.rs,msg.rs,adopt.rs,multitest.rssmartcontracts/contracts/community-tax-autolp/src/pair.rs,contract.rssmartcontracts/contracts/factory/src/contract.rsfrontend-dapp/src/pages/CreatePairPage.tsx,services/terraclassic/factory.tsfrontend-dapp/src/pages/ManageTokenPage.tsx,ManageTokenPage.test.tsxfrontend-dapp/src/services/terraclassic/communityTaxToken.ts(registerListedPair,queryCommunityTaxIsExempt)frontend-dapp/src/services/terraclassic/pair.ts(getPool),services/indexer/client.ts(getTokenPairs,getCommunityTokens,getPairStats)scripts/lib/deploy-community-tax-local.sh,scripts/qa/localterra-community-tax-smoke.shdocs/contracts-terraclassic.md§ Classification,skills/AGENTS_COMMUNITY_TAX_CW20.md,AGENTS_COMMUNITY_TAX_AUTOLP.md,AGENTS_COMMUNITY_TAX_EXEMPT.md,AGENTS_FRONTEND_CREATE_TOKEN.md,AGENTS_FRONTEND_TOKEN_MIGRATE.mdRecommended direction
A — Manager role is always tax-skip (on-chain)
In
is_manager_exempt/is_manager_directory_tax_skip/ hop-trader skip /IsProtocolExempt.manager/GetExemptions.manager: treatconfig.manageras exempt in addition toMANAGER_EXEMPT. Same Honest skip as #609. Launch guards unchanged. Instantiate/adopt do not need to writeMANAGER_EXEMPT(role is enough). Swap/Trade Max already zeros extra-debit whenmanager === true— query must start returning that for the manager without the SKU.Crate + migrate 11619 (and LocalTerra tax wasm). Keep 11611 listed until Refresh (F6); disclose 11611 instances stay pre-skip until CMM migrate.
B — Autoregister on CL8Y
CreatePairB1 (retail, ships without factory migrate). After a successful
/createtx, query factoryPair { asset_infos }. For each asset whose LCDcode_idis the community-tax pin,register_listed_pairfrom the same wallet. UX is one flow (sequential txs or a follow-up the page runs automatically). Toast only after register succeeds oralready. If register fails, show a hard error and point at Manage catch-up — do not pretend the market is listed.B2 (all creators, including terrad / other UIs). Factory instantiate-reply (or equivalent post-pair persist): cw2/code-id gate, then
WasmMsg::Executeregister_listed_pairon that asset. Fail-closed if the token is tax wasm and register fails (exceptalready). Honest assets: no execute. Requires factory migrate + docs. Do not call unknown CW20 executes.C — AutoLP bind registers
When AutoLP
pairis set (instantiate or managerUpdateConfig), also executeregister_listed_paironcfg.tokenfor the factory-returned pair addr. Same lookup as M610-1. Idempotent. Skim still never runs from tokenTransfer/Send(T592-10). Manage AutoLP pair field: after save, pair must queryIsProtocolExempt.protocol == true.D — Manage catch-up (highest LP)
On
/token/:addr/manage, when the connected wallet isGetConfig.managerand the token is the tax template:getTokenPairsand/or factory pagination + LCDPairverify). Drop non-factory.IsProtocolExempt { address: pair }(or equivalent).buy_bps/sell_bps/transfer_bpsare 0.Poolreserves. Prefer hub/oracle USD TVL when both sides price; else rank by tax-token reserve, then other-side reserve. Tie: lower pair address. Skip pairs that fail factory verify.registerListedPair. After success, refetch; if more unregistered remain, keep the alert and retarget the button at the next highest (still one button).GET /api/v1/community-tokens?manager=. If another attested tax token has an unregistered factory pair, a second line links to that token’s Manage. No extra register buttons on this page./tokensmay show a compact “needs pool registration” mark; not a substitute for the Manage alert.E — Docs / seed
Document that retail markets are listed-pair tax once registered; manager role skips tax; Terraport stays 1:1. LocalTerra seed already registers — keep that; add an assert that
IsProtocolExemptis true for the seed pair andIsProtocolExempt.manageris true for test1 as manager without ExemptionDirectory.Acceptance criteria
IsProtocolExempt.protocoltrue on the pair). Honest-only pairs unchanged (no extra execute).CreatePair(after factory migrate) autoregisters tax assets via cw2/code-id gate. Creating an honest/honest pair does not call tax executes. Creating tax/honest registers only the tax token. Creating tax/tax (if ever allowed) registers both.?pair=/?payee=prefill.config.managerskips buy, sell, and transfer tax (pair-direct and official-router hoptrader).TaxPreviewmatches execute. Pair inbound stays 1:1. Extra-debit Max is 0 for the connected manager.trading_enabled, cooldown,max_walleton user-side buys/transfers). Manager sell-to-listed-pair still bypassesmax_wallet(T592-11).Test plan — happy / functional paths
/createvs UST1 or EMBER. After the flow, LCDIsProtocolExempton the new pair is protocol true. Provide 1:1. RetailSend+Swapextra-debits whensell_bps > 0; managerSend+SwapHonest.TaxPreview. Manager provide + swap Honest.terradcreate tax/EMBER without dApp; pair is registered in the same tx (or documented factory reply). Honest/honest create unchanged.RegisterListedPairalready: true; Manage alert gone once all factory pairs are listed.auto_v2_lp, Create Pair, Manage bind pair → tokenIsProtocolExempt.protocoltrue and AutoLPGetConfig.pairset.SkimToLpstill permissionless; not called from token transfer. Floor unchanged (M610-3)./createyet: no alert (nothing to register).register_listed_paironly for tax code ids.MANAGER_EXEMPTrow; AutoLP set-pair emits token register; factory create tax asset registers / honest does not; Terraport-shaped addr rejected.Test plan — attack / abuse / hack vectors
register_listed_pair(would revert). Tax/honest must not execute on the honest side.?manager=/?pair=/?payee=must not change CTA target or invoice payee.trading_enabledsubject.remove_exemptmust not strip factory/router/pairs. Adding the pair toMANAGER_EXEMPTstays forbidden (E609-3).Swap.traderstill fail-closed (T592-13). Manager-as-trader is Honest tax, not fail-closed.route/solve(#585). Alert may still register a frozen factory pair (listing ≠ thaw).SkimToLp.SKIMMINGresidual M610-8 unchanged.Verification criteria
Close only when all of the following pass:
make deploy-localseed pair still registered; new dApp Create Pair for a second tax/EMBER (or tax/UST1) pair is registered without a hand-rolled terrad execute; manager test1 provide + swap are Honest; a second wallet pays sell extra-debit on that pair.IsProtocolExempton the new pairprotocol: true; on the managermanager: truewithout ExemptionDirectory SKU.Do not close if factory B2 is skipped without an explicit leftover ticket and dApp B1 + Manage D are green — or the inverse (factory only, no Manage catch-up for adopt leftovers).
Out of scope
mentioned in issue #634
Adopt leftovers (existing CL8Y pairs + Terraport/GDEX) are not this ticket. See #634: migrate-page venue inventory, governance-refresh instructions, and CL8Y-only register tool after pins match. This issue stays new Create Pair / AutoLP bind / Manage highest-LP / manager tax skip.
mentioned in merge request !1137
mentioned in commit
bb0194082cmentioned in issue #635
marked as related to #635
!1137 merge note (no CI wait)
Merged !1137 into
mainasbb019408(source8b55d4fd). GitLab reported no conflicts (can_be_merged); localgit merge-treeagainst currentmainalso produced a clean tree (MR was 0 behind, 1 ahead).Sanity / AC
make verify-issue-633on the source branch: 6/6 PASS (token + AutoLP + factory crates, Manage/Create Pair Vitest, R633 docs; twice).MANAGER_EXEMPT; TaxPreview matches. Live 11611/11619 still pre-skip until CMM token migrate.max_wallet/trading_enabledstill apply to manager.GitLab CI on !1137 failed with
ci_quota_exceeded(frontend/contracts/audit/gitleaks) — same quota class as !1136, not a code defect.Leftovers (do not reopen this issue)
Tracked on #635: factory migrate (LocalTerra + columbus-5), CMM token migrate for live 11611/11619 manager-role skip, LocalTerra Create Pair + Manage live checks.
Migrate-page venue inventory stays on #634.
Adopt leftovers (venue inventory, F6/Refresh copy, post-refresh CL8Y register on
/token/migrate) are implemented in #634 / !1138. This issue stays Manage + new Create Pair / AutoLP register — do not stretch Manage-only copy to cover migrate inventory.mentioned in commit
67a8bdb16cLocalTerra verify (not columbus-5)
Pushed to
mainas67a8bdb1(scripts/qa/localterra-633-autoregister.sh+VERIFY633_REQUIRE_CHAIN=1 make verify-issue-633). Fresh LocalTerra deploy (current-wasm factory — no separate leftover factory migrate on this chain). Do not reopen this issue — columbus-5 leftovers stay on #635.Ran
VERIFY633_REQUIRE_CHAIN=1 make verify-issue-633→ 7/7 PASS (token + AutoLP + factory crates, Manage/Create Pair Vitest, R633 docs, LocalTerra live).CreatePairtax/UST1 autoregistered in the same create path (no hand-rolledregister_listed_pair).CreatePairsucceeded (no blind tax execute).IsProtocolExempt.protocolon the pair.IsProtocolExempt.managertrue without ExemptionDirectory.Send+SwapHonest; third wallet (test3) sell extra-debit on the new registered pair.UpdateConfig { pair }idempotent on an already-registered pair.Checklist (issue body + comments)
deploy-localprotocol: true; managermanager: truewithout ExemptionDirectorymake verify-issue-633)make verify-issue-593/609/610/626not re-run in this session (merge-time gates)Close decision
Leave closed. LocalTerra R633 live rungs are now green on
main. Remaining body items (Manage live UI, sibling verify targets, columbus-5 migrates) stay on #635. No R633 invariant was wrong.How to re-run:
make setup-cloud-localterra(ormake deploy-localon a healthy chain), thenVERIFY633_REQUIRE_CHAIN=1 make verify-issue-633.Columbus-5 leftover verify #635 is closed (factory 11629, token 11630, AutoLP 11633, Coolify 11630). Do not reopen this issue unless an R633 invariant is wrong.
mentioned in commit
4379639cebmentioned in issue #558