Skip to content
Cosmopediaby Unity Nodes
DiscussionsConversationLuggom — a Cosmos SDK app-chain where you mine LUG by paying real taxes (public testnet)Forum ↗

Luggom — a Cosmos SDK app-chain where you mine LUG by paying real taxes (public testnet)

Conversation26 posts360 views15 likesLast activity 6d ago
LU
LugoVaneOP
19d ago 1

Hi everyone, ``` We just opened the public testnet of Luggom, a Cosmos SDK app-chain with one job: turn a real tax payment into money you own, proven ``` end to end with TLSNotary. ``` No KYC. No third parties. The bank session never leaves the user's device. ## Why another chain? Because Cosmos is at its best when an app-chain brings a new kind of user. Luggom brings the taxpayer. There is no subsidy and no token sale. The inflow is a real payment that already leaves people's accounts every year. The chain's job ``` is to prove it happened and mint against it. ``` ## How it works 1. The user pays a real tax at their bank, as always. 2. TLSNotary proves the paid receipt without exposing the session. 3. The notary signs the attestation; the chain verifies it and mints. 4. Each receipt mines exactly once (payment_id + proof_commitment anti-replay). The desktop app (Tauri/Rust) builds the proof request and signs locally. The seed never leaves the device. ``` What is live ``` - chain-id: `luggom-test` - RPC: `https://rpc.luggom.com` - LCD: `https://lcd.luggom.com` - API: `https://api.luggom.com` - Notary: `wss://notary.luggom.com/session` - Full nodes: open (p2p…

Excerpt (1195 of 3463 characters). Read the whole post on the forum ↗

ZE
Zealynx
19d ago 1

Nice to see the inflow come from real payments instead of emissions. I had a read through x/luggom, and three things stood out around mining and anti-replay: • `miner_address` isn’t part of the signed attestation. `BuildAttestationPayload` covers the proof commitment, payment, entity, date, tax nature, payer and provider, but not who receives the mint. The mempool is public, so anyone who sees a `MsgMineTaxPayment` (or anything sitting between the notary and the user) can resend the same attestation from their own address. Whoever lands first gets the LUG, and the real payer’s tx then fails with `ErrProofAlreadyUsed`. Adding the miner address to the payload closes it. • The domain is `luggom-attestation/v3` with no chain-id in it. If the notary key carries over from `luggom-test` to mainnet, every receipt already mined on testnet (like the first Viacredi one) can be mined again on mainnet, because `UsedDocumentHashes` and `UsedPaymentIDs` start empty there and the 365 day window is still open. Putting the chain-id in the payload, or using a fresh notary key for mainnet, fixes it. • `brlUsdRate` and `cadUsdRate` are hardcoded at 5.0 and 1.37. That’s fine on a testnet,…

Excerpt (1196 of 1420 characters). Read the whole post on the forum ↗

LU
LugoVane
19d ago

Thanks Zealynx — all three hold up against the code, and they’re the right things to be looking at. 1- miner_address outside the digest. Correct. BuildAttestationPayload signs nine fields and the recipient isn’t one of them, so anyone who sees the MsgMineTaxPayment can resubmit the same attestation to their own address; whoever lands first gets the LUG and the payer gets ErrProofAlreadyUsed. We’re raising the domain to luggom-attestation/v4 with miner_address and chain_id inside the signed payload. The miner address enters the attestation at session registration — the wallet tells the notary who will receive the mint before the proof is made — so it’s covered by the notary’s signature, not supplied afterwards by the API. chain-id. Same change closes it. v4 carries the chain id and the keeper reconstructs it from ctx.ChainID(), so a luggom-test receipt can’t be replayed on mainnet even if the notary key were carried over. We’ll also rotate the notary key at mainnet as defence in depth. • brlUsdRate / cadUsdRate. Agreed, and it’s at the top of the oracle work, not a cosmetic default. Those are minting parameters: any gap to market is over- or under-minting. The plan is…

Excerpt (1197 of 2830 characters). Read the whole post on the forum ↗

ZE
Zealynx
19d ago 1

