@flayerlabs/gamemode-gate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +79 -0
- package/dist/chain/discover.d.ts +158 -0
- package/dist/chain/discover.d.ts.map +1 -0
- package/dist/chain/discover.js +169 -0
- package/dist/chain/discover.js.map +1 -0
- package/dist/chain/signer.d.ts +46 -0
- package/dist/chain/signer.d.ts.map +1 -0
- package/dist/chain/signer.js +60 -0
- package/dist/chain/signer.js.map +1 -0
- package/dist/claims.d.ts +62 -0
- package/dist/claims.d.ts.map +1 -0
- package/dist/claims.js +128 -0
- package/dist/claims.js.map +1 -0
- package/dist/demo.d.ts +40 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +90 -0
- package/dist/demo.js.map +1 -0
- package/dist/economy.d.ts +46 -0
- package/dist/economy.d.ts.map +1 -0
- package/dist/economy.js +100 -0
- package/dist/economy.js.map +1 -0
- package/dist/game-registry.d.ts +17 -0
- package/dist/game-registry.d.ts.map +1 -0
- package/dist/game-registry.js +90 -0
- package/dist/game-registry.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +87 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +222 -0
- package/dist/ledger.js.map +1 -0
- package/dist/registry.d.ts +55 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +164 -0
- package/dist/registry.js.map +1 -0
- package/dist/room.d.ts +98 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +215 -0
- package/dist/room.js.map +1 -0
- package/dist/server.d.ts +92 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +695 -0
- package/dist/server.js.map +1 -0
- package/dist/sessions.d.ts +40 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +144 -0
- package/dist/sessions.js.map +1 -0
- package/dist/settlement.d.ts +24 -0
- package/dist/settlement.d.ts.map +1 -0
- package/dist/settlement.js +69 -0
- package/dist/settlement.js.map +1 -0
- package/dist/store.d.ts +24 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +125 -0
- package/dist/store.js.map +1 -0
- package/dist/turnstile.d.ts +19 -0
- package/dist/turnstile.d.ts.map +1 -0
- package/dist/turnstile.js +61 -0
- package/dist/turnstile.js.map +1 -0
- package/package.json +54 -0
- package/src/chain/discover.ts +243 -0
- package/src/chain/signer.ts +118 -0
- package/src/claims.ts +140 -0
- package/src/demo.ts +149 -0
- package/src/economy.ts +126 -0
- package/src/game-registry.ts +107 -0
- package/src/index.ts +45 -0
- package/src/ledger.ts +355 -0
- package/src/registry.ts +212 -0
- package/src/room.ts +297 -0
- package/src/server.ts +833 -0
- package/src/sessions.ts +155 -0
- package/src/settlement.ts +72 -0
- package/src/store.ts +127 -0
- package/src/turnstile.ts +74 -0
package/dist/claims.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { maxUint256 } from 'viem';
|
|
2
|
+
import { Ledger } from './ledger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turning points into something a player can actually spend.
|
|
5
|
+
*
|
|
6
|
+
* Two parties have to agree before a buy can happen, and they check different things. The ledger
|
|
7
|
+
* knows what this player earned and what they have already been authorised for. The signer knows
|
|
8
|
+
* whether it is willing to vouch for this pool at all. Only when both say yes does an
|
|
9
|
+
* authorisation exist.
|
|
10
|
+
*
|
|
11
|
+
* The order matters and is not interchangeable: the hold is recorded first, then signed. A
|
|
12
|
+
* signature that exists without a hold is spending nobody is tracking; a hold without a signature
|
|
13
|
+
* is a refund waiting to happen. Only one of those costs money.
|
|
14
|
+
*/
|
|
15
|
+
/** How long an authorisation stays valid. Long enough to approve in a wallet, short enough that an
|
|
16
|
+
* abandoned one does not sit around spendable. */
|
|
17
|
+
export const CLAIM_AUTHORIZATION_TTL_MS = 5 * 60_000;
|
|
18
|
+
const DEADLINE_SECONDS = CLAIM_AUTHORIZATION_TTL_MS / 1_000;
|
|
19
|
+
const REQUEST_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
20
|
+
/** Request ids cross an unauthenticated parser before they reach Postgres, so bound them here too. */
|
|
21
|
+
export function isClaimRequestId(value) {
|
|
22
|
+
return typeof value === 'string' && REQUEST_ID.test(value);
|
|
23
|
+
}
|
|
24
|
+
/** The signed message carries uint256, while JavaScript's bigint has no upper bound. */
|
|
25
|
+
export function isClaimAmount(value) {
|
|
26
|
+
return typeof value === 'bigint' && value > 0n && value <= maxUint256;
|
|
27
|
+
}
|
|
28
|
+
export class Claims {
|
|
29
|
+
pool;
|
|
30
|
+
ledger;
|
|
31
|
+
signer;
|
|
32
|
+
onStrandedHold;
|
|
33
|
+
now;
|
|
34
|
+
constructor(pool, ledger, signer,
|
|
35
|
+
/** Called when a hold could neither be signed nor released, and needs a human. */
|
|
36
|
+
onStrandedHold,
|
|
37
|
+
/** An internal seam for exact window-boundary tests. Production uses the process clock. */
|
|
38
|
+
now = Date.now) {
|
|
39
|
+
this.pool = pool;
|
|
40
|
+
this.ledger = ledger;
|
|
41
|
+
this.signer = signer;
|
|
42
|
+
this.onStrandedHold = onStrandedHold;
|
|
43
|
+
this.now = now;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Authorise a player to spend up to `upToWei`, or explain why not.
|
|
47
|
+
*
|
|
48
|
+
* The ledger's ceiling is a conservative mirror of the chain's: the contract tracks
|
|
49
|
+
* `walletSpentWei` per pool per wallet and enforces it whatever we believe. Being ahead of the
|
|
50
|
+
* chain is safe — we simply authorise less than we could. Being behind it would mean issuing a
|
|
51
|
+
* signature that reverts at the swap, which to a player is indistinguishable from being robbed.
|
|
52
|
+
*/
|
|
53
|
+
async authorise(roundId, player, upToWei, requestId) {
|
|
54
|
+
if (!isClaimRequestId(requestId))
|
|
55
|
+
return { ok: false, refuse: 'claim.request_invalid' };
|
|
56
|
+
if (!isClaimAmount(upToWei))
|
|
57
|
+
return { ok: false, refuse: 'claim.amount_invalid' };
|
|
58
|
+
const round = await this.pool.query(`select pool_id from rounds where id = $1`, [roundId]);
|
|
59
|
+
const row = round.rows[0];
|
|
60
|
+
if (!row)
|
|
61
|
+
return { ok: false, refuse: 'claim.no_such_round' };
|
|
62
|
+
// The deadline is decided before the hold so both agree on when it stops being spendable.
|
|
63
|
+
const now = this.now();
|
|
64
|
+
const deadline = BigInt(Math.floor(now / 1000) + DEADLINE_SECONDS);
|
|
65
|
+
// Idempotency and the clock are decided under the same player lock. If an accepted request is
|
|
66
|
+
// still committing at the buzzer, a retry waits for it and recovers it rather than observing
|
|
67
|
+
// no row outside the transaction and being refused as a new, late request.
|
|
68
|
+
const held = await this.ledger.claim(roundId, player, upToWei, requestId, deadline, now);
|
|
69
|
+
if (!held.ok)
|
|
70
|
+
return { ok: false, refuse: held.refuse };
|
|
71
|
+
// Another replica may have completed the request while this one waited for the balance lock.
|
|
72
|
+
if (held.claim.signature && held.claim.signer) {
|
|
73
|
+
return { ok: true, payload: payload(row.pool_id, held.claim) };
|
|
74
|
+
}
|
|
75
|
+
if (held.claim.status === 'released')
|
|
76
|
+
return { ok: false, refuse: 'claim.request_released' };
|
|
77
|
+
return this.finish(row.pool_id, held.claim);
|
|
78
|
+
}
|
|
79
|
+
async finish(poolId, claim) {
|
|
80
|
+
try {
|
|
81
|
+
const candidate = await this.signer.sign(claim.player, poolId, claim.deadline, claim.wei, claim.nonce);
|
|
82
|
+
// Never return bytes that were not durably recorded. Concurrent signers both return the one
|
|
83
|
+
// stored winner, so retries stay byte-identical even if an implementation signs randomly.
|
|
84
|
+
const completed = await this.ledger.completeClaim(claim.nonce, candidate.signature, candidate.signer);
|
|
85
|
+
if (!completed?.signature || !completed.signer)
|
|
86
|
+
throw new Error('claim could not be completed');
|
|
87
|
+
return { ok: true, payload: payload(poolId, completed) };
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// The hold was taken and nothing will ever be spent against it, so it goes back. Without
|
|
91
|
+
// this, a signer outage quietly eats a player's allowance for the rest of the round.
|
|
92
|
+
//
|
|
93
|
+
// If the release ALSO fails, the original error still wins. Replacing it with a database
|
|
94
|
+
// error would hide the reason and leave the nonce nowhere in the logs — so it is named here,
|
|
95
|
+
// because that number is what a person needs to give the allowance back by hand.
|
|
96
|
+
await this.ledger.releaseUnsigned(claim.nonce).catch((releaseError) => {
|
|
97
|
+
this.onStrandedHold?.(claim.nonce, releaseError);
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Give back every authorisation that expired unspent. */
|
|
103
|
+
releaseExpired() {
|
|
104
|
+
return this.ledger.releaseExpired(Math.floor(this.now() / 1000));
|
|
105
|
+
}
|
|
106
|
+
/** What this player could spend right now. */
|
|
107
|
+
availableWei(roundId, player) {
|
|
108
|
+
return this.ledger.availableWei(roundId, player);
|
|
109
|
+
}
|
|
110
|
+
/** Full authoritative balance for live snapshots. */
|
|
111
|
+
economyBalance(roundId, player) {
|
|
112
|
+
return this.ledger.economyBalance(roundId, player);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function payload(poolId, claim) {
|
|
116
|
+
if (!claim.signature || !claim.signer)
|
|
117
|
+
throw new Error('claim is not signed');
|
|
118
|
+
return {
|
|
119
|
+
buyer: claim.player,
|
|
120
|
+
poolId: poolId,
|
|
121
|
+
deadline: claim.deadline,
|
|
122
|
+
maxSpendWei: claim.wei,
|
|
123
|
+
nonce: claim.nonce,
|
|
124
|
+
signature: claim.signature,
|
|
125
|
+
signer: claim.signer,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../src/claims.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,MAAM,EAA+B,MAAM,aAAa,CAAC;AAGlE;;;;;;;;;;;GAWG;AAEH;mDACmD;AACnD,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,GAAG,MAAM,CAAC;AACrD,MAAM,gBAAgB,GAAG,0BAA0B,GAAG,KAAK,CAAC;AAC5D,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAEzD,sGAAsG;AACtG,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,IAAI,UAAU,CAAC;AACxE,CAAC;AAMD,MAAM,OAAO,MAAM;IAEE;IACA;IACA;IAEA;IAEA;IAPnB,YACmB,IAAU,EACV,MAAc,EACd,MAAqB;IACtC,kFAAkF;IACjE,cAAwD;IACzE,2FAA2F;IAC1E,MAAoB,IAAI,CAAC,GAAG;QAN5B,SAAI,GAAJ,IAAI,CAAM;QACV,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAe;QAErB,mBAAc,GAAd,cAAc,CAA0C;QAExD,QAAG,GAAH,GAAG,CAAyB;IAC5C,CAAC;IAEJ;;;;;;;OAOG;IACH,KAAK,CAAC,SAAS,CAAC,OAAe,EAAE,MAAgB,EAAE,OAAe,EAAE,SAAiB;QACnF,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QACxF,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;QAElF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAsB,0CAA0C,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAChH,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QAE9D,0FAA0F;QAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC;QACnE,8FAA8F;QAC9F,6FAA6F;QAC7F,2EAA2E;QAC3E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAExD,6FAA6F;QAC7F,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;QAC7F,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,KAAY;QAC/C,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACtC,KAAK,CAAC,MAAuB,EAC7B,MAAuB,EACvB,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,GAAG,EACT,KAAK,CAAC,KAAK,CACZ,CAAC;YACF,4FAA4F;YAC5F,0FAA0F;YAC1F,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;YACtG,IAAI,CAAC,SAAS,EAAE,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAChG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yFAAyF;YACzF,qFAAqF;YACrF,EAAE;YACF,yFAAyF;YACzF,6FAA6F;YAC7F,iFAAiF;YACjF,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,YAAqB,EAAE,EAAE;gBAC7E,IAAI,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;YACH,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,cAAc;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,8CAA8C;IAC9C,YAAY,CAAC,OAAe,EAAE,MAAgB;QAC5C,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED,qDAAqD;IACrD,cAAc,CAAC,OAAe,EAAE,MAAgB;QAC9C,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;CACF;AAED,SAAS,OAAO,CAAC,MAAc,EAAE,KAAY;IAC3C,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC9E,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,MAAuB;QACpC,MAAM,EAAE,MAAuB;QAC/B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,GAAG;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC"}
|
package/dist/demo.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { GameModule } from '@flayerlabs/gamemode-spec';
|
|
4
|
+
export interface DemoOptions<Config, State, Event, Action, PublicView, PlayerView> {
|
|
5
|
+
game: GameModule<Config, State, Event, Action, PublicView, PlayerView>;
|
|
6
|
+
config: Config;
|
|
7
|
+
/** Default `postgres://postgres:gamemode@127.0.0.1:55439/gamemode`, or `DATABASE_URL`. */
|
|
8
|
+
databaseUrl?: string;
|
|
9
|
+
/** How long before the round opens. Short, because someone is waiting. */
|
|
10
|
+
lobbyMs?: number;
|
|
11
|
+
roundMs?: number;
|
|
12
|
+
/** The cap a wallet may spend, as a real launch would set on chain. Default 0.025 ETH. */
|
|
13
|
+
walletCapWei?: bigint;
|
|
14
|
+
pointsPerDollar?: number;
|
|
15
|
+
usdPerEth?: number;
|
|
16
|
+
roundId?: string;
|
|
17
|
+
/** Exact origin where this demo gate will listen. Default `http://127.0.0.1:4000`. */
|
|
18
|
+
gateOrigin?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface Demo {
|
|
21
|
+
app: FastifyInstance;
|
|
22
|
+
roundId: string;
|
|
23
|
+
opensAt: number;
|
|
24
|
+
/** The throwaway wallet standing in for the player's real one. */
|
|
25
|
+
walletAddress: `0x${string}`;
|
|
26
|
+
/** Everything the fake wallet was asked to buy, in order. Empty until somebody buys. */
|
|
27
|
+
purchases: {
|
|
28
|
+
maxSpendWei: string;
|
|
29
|
+
at: number;
|
|
30
|
+
}[];
|
|
31
|
+
pool: pg.Pool;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Boot a gate, a round and a stand-in wallet.
|
|
35
|
+
*
|
|
36
|
+
* The caller owns serving their own game's files and calling `listen`, because how a game is built
|
|
37
|
+
* is the one part of this that genuinely differs between games.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createDemoGate<Config, State, Event, Action, PublicView, PlayerView>(options: DemoOptions<Config, State, Event, Action, PublicView, PlayerView>): Promise<Demo>;
|
|
40
|
+
//# sourceMappingURL=demo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AA+B5D,MAAM,WAAW,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU;IAC/E,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,eAAe,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,aAAa,EAAE,KAAK,MAAM,EAAE,CAAC;IAC7B,wFAAwF;IACxF,SAAS,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjD,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;CACf;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EACvF,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,GACzE,OAAO,CAAC,IAAI,CAAC,CA4Ef"}
|
package/dist/demo.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
3
|
+
import { createGate } from './server.js';
|
|
4
|
+
import { Room } from './room.js';
|
|
5
|
+
import { Ledger } from './ledger.js';
|
|
6
|
+
import { Claims } from './claims.js';
|
|
7
|
+
import { Sessions } from './sessions.js';
|
|
8
|
+
import { PayloadSigner } from './chain/signer.js';
|
|
9
|
+
import { migrate } from './store.js';
|
|
10
|
+
import { resolveEconomy } from './economy.js';
|
|
11
|
+
/**
|
|
12
|
+
* A whole gate on a laptop, with the chain taken out.
|
|
13
|
+
*
|
|
14
|
+
* Real gate, real database, real socket, real EIP-712 signatures. What is faked is the one thing a
|
|
15
|
+
* laptop cannot provide: a chain. The wallet is a throwaway key and the purchase is recorded rather
|
|
16
|
+
* than submitted, so every step up to and including the signature is the genuine one — which is
|
|
17
|
+
* what makes this a playtest rather than a mock-up.
|
|
18
|
+
*
|
|
19
|
+
* This lived inside the quiz example, where no other game could reach it. Nothing about it was ever
|
|
20
|
+
* quiz-specific.
|
|
21
|
+
*
|
|
22
|
+
* Not for production, and it refuses to pretend otherwise: the signing key is a constant, the pool
|
|
23
|
+
* id is invented, and no chain is ever consulted.
|
|
24
|
+
*/
|
|
25
|
+
/** A key nobody should ever fund. Constant so a restart does not change who you are. */
|
|
26
|
+
const DEMO_SIGNING_KEY = `0x${'11'.repeat(32)}`;
|
|
27
|
+
const DEMO_WALLET_KEY = `0x${'77'.repeat(32)}`;
|
|
28
|
+
const DEMO_POOL_ID = `0x${'ab'.repeat(32)}`;
|
|
29
|
+
const DEMO_CALCULATOR = `0x${'cd'.repeat(20)}`;
|
|
30
|
+
const ETH = 1000000000000000000n;
|
|
31
|
+
/**
|
|
32
|
+
* Boot a gate, a round and a stand-in wallet.
|
|
33
|
+
*
|
|
34
|
+
* The caller owns serving their own game's files and calling `listen`, because how a game is built
|
|
35
|
+
* is the one part of this that genuinely differs between games.
|
|
36
|
+
*/
|
|
37
|
+
export async function createDemoGate(options) {
|
|
38
|
+
const { game, config, databaseUrl = process.env.DATABASE_URL ?? 'postgres://postgres:gamemode@127.0.0.1:55439/gamemode', lobbyMs = 8_000, roundMs = 10 * 60_000, walletCapWei = ETH / 40n, pointsPerDollar = 100, usdPerEth = 3_000, roundId = 'dev', gateOrigin = 'http://127.0.0.1:4000', } = options;
|
|
39
|
+
// The same check the real gate runs at boot. A demo that silently used an impossible economy
|
|
40
|
+
// would teach a builder that their numbers work when they do not.
|
|
41
|
+
const economy = resolveEconomy({
|
|
42
|
+
maxPointsPerPlayer: game.rewardBounds(config).maxPointsPerPlayer,
|
|
43
|
+
walletCapWei,
|
|
44
|
+
pointsPerDollar,
|
|
45
|
+
usdPerEth,
|
|
46
|
+
});
|
|
47
|
+
if (!economy.ok) {
|
|
48
|
+
throw new Error(`That economy does not work: ${economy.problem}. Try ${economy.suggestedPointsPerDollar} points per dollar.`);
|
|
49
|
+
}
|
|
50
|
+
const pool = new pg.Pool({ connectionString: databaseUrl });
|
|
51
|
+
await migrate(pool);
|
|
52
|
+
// Same round id every run, so a reload is the same game rather than an accumulating pile.
|
|
53
|
+
await pool.query('delete from rounds where id = $1', [roundId]);
|
|
54
|
+
const opensAt = Date.now() + lobbyMs;
|
|
55
|
+
await Room.create(pool, game, {
|
|
56
|
+
id: roundId,
|
|
57
|
+
config,
|
|
58
|
+
seed: Math.floor(Math.random() * 1_000_000),
|
|
59
|
+
window: { opensAt, closesAt: opensAt + roundMs },
|
|
60
|
+
weiPerPoint: economy.economy.weiPerPoint,
|
|
61
|
+
walletCapWei,
|
|
62
|
+
poolId: DEMO_POOL_ID,
|
|
63
|
+
});
|
|
64
|
+
const app = createGate({
|
|
65
|
+
pool,
|
|
66
|
+
game,
|
|
67
|
+
config,
|
|
68
|
+
pointsPerDollar,
|
|
69
|
+
usdPerEth,
|
|
70
|
+
sessions: new Sessions(process.env.SESSION_SECRET ?? 'dev-secret-that-is-long-enough-to-pass', 'localhost', gateOrigin),
|
|
71
|
+
claims: new Claims(pool, new Ledger(pool), new PayloadSigner(DEMO_SIGNING_KEY, 8453, DEMO_CALCULATOR)),
|
|
72
|
+
// Anything, because this is a laptop and the browser is on some port nobody can predict.
|
|
73
|
+
allowedOrigins: true,
|
|
74
|
+
});
|
|
75
|
+
const wallet = privateKeyToAccount(DEMO_WALLET_KEY);
|
|
76
|
+
const purchases = [];
|
|
77
|
+
// Standing in for the page a game is embedded in. In production this is flaunch.gg holding the
|
|
78
|
+
// player's real wallet; here the signature is real and the purchase is only written down.
|
|
79
|
+
app.get('/dev/wallet', () => ({ address: wallet.address }));
|
|
80
|
+
app.post('/dev/wallet/sign', async (request) => ({
|
|
81
|
+
signature: await wallet.signMessage({ message: request.body.message }),
|
|
82
|
+
}));
|
|
83
|
+
app.post('/dev/wallet/buy', (request) => {
|
|
84
|
+
const { maxSpendWei } = request.body;
|
|
85
|
+
purchases.push({ maxSpendWei, at: Date.now() });
|
|
86
|
+
return { spentWei: maxSpendWei };
|
|
87
|
+
});
|
|
88
|
+
return { app, roundId, opensAt, walletAddress: wallet.address, purchases, pool };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=demo.js.map
|
package/dist/demo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning points into money, and refusing a round whose sums do not work.
|
|
3
|
+
*
|
|
4
|
+
* This file exists because of a real incident. A gate ran with a per-wallet cap twenty times the
|
|
5
|
+
* value its own deployment guide prescribed, and over 48 hours roughly 2 ETH went to wallets whose
|
|
6
|
+
* scores were in the bot range. Nothing was broken: every detector worked. One number in an
|
|
7
|
+
* environment variable was wrong, and no code was in a position to notice.
|
|
8
|
+
*
|
|
9
|
+
* So the arithmetic lives here, runs at round creation with the actual values, and refuses. A
|
|
10
|
+
* creator sets one human number — how many points a dollar is worth — and never sees wei.
|
|
11
|
+
*/
|
|
12
|
+
export interface EconomyInput {
|
|
13
|
+
/** The most one player can earn, declared by the game itself via `rewardBounds`. */
|
|
14
|
+
maxPointsPerPlayer: number;
|
|
15
|
+
/** The pool's on-chain per-wallet ceiling. The chain enforces this whatever we do. */
|
|
16
|
+
walletCapWei: bigint;
|
|
17
|
+
/** The creator's one knob. 100 means 100 points is worth a dollar. */
|
|
18
|
+
pointsPerDollar: number;
|
|
19
|
+
usdPerEth: number;
|
|
20
|
+
}
|
|
21
|
+
export interface Economy {
|
|
22
|
+
weiPerPoint: bigint;
|
|
23
|
+
/** What a flawless round pays. Always at or under `walletCapWei`, or this would not exist. */
|
|
24
|
+
maxPayoutWei: bigint;
|
|
25
|
+
}
|
|
26
|
+
export type EconomyResult = {
|
|
27
|
+
ok: true;
|
|
28
|
+
economy: Economy;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
problem: string;
|
|
32
|
+
suggestedPointsPerDollar: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the rate for one round, or refuse with the number that fixes it.
|
|
36
|
+
*
|
|
37
|
+
* The rule is single and total: **a flawless round must fit under the wallet cap.** If it does not,
|
|
38
|
+
* a player earns points the chain will not let them spend — the game shows a reward and the buy
|
|
39
|
+
* refuses it, which is the worst failure available because it looks like theft.
|
|
40
|
+
*
|
|
41
|
+
* Refusals carry the corrected `pointsPerDollar` rather than a diagnosis. An operator reading
|
|
42
|
+
* "raise it to 250" fixes this in one step; an operator reading "the cap binds below the ceiling"
|
|
43
|
+
* goes and reads this file.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveEconomy(input: EconomyInput): EconomyResult;
|
|
46
|
+
//# sourceMappingURL=economy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"economy.d.ts","sourceRoot":"","sources":["../src/economy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,wBAAwB,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CAwDjE"}
|
package/dist/economy.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning points into money, and refusing a round whose sums do not work.
|
|
3
|
+
*
|
|
4
|
+
* This file exists because of a real incident. A gate ran with a per-wallet cap twenty times the
|
|
5
|
+
* value its own deployment guide prescribed, and over 48 hours roughly 2 ETH went to wallets whose
|
|
6
|
+
* scores were in the bot range. Nothing was broken: every detector worked. One number in an
|
|
7
|
+
* environment variable was wrong, and no code was in a position to notice.
|
|
8
|
+
*
|
|
9
|
+
* So the arithmetic lives here, runs at round creation with the actual values, and refuses. A
|
|
10
|
+
* creator sets one human number — how many points a dollar is worth — and never sees wei.
|
|
11
|
+
*/
|
|
12
|
+
/** 1 ETH in wei. */
|
|
13
|
+
const WEI_PER_ETH = 1000000000000000000n;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the rate for one round, or refuse with the number that fixes it.
|
|
16
|
+
*
|
|
17
|
+
* The rule is single and total: **a flawless round must fit under the wallet cap.** If it does not,
|
|
18
|
+
* a player earns points the chain will not let them spend — the game shows a reward and the buy
|
|
19
|
+
* refuses it, which is the worst failure available because it looks like theft.
|
|
20
|
+
*
|
|
21
|
+
* Refusals carry the corrected `pointsPerDollar` rather than a diagnosis. An operator reading
|
|
22
|
+
* "raise it to 250" fixes this in one step; an operator reading "the cap binds below the ceiling"
|
|
23
|
+
* goes and reads this file.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveEconomy(input) {
|
|
26
|
+
const { maxPointsPerPlayer, walletCapWei, pointsPerDollar, usdPerEth } = input;
|
|
27
|
+
if (!Number.isFinite(pointsPerDollar) || pointsPerDollar <= 0) {
|
|
28
|
+
return { ok: false, problem: 'points per dollar must be a positive number', suggestedPointsPerDollar: 100 };
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isFinite(usdPerEth) || usdPerEth <= 0) {
|
|
31
|
+
return { ok: false, problem: 'the ETH price must be a positive number', suggestedPointsPerDollar: pointsPerDollar };
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isFinite(maxPointsPerPlayer) || maxPointsPerPlayer <= 0) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
problem: 'the game says a player can earn at most 0 points, so there is nothing to pay',
|
|
37
|
+
suggestedPointsPerDollar: pointsPerDollar,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (walletCapWei <= 0n) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
problem: 'this launch has no per-wallet spending cap, so nothing can be authorised',
|
|
44
|
+
suggestedPointsPerDollar: pointsPerDollar,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// How many points one ETH buys. Checked before it becomes a divisor: `pointsPerDollar` is
|
|
48
|
+
// guarded as positive above, but a small enough positive value still rounds this product to
|
|
49
|
+
// zero — and dividing by it threw `Division by zero` out of the one function whose entire job is
|
|
50
|
+
// to refuse a bad economy with a sentence an operator can act on.
|
|
51
|
+
const pointsPerEth = Math.round(usdPerEth * pointsPerDollar);
|
|
52
|
+
if (pointsPerEth <= 0) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
problem: 'one point would be worth more than a whole ETH, which cannot be what was meant',
|
|
56
|
+
suggestedPointsPerDollar: minimumPointsPerDollar(input),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const weiPerPoint = WEI_PER_ETH / BigInt(pointsPerEth);
|
|
60
|
+
if (weiPerPoint <= 0n) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
problem: 'each point would be worth less than the smallest unit of ETH',
|
|
64
|
+
suggestedPointsPerDollar: Math.max(1, Math.floor(pointsPerDollar / 1000)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const maxPayoutWei = BigInt(Math.floor(maxPointsPerPlayer)) * weiPerPoint;
|
|
68
|
+
if (maxPayoutWei > walletCapWei) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
problem: 'a perfect round would earn more than this launch lets one wallet spend',
|
|
72
|
+
suggestedPointsPerDollar: minimumPointsPerDollar(input),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { ok: true, economy: { weiPerPoint, maxPayoutWei } };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The smallest `pointsPerDollar` that keeps a flawless round inside the cap.
|
|
79
|
+
*
|
|
80
|
+
* Rounded up, then nudged until it actually passes: the division that produces it floors, and a
|
|
81
|
+
* value that is correct to the last wei but fails the check is worse than useless in an error
|
|
82
|
+
* message. Two steps is enough for any input — the loop is a guard against an off-by-one in the
|
|
83
|
+
* rounding, not an iterative search.
|
|
84
|
+
*/
|
|
85
|
+
function minimumPointsPerDollar(input) {
|
|
86
|
+
const { maxPointsPerPlayer, walletCapWei, usdPerEth } = input;
|
|
87
|
+
const points = BigInt(Math.floor(maxPointsPerPlayer));
|
|
88
|
+
const maxWeiPerPoint = walletCapWei / points;
|
|
89
|
+
if (maxWeiPerPoint <= 0n)
|
|
90
|
+
return Math.ceil(maxPointsPerPlayer);
|
|
91
|
+
let candidate = Math.ceil(Number(WEI_PER_ETH / maxWeiPerPoint) / usdPerEth);
|
|
92
|
+
for (let i = 0; i < 3; i++) {
|
|
93
|
+
const result = resolveEconomy({ ...input, pointsPerDollar: candidate });
|
|
94
|
+
if (result.ok)
|
|
95
|
+
return candidate;
|
|
96
|
+
candidate += 1;
|
|
97
|
+
}
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=economy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"economy.js","sourceRoot":"","sources":["../src/economy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FastifyInstance } from 'fastify';
|
|
2
|
+
import type { Pool } from 'pg';
|
|
3
|
+
export interface GameRegistryOptions {
|
|
4
|
+
pool: Pool;
|
|
5
|
+
/** The flaunch.gg origins allowed to resolve registrations in a browser. */
|
|
6
|
+
allowedOrigins: readonly string[];
|
|
7
|
+
/** Server-to-server bearer token held only by the Flaunch launch backend. */
|
|
8
|
+
writerToken: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The stable, Flaunch-owned public read service for chain + coin → game discovery.
|
|
12
|
+
*
|
|
13
|
+
* Creator gates cannot host this route: a page needs the registration to discover which creator
|
|
14
|
+
* gate to call. Registration writes remain a trusted launch-backend operation against the store.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createGameRegistry(options: GameRegistryOptions): FastifyInstance;
|
|
17
|
+
//# sourceMappingURL=game-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-registry.d.ts","sourceRoot":"","sources":["../src/game-registry.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAC;AAGxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAU/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,4EAA4E;IAC5E,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;CACrB;AA4BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CAqDhF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Fastify, {} from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { isAddress } from 'viem';
|
|
5
|
+
import { InvalidRegistration, RegistrationConflict, disableGameRegistration, pinGameRegistration, resolveGameRegistration, } from './registry.js';
|
|
6
|
+
const authorised = (header, token) => {
|
|
7
|
+
const supplied = header?.startsWith('Bearer ') ? header.slice('Bearer '.length) : '';
|
|
8
|
+
const digest = (value) => createHash('sha256').update(value).digest();
|
|
9
|
+
return timingSafeEqual(digest(supplied), digest(token));
|
|
10
|
+
};
|
|
11
|
+
const routeChainId = (value) => {
|
|
12
|
+
if (!/^[1-9][0-9]*$/.test(value))
|
|
13
|
+
return null;
|
|
14
|
+
const parsed = Number(value);
|
|
15
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
16
|
+
};
|
|
17
|
+
const internalRoute = (request, token) => {
|
|
18
|
+
if (!authorised(request.headers.authorization, token)) {
|
|
19
|
+
return { ok: false, status: 401, message: 'registry writer authentication failed' };
|
|
20
|
+
}
|
|
21
|
+
const { chainId: rawChainId, coin } = request.params;
|
|
22
|
+
const chainId = routeChainId(rawChainId);
|
|
23
|
+
return chainId === null
|
|
24
|
+
? { ok: false, status: 400, message: 'that is not a chain id' }
|
|
25
|
+
: { ok: true, chainId, coin };
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The stable, Flaunch-owned public read service for chain + coin → game discovery.
|
|
29
|
+
*
|
|
30
|
+
* Creator gates cannot host this route: a page needs the registration to discover which creator
|
|
31
|
+
* gate to call. Registration writes remain a trusted launch-backend operation against the store.
|
|
32
|
+
*/
|
|
33
|
+
export function createGameRegistry(options) {
|
|
34
|
+
if (options.writerToken.length < 32)
|
|
35
|
+
throw new RangeError('registry writer token must be at least 32 characters');
|
|
36
|
+
const app = Fastify({ bodyLimit: 1_024 });
|
|
37
|
+
void app.register(cors, { origin: [...options.allowedOrigins], methods: ['GET'] });
|
|
38
|
+
app.get('/games/:chainId/:coin', async (request, reply) => {
|
|
39
|
+
const { chainId: rawChainId, coin } = request.params;
|
|
40
|
+
const chainId = routeChainId(rawChainId);
|
|
41
|
+
if (chainId === null) {
|
|
42
|
+
return reply.code(400).send({ message: 'that is not a chain id' });
|
|
43
|
+
}
|
|
44
|
+
if (!isAddress(coin, { strict: false })) {
|
|
45
|
+
return reply.code(400).send({ message: 'that is not a coin address' });
|
|
46
|
+
}
|
|
47
|
+
const registration = await resolveGameRegistration(options.pool, chainId, coin);
|
|
48
|
+
return registration ?? reply.code(404).send({ message: 'that coin has no registered game' });
|
|
49
|
+
});
|
|
50
|
+
app.put('/internal/games/:chainId/:coin', async (request, reply) => {
|
|
51
|
+
const route = internalRoute(request, options.writerToken);
|
|
52
|
+
if (!route.ok)
|
|
53
|
+
return reply.code(route.status).send({ message: route.message });
|
|
54
|
+
const body = (request.body ?? {});
|
|
55
|
+
try {
|
|
56
|
+
const registration = await pinGameRegistration(options.pool, {
|
|
57
|
+
chainId: route.chainId,
|
|
58
|
+
coin: route.coin,
|
|
59
|
+
gameId: body.gameId,
|
|
60
|
+
gateOrigin: body.gateOrigin,
|
|
61
|
+
deployId: body.deployId,
|
|
62
|
+
});
|
|
63
|
+
return reply.send(registration);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof InvalidRegistration)
|
|
67
|
+
return reply.code(400).send({ message: error.message });
|
|
68
|
+
if (error instanceof RegistrationConflict) {
|
|
69
|
+
return reply.code(409).send({ message: 'that coin already has a different game' });
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
app.delete('/internal/games/:chainId/:coin', async (request, reply) => {
|
|
75
|
+
const route = internalRoute(request, options.writerToken);
|
|
76
|
+
if (!route.ok)
|
|
77
|
+
return reply.code(route.status).send({ message: route.message });
|
|
78
|
+
try {
|
|
79
|
+
await disableGameRegistration(options.pool, route.chainId, route.coin);
|
|
80
|
+
return reply.code(204).send();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof InvalidRegistration)
|
|
84
|
+
return reply.code(400).send({ message: error.message });
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return app;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=game-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-registry.js","sourceRoot":"","sources":["../src/game-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Public server-side surface for a production Game Mode gate. */
|
|
2
|
+
export { Claims, DEFAULT_SETTLEMENT_TAIL_MS, Room, Sessions, createGate, } from './server.js';
|
|
3
|
+
export type { Admission, AdmissionRequest, Admit, ConfigForLaunch, GateOptions, VerifiedLaunch, } from './server.js';
|
|
4
|
+
export { createTurnstileAdmit } from './turnstile.js';
|
|
5
|
+
export type { TurnstileAdmitOptions } from './turnstile.js';
|
|
6
|
+
export { Ledger } from './ledger.js';
|
|
7
|
+
export type { BalanceKey, Claim, ClaimResult } from './ledger.js';
|
|
8
|
+
export { CLAIM_AUTHORIZATION_TTL_MS } from './claims.js';
|
|
9
|
+
export { PayloadSigner, encodeHookData } from './chain/signer.js';
|
|
10
|
+
export type { SignedPayload } from './chain/signer.js';
|
|
11
|
+
export { Discovery, poolIdOf, verifyLaunch } from './chain/discover.js';
|
|
12
|
+
export type { ChainReader, Deployment, Launch, LaunchCheck, PoolKey, Refused } from './chain/discover.js';
|
|
13
|
+
export { Settlement } from './settlement.js';
|
|
14
|
+
export { resolveEconomy } from './economy.js';
|
|
15
|
+
export type { Economy, EconomyInput, EconomyResult } from './economy.js';
|
|
16
|
+
export { migrate } from './store.js';
|
|
17
|
+
export { createDemoGate } from './demo.js';
|
|
18
|
+
export type { Demo, DemoOptions } from './demo.js';
|
|
19
|
+
export { InvalidRegistration, RegistrationConflict, checkRegistration, disableGameRegistration, gateOrigin, pinGameRegistration, resolveGameRegistration, securityProfile, } from './registry.js';
|
|
20
|
+
export type { Registration, SecurityProfile } from './registry.js';
|
|
21
|
+
export { createGameRegistry } from './game-registry.js';
|
|
22
|
+
export type { GameRegistryOptions } from './game-registry.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,OAAO,EACL,MAAM,EACN,0BAA0B,EAC1B,IAAI,EACJ,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,eAAe,EACf,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC1G,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,UAAU,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Public server-side surface for a production Game Mode gate. */
|
|
2
|
+
export { Claims, DEFAULT_SETTLEMENT_TAIL_MS, Room, Sessions, createGate, } from './server.js';
|
|
3
|
+
export { createTurnstileAdmit } from './turnstile.js';
|
|
4
|
+
export { Ledger } from './ledger.js';
|
|
5
|
+
export { CLAIM_AUTHORIZATION_TTL_MS } from './claims.js';
|
|
6
|
+
export { PayloadSigner, encodeHookData } from './chain/signer.js';
|
|
7
|
+
export { Discovery, poolIdOf, verifyLaunch } from './chain/discover.js';
|
|
8
|
+
export { Settlement } from './settlement.js';
|
|
9
|
+
export { resolveEconomy } from './economy.js';
|
|
10
|
+
export { migrate } from './store.js';
|
|
11
|
+
export { createDemoGate } from './demo.js';
|
|
12
|
+
export { InvalidRegistration, RegistrationConflict, checkRegistration, disableGameRegistration, gateOrigin, pinGameRegistration, resolveGameRegistration, securityProfile, } from './registry.js';
|
|
13
|
+
export { createGameRegistry } from './game-registry.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,OAAO,EACL,MAAM,EACN,0BAA0B,EAC1B,IAAI,EACJ,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC;AASrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,UAAU,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|