FIRST USE - REVIEW BEFORE SIGNING
Joining creates one personal PDA only after the wallet reviews a live quote
Opening Market derives MarketUserState from the connected wallet and checks it through the configured RPC. If the account is missing, the trading body becomes inert and a dedicated join card appears. The card shows MarketUserState rent, the estimated validator network fee, and the total. No listing query, purchase, cancellation, account creation, or wallet popup starts merely because the panel opened.
The owner-approved join transaction creates one 64-byte MarketUser v1 PDA. Its deterministic address uses the owner and configured Market Program. The account stores layout magic and version, PDA bump, active count, owner, update slot, available blank land contracts, reserved blank land contracts, and four trailing zero bytes. It contains no trade references or page metadata.
Solana calls the lamports held by a rent-exempt account a minimum balance. In product terms it is an account-storage reserve, not revenue paid to NiceChunk. The current program does not expose a membership-close instruction, so players should treat the reserve as locked for now. The smaller network fee pays validators for processing join_market and is not recoverable. The one-percent market fee is different again and applies only when a Listing sells.
The UI obtains each number from getMinimumBalanceForRentExemption and getFeeForMessage. RPC policy and Solana rent parameters can change, so the wallet-facing quote is authoritative instead of a hardcoded documentation estimate.
Stores owner, active-listing count, update slot, and available and reserved land-contract balances.
There is no MarketHistoryPage account or storage expansion transaction.
The same account is updated when listings are created, canceled, or sold.
CONTRACTS · TREASURY ISSUANCE
Blank Land Contract is the first contract and costs exactly 10 NCK
Contracts is the first specific market category after All, and Blank Land Contract is pinned first inside it. The current treasury catalog contains this base contract: each unit grants the right to register one complete canonical 16 × 16 Chunk. Larger rectangular parcels require one contract for every Chunk, so a 2 × 3 parcel requires six contracts.
The buyer must already have MarketUser v1. A direct tag-4 purchase validates the configured NCK mint, the buyer token account, and the treasury token account, transfers exactly 10,000,000 base units per contract, and credits the available u32 balance. One purchase accepts 1 through 4,096 contracts. It creates no Listing PDA, consumes no Backpack slot, has no seller, and does not apply the one-percent Listing settlement fee.
Land creation uses a Building-program PDA authority to move the required quantity from available to reserved before BuildSite v3 is created. Each Chunk index is then registered in stable order. The final registration atomically consumes the reservation and activates the immutable land. If indexing fails, cancellation removes registered Chunk indexes in reverse order, releases the complete reservation, and closes only the incomplete BuildSite; active land cannot be resized or canceled.
The contract transfers exactly 10,000,000 base units for the six-decimal NCK mint.
Required contracts equal chunksX multiplied by chunksZ, up to 4,096 for one parcel.
A treasury contract is a balance in MarketUser, not a Listing PDA or Backpack record.
EXACT INVENTORY CUSTODY
A listing escrows one complete slot and consumes one active-listing count
A Listing PDA is derived from the seller and a little-endian u64 listing ID. Creation requires a joined seller, a new uninitialized Listing address, fewer than 50 active listings, and a transferable source. The seller also funds the Listing account's own 216-byte rent reserve; that account is distinct from membership rent.
Backpack mode copies the selected seller-owned 80-byte slot, validates it, creates the Listing, and removes the exact dense record through the Backpack Program. Equipment mode resolves one of nine public Equipment PDA records and releases its custodied slot into the Listing. Historical construction-item records are retired and rejected rather than converted into land contracts. Solana instruction atomicity restores all source state if any later step fails.
The stored source slot preserves kind, quantity, block coordinates and ID or item identity, PDA, volume, mass metadata, durability, grade, level, quality, and metadata bytes. The client reconstructs the real material, block, or forged model from those fields. Search and category controls help navigation, but settlement follows the committed slot bytes.
Version five keeps the exact 80-byte escrow slot, seller, price, source, timestamps, state, and buyer.
There is no partial quantity argument; the quantity encoded in the slot moves with it.
Land contracts exist only as MarketUser balances and cannot be listed from legacy inventory records.
SOL OR NCK SETTLEMENT
Payment, Backpack delivery, Listing state, and seller count share one atomic boundary
Purchase validates an active version-5 Listing, its seller-scoped PDA, a different signing buyer, the selected currency route, the destination Backpack, MaterialPhysics, and both MarketUserState accounts. The seller membership is writable because settlement decrements its active count. The buyer membership is read-only because purchase only verifies that the buyer joined.
SOL transfers lamports to the seller and fixed treasury. NCK uses the configured mint, six decimals, validated token accounts, and transfer_checked. Both routes compute floor(price times 100 divided by 10,000), so the current market fee is one percent in base units and very small prices can round to zero fee. This settlement fee is unrelated to membership rent or validator network fees.
After payment, the MarketAuthority PDA signs a strict append of the exact escrow slot into the buyer Backpack. The Listing is marked sold with buyer, slot, and Unix time, then seller active_count decreases by one. If token, Backpack, authority, arithmetic, membership, or any later write fails, all transfers and program-account mutations roll back. A processed failed transaction may still consume its network fee.
Cancellation validates seller membership and a destination Backpack, returns the exact slot, marks the Listing canceled, and decrements active_count. Sold and canceled Listing PDAs currently remain allocated because the program has no Listing-close instruction, but the client creates no per-wallet history index and exposes no History view.
The buyer cannot override the price or currency committed by the seller's active Listing.
Integer floor rounding is applied in the selected currency's base units.
Both accounts must already exist, while only seller active_count changes.
BOUNDED RPC READS
The client reads active listings and shows authoritative item identities
Active listing discovery uses a Listing data-size filter plus optional state, seller, and currency filters. Browser pagination limits rendered rows. Loading states show a spinner rather than fake orders, while an RPC failure remains visible with a retry action.
Contracts is a treasury catalog view and appears before raw materials, building items, equipment, and clothing. Ordinary listing categories derive from the committed source slot. Cards and details use the real Chunk.js item or forged model instead of a generic tool placeholder; mass, cubic-centimeter volume, quality, durability, item identity, Listing PDA, and Explorer links come from decoded chain fields.
On mobile, Backpack and Listings are separate views rather than vertically compressed panels. Create, buy, cancel, and join controls enter a pending state during wallet and chain work, preventing accidental repeated submissions. Confirmed mutations refresh Backpack, Equipment, and active listings.
The final client does not load completed-trade pages, render a History tab, estimate history expansion, or submit an expansion instruction. This removes unnecessary RPC reads and account rent from normal market use.
The client filters program accounts and paginates the decoded result.
No history account, page read, history tab, or expansion action exists.
A tab switch gives each task the available screen instead of stacking both.
INDEPENDENT CHAIN CHECKS
Verify membership, escrow, settlement, and limits as separate facts
For membership, derive MarketUser v1 from the owner and configured Market Program. Verify the account owner, 64-byte length, magic, version, stored owner, active_count at or below 50, update slot, available and reserved u32 land-contract balances, and four zero trailing bytes. Compare the join transaction payer change with the pre-transaction rent minimum and actual network fee instead of treating the UI estimate as a receipt.
For a treasury contract purchase, verify quantity 1 through 4,096, an exact quantity × 10,000,000 NCK base-unit transfer from the buyer to the configured treasury, no new Listing PDA, and the same quantity credited to available contracts. For land registration, verify the reserve transition, one immutable Chunk index per contract, final consumption, or reverse-order cleanup followed by full release.
For listing creation, derive the seller-and-ID Listing PDA, verify version five and active state, compare the complete source slot with the seller Backpack or Equipment record removed in the same transaction, and confirm active_count increased by one without exceeding 50.
For purchase, verify the buyer differs from seller, the stored price and currency drove transfers, the one-percent split matches integer math, the buyer Backpack gained the exact escrow slot, the Listing names the buyer and sold timestamp, and seller active_count fell. For cancellation, verify exact return to a seller Backpack, canceled state, and the same decrement.
A submitted signature, thumbnail, local toast, category label, or fee estimate is supporting UI, not final proof. The strongest evidence is one confirmed transaction whose program-owned accounts and balances agree with every expected transition.
Use the actual 64-byte length, current rent minimum, and transaction metadata.
Compare seller removal, Listing source slot, and buyer or seller destination append.
Creation increments and successful settlement or cancellation decrements exactly once.
MEMBERSHIP, LIMIT, ADDRESS, AND SETTLEMENT EQUATIONS
Every market charge and state transition is independently checkable
Membership rent, validator fees, Listing rent, the one-percent settlement fee, and the fixed treasury contract price are separate costs. Deterministic PDAs, contract balances, and exact slot bytes make each transition verifiable.
Explicit membership address and live join cost
P_user(o)=findProgramAddress([UTF8("market-user-v1"),o],G); joinCost=rent_RPC(64)+networkFee_RPC(joinTx)G is the configured unified Game Program. join_market inner tag 3 creates only P_user, with the wallet as signer and payer. The game obtains both displayed lamport values from the active RPC immediately before confirmation. The 64-byte rent reserve remains locked because the current program has no membership-close instruction.
- o
- The wallet that owns, signs for, and funds its market membership account.
- rent_RPC(64)
- The current getMinimumBalanceForRentExemption result for the exact 64-byte account.
- networkFee_RPC(joinTx)
- The current getFeeForMessage result for the compiled join transaction.
Active-listing limit without history capacity
0<=active_count<=50; create: active_count'=active_count+1; cancel_or_sale: active_count'=active_count-1MarketUserState rejects a creation once active_count reaches 50 and rejects a decrement from zero. There is no history_count, history_capacity, page counter, or expansion path in the final account layout.
- active_count
- The number of active Listing accounts currently owned by the seller wallet.
- 50
- The fixed per-wallet ceiling that prevents market escrow from becoming unbounded inventory storage.
Treasury contract price and Chunk-aligned land cost
contractPrice(q)=q × 10 NCK; requiredContracts=chunksX × chunksZ; reserve: (available,reserved)->(available-required,reserved+required); activate: reserved->reserved-required; rollback: (available,reserved)->(available+required,reserved-required)A treasury purchase accepts 1 through 4,096 blank land contracts and credits MarketUser directly without a Listing or Backpack slot. BuildSite v3 accepts only complete 16 × 16 Chunks, reserves the exact product before indexing, consumes it when the final Chunk becomes active, and can release it only while reversing an incomplete registration.
- q
- The number of blank land contracts bought from the NiceChunk treasury in one transaction.
- chunksX × chunksZ
- The immutable rectangular land area measured in complete canonical Chunks.
- available, reserved
- The two u32 contract balances stored at bytes 52 through 59 of MarketUser v1.
Listing identity and exact-slot escrow
P_listing(s,id)=findProgramAddress([UTF8("listing"),s,LE64(id)],G); Listing.len=216; escrowSlot=Backpack_before[index][0..80); Backpack_after=removeDense(Backpack_before,index)Creation requires an existing seller membership, an owner signature, a new seller-scoped Listing PDA, and active_count below 50. Backpack and equipment sources both resolve to one complete validated 80-byte slot. The program stores that record before removing it from inventory; there is no partial-stack amount argument.
- s, id
- Seller public key and unsigned 64-bit seller-scoped Listing identifier.
- escrowSlot
- The exact block, material, or forged-item record committed by the program.
- removeDense
- Backpack removal that compacts later occupied slot indexes.
Atomic payment, delivery, and fee transition
fee=floor(price*100/10000); sellerAmount=price-fee; buy: activeListing+buyerSpace+bothMemberships -> soldListing+buyerSlot+sellerCountMinusOneSOL and NCK settlement use the price and currency stored in the Listing. Account ownership, treasury, mint, token accounts, buyer Backpack space, and both membership PDAs are validated before completion. Solana atomicity rolls back payment, delivery, Listing state, and membership updates together when any instruction step fails, although a processed failed transaction can still consume its validator fee.
- 100/10000
- The current compile-time one-percent marketplace settlement fee.
- buyerSpace
- One free destination Backpack record for the complete escrow slot.
- bothMemberships
- Existing seller and buyer MarketUserState PDAs; purchase never creates either account implicitly.
The final Market namespace exposes listings, land contracts, and Treasury Swap
Rustprograms/nicechunk_market/src/lib.rs match tag {
0 => create_listing(program_id, accounts, payload),
1 => cancel_listing(program_id, accounts),
2 => buy_listing(program_id, accounts),
3 => join_market(program_id, accounts),
4 => buy_treasury_contract(program_id, accounts, payload),
5 => update_land_contract_reservation(
program_id,
accounts,
payload,
LandContractReservationOperation::Reserve,
),
6 => update_land_contract_reservation(
program_id,
accounts,
payload,
LandContractReservationOperation::Consume,
),
7 => update_land_contract_reservation(
program_id,
accounts,
payload,
LandContractReservationOperation::Release,
),
8 => treasury_swap::initialize_treasury_swap(program_id, accounts, payload),
9 => treasury_swap::configure_treasury_swap(program_id, accounts, payload),
10 => treasury_swap::deposit_treasury_swap_sol(program_id, accounts, payload),
11 => treasury_swap::withdraw_treasury_swap_sol(program_id, accounts, payload),
12 => treasury_swap::deposit_treasury_swap_nck(program_id, accounts, payload),
13 => treasury_swap::withdraw_treasury_swap_nck(program_id, accounts, payload),
14 => treasury_swap::swap_sol_for_nck(program_id, accounts, payload),
15 => treasury_swap::swap_nck_for_sol(program_id, accounts, payload),
_ => Err(NicechunkMarketError::InvalidInstruction.into()),
}
Tags 0 through 3 retain explicit membership and Listing settlement. Tag 4 buys fixed-price blank land contracts from the treasury, while tags 5 through 7 are Building-authority-only reserve, consume, and release transitions. Tags 8 through 15 initialize, configure, fund, withdraw, and execute the two Treasury Swap directions without creating market-history accounts.
The membership account enforces the 50-listing ceiling
Rustprograms/nicechunk_market/src/membership.rs pub fn increment_active(data: &mut [u8], updated_slot: u64) -> ProgramResult {
let active_count = Self::active_count(data)?;
if active_count >= MAX_ACTIVE_LISTINGS {
return Err(NicechunkMarketError::ActiveListingLimitReached.into());
}
data[Self::ACTIVE_COUNT_OFFSET] = active_count + 1;
Self::write_updated_slot(data, updated_slot)
}
Every new listing consumes one seller membership count. The independent Listing PDA holds the escrowed item; MarketUserState does not copy item or completed-trade records.
The browser requests current membership rent and fee values
JavaScriptsrc/chain/nicechunkChain.js const [userStateRentLamports, networkFeeLamports] = await Promise.all([
conn.getMinimumBalanceForRentExemption(marketUserLength, "confirmed"),
estimateMarketTransactionFeeLamports(conn, ownerKey, instruction),
]);
const storageRentLamports = userStateRentLamports;
The quote follows the configured RPC and final 64-byte account length. The UI labels storage rent and validator network fee separately and does not treat either as the one-percent trading fee.
Purchase requires both existing membership PDAs
Rustprograms/nicechunk_market/src/lib.rs validate_existing_market_user(program_id, seller_market_user, seller.key)?;
validate_existing_market_user(program_id, buyer_market_user, buyer.key)?;
let (seller_amount, fee_amount) = split_market_payment(price_base_units)?;
The buyer cannot be charged for a hidden membership allocation. Seller membership is later decremented, while buyer membership is validated read-only.
IMPLEMENTATION EVIDENCE
Where these claims come from
Each claim is intentionally scoped to a concrete implementation path. These references are for verification, not decoration.
programs/nicechunk_market/src/membership.rs
Defines MarketUser v1, its 64-byte owner and update fields, available and reserved blank-land balances, the 50-active limit, and checked listing and contract transitions.
programs/nicechunk_market/src/lib.rs
Routes Listing create, cancel, buy, explicit join, fixed-price treasury contract purchase, and Building-authorized reserve, consume, and release tags.
programs/nicechunk_market/src/state.rs
Defines version-5 216-byte Listing records, complete 80-byte source slots, active, canceled, and sold states, price and currency fields, buyer identity, and settlement timestamps.
programs/nicechunk_market/src/errors.rs
Defines active-listing, market-user, already-joined, custody, payment, token, Backpack, and account validation failures without history-page errors.
src/chain/nicechunkChain.js
Derives and decodes MarketUser v1, submits join, Listing settlement, and treasury contract purchases, and coordinates contract reservation with immutable BuildSite v3 registration.
play/play-market.js
Blocks first use behind an explicit membership quote, pins the Contracts category and treasury contract first, prevents duplicate operations, and reconstructs authoritative item details.
play/index.html
Provides membership cost disclosure, the Contracts category, accessible pending states, desktop market layout, and mobile Listings and Backpack views without a History tab.
scripts/update-locales.mjs
Supplies membership, rent, pending, error, and rules-link copy through the Play locale dictionaries without retired history-capacity strings.
play/tests/market-forged-chain-instruction.test.mjs
Checks exact unified join, Backpack listing, and equipment listing account layouts and validates the final 64-byte MarketUserState ABI.
play/tests/market-ui-browser.test.mjs
Exercises desktop and mobile Market rendering, real item icons and details, pending transaction controls, retryable RPC failures, explicit membership, and absence of retired history UI.