Good to hear, and binding the miner at session registration is the clean way to do it.

On payment-date vs mining-date, I’d go with the payment date, and the current code shows why. A receipt can be mined up to 365 days after the payment, and the USD/LUG parity (the CHF/EUR/GBP legs and the basket multiplier) is read at mining time; once BRL comes from PTAX, the BRL/USD rate will be too. So every receipt becomes a free option for a year: wait for a good FX day, and mine before the anniversary of the chain start, because the multiplier steps 10% there and the same receipt mints about 9% less LUG one block later. Pricing the whole conversion at the attested payment date takes that away, and the mint then depends on the payment, not on when it’s submitted. It means keeping one oracle snapshot per day on chain so the keeper can look that day up.

On the banks I can’t help, we don’t have sanitised US or Canadian receipts. And I’ll leave the basket design itself to people who do monetary design. The minting path is the part I’d keep reviewing.

LU
LugoVane
19d ago

Hey Zealynx — seriously, thank you. That conversation was worth far more than the three findings, and the option framing changed the design. All three are addressed: Attestation v4. luggom-attestation/v4 is live: miner_address and chain_id are inside the signed payload, and the miner is declared by the wallet at session registration, so the mempool or the API can no longer redirect a mint or replay it on another chain. Commit 81a8e37; the two notary files are public now (bd4d27f). Receipt privacy. payment_id (the settlement id) and payer_tax_id (CNPJ/EIN/BN) are hashed before they reach the chain (0ffaeb9). The government that received the tax stays public — the mine event emits tax_entity_id next to the amount. Validated on the testnet: tx 5D9B60EC…, height 21091, code 0. Payment date vs mining date. You were right, and the option argument is why we changed it. Minting is priced entirely at the attested date: a per-UTC-day oracle snapshot (basket legs, plus BRL/CAD once the feeds land), the multiplier computed at that instant, and a 7-day lookback so a Saturday payment resolves to the last published rate. Tests: the same receipt mined on different days mints the…

Excerpt (1196 of 1942 characters). Read the whole post on the forum ↗

ZE
Zealynx
19d ago

Nice, v4 and the hashing look right.

On weekends I’d carry the last published rate, it’s how PTAX works anyway (only published on business days, the previous one applies). Two details: key each snapshot by the date of the fixing, not the day the keeper wrote it, so a late or backfilled write can’t change what a receipt already resolves to. And fail closed when the 7 day lookback finds nothing, instead of reaching further back.

On future dates I’d reject them explicitly, in the keeper. With a 7 day lookback a payment_date a few days ahead doesn’t fail closed, it resolves to today’s snapshot. At 0ffaeb9 the max age check in sacrifice_tax_payment.go only bounds the past, so a future date passes on chain; the only future check I found is in api/reclaimverify, with a one day margin, and none on the taxproof path.

One more on the minting path: taxproof.go still stamps PaymentDate with now for BR receipts where no date is extracted. Once pricing moves to the payment date, that fallback brings back the option you just removed, since the prover picks when to attest. I’d fail closed for BR too, like US and CA.

LE
lesnikutsa
18d ago 1

Hi Lugo,

I’m representing UTSA, an independent blockchain validator and infrastructure provider operating across 25+ networks, primarily Cosmos SDK, Gno, and EVM ecosystems.

We run our own infrastructure across multiple locations and provide public RPC/API endpoints, monitoring, and technical guides.

We have also developed our own multichain explorer from scratch:

https://explorer.utsa.tech

We’d be interested in joining the Luggom public testnet as an independent validator and helping improve network decentralization.

Could you please clarify:

  • Are you currently accepting new external validators?
  • Can you provide the required 200 testnet LUG for validator registration?
  • Which release should new validators use, given the recent protocol fixes?
  • Is there a planned path for reliable testnet validators to participate in mainnet?

We’re happy to contribute infrastructure and provide technical feedback as the network develops.

Best regards,
UTSA Validator

LU
LugoVane
18d ago

