GRC / PUBLIC DOCUMENTATION
Spielintegration / Game integration
Inventar lesen, Items mit Zustimmung nutzen und Spielregeln im eigenen Backend prüfen. / Read inventory, use items with consent and validate gameplay on your backend.
Browser- und Mobilegames anbinden
Browser- und Mobilegames können die vorhandenen HTTP-Schnittstellen über ihr eigenes Backend integrieren. Cologna und klick-game bleiben spätere externe API-Nutzer; ein fertiger Adapter für diese Spiele wird nicht behauptet. Die Marketplace-Oberfläche ist ausgeliefert. Eine öffentliche Katalog-/Item-API und der vollständige externe Spielablauf sind noch nicht abgenommen. Die folgenden Pfade beschreiben implementierte Schnittstellen und weisen keinen öffentlichen API-Host zu.
Item-Ablauf · Vollständige Item-API · Backend-Client · Cologna-Quellbeispiel
1. Inventar und Onchain-Besitz
GET /v1/info, GET /v1/listings und GET /v1/inventory/{account} sind in der Marketplace-OpenAPI beschrieben. Deren GET /healthz ist keine öffentliche Bereitschaftszusage. IDs, Cursor und Beträge bleiben Dezimalzeichenfolgen; Seiten sind kein eingefrorener Snapshot. Checkpoint-Hashes vergleichen und bei Bedarf neu beginnen. 503 INDEX_NOT_READY ist keine erfolgreiche leere Seite; 429 verlangt Behandlung des Limits.
Mint, Besitz und Transfer laufen im bestehenden ERC-1155-Contract GRCGameItems. Ein Kauf, eine Onchain-Freigabe oder ein Transfer braucht eine separat bestätigte Wallet-Transaktion. Exakten nativen Preis und Gebühren anzeigen; Schlüssel und Recovery-Phrase bleiben in der Wallet. Ein Accountname oder Katalogeintrag beweist kein aktuelles Eigentum. Genehmigte Netzidentität, Contract, Runtimecode und Registry unabhängig konfigurieren; weder Katalog noch /info liefern neue Vertrauensanker.
2. OwnerGrant → equip → signierte Aktion → aktueller Read
- Instanz und Spielprofil binden. Der Item-Dienst führt globalen Zustand je Netz/Contract/Token, unabhängig von Spiel und Besitzer. Veränderlicher Zustand gilt für
instance-Assets mitmaxSupply=1; beliebiges JSON, Stapel, Repair und Reset gehören nicht zum vorhandenen Modell. Der Betreiber pinnt Definition, Profil, Registry, Dienst- und getrennte Spielschlüssel. Der Spielserver verwendetcreateGameItemsClient(config, options)aus dem vorhandenen Node-Backend-Client; dieser gehört nicht ins Browserbundle. - Eigene Wallet-Zustimmung einholen.
buildGameItemOwnerGrantTypedDatabereitet den vollständigen lesbaren EIP-712-OwnerGrant vor. Die Webwallet zeigt Spiel-/Dienst-Origin, Asset, Profil, Gesamtschadenbudget, separate Verbrauchserlaubnis und Ablauf von höchstens 300 Sekunden. Die vollständige OwnerAuthorization ist{schema:"grc.game-item-owner-authorization/v1", grant, signature}. Game-Login ist keine Freigabe; der Spielschlüssel kann keine Besitzerunterschrift ersetzen. Unterstützt sind EOA/secp256k1, nicht EIP-1271. Desktop-/Android-Wallets unterstützen diese Zustimmung noch nicht; eine native Mobilegame-Walletübergabe wird hier nicht angeboten. - Aktuelles equip anfordern.
client.equip(ownerAuthorization)sendetPOST /v1/game-items/equipmit{grant}, wobeigrantdie vollständige OwnerAuthorization enthält. Die API prüft Signatur, Gültigkeit, Profil, Nonce/Restbudget und tatsächlichen Besitz am kanonischen bestätigten Block sowie am aktuellen Tip. Der Backend-Client erhält signierten Zustand und Besitzbeobachtung; Spielnutzen bleibt bei abgelaufenem, kaputtem oder verbrauchtem Zustand deaktiviert. Erneutes equip füllt kein Budget auf. - Autoritatives Gameplay signieren. Erst nach eigener Spielprüfung signiert das Backend die Ed25519-Aktion
wearoderconsumemitoperationId,expectedRevision, den Definitions-/Profil-/Grantdigests und dem beobachteten Account/Block.client.submitAction(ownerAuthorization, action)sendetPOST /v1/game-items/actionsmit{grant,action}. Browser und Mobileclient bestimmen keinen autoritativen Schaden und erhalten keine Spiel- oder Dienstschlüssel. Die API prüft die Spielsignatur; den tatsächlichen Spielverlauf muss das Spielbackend prüfen. - Aktuellen Zustand getrennt lesen.
client.readState()nutztGET /v1/game-items/assets/:tokenId. Der Dienst signiert die globale Haltbarkeit und den Verbrauch;observation:nullist kein Besitz- oder Kaufbeweis.GET /v1/game-items/infobestätigt nur unabhängig konfigurierte Bindungen. Kein Read registriert oder repariert ein Item.
3. CAS, Replay und Zustandsgrenzen
Eine SQLite-Transaktion vergleicht die globale Revision und das Restbudget, bindet Vorgangs-ID und Requestdigest und speichert Zustand und exaktes Ergebnis atomar. Identische Wiederholungen liefern das historische Ergebnis; andere Bytes zur selben ID oder eine veraltete Revision werden mit 409 abgewiesen. Zwei Spiele können nicht dieselbe Revision verbrauchen. Blockchain und Datenbank bilden trotzdem keine gemeinsame atomare Transaktion.
Kein POST wird automatisch wiederholt. Bei ITEM_ACTION_OUTCOME_UNCERTAIN darf client.retryAction(operationId) nur den identischen gespeicherten Request senden. Der Spielserver speichert Grant und signierte Aktion vor dem Senden in einem privaten dauerhaften Outbox. Der Clientcache lebt nur im Prozess; ein Pending-Import nach Neustart ist nicht implementiert. Danach identische HTTP-Recovery aus dem vertrauenswürdigen Outbox selbst integrieren oder den ungewissen Vorgang gesperrt lassen. Eine konkrete Ablehnung erlaubt kein stilles Umsignieren.
Ein Replay mit replayed:true,historical:true erneuert weder Besitz, Ablauf, Budget noch die aktuelle Clientbaseline. Für Gegenwartswerte separat lesen. grantBudget ist eine unsignierte Transport-/Dienstaussage außerhalb der Snapshotsignatur; die API-Datenbank erzwingt das Budget. Persistente Baselines mit read/compareAndSwap sind Aufgabe des Aufrufers; eine gültige Dienstsignatur beweist weder Konsens noch vollständige Historie oder Schutz gegen eine offline zurückgespielte Datenbank.
Ein beobachteter Besitzerwechsel sperrt weitere Aktionen des alten Besitzers. Ein nicht beobachteter Hin-und-zurück-Transfer ist keine vollständige Widerrufsgarantie. Verkauf, Transfer und Spielwechsel reparieren keine Haltbarkeit und setzen Verbrauch nicht zurück. consume deaktiviert Spielnutzen, verbrennt aber kein NFT. Ein nicht verwahrendes Angebot reserviert weder das Item noch seinen Zustand; der Verkäufer kann es bis zum Kauf weiter benutzen.
4. Spielwirkung, Anzeige und getrennte Events
Das Cologna-Beispiel verwendet ausdrücklich 200 Basispunkte der Grundrüstung cologna.base-armor mit Abrundung. Andere Spiele benötigen eigene registrierte Profile und autoritative Regeln; es gibt keine universelle Spielbalance. Der optionale Marketplace-Reader GET /v1/item-state/assets/:tokenId lässt die Dienstsignatur von seinem Backend prüfen. Die Browseranzeige ist keine unabhängige Ed25519- oder Besitzprüfung und bindet den Zustand nicht atomar an den Kauf.
Die Query-freien Itemrouten benötigen exakt registrierte Browser-Origins; CORS authentifiziert keinen Spieler. Serveraufrufe dürfen Origin weglassen, aber keine erforderliche Signatur. /healthz des Item-Dienstes bleibt intern. Hinter Loopback teilen Proxyaufrufe das Socket-IP-Limit. Stale-/Reorg-/RPC-Fehler sind Fehler, keine erfundenen Inventar- oder Haltbarkeitswerte. Spielereignisse und Rewards laufen über getrennte Plattformdienste; Tickets und Schlüssel bleiben im Backend. Allgemeiner Wallet-Login, OIDC, Webhooks oder ein Mobile-Purchase-Adapter sind kein Teil dieser Schnittstellen.
Connect browser and mobile games
Browser and mobile games can integrate the existing HTTP interfaces through their own backend. Cologna and klick-game remain future external API consumers; no completed adapter for those games is claimed. The marketplace UI has been delivered. Public catalogue/item API operation and the complete external game flow have not been accepted yet. These paths describe implemented interfaces and assign no public API host.
Item flow · Complete item API · Backend client · Cologna source example
1. Inventory and on-chain ownership
GET /v1/info, GET /v1/listings and GET /v1/inventory/{account} are described by the marketplace OpenAPI. Its GET /healthz does not promise public readiness. Keep IDs, cursors and amounts as decimal strings; pages are not frozen snapshots. Compare checkpoint hashes and restart traversal when necessary. 503 INDEX_NOT_READY is not a successful empty page; handle 429 rate limits.
Mint, ownership and transfer use the existing ERC-1155 GRCGameItems contract. Purchases, on-chain approvals and transfers require separately confirmed wallet transactions. Display exact native prices and fees; keys and recovery phrases stay in the wallet. An entered account or catalogue entry does not prove current ownership. Configure approved network identity, contract, runtime code and registry independently; neither the catalogue nor /info supplies new trust anchors.
2. OwnerGrant → equip → signed action → current read
- Bind the instance and game profile. The item service keeps global state per network/contract/token, independent of game and owner. Mutable state supports
instanceassets withmaxSupply=1; arbitrary JSON, stacks, repair and reset are outside the implemented model. The operator pins definitions, profiles, registry, service and separate game keys. The game server usescreateGameItemsClient(config, options)from the existing Node backend client; keep it out of browser bundles. - Obtain separate wallet consent.
buildGameItemOwnerGrantTypedDataprepares the complete readable EIP-712 OwnerGrant. The web wallet shows game/service Origins, asset, profile, total damage budget, separate consume permission and expiry of at most 300 seconds. The complete OwnerAuthorization is{schema:"grc.game-item-owner-authorization/v1", grant, signature}. Game login is not consent; a game key cannot replace the owner's signature. EOA/secp256k1 is supported; EIP-1271 is not. Desktop/Android wallets do not support this consent yet; no native mobile-game wallet handoff is provided here. - Request a current equip.
client.equip(ownerAuthorization)sendsPOST /v1/game-items/equipwith{grant}, wheregrantcontains the complete OwnerAuthorization. The API verifies signature, expiry, profile, nonce/remaining budget and actual ownership at both a canonical confirmed block and the current tip. The backend client receives signed state and an ownership observation; expired, broken or consumed state must disable game utility. Re-equipping replenishes no budget. - Sign authoritative gameplay. After its own gameplay validation, the backend signs an Ed25519
wearorconsumeaction withoperationId,expectedRevision, definition/profile/grant digests and observed account/block.client.submitAction(ownerAuthorization, action)sendsPOST /v1/game-items/actionswith{grant,action}. Browser and mobile clients do not choose authoritative damage or receive game/service keys. The API verifies the game signature; the game backend must establish gameplay truth. - Read current state separately.
client.readState()usesGET /v1/game-items/assets/:tokenId. The service signs global durability and consumption;observation:nullis no ownership or purchase proof.GET /v1/game-items/infoonly confirms independently configured bindings. Reads never register or repair an item.
3. CAS, replay and state boundaries
One SQLite transaction compares global revision and remaining budget, binds operation ID and request digest, and atomically stores state and the exact outcome. Identical retries return the historical outcome; different bytes for the same ID or a stale revision return 409. Two games cannot consume one revision. Blockchain and database still do not form a single atomic transaction.
No POST is automatically retried. On ITEM_ACTION_OUTCOME_UNCERTAIN, client.retryAction(operationId) can only resend the identical retained request. Before submission, the game server persists the grant and signed action in a private durable outbox. The client cache is process-local; importing pending requests after restart is not implemented. Integrate identical HTTP recovery from that trusted outbox yourself or keep the uncertain operation blocked. An explicit refusal never permits silent re-signing.
A replayed:true,historical:true outcome refreshes neither ownership, expiry, budget nor the current client baseline. Read separately for current values. grantBudget is an unsigned transport/service statement outside the snapshot signature; the API database enforces the budget. Persistent baselines with read/compareAndSwap belong to the caller. A valid service signature proves neither consensus, complete history nor protection from an offline database rollback.
An observed ownership change refuses further actions by the old owner. An unseen out-and-back transfer provides no complete revocation guarantee. Sales, transfers and game switches never repair durability or reset consumption. consume disables utility and leaves the NFT on-chain. A noncustodial listing reserves neither the item nor its state; the seller may keep using it until purchase.
4. Game effects, display and separate events
The Cologna example explicitly applies 200 basis points of cologna.base-armor with floor rounding. Other games need their own registered profiles and authoritative rules; no universal game balance is implied. The optional marketplace reader GET /v1/item-state/assets/:tokenId authenticates the service signature on its backend. Its browser display is not independent Ed25519 or ownership verification and does not atomically bind state to a purchase.
Query-free item routes require exactly registered browser Origins; CORS does not authenticate a player. Server requests may omit Origin, but never a required signature. Item-service /healthz stays internal. Behind loopback, proxied callers share the socket-IP rate budget. Stale/reorg/RPC failures are errors, never invented inventory or durability values. Game events and rewards use separate platform services; tickets and keys stay on the backend. General wallet login, OIDC, webhooks and a mobile purchase adapter are outside these interfaces.
Item-API und Backend: vollständige Quellen / Complete item API and backend sources
Die folgenden vollständigen Quelltexte sind als lesbarer Text in diese Seite eingebettet und separat über Originalpfad, Bytezahl und SHA256 gebunden. Sie gehören nicht zum historischen Source14-Satz unten. Versionsangaben und Testergebnisse innerhalb der unveränderten Originale bezeichnen deren damaligen Umfang; sie sind keine neue öffentliche Backend- oder Releaseabnahme. Relative Quelllinks beziehen sich auf den ursprünglichen Checkout.
These complete sources are embedded as readable text in this page, separately bound by original path, byte count and SHA256. They are separate from the historical Source14 set below. Versions and test results inside unchanged originals describe their original scope; they provide no new public-backend or release acceptance. Relative source links refer to the original checkout.
Integrationsvertrag / Integration contract
platform/docs/custom-game-items.md · 6823 Bytes · SHA256 4b94a6826beb45518bd46bee564dfbaba965dc0ddef0c545839a60ca0fd07794
# Custom game items: integration contract
Platform 0.6.0 provides signed API updates for global durability and consumption,
with mint, ownership and transfers remaining on-chain. The
[persistent service](../services/game-items/README.md),
[protocol](../packages/game-items-protocol/README.md) and
[Node backend client](game-items-backend-client.md) share the same item/signature
schemas. There is no hosted API or live-chain acceptance claim. Cologna and
klick-game remain external clients that can adopt these interfaces later.
## Service boundaries
- An explicitly pinned registry lists approved full network/contract/deployed-code,
immutable asset definitions, currently accepted game profiles and game/service
authority keys. First bootstrap registers these assets exactly once; reads and
game switches never initialize an absent state. Registry/key/policy identity
changes require a reviewed migration; no migration/reset administration is
implemented. Live/reopen file and lease binding is checked, but restoring an
older database offline with the same accepted binding is not independently
detected. Backup/restore and rollback protection require separate operational
review; they are not acceptance guarantees of this API.
- One global row per `networkSHA:itemsAddress:tokenId` contains state. GameID and
owner are not part of this primary key. Profiles/configuration and optional
future game-specific extensions live in separate namespaces and never shadow
global durability/consumed. `maxSupply=1` is required for mutable instance state.
- A real bounded confirmed-RPC checker rechecks full network, pinned runtime
code, cap/minted supply/balance and canonical block before each entitlement or
action. State signatures alone, current indexer inventory and old game login
are insufficient. Preserve canonicality/freshness failure rather than granting
utility. No ownerOf assumption for ERC-1155.
## HTTP contract
| Route | Request/behavior |
| --- | --- |
| `GET /v1/game-items/info` | Public service network/contracts/registry identity; never use it to discover the expected trust configuration |
| `GET /v1/game-items/assets/:tokenId` | Public global state for a contract/network fixed by service config; returns service-signed snapshot, `observation:null`; no registration or state mutation |
| `POST /v1/game-items/equip` | Body `{grant}` where grant is the complete OwnerAuthorization envelope; verify owner signature, profile digest, grant scope/expiry and fresh real account ownership; bind nonce/rest budget once, return current signed snapshot |
| `POST /v1/game-items/actions` | Body `{grant,action}` with the complete OwnerAuthorization and game Ed25519 action envelopes. Action names only `wear`/`consume`; includes global expectedRevision, operationId, profile/definition/grant digests and observed account/block. Revalidate actual ownership, never trust claimed checkpoint alone |
These are the implemented service routes. See the service README for exact
response/error bodies, bounded Origin/preflight rules and operator inputs. The
grant envelope is exactly `{schema: "grc.game-item-owner-authorization/v1", grant,
signature}`. Re-equip retains original nonce, expiry and rest budget. Results use
`grc.game-items-result/v1` with the full signed snapshot and a separate
`grantBudget`. That budget is unsigned transport/service information; authoritative
spend enforcement stays in the API database. Current Marketplace
`/v1/inventory/:account` remains a separate unauthenticated read service.
Inside one transaction, action admission must compare global revision and retained
grant budget, reserve/consume a unique operation ID with immutable request digest,
write next state/remaining budget, and retain the exact outcome. Repeated ID/same
bytes returns historical/idempotent outcome; changed bytes returns 409; stale
revision returns 409. Concurrent games cannot each consume the same revision.
Duplicate retrieval after expiry must never produce a fresh ownership claim or
increment revision/spending. Update signed snapshots through a separate fresh read.
## Wallet and game integration status
The dedicated Web Wallet implements separate consent showing the complete
EIP-712 owner-grant typed data: exact game Origin, supported asset, profile, global
wear budget, separate consume capability and expiry. It requires independently
pinned registry trust; native Desktop/Android clients do not support this flow.
The existing GRC Game login is only a session login and cannot serve as this
consent. EOA/secp256k1 recovery is supported; EIP-1271 is not. No backend
may invent/sign a player's grant with its game key. General revocation
administration and detection of unseen out-and-back transfers are not implemented;
returned ownership retains at most the original grant expiry/rest budget.
Each game backend validates authoritative gameplay before signing actions.
Browser events do not choose damage/durability or receive authority keys. A Cologna
profile may add 200 bps to its explicitly defined armor base; another game needs an
explicit adapter/profile and may apply different rules. No universal gameplay
balance is implied. API consumption leaves the on-chain token and its ownership
unchanged. The optional Marketplace Item State reader displays durability and
consumption with service signatures authenticated by its backend;
`observation:null` provides no ownership or purchase proof. Wallet state display
remains unintegrated. An actual burn requires a separate on-chain action. Stacks
need a separate shared quantity/transfer policy.
## Validation scope
Selected local checks passed separately; their counts describe different suites:
| Layer | Actual result | Scope |
| --- | --- | --- |
| Protocol and signature SDK | 14/14 pure cases | Synthetic EOA/Ed25519 keys, policies, signatures and global state transitions; three unchanged syntax passes inherited |
| Persistent API | 27/27 pure cases, nine syntax checks | Actual private SQLite with synthetic owner/RPC responses and in-memory HTTP adapter; no listener or chain |
| HTTP client and snapshot authentication | 10/10 pure cases, five syntax checks | In-memory Fetch responses, current/historical service statements, retained client baseline, uncertain delivery and response bounds |
The pure-suite counts above are separate from the bounded local Web Wallet/API/
devnet browser check, which passed grant approval/denial and CORS/equip with
canonical local-chain receipts. Marketplace application/browser, native grant
support, operator RPC/indexer setup, external-game authoritative actions,
cross-game use/transfer/escrow, hosting and operator acceptance remain separate.
No offline backup/restore, permanent revocation, universal game balance or release
acceptance is implied.
API: Routen, Konfiguration und Grenzen / Routes, configuration and limits
platform/services/game-items/README.md · 9372 Bytes · SHA256 acecb0512ca367a9d242d6e5e0aa9b118230eb923f74892e0003e76915fd8803
# Persistent game-item state API
This separate Node.js 24 service keeps durability and consumption in a private
SQLite database. Mint, ownership, escrow, burn and transfer remain on-chain in
GRCGameItems. API consumption marks the global item used; it does not burn the NFT.
No keys, RPC URLs or authoritative gameplay values come from a browser request.
## Explicit operator inputs
The CLI takes no arguments. Set `GRC_GAME_ITEMS_CONFIG` to a private absolute JSON
file and `GRC_GAME_ITEMS_CONFIG_SHA256` to its exact SHA256, then run:
```sh
node services/game-items/server.js
```
The exact `grc.game-items-runtime/v1` fields are `origin`, `host`, `port`, `rpcUrl`,
`registryFile`, `registrySha256`, `privateKeyFile`, `keyId`, `databaseFile`, `limits`
and `ownership`, plus `schema`. `host` must be `127.0.0.1`; no port/network/key/
contract/policy defaults exist. HTTPS RPC is allowed; HTTP requires a numeric
loopback host. Credentials, query and fragment are rejected. Keys and database
require private caller-owned POSIX storage; use 0700 directories and 0600 files.
The immutable config/registry file readers and private DB/lease core reuse
`reference-game/files.js` and `reference-game/journal.js`, unchanged. This service
uses its own purpose-bound database, namespaces and domain errors.
`limits` explicitly supplies `maxAssets`, `maxGrants`, `maxActions`, `maxQueued`,
`requestsPerMinute`, `maxRateEntries`, `bodyBytes`, `bodyTimeoutMs` and
`operationTimeoutMs`. `ownership` supplies `confirmations`, `maxBlockAgeSeconds`,
`maxFutureSkewSeconds`, `maxOwnershipAgeSeconds` and approved `runtimeCodeHash`.
Requests have a maximum 15-second operation budget; RPC transport separately caps
each request at 4 seconds and 1 MiB, rejects redirects/compression, and uses no
ambient proxy or retry. These are safety bounds, not measured service capacity.
The SHA-pinned public `grc.game-items-registry/v1` contains exactly:
- `stateAuthority`: the existing protocol's full network identity/hash, contract,
service Origin and Ed25519 public keys. The selected private service key must
match its explicitly selected `keyId`.
- `definitions`: entries `{definition,registrationOperationId}` using the existing
immutable definition schema and an explicit UUIDv4 bootstrap ID. Only cap-one
`instance` assets are supported. No stack/quantity state is inferred.
- `games`: entries `{adapter,profiles}` using the existing concrete protocol
schemas. Profiles are selected by this independent operator registry's current
digest/revision and explicit per-game policies. Game and service public keys
must be disjoint. This registry does not extend a signed profile's 300-second TTL.
First bootstrap registers the configured global assets once at revision1. GETs,
game switches, restart and owner changes never initialize or repair state. A
registry/key/policy identity change requires an explicit reviewed migration.
The reused store binds its lease/config identity and rechecks file identity while
running. It does not independently detect an older database copied back offline
with the same accepted lease and configuration. Stop the service before any
backup/restore; review the complete database/lease/registry binding and rollback
risk separately. No backup/restore acceptance or independent offline rollback
protection is claimed. This candidate provides no migration, restore, revocation
administration or privileged reset endpoint.
## HTTP and backend integration
The separate server-only SDK and protocol are reused, including the EIP712 owner
grant and distinct Ed25519 purposes. HTTP clients must receive expected registry,
network and authority inputs independently; `/info` is not a trust bootstrap.
| Method and path | Behavior |
| --- | --- |
| `GET /v1/game-items/info` | Public configured network/contracts/registry/authority and definition/game IDs; no RPC/config/key/DB paths |
| `GET /v1/game-items/assets/:tokenId` | Registered canonical positive decimal ID; fresh signed global snapshot with `observation:null`; no entitlement or mutation |
| `POST /v1/game-items/equip` | Exactly `{grant}` with the full OwnerAuthorization envelope; checks signature/TTL/current registry and fresh actual ownership, registers nonce/budget once |
| `POST /v1/game-items/actions` | Exactly `{grant,action}` with full owner and game envelopes; checks trusted game policy, own DB revision/budget and fresh ownership, then atomically commits |
| `GET /healthz` | Internal DB/process health only, explicitly not chain readiness; do not publish through an edge |
Paths are query-free. Browser Origins must exactly match the service or a
registered game's Origin; empty/null/foreign/duplicate Origin is rejected. Server
requests may omit Origin but still need signatures for equip/action. OPTIONS is
limited to the listed public method/path pairs and Content-Type. No credential
cookie, bearer bypass, private route, metadata/image fetch or client-selected RPC
exists. Rate accounting uses the actual socket peer and ignores forwarded headers;
behind a loopback proxy callers share one aggregate rate budget.
Results are `{schema:"grc.game-items-result/v1",replayed,historical,snapshot,
grantBudget}`. The snapshot is the full service attestation; `grantBudget` is null
for GET or `{ownerGrantSha256,maximumDamage,spentDamage,remainingDamage,expiresAt}`.
Equip returns the current state even if broken/consumed: the game must disable
modifiers using that state. A snapshot signature is a service statement, not an
independent consensus/state proof. Errors are `{error:"CODE"}` with bounded codes:
400 malformed,401 signature,403 Origin/purpose/expiry/binding/policy,404 unknown,
409 CAS/replay/budget/ownership/capacity,408 timeout/abort,413 body,429 rate/queue,
503 RPC/reorg/stale/DB unavailable. Oversized/aborted streaming bodies may close
their connection. No key/parser/config content is echoed.
## Ownership, consent and replay boundaries
An owner signs the new purpose-specific EIP712 OwnerGrant, not a game login,
transaction or on-chain approval. It binds full network, asset/definition/profile,
service authority/Origin, game/Origin, owner, nonce, <=300-second expiry and explicit
total damage/consume permission. The game backend validates authoritative gameplay
and signs `wear`/`consume` actions; the API cannot establish gameplay truth from
that signature alone. The service does not receive a game's private key.
The actual owner checker binds token ID `(collectionId << 128) | itemId`, approved
runtime code, lifetime `maxSupply=1`, `mintedSupply=1` and account `balanceOf=1` at
both a canonical confirmed block and the separately pinned latest tip. Stale,
wrong network/code, known transfer disagreement, reorg or changing tip rejects
admission. Calls use EIP1898 canonical hash selectors; unsupported RPC fails closed.
For equip, the checker selects the latest sufficiently confirmed height. For an
action it actually reads the signed claimed height/hash: an older fresh canonical
block remains usable after ordinary head advancement if still sufficiently
confirmed, no earlier than the approved anchor and within the explicit age policy.
Code/cap/minted/balance are read at that exact hash and the pinned current tip,
then both canonicality and network are checked again. Unconfirmed claims return
`ITEM_OWNER_UNCONFIRMED`; a wrong hash returns `ITEM_OWNER_CLAIM_MISMATCH`, both409.
A known transfer returns `ITEM_OWNER_CHANGED_AT_TIP` or
`ITEM_CONFIRMED_OWNER_REQUIRED`409. Stale/reorg/unavailable is not an accepted
transfer refusal. Re-equip/read and let the authoritative backend sign a fresh
operation after an actual refusal; no automatic chain transaction retry or silent
rewrite of the old signed action occurs.
One SQLite transaction commits global revision/previous digest, damage/consumed,
retained grant budget/nonce, unique operation ID/request digest and exact outcome.
No await or untrusted callback runs inside it; signing happens before strict-CAS
commit. Conflicting games/IDs cannot each consume one revision. Exact saved retries
after expiry return the old snapshot/budget with `replayed:true,historical:true`;
they do not refresh ownership, signatures, budget or a client's monotonic baseline.
Use a separate GET for current state. Read storage capacity failures are preserved;
no replay rows are silently pruned to free a nonce.
Transfers/escrow/burn do not repair durability, undo consumption or replenish any
grant. A returned owner's still-unexpired old grant retains only its original
remaining budget; observed transfer does not silently create a new grant. An
unobserved out-and-back transfer cannot be used as a full revocation guarantee
without an on-chain nonce or transfer-history mechanism. Chain and SQLite are
separate systems: a TOCTOU/finality interval remains after the final observation.
No public/operator/network/mint/funding/hardware acceptance follows from this code.
## Validation scope
`test/game-items-service.test.js` exercises actual private SQLite with explicit
synthetic ownership callbacks; `test/game-items-ownership.test.js` supplies
synthetic RPC responses against the actual ABI and hash selectors. They start no
HTTP listener/chain and establish no live RPC, Caddy, wallet, gameplay or hosted
API acceptance. Test keys are public deterministic fixtures, never operator keys.
Backend-Client: Baseline und Recovery / Baseline and recovery
platform/docs/game-items-backend-client.md · 6956 Bytes · SHA256 c3cf7c2c11e2ba31abb3cc9ce53bf48efb8fcd0496e118282222606fd05930ca
# Custom item backend client Platform 0.6.0 includes this Node backend HTTP client and integration example for the [persistent item API](../services/game-items/README.md). The selected client suite passed 10/10 with five syntax checks, separately from the protocol's 14 and API's 27 pure cases. These are local synthetic checks, not a hosted API or live-chain/game acceptance. Cologna and klick-game remain external adopters; no game repository is migrated here. ## Configuration and owner consent Create `createGameItemsClient(config, options)` with one explicit cap-one asset, game adapter/profile, complete network identity, contract, registry digest and service authority. Configure confirmation/observation-age policy locally. An `/info` response is checked against those values and never supplies new trust, keys, URLs or schemas. The client makes only the four fixed service routes. Use `buildGameItemOwnerGrantTypedData` for the complete readable EIP-712 payload. The owner must approve that new grant purpose in a separately implemented wallet consent UI. Existing game login is not grant authorization. The backend never creates the owner signature. Owner recovery supports EOA/secp256k1 only; this client does not support EIP-1271 contract-wallet validation. Ed25519 game and service keys remain distinct and stay on their respective backends. ## State authenticity, bootstrap and rollback `authenticateGameItemSnapshot` is a separate API from the original verifier that requires an independently expected revision/digest. It validates the existing snapshot/signature schema and configured authority without pretending a dynamic GET already has independently known state. In `current` mode, `freshnessVerified` means only that the signed service window is open and its stated observation satisfies configured age. It does not verify an RPC, ownership, consensus, actual DB CAS or gameplay authorization. A first read is a service assertion bootstrap. Supply a trusted `baselineStore` with `read(assetId)` and `compareAndSwap(assetId, previous, next) -> boolean` for retained revision/digest checks. The store must return immutable snapshots and implement atomic value/token comparison across its own writers. Preserve its binding to definition, authority and asset across restart; protect it against replacement or rollback. The client does not verify that persistence. The default is memory for that client instance. The API likewise does not independently detect an older database restored offline with the same accepted lease/configuration. Neither layer provides general revocation or backup/restore administration; do not infer those guarantees from a valid signature or a remembered revision whose storage can itself be rolled back. Lower revisions, equal-revision divergent state, healing and consumed-state reset are rejected. A one-step advance checks its predecessor digest; larger revision gaps are explicitly qualified and do not prove the complete intervening history. Historical action replay uses an explicitly separate authenticity mode. Its original issuance/expiry remain visible, `freshnessVerified:false`, and its state never advances the current client baseline. Validation of a historical observation refers to signed issuance solely to check the old statement's structure/age; that clock is never reported as present freshness. Read separately for a new current service statement. No snapshot response changes the wire format. ## Equip, actions and uncertain delivery `equip(ownerAuthorization)` obtains a current account-bound service statement. The API, rather than the client, performs the fresh real on-chain ownership check. Use its exact checkpoint and revision when the game backend signs the action. `submitAction(ownerAuthorization, action)` validates the existing signed action, keeps its exact bytes during uncertain delivery, and checks the returned global state against the predicted transition. It never chooses damage or signs an owner grant. Only one mutation is in flight per client instance. No POST is automatically retried. On `ITEM_ACTION_OUTCOME_UNCERTAIN`, call `retryAction(operationId)` explicitly to send the identical retained request. The service may return its original historical outcome after expiry. A concrete 409 is a rejection, never permission to silently alter/re-sign the action. A normal head advance does not itself invalidate a fresh confirmed signed checkpoint: the API checks that original height/hash and the current tip. Its concrete refusals include `ITEM_OWNER_CLAIM_MISMATCH` (wrong hash), `ITEM_OWNER_UNCONFIRMED`, and a known transfer as `ITEM_OWNER_CHANGED_AT_TIP` or `ITEM_CONFIRMED_OWNER_REQUIRED`, all 409. After an actual refusal, obtain a fresh equip and a new authoritative game decision. Stale/reorg/unavailable responses remain failures; they are not accepted transfer evidence or permission to retry with silently rewritten signature bytes. The client's uncertain-request cache is in memory. The host must persist its exact grant/action in a private durable outbox before submission. This client does not import a persisted pending operation after client/process restart; use the API's documented identical-request recovery from that trusted outbox rather than create a replacement operation, or keep the uncertain operation blocked. `close()` aborts this client's requests and erases its in-memory context. `grantBudget` lies outside the snapshot signature. The client checks its grant binding and arithmetic, then labels it `transport-service-statement-not-snapshot-signed`. HTTPS/transport configuration protects that service answer; it is not a signed budget proof. The persistent API alone enforces authoritative spend, nonce, idempotency and CAS. Service-side transfer/escrow checks never reset global state. ## Example and validation scope `examples/custom-game-items/backend.js` previews Cologna's explicit 200-basis-point armor profile with floor rounding against `cologna.base-armor`. It signs wear only after the game backend finalizes authoritative gameplay and stores an outbox entry. Another game needs its own registered profile; this is not universal game balance. Consumption leaves the token on-chain; repair, reset, stack wear and actual burn are outside this implementation. The ten selected client tests passed using in-memory generated synthetic keys and Fetch responses, with no listener, public RPC, browser, wallet or network service. They cover statement freshness/authenticity, historical replay without baseline update, caller-store rollback/equivocation/conflict, out-of-order reads, uncertain POST delivery, error preservation, unsigned budget consistency and response bounds. The [integration guide](custom-game-items.md#validation-scope) distinguishes the separate protocol/API results. No hosted API, wallet consent UI, end-to-end chain or gameplay acceptance, backup/restore acceptance or release pass follows.
Cologna: autoritative Aktionen / Authoritative actions
platform/examples/custom-game-items/backend.js · 4055 Bytes · SHA256 b1bb2878f9be27e2acd1bd59587bed0accadd5a85e3ea097a779384d2c52e4d1
import { evaluateGameItemModifier, gameItemSha256, ITEM_SCHEMAS } from "../../packages/game-items-protocol/index.js";
import { buildGameItemOwnerGrantTypedData, signGameItemAttestation } from "../../sdks/server/grc-game-items-server.js";
/** Return the complete readable payload to a separately implemented wallet consent UI. */
export function ownerConsentPayload(proposedGrant, trustedContext, nowSeconds) {
return buildGameItemOwnerGrantTypedData(proposedGrant, { ...trustedContext, nowSeconds });
}
/** Cologna-specific preview; another game must register its own adapter/profile. */
export async function equipColognaChainmail({ client, ownerAuthorization, trustedContext, baseArmor }) {
if (trustedContext.adapter.gameId !== "cologna") throw new Error("COLOGNA_ADAPTER_REQUIRED");
const armorModifier = trustedContext.profile.modifiers.find((modifier) => modifier.stat === "cologna.armor");
if (!armorModifier || armorModifier.valueBps !== "200" || armorModifier.base !== "cologna.base-armor") throw new Error("COLOGNA_CHAINMAIL_PROFILE_REQUIRED");
const equipped = await client.equip(ownerAuthorization);
const preview = await evaluateGameItemModifier(baseArmor, "cologna.armor", {
...trustedContext, state: equipped.statement.payload.state,
});
return { equipped, armor: preview.value, active: preview.active,
basisPoints: "200", base: "cologna.base-armor", rounding: "floor",
trust: "Pinned item-service assertion plus this game's registered profile; no independent ownership proof." };
}
/**
* Call only after this backend has finalized authoritative gameplay. Browser
* telemetry never chooses damage or receives the game signing key. Persist the
* owner authorization, operation ID and exact signed action in a private durable
* outbox BEFORE submitAction; retain them when the HTTP result is uncertain.
*/
export async function applyAuthoritativeChainmailWear({ client, ownerAuthorization, trustedContext,
gameSigningKey, finalizedHit, persistPending, nowSeconds }) {
if (trustedContext.adapter.gameId !== "cologna" || !finalizedHit?.authoritative || typeof persistPending !== "function") throw new Error("AUTHORITATIVE_HIT_AND_OUTBOX_REQUIRED");
const equipped = await client.equip(ownerAuthorization);
const state = equipped.statement.payload.state, observation = equipped.statement.payload.observation;
const payload = { schema: ITEM_SCHEMAS.action, gameId: trustedContext.adapter.gameId,
definitionSha256: await gameItemSha256(trustedContext.definition), profileSha256: await gameItemSha256(trustedContext.profile),
ownerGrantSha256: await gameItemSha256(ownerAuthorization.grant), operationId: finalizedHit.operationId,
expectedRevision: state.revision, type: "wear", damage: finalizedHit.damage,
ownership: { account: observation.account, blockNumber: observation.blockNumber, blockHash: observation.blockHash } };
const actionContext = { ...trustedContext, purpose: "action", state, ownerAuthorization, nowSeconds,
expectedRevision: state.revision, remainingGrantDamage: equipped.result.grantBudget.remainingDamage };
const action = await signGameItemAttestation({ schema: ITEM_SCHEMAS.attestation, purpose: "action",
authorityOrigin: trustedContext.adapter.origin, keyId: trustedContext.actionKeyId,
issuedAt: String(nowSeconds), expiresAt: String(nowSeconds + 30), payload }, actionContext, gameSigningKey);
await persistPending({ operationId: payload.operationId, grant: ownerAuthorization, action });
// Normal head progress may preserve this still-fresh confirmed checkpoint.
// No retries or re-signing here: ITEM_OWNER_CLAIM_MISMATCH, ITEM_OWNER_UNCONFIRMED,
// ITEM_OWNER_CHANGED_AT_TIP or ITEM_CONFIRMED_OWNER_REQUIRED are actual refusals.
// Fresh equip plus a new authoritative decision/action is then required.
// ITEM_ACTION_OUTCOME_UNCERTAIN retains the original in-memory request so the
// host can explicitly call client.retryAction(operationId). See restart limits.
return client.submitAction(ownerAuthorization, action);
}
Vollständige Referenzen · Complete references
Unveränderte Source14-Snapshots / Unmodified Source14 snapshots. Markdown und OpenAPI sind Referenzdateien; ihre relativen Quelllinks beziehen sich auf den ursprünglichen Checkout. / Relative source links belong to the original checkout.
| Quellpfad / Source path | SHA256 | Bytes |
|---|---|---|
marketplace/docs/game-integration.md | f963f69189a2e3d9cd38d6b2d52861f27ba9c88d4074460228e1384e6235beae | 2812 |
marketplace/docs/openapi.json | bbc230c32304c867db5d7eb5d3c57ea5026997eebd4fd10bb2be47fc70fb037d | 23512 |
Quellstand / Source: 41a980f98885f57966f39ec2b73631884230728b. Alle Snapshot-Hashes / All snapshot hashes. Die lesbaren Guides sind Zusammenfassungen; die verlinkten Dateien enthalten die vollständige Referenz. / The readable guides are summaries; the linked files contain the complete reference.