The 4 contracts
Role wiring (from
script/Deploy.s.sol): SkillFactory holds REGISTRAR_ROLE on SkillRegistry and LISTER_ROLE on Marketplace; Marketplace holds MINTER_ROLE on SkillCollection.
Live on GIWA Sepolia (chain ID 91342), verified
All 4 contracts are deployed and verified on Blockscout. GIWA Sepolia is a testnet; there is no GIWA mainnet yet.
Machine-readable address list:
contracts/deployments/91342.json. ABIs and addresses reach the apps through packages/chain (abi/*.ts as-const modules and deployments/91342.ts), regenerated from source by contracts/export-artifacts.sh on every function/event change.
The catalog is already seeded with the 5 launch skills (IDs 1-5). See the supply/price table in Economy & Business Model.
Release flow: shipping a new skill (platform, 1 transaction)
Purchase flow
Functions the frontend calls
1
Quote the price: required before every purchase
Marketplace.priceOf(skillId) is a view function returning the current unit price, already including the scarcity premium and demand-decay discount. The frontend must always quote through priceOf; basePrice alone is stale the moment any unit sells or any time passes. See the pricing formulas in Economy & Business Model.2
Buy
Marketplace.buy(skillId, amount) is payable, msg.value = priceOf(skillId) * amount (quote immediately before submitting, to minimize drift from a moving price). Overpayment is automatically refunded in the same transaction; underpayment reverts with InsufficientPayment(expected, actual).3
Stock and base price
Marketplace.sales(skillId) returns (basePrice, maxSupply, minted, active, lastPurchaseAt), used for “X of Y left” stock display, rarity tier, and a discount badge (by comparing against priceOf). Never used to compute the actual purchase price.4
Check ownership
SkillCollection.balanceOfBatch(owners, skillIds) is used by the server at matchmaking time to verify a loadout, and by the frontend for inventory/collection views (one call covers many IDs; single-item balanceOf isn’t used).5
Read the catalog
SkillRegistry.nextSkillId() then getSkill(skillId) for 1..n returns the full SkillDef (charges/cooldown/maxPerLoadout/rarity) for rendering rules and the market.Deliberately not called by the app
SkillCollection.uri(skillId)andSkillDef.metadataURI: name, description, and asset URLs are served by the game server’sGET /metadata/{id}.json(built from on-chaineffectType/rarity) instead. The server, not on-chainuri(), is the source of truth for what the app displays, thoughuri()remains available for third-party wallets/marketplaces that expect the standard ERC-1155 metadata path.SkillCollection.royaltyInfo(tokenId, salePrice): read independently by third-party secondary marketplaces; the app has no reason to call it itself.Marketplace.pricingParams(): discount/hot-item badges are computed from the difference betweenpriceOfandbasePrice; the raw parameters aren’t needed client-side.- All admin write functions (
createSkill,setActive,createSale,setPricingParams,setTreasury,setURI,setDefaultRoyalty,mint, role management): operated by the platform viaforge script/the explorer, never from the app UI. withdraw(): permissionless (funds can only ever flow totreasury), triggered manually to sweep revenue; there’s no automated schedule for it yet.- Secondary transfers (
setApprovalForAll/safeTransferFrom): a peer-to-peer marketplace UI isn’t in scope yet (see Roadmap & Status).
Events
Testing
setPricingParams validation).