@flayerlabs/gamemode-gate 0.5.6 → 0.7.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/DEPLOY.md +31 -3
- package/README.md +10 -5
- package/dist/chain/chains.d.ts +14 -6
- package/dist/chain/chains.d.ts.map +1 -1
- package/dist/chain/chains.js +11 -5
- package/dist/chain/chains.js.map +1 -1
- package/dist/chain/discover.d.ts +61 -3
- package/dist/chain/discover.d.ts.map +1 -1
- package/dist/chain/discover.js +181 -21
- package/dist/chain/discover.js.map +1 -1
- package/dist/chain/secure.d.ts +28 -0
- package/dist/chain/secure.d.ts.map +1 -1
- package/dist/chain/secure.js +55 -20
- package/dist/chain/secure.js.map +1 -1
- package/dist/chain/signer.d.ts +19 -13
- package/dist/chain/signer.d.ts.map +1 -1
- package/dist/chain/signer.js +11 -10
- package/dist/chain/signer.js.map +1 -1
- package/dist/claims.d.ts +14 -30
- package/dist/claims.d.ts.map +1 -1
- package/dist/claims.js +55 -76
- package/dist/claims.js.map +1 -1
- package/dist/demo.d.ts +1 -1
- package/dist/demo.d.ts.map +1 -1
- package/dist/demo.js +3 -3
- package/dist/demo.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +39 -45
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +71 -172
- package/dist/ledger.js.map +1 -1
- package/dist/main.d.ts +4 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -6
- package/dist/main.js.map +1 -1
- package/dist/room.d.ts +16 -0
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +29 -6
- package/dist/room.js.map +1 -1
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +43 -34
- package/dist/server.js.map +1 -1
- package/dist/settlement.d.ts +0 -3
- package/dist/settlement.d.ts.map +1 -1
- package/dist/settlement.js +12 -44
- package/dist/settlement.js.map +1 -1
- package/dist/standings.d.ts.map +1 -1
- package/dist/standings.js +7 -1
- package/dist/standings.js.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +36 -19
- package/dist/store.js.map +1 -1
- package/dist/version.js +2 -2
- package/package.json +2 -2
- package/src/chain/chains.ts +17 -7
- package/src/chain/discover.ts +212 -21
- package/src/chain/secure.ts +75 -31
- package/src/chain/signer.ts +24 -30
- package/src/claims.ts +54 -78
- package/src/demo.ts +5 -5
- package/src/index.ts +6 -6
- package/src/ledger.ts +100 -248
- package/src/main.ts +11 -6
- package/src/room.ts +29 -3
- package/src/server.ts +58 -34
- package/src/settlement.ts +12 -43
- package/src/standings.ts +9 -1
- package/src/store.ts +36 -19
- package/src/version.ts +2 -2
package/src/claims.ts
CHANGED
|
@@ -2,26 +2,23 @@ import type { Pool } from 'pg';
|
|
|
2
2
|
import type { PlayerId } from '@flayerlabs/gamemode-spec';
|
|
3
3
|
import type { EconomyBalance } from '@flayerlabs/gamemode-spec/live';
|
|
4
4
|
import { maxUint256 } from 'viem';
|
|
5
|
-
import { Ledger, type
|
|
5
|
+
import { Ledger, type Authorisation as StoredAuthorisation } from './ledger.js';
|
|
6
6
|
import type { PayloadSigner, SignedPayload } from './chain/signer.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Turning points into something a player can actually spend.
|
|
10
10
|
*
|
|
11
11
|
* Two parties have to agree before a buy can happen, and they check different things. The ledger
|
|
12
|
-
* knows what this player earned and what
|
|
13
|
-
*
|
|
14
|
-
* authorisation exist.
|
|
12
|
+
* knows what this player earned and what the chain says they have spent. The signer knows whether
|
|
13
|
+
* it is willing to vouch for this pool at all. Only when both say yes does an authorisation exist.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* What gets signed is a ceiling, not a purchase: the most this wallet may ever have spent on the
|
|
16
|
+
* pool. The calculator compares it against its own running total, so the gate reserves nothing
|
|
17
|
+
* and a signature that is never used costs nobody anything. The one thing this file must get
|
|
18
|
+
* right is idempotency — a retried request must receive the same bytes — and that is a primary
|
|
19
|
+
* key, not a lock.
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
/** How long an authorisation stays valid. Long enough to approve in a wallet, short enough that an
|
|
22
|
-
* abandoned one does not sit around spendable. */
|
|
23
|
-
export const CLAIM_AUTHORIZATION_TTL_MS = 5 * 60_000;
|
|
24
|
-
const DEADLINE_SECONDS = CLAIM_AUTHORIZATION_TTL_MS / 1_000;
|
|
25
22
|
const REQUEST_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
26
23
|
|
|
27
24
|
/** Request ids cross an unauthenticated parser before they reach Postgres, so bound them here too. */
|
|
@@ -35,7 +32,7 @@ export function isClaimAmount(value: unknown): value is bigint {
|
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
export type Authorisation =
|
|
38
|
-
| { ok: true; payload: SignedPayload }
|
|
35
|
+
| { ok: true; payload: SignedPayload; /** Exact-in amount for this buy, clamped to what was available. */ spend: bigint }
|
|
39
36
|
| { ok: false; refuse: string };
|
|
40
37
|
|
|
41
38
|
export class Claims {
|
|
@@ -43,76 +40,68 @@ export class Claims {
|
|
|
43
40
|
private readonly pool: Pool,
|
|
44
41
|
private readonly ledger: Ledger,
|
|
45
42
|
private readonly signer: PayloadSigner,
|
|
46
|
-
/** Called when a hold could neither be signed nor released, and needs a human. */
|
|
47
|
-
private readonly onStrandedHold?: (nonce: bigint, error: unknown) => void,
|
|
48
43
|
/** An internal seam for exact window-boundary tests. Production uses the process clock. */
|
|
49
44
|
private readonly now: () => number = Date.now,
|
|
50
45
|
) {}
|
|
51
46
|
|
|
52
47
|
/**
|
|
53
|
-
* Authorise a player to spend up to `
|
|
48
|
+
* Authorise a player to spend up to `spendWei`, or explain why not.
|
|
54
49
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
50
|
+
* Returns the largest amount available rather than refusing an over-ask: a player pressing buy
|
|
51
|
+
* wants to spend what they have, and telling them "you asked for too much" when they asked for
|
|
52
|
+
* everything is a worse product than giving them everything. The ceiling in the signature is the
|
|
53
|
+
* same either way — it is what they have earned, not what they asked for.
|
|
54
|
+
*
|
|
55
|
+
* The ledger's view of spend is a conservative mirror of the chain's. Being behind it means
|
|
56
|
+
* signing a ceiling the calculator will refuse at the swap, which to a player is
|
|
57
|
+
* indistinguishable from being robbed; settlement keeps the gap to one reconciliation pass.
|
|
59
58
|
*/
|
|
60
|
-
async authorise(roundId: string, player: PlayerId,
|
|
59
|
+
async authorise(roundId: string, player: PlayerId, spendWei: bigint, requestId: string): Promise<Authorisation> {
|
|
61
60
|
if (!isClaimRequestId(requestId)) return { ok: false, refuse: 'claim.request_invalid' };
|
|
62
|
-
if (!isClaimAmount(
|
|
61
|
+
if (!isClaimAmount(spendWei)) return { ok: false, refuse: 'claim.amount_invalid' };
|
|
63
62
|
|
|
64
63
|
const round = await this.pool.query<{ pool_id: string }>(`select pool_id from rounds where id = $1`, [roundId]);
|
|
65
64
|
const row = round.rows[0];
|
|
66
65
|
if (!row) return { ok: false, refuse: 'claim.no_such_round' };
|
|
66
|
+
const poolId = row.pool_id as `0x${string}`;
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
// still committing at the buzzer, a retry waits for it and recovers it rather than observing
|
|
73
|
-
// no row outside the transaction and being refused as a new, late request.
|
|
74
|
-
const held = await this.ledger.claim(roundId, player, upToWei, requestId, deadline, now);
|
|
75
|
-
if (!held.ok) return { ok: false, refuse: held.refuse };
|
|
68
|
+
// An existing request deliberately wins over the clock and the current balance: it was issued
|
|
69
|
+
// under the earlier call's window, and a retry must get the same answer.
|
|
70
|
+
const existing = await this.ledger.findAuthorisation(roundId, player, requestId);
|
|
71
|
+
if (existing) return this.answer(poolId, existing, spendWei);
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
return { ok: true, payload: payload(row.pool_id, held.claim) };
|
|
80
|
-
}
|
|
81
|
-
if (held.claim.status === 'released') return { ok: false, refuse: 'claim.request_released' };
|
|
82
|
-
return this.finish(row.pool_id, held.claim);
|
|
83
|
-
}
|
|
73
|
+
const quote = await this.ledger.quote(roundId, player, this.now());
|
|
74
|
+
if (!quote.ok) return { ok: false, refuse: quote.refuse };
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return { ok: true, payload: payload(poolId, completed) };
|
|
99
|
-
} catch (error) {
|
|
100
|
-
// The hold was taken and nothing will ever be spent against it, so it goes back. Without
|
|
101
|
-
// this, a signer outage quietly eats a player's allowance for the rest of the round.
|
|
102
|
-
//
|
|
103
|
-
// If the release ALSO fails, the original error still wins. Replacing it with a database
|
|
104
|
-
// error would hide the reason and leave the nonce nowhere in the logs — so it is named here,
|
|
105
|
-
// because that number is what a person needs to give the allowance back by hand.
|
|
106
|
-
await this.ledger.releaseUnsigned(claim.nonce).catch((releaseError: unknown) => {
|
|
107
|
-
this.onStrandedHold?.(claim.nonce, releaseError);
|
|
108
|
-
});
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
76
|
+
const spend = spendWei < quote.available ? spendWei : quote.available;
|
|
77
|
+
const signed = await this.signer.sign(player as `0x${string}`, poolId, quote.deadlineSeconds, quote.ceiling);
|
|
78
|
+
const stored = await this.ledger.storeAuthorisation(roundId, {
|
|
79
|
+
player,
|
|
80
|
+
requestId,
|
|
81
|
+
requestedWei: spendWei,
|
|
82
|
+
spendCeilingWei: signed.spendCeilingWei,
|
|
83
|
+
spendWei: spend,
|
|
84
|
+
deadline: signed.deadline,
|
|
85
|
+
signature: signed.signature,
|
|
86
|
+
signer: signed.signer,
|
|
87
|
+
});
|
|
88
|
+
return this.answer(poolId, stored, spendWei);
|
|
111
89
|
}
|
|
112
90
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return
|
|
91
|
+
private answer(poolId: `0x${string}`, stored: StoredAuthorisation, requestedWei: bigint): Authorisation {
|
|
92
|
+
if (stored.requestedWei !== requestedWei) return { ok: false, refuse: 'claim.request_mismatch' };
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
payload: {
|
|
96
|
+
buyer: stored.player as `0x${string}`,
|
|
97
|
+
poolId,
|
|
98
|
+
deadline: stored.deadline,
|
|
99
|
+
spendCeilingWei: stored.spendCeilingWei,
|
|
100
|
+
signature: stored.signature,
|
|
101
|
+
signer: stored.signer,
|
|
102
|
+
},
|
|
103
|
+
spend: stored.spendWei,
|
|
104
|
+
};
|
|
116
105
|
}
|
|
117
106
|
|
|
118
107
|
/** What this player could spend right now. */
|
|
@@ -125,16 +114,3 @@ export class Claims {
|
|
|
125
114
|
return this.ledger.economyBalance(roundId, player);
|
|
126
115
|
}
|
|
127
116
|
}
|
|
128
|
-
|
|
129
|
-
function payload(poolId: string, claim: Claim): SignedPayload {
|
|
130
|
-
if (!claim.signature || !claim.signer) throw new Error('claim is not signed');
|
|
131
|
-
return {
|
|
132
|
-
buyer: claim.player as `0x${string}`,
|
|
133
|
-
poolId: poolId as `0x${string}`,
|
|
134
|
-
deadline: claim.deadline,
|
|
135
|
-
maxSpendWei: claim.wei,
|
|
136
|
-
nonce: claim.nonce,
|
|
137
|
-
signature: claim.signature,
|
|
138
|
-
signer: claim.signer,
|
|
139
|
-
};
|
|
140
|
-
}
|
package/src/demo.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface Demo {
|
|
|
62
62
|
/** The throwaway wallet standing in for the player's real one. */
|
|
63
63
|
walletAddress: `0x${string}`;
|
|
64
64
|
/** Everything the fake wallet was asked to buy, in order. Empty until somebody buys. */
|
|
65
|
-
purchases: {
|
|
65
|
+
purchases: { spend: string; at: number }[];
|
|
66
66
|
pool: pg.Pool;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -160,7 +160,7 @@ export async function createDemoGate<Config, State, Event, Action, PublicView, P
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
const wallet = privateKeyToAccount(DEMO_WALLET_KEY);
|
|
163
|
-
const purchases: {
|
|
163
|
+
const purchases: { spend: string; at: number }[] = [];
|
|
164
164
|
|
|
165
165
|
// Standing in for the page a game is embedded in. In production this is flaunch.gg holding the
|
|
166
166
|
// player's real wallet; here the signature is real and the purchase is only written down.
|
|
@@ -169,9 +169,9 @@ export async function createDemoGate<Config, State, Event, Action, PublicView, P
|
|
|
169
169
|
signature: await wallet.signMessage({ message: (request.body as { message: string }).message }),
|
|
170
170
|
}));
|
|
171
171
|
app.post('/dev/wallet/buy', (request) => {
|
|
172
|
-
const {
|
|
173
|
-
purchases.push({
|
|
174
|
-
return { spent:
|
|
172
|
+
const { spend } = request.body as { spend: string };
|
|
173
|
+
purchases.push({ spend, at: Date.now() });
|
|
174
|
+
return { spent: spend };
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
return { app, roundId, opensAt, walletAddress: wallet.address, purchases, pool };
|
package/src/index.ts
CHANGED
|
@@ -23,12 +23,11 @@ export { createTurnstileAdmit } from './turnstile.js';
|
|
|
23
23
|
export type { TurnstileAdmitOptions } from './turnstile.js';
|
|
24
24
|
|
|
25
25
|
export { Ledger } from './ledger.js';
|
|
26
|
-
export type {
|
|
27
|
-
export { CLAIM_AUTHORIZATION_TTL_MS } from './claims.js';
|
|
26
|
+
export type { Authorisation, Quote } from './ledger.js';
|
|
28
27
|
export { PayloadSigner, encodeHookData } from './chain/signer.js';
|
|
29
28
|
export type { SignedPayload } from './chain/signer.js';
|
|
30
|
-
export { Discovery, poolIdOf, verifyLaunch } from './chain/discover.js';
|
|
31
|
-
export type { ChainReader, Deployment, Launch, LaunchCheck, PoolKey, Refused } from './chain/discover.js';
|
|
29
|
+
export { Discovery, WiringCache, poolIdOf, resolvePositionManager, verifyLaunch } from './chain/discover.js';
|
|
30
|
+
export type { ChainReader, Deployment, Launch, LaunchCheck, PoolKey, Refused, WiringCheck } from './chain/discover.js';
|
|
32
31
|
export { Settlement } from './settlement.js';
|
|
33
32
|
export { Standings } from './standings.js';
|
|
34
33
|
export { ETH_DECIMALS, resolveEconomy, unitsPerToken } from './economy.js';
|
|
@@ -66,12 +65,13 @@ export type { VerifiedPlayerJoin, VerifyPlayerJoinTicketOptions } from './player
|
|
|
66
65
|
*/
|
|
67
66
|
export { startGate, resolveGateEnv } from './main.js';
|
|
68
67
|
export type { ResolvedGateConfig, SpendTokenPricing, StartedGate, StartGateOverrides } from './main.js';
|
|
69
|
-
export type { GateAnnouncement, ServedGateAnnouncement } from './server.js';
|
|
68
|
+
export type { GateAnnouncement, PositionManagerPolicy, ServedGateAnnouncement } from './server.js';
|
|
70
69
|
export { GATE_MARKER, GATE_VERSION } from './version.js';
|
|
71
70
|
export { NotSignedIn } from './sessions.js';
|
|
72
71
|
export { DEPLOYMENTS, PAIRED_DEPLOYMENTS, deploymentFor } from './chain/chains.js';
|
|
73
72
|
export type { FlaunchVariant, GateDeployment } from './chain/chains.js';
|
|
74
|
-
export { POSITION_MANAGER_ROLE, assertSecureConfig } from './chain/secure.js';
|
|
73
|
+
export { POSITION_MANAGER_ROLE, assertSecureConfig, checkPositionManagerWiring } from './chain/secure.js';
|
|
74
|
+
export type { PositionManagerWiring } from './chain/secure.js';
|
|
75
75
|
export { RegistryPrices, minimumWalletCap, withHeadroom } from './chain/registry-prices.js';
|
|
76
76
|
export type { AnnounceOptions, RegistryPricesOptions, RegistryQuote, RegistryReader } from './chain/registry-prices.js';
|
|
77
77
|
|