For the complete documentation index, see llms.txt. This page is also available as Markdown.

Sell/Buy/Burn Flows

All primary flows in the Accumulator system.

Seller lifecycle: from order creation (SHELL deposit) to eccUSDC payout:

Sell Shell (create sell order)

A seller creates a lot by sending ECC SHELL to the Root's receive(). The amount must correspond to exactly one denomination:

Denomination
SHELL required (nanoSHELL)

1 eccUSDC

100,000,000,000 (100 × 10⁹)

10 eccUSDC

1,000,000,000,000

100 eccUSDC

10,000,000,000,000

1000 eccUSDC

100,000,000,000,000

What happens on-chain:

  1. Root validates shellAmount % SHELL_PER_USDC == 0 and the resulting denomination is one of {1, 10, 100, 1000}.

  2. Assigns orderId = nextId[D], increments nextId[D] and available[D].

  3. Adds shellAmount to _sellerShellPool.

  4. Deploys a new ShellSellOrderLot contract with deterministic address derived from (code, root, denom, orderId).

  5. Emits SellOrderCreated twice: once to the seller's external address (for per-user subscription) and once to external address 610 (for global monitoring).

Result: the seller holds a lot contract and waits for a buyer to match it.


Buy Shell (deposit eccUSDC)

A buyer sends eccUSDC to the Root (directly or via Exchange). The amount must be in whole eccUSDC units (amount % 1_000_000 == 0).

Matching algorithm (largest-first FIFO):

Important details:

  • Matching is greedy: it takes as many lots as possible from the largest denomination first.

  • If sellers partially cover the amount, the rest is minted. The buyer always gets 100% of the SHELL.

  • The eccUSDC stays on the Root. It is tracked in _usdcBalance and becomes available for seller claims and NACKL redemption.

  • Two events are emitted: ShellPurchased (to ext addr 611) and MatchedOrders (to ext addr 617, with current soldPrefix values).

Note on buyShellFor vs receive: Both accept eccUSDC and trigger the same _processUsdcDeposit logic. The difference: receive() rejects messages carrying more than one ECC currency (require(currencies.keys().length <= 1)), while buyShellFor() only checks that eccUSDC is present. If a multi-currency message arrives via buyShellFor, the non-USDC ECC will remain on the contract.


Claim — seller collects eccUSDC

After a lot is matched (sold), the seller calls claim() on their lot contract to receive the eccUSDC payout.

1

SellOrderLot.claim()

Sets _claimed = true, then calls Root.claimUSDC(denom, orderId, owner).

2

Root.claimUSDC()

Verifies the caller's address matches the expected lot address (recomputed deterministically), checks orderId <= soldPrefix[D] (lot is sold), checks owedCount[D] > 0, and checks _usdcBalance >= owedTotal. If all pass:

  • Sends D × USDC_DECIMALS_FACTOR eccUSDC directly to the seller (not to the lot).

  • Calls SellOrderLot.onReceiveUSDC(payout) to confirm.

  • Decrements owedCount[D] and _usdcBalance.

3

SellOrderLot.onReceiveUSDC()

Verifies the amount, emits OrderDestroyed, self-destructs back to the Root.

4

If the lot is not yet sold

claimUSDC reverts (require fails), the message bounces back, and the lot's onBounce handler resets _claimed = false. The seller can try again later.

5

Double-claim protection

Double-claim protection is multi-layered: the lot checks !_claimed before calling, the Root checks owedCount > 0, and the lot self-destructs after success — so the contract ceases to exist.


Redeem (Burn-to-earn) NACKL

A NACKL holder sends ECC NACKL to the Root's receive() to burn it and claim a share of the "free reserve" — eccUSDC not owed to any seller.

Payout formula:

Where M(t) = T_KM × (1 - exp(-u_M × t)), capped at NACKL_T. t is seconds since _unixstart.

Key point: currentSupply is not M(t) — it's M(t) minus all NACKL burned to date. As more NACKL is burned, the denominator shrinks, so each subsequent burn receives a larger share of the remaining reserve. This is by design: later redeemers get proportionally more of whatever eccUSDC is left.

1

Burn the NACKL

Burn the NACKL via gosh.burnecc().

2

Compute currentSupply and check it

Compute currentSupply and check it's sufficient.

3

Compute redeemable and check it

Compute redeemable (free reserve) and check it's positive.

4

Compute payout

Compute payout = redeemable * burnAmount / currentSupply.

5

Update balances

Increment _nacklBurned, decrement _usdcBalance.

6

Send payout

Send eccUSDC to the sender.


Exchange TIP3 to eccUSDC through Exchange

The Exchange also has onTransferReceived — a callback from its TIP-3 USDC wallet. When someone sends TIP-3 USDC to the Exchange's wallet, it mints equivalent eccUSDC and sends it back to the depositor's address (not to the Accumulator). The depositor can then send it to the Accumulator directly.

Last updated