Thanks Carlos — all four points hit the mark, and three of them actually changed our design, not just the code. Here is what just shipped (commit 747bd45, live on testnet now): • The fixing date now comes from the message instead of the keeper’s clock. `MsgUpdateUsdOracleBasket` carries the `rate_date`, the daily snapshot is keyed by it, and each day can only be written once—any duplicate write gets rejected. Days covered by the compiled history reject writes too, ensuring the on-chain record can never disagree with the actual price used. To price receipts older than the upgrade, 366 days of published fixings are embedded directly into the binary, with their SHA-256 pinned in a test. No runtime fetches, so two nodes can’t price the same receipt differently. We actually went a step further than just keying by the fixing date. A receipt dated `D` resolves to the last fixing strictly before `D` (`D-1` down to `D-7`, never `D`). The reason is the flip side of your point: the fixing for `D` can only be written during `D`, meaning including `D` would make the price depend on whether the miner submitted before or after the feeder ran. With a strict cut, the candidate set for `D`…

Excerpt (1197 of 3634 characters). Read the whole post on the forum ↗

LU
LugoVane
18d ago

Hi UTSA, Thanks for reaching out — and for the detail on your setup. Yes on all four, with the caveats spelled out • New external validators: yes. The testnet is open to outside validators and we are actively onboarding. It is invite-based only in the practical sense that the operator funds the bond, not because the set is closed. • The 200 testnet LUG: yes. The operator sends 200 LUG plus a margin for the fee. Post your luggom1… address and your intended moniker in the Luggom Telegram group — the invite is in the project README — and we will send it. Worth being precise about what this is: the 200 LUG is transferred to your account and you sign create-validator with your own key. The bond stays in your account, so slashing during the testnet is yours to manage. There is no custody on our side. Two practical warnings, because these are the two ways this usually goes wrong: • Sync first. Wait for catching_up: false and for your height to track the network before sending create-validator. A validator that bonds while it is still catching up starts missing blocks immediately and gets jailed. • Send the account address, luggom1… (luggomd keys show -a), not the…

Excerpt (1197 of 3159 characters). Read the whole post on the forum ↗

ZE
Zealynx
18d ago 1

Good call on the strict cut, it also takes the race between the miner and the feeder out of the price.

One thing write-once makes more important: the BRL and CAD legs are only checked against a fixed band, [0.50, 20.00] in validateOraclePerUsdLeg. A BRL/USD of 0.50 passes and converts a BRL receipt at about 10x. The day can’t be rewritten, and receipts dated the next day always resolve to it, so they keep minting at that rate for the whole 365 day window. The feeder’s median covers one bad source, not a bad write from the updater key or a bug in the feeder. A bound against the previous fixing in the keeper would contain it: reject a move of more than a few percent, and those receipts fall back to the fixing before.

On the notary logs: the fix stops new leaks, but what’s already in journald from the testnet sessions still holds payer names and tax IDs, so I’d vacuum the journal on that host too.

LU
LugoVane
17d ago

Hi… Zealynx You’re right on both counts — and the first was the sharper one: the median protects against a bad source, not against a bad write that reaches the chain. Since the day is write-once, one bad write prices every receipt for 365 days. Different threat from the one the feeder solves. It’s on chain now (commit 75be7ee, live since height ~46.3k). The keeper refuses a fixing that moves more than 10% per elapsed day from the previous one, capped at 50%, on all five legs. The limit scales with the gap, so a feeder that was down for three days can still catch up, and the previous fixing is resolved read-only (embedded history first, then store), so it works from the first post-table day. A refused write leaves the day empty and mining falls back to the earlier fixing — fail closed, never a mispriced mint. Tests pin the case you described: brl_usd 5.0 → 0.50 is rejected even though it sits inside the old absolute band, and a 10× move on a basket leg is rejected too. Honest limitation: this stops the catastrophic write, not a slow drift — a compromised key can still walk 10% a day, but it has to do it visibly, over weeks. On the journal: done before your message…

Excerpt (1195 of 2528 characters). Read the whole post on the forum ↗

ZE
Zealynx
17d ago 1

Nice, that was fast. One thing on the new bound: it’s measured from the previous fixing even when that fixing is the bad one, so it also caps the way back. A key that wins the daily write four days in a row can walk brl_usd 5.0 → 4.5 → 4.05 → 3.65 → 3.29, each step inside the 10%. The true 5.0 is then 52% above 3.29, over the 50% cap, so every honest write after that is refused, gov’s included since the check runs for every signer. The next seven payment days price at 3.29 through the lookback, then mining pauses until someone writes in-between values by hand, or until 30 days pass and the guard stops finding a previous fixing, at which point the next write goes through with no bound at all. Having the feeder do that itself, writing the bounded step toward the median when the median is out of range instead of nothing, walks a bad fixing back at the speed it can be walked in. On the attestation budget in c5ba84f: notary_payees is an ordinary param and x/gov runs a proposal’s messages in order, so one proposal can add an address with MsgUpdateParams and pay it the whole notary reserve with MsgSpendFromReserve in the same execution. A delay before a new payee can receive, or a per…

Excerpt (1198 of 1280 characters). Read the whole post on the forum ↗

LU
LugoVane
16d ago

Both land, and the first one bit exactly as you described. What we did: Walking a bad fixing back. The feeder now reads the previous fixing and, when the median is out of range, writes the largest allowed step towards it instead of nothing. One thing your sketch would have hit: prev × 1.10 rounds at 18 decimals and can land one ulp above the limit, so the keeper refuses it and the walk-back stalls on an identical value forever. We nudge the step down by up to 4 ulp before submitting, and if it still does not fit we submit prev (a zero move is always accepted). There is a test over the corner cases, including one from the embedded table that reproduces the 1-ulp case. Reading the previous fixing is now a query (query luggom oracle-fixing [day], with source: history|store), which also fixes the audit gap: the on-chain fixing was not readable before. A read failure is fail-closed now — the feeder does not submit without the bound. Note the honest edge: after 30 days with no write there is no previous fixing to compare against, so the first write is unbounded; the absolute bands still apply. The payee hole. A payee must be listed for 7 days before it can receive, and the…

Excerpt (1197 of 2115 characters). Read the whole post on the forum ↗

ZE
Zealynx
16d ago

Nice catch on the 1 ulp case, that one would have stalled without anyone noticing.

On your question, I wouldn’t add the absolute cap. The fractional one already scales with whatever the reserve holds, and the window is what bounds the speed. The part I’d pin down is the delay against the voting period. 7 days works because voting_period on luggom-test is 2 days, so a removal can pass before a new payee turns eligible. If mainnet ever runs a voting period longer than 7 days, a spend proposal can be queued to end right when the payee turns eligible, and a removal submitted after the add passes can’t execute first. Deriving the delay from x/gov’s voting period at spend time, with 7 days as the floor, keeps that true whatever gov sets.

On the 30 day edge, keeping the last written fixing in its own item would give the bound an anchor forever, and after a long gap the 50% cap is already the right limit.

LU
LugoVane
15d ago

Thanks — and agreed on the cap: the fractional one scales with whatever the reserve holds, and the window is what bounds the speed. No
absolute cap

Both suggestions go in:

  • Last fixing in its own item. That kills the 30-day hole: the anchor always exists, and after a long gap the 50% cap is already the
    right limit. We will fall back to the current lookup for the first write after the upgrade, so no migration is needed.
  • Delay derived from the voting period. Reading x/gov’s voting_period at spend time, with 7 days as the floor. We are planning max(7d, 2
    × voting_period) — one period plus deposit and execution felt too tight to me. Would you read it as 2×, or is there a smaller factor
    you would be comfortable with?
ZE
Zealynx
15d ago

I’d size it from the deposit period rather than as a multiple. A removal only starts voting once it reaches min_deposit, and x/gov runs a passed proposal in the EndBlock where its vote ends, so execution adds nothing on top. The worst case is max_deposit_period + voting_period, plus a day or so for someone to notice the add and submit. On luggom-test both are 2 days, so 2× voting_period lands on exactly that, but it stops covering it if gov ever raises the deposit period on its own. Reading both at spend time, max(7d, max_deposit_period + voting_period + 1d), stays right either way.

An expedited removal can be quicker (1 day at 66.7% on luggom-test), but if it misses that threshold it becomes a normal proposal whose vote ends a full voting_period after it started, so I’d still size for the normal path.

LU
LugoVane
14d ago 1

Deployed — tag testnet-2026-10-27 (commit 298b619), live on the testnet since height ~98k.

Both of your points are in: the fixing anchor is permanent now (a bad fixing can always be walked back, even after a long gap, and the
50% cap is the limit once the gap is long), and the notary delay is derived at spend time as max(7d, max_deposit_period + voting_period

  • 1d) — your formula, not my multiple.

Thanks again for the two rounds; the protocol is materially better for them. We’ll keep shipping — looking forward to your next pass
whenever you have the time.

LU
LugoVane
14d ago 1

One more, since it may matter for your review: we are porting the miner to Windows — same proof path (Firefox, the extension, the local
CA), so the trust model does not change, only the platform.

On rails: a second Brazilian bank is being mapped so we can define its canonical field, and we are collecting receipt structures for the
US and Canada — field names and formats only, never documents — to add a rail. One county lead so far (Hennepin, which is already
whitelisted).

If you have a view on how a US rail should define its canonical field — in many cases the receipt lives on a county portal rather than
in a bank session, so the provider model is not the same — I would take it.

ZE
Zealynx
14d ago 1

Nice, glad both landed in testnet-2026-10-27. On a US rail’s canonical field, two things I’d settle before the first county goes live: • Scope the id to its issuer. A PIX E2E is unique everywhere because it carries the bank’s ISPB, but a county receipt or confirmation number is only unique inside that one portal. The chain checks UsedPaymentIDs on payment_id alone, and HashPaymentID hashes just the raw number, so if two counties ever issue the same one, the second real payer gets ErrPaymentIDAlreadyUsed. Hashing the tax entity slug together with the number (us:county:hennepin_mn plus the receipt) fixes that. I’d also accept a single page per rail: card payments on county portals usually go through a processor whose confirmation number isn’t the county’s receipt number, and if both pages are ever accepted, one payment mines twice. • Make sure only the payer can produce it. In a bank session only the account holder sees the receipt. County parcel pages are usually public (Hennepin’s property search shows any parcel’s taxes without a login), so if the rail ever attests a parcel lookup that shows the tax as paid, anyone can notarize someone else’s payment and mint before the…

Excerpt (1199 of 1704 characters). Read the whole post on the forum ↗

LU
LugoVane
13d ago 2

Thanks Zealynx You’re right on all three, and two of them are gates, not nits. Here’s what we plan to change; tell us if this is how you’d do it before we write code. • Scope the payment id to its issuer — agreed, as a per-rail rule. Confirmed in code: HashPaymentID hashes only the raw id (sha256(UPPER(TRIM(raw)))) and UsedPaymentIDs is keyed by that alone, so two counties issuing the same number would make the second legitimate payer fail with ErrPaymentIDAlreadyUsed (1203). No double mint, but a real liveness bug. Plan: the canonical id becomes sha256(tax_entity_slug || “|” || canonical_id) for rails whose id is portal-scoped, exactly as you suggest. … One deliberate difference: we want to apply it per rail, not globally. BRL keeps sha256(E2E) — the ISPB already makes it globally unique — and only portal-scoped rails get the entity in the preimage. Reason: changing the preimage globally would make already-mined receipts mineable again (their old hash is on chain, the new hash is different and passes the uniqueness check). Per-rail we don’t reopen any history. Agree? We’re also taking “one page per rail” literally: the rail spec pins exactly ONE canonical…

Excerpt (1198 of 3414 characters). Read the whole post on the forum ↗

CR
cryptogemfunds
12d ago 1
LugoVane:

https://lcd.luggom.com

explorer.nodesafe-app.xyz

NodeSafe Dashboard

Ping Dashboard is a block explorer/web wallet for blockchains built on Cosmos SDK, Cosmoshub, Osmosis, Juno, Evmos, Injective, Canto and 70+ blockchains listed on ping.pub

i i had joined,

ZE
Zealynx
12d ago 1

Yes to (a), and your reason is the one that matters: changing the preimage for every rail would give each receipt that already mined a new hash that passes UsedPaymentIDs. US rails have no history yet, so they start scoped. One thing to settle per rail: the receipt number is all UsedPaymentIDs sees, so if a county later reformats it (drops dashes or leading zeros) or moves to a portal vendor that renumbers old payments, a payment that already mined comes back with a new hash. I’d normalize the id per rail and keep parcel, tax year and installment as a second key, with a rule for how partial payments count against it. On (b), authenticated and re-issuable is better than reflective, but authenticated isn’t the same as payer-specific. On some county portals any registered user can add any parcel to their account and see whether it’s paid, so a logged-in page can still show someone else’s payment. I’d write it as “a page that lists payments made from that login”, and check each county with a second account: if it can add the parcel and see it paid, or open the first account’s receipt detail by its URL, the page doesn’t qualify. On settled: for e-checks, cleared isn’t final either.…

Excerpt (1199 of 1502 characters). Read the whole post on the forum ↗

LU
LugoVane
12d ago

The spec is updated with all three of your refinements — per-rail normalisation plus the
(parcel, tax year, installment) key, “a page that lists the payments made from that login” with the
second-account test, and 60-day ACH seasoning (chargeback window per processor for cards).

Implementation lands with the first US rail; the second key needs its own upgrade since it’s new
consensus state. Thanks — this closed three real gaps.

QX
qxnico
7d ago 1

Welcome aboard! As you build the chain, if you have questions or doubts about the Stack while building the chain, please always feel free to reach out here, or directly.

On the product side, are you looking to create a verified taxpayer type of proof, or more focused on using that minted tax payment as credits that the municipalities can offer? (Lugano like!)

LU
LugoVane
7d ago

Thanks — good question, and neither is exactly right, which is the interesting part. We’re building a verified-taxpayer proof, not a municipal credit. The municipality is only the registered recipient on-chain (tax_entity_id): it issues nothing, backs nothing, and can’t freeze anything. The user proves they already paid a real tax — TLSNotary attestation, no PII on-chain — and the chain mints LUG against that payment at the basket price. So the tax payment is the minting event, not a voucher: proof of a sacrifice that already happened. The taxpayer keeps a share of it as LUG (30% in the current split) — a non-confiscable asset they can hold, spend with businesses, or stake. One precision on language: it is not a stablecoin. No peg, no issuer, no redemption. The design target is that LUG orbits the basket — above and below, never pegged to it: the basket is the emission cost, the vault sells at that same price (a ceiling), and mining becomes much less attractive far below it (a soft floor). The market still sets the price. Where we are: the public testnet has been live since 21 October, with real mining on a Brazilian bank (Banco do Brasil, block 144.732, code 0).…

Excerpt (1199 of 2266 characters). Read the whole post on the forum ↗

QX
qxnico
6d ago

I can chat to the product points (I am part of the team at Cosmos Labs, so I cannot help with the validator/developer side other than recommendations ) LugoVane: We’re building a verified-taxpayer proof, not a municipal credit. The municipality is only the registered recipient on-chain (tax_entity_id): it issues nothing, backs nothing, and can’t freeze anything. The user proves they already paid a real tax — TLSNotary attestation, no PII on-chain — and the chain mints LUG against that payment at the basket price. So the tax payment is the minting event, not a voucher: proof of a sacrifice that already happened. The taxpayer keeps a share of it as LUG (30% in the current split) — a non-confiscable asset they can hold, spend with businesses, or stake. Gotcha! Seems interesting. The mention of municipality comes from what I’ve seen places like Lugano do: they incentivize people to use their payment system in exchange for an asset they can use to get rewards or discounts in their neighborhood. This could also help drive the “pay taxes” positive behavior with a similar loop. I see your point, though, regarding other uses! Very cool to hear it is being minted…

Excerpt (1197 of 1226 characters). Read the whole post on the forum ↗

← Back to Discussions