@flayerlabs/gamemode-gate 0.1.0 → 0.3.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/README.md +81 -2
- package/dist/bearer.d.ts +3 -0
- package/dist/bearer.d.ts.map +1 -0
- package/dist/bearer.js +8 -0
- package/dist/bearer.js.map +1 -0
- package/dist/demo.d.ts.map +1 -1
- package/dist/demo.js +6 -1
- package/dist/demo.js.map +1 -1
- package/dist/economy.d.ts +9 -0
- package/dist/economy.d.ts.map +1 -1
- package/dist/economy.js +2 -2
- package/dist/economy.js.map +1 -1
- package/dist/game-registry.d.ts.map +1 -1
- package/dist/game-registry.js +6 -8
- package/dist/game-registry.js.map +1 -1
- package/dist/game-server-awards.d.ts +76 -0
- package/dist/game-server-awards.d.ts.map +1 -0
- package/dist/game-server-awards.js +172 -0
- package/dist/game-server-awards.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +0 -2
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +0 -7
- package/dist/ledger.js.map +1 -1
- package/dist/player-join-tickets.d.ts +29 -0
- package/dist/player-join-tickets.d.ts.map +1 -0
- package/dist/player-join-tickets.js +112 -0
- package/dist/player-join-tickets.js.map +1 -0
- package/dist/registry.d.ts +5 -9
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +24 -4
- package/dist/registry.js.map +1 -1
- package/dist/room.d.ts +8 -2
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +50 -9
- package/dist/room.js.map +1 -1
- package/dist/server.d.ts +16 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +162 -10
- package/dist/server.js.map +1 -1
- package/dist/sessions.d.ts +2 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +4 -0
- package/dist/sessions.js.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +53 -0
- package/dist/store.js.map +1 -1
- package/package.json +2 -2
- package/src/bearer.ts +8 -0
- package/src/demo.ts +6 -1
- package/src/economy.ts +11 -2
- package/src/game-registry.ts +12 -10
- package/src/game-server-awards.ts +285 -0
- package/src/index.ts +13 -0
- package/src/ledger.ts +0 -10
- package/src/player-join-tickets.ts +151 -0
- package/src/registry.ts +34 -10
- package/src/room.ts +70 -9
- package/src/server.ts +231 -22
- package/src/sessions.ts +5 -0
- package/src/store.ts +53 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @flayerlabs/gamemode-gate
|
|
2
2
|
|
|
3
|
-
Run
|
|
3
|
+
Run authoritative Game Mode rooms, spending allowance, admission and game discovery. You can run
|
|
4
|
+
SDK rules or accept point awards from an existing authoritative game server.
|
|
4
5
|
|
|
5
6
|
## Requirements
|
|
6
7
|
|
|
@@ -46,7 +47,12 @@ instead of submitting them to a chain. Do not use `createDemoGate` in production
|
|
|
46
47
|
|
|
47
48
|
## Build a production gate
|
|
48
49
|
|
|
49
|
-
Run `migrate(pool)` before the gate accepts traffic.
|
|
50
|
+
Run `migrate(pool)` before the gate accepts traffic.
|
|
51
|
+
|
|
52
|
+
If you already ran the pre-release schema, run `migrate(pool)` again. An empty setup upgrades in
|
|
53
|
+
place. Delete any pre-release test rounds first because their game ID and point limit are unknown.
|
|
54
|
+
|
|
55
|
+
A `createGate` setup needs:
|
|
50
56
|
|
|
51
57
|
- `Sessions` configured with the page domain and the gate's exact public origin
|
|
52
58
|
- `Claims`, `Ledger` and `PayloadSigner` for spending authorisations
|
|
@@ -65,6 +71,79 @@ checks session creation. Game action and claim policy remain the gate operator's
|
|
|
65
71
|
Read the [production gate guide](https://github.com/flayerlabs/gamemode-sdk/blob/main/docs/guides/run-a-gate.md)
|
|
66
72
|
before deploying a gate.
|
|
67
73
|
|
|
74
|
+
## Connect an existing game server
|
|
75
|
+
|
|
76
|
+
Use `createGameServerGate()` when your server already owns gameplay and scoring:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import {
|
|
80
|
+
createGameServerGate,
|
|
81
|
+
verifyPlayerJoinTicket,
|
|
82
|
+
} from '@flayerlabs/gamemode-gate'
|
|
83
|
+
|
|
84
|
+
const awardToken = process.env.AWARD_TOKEN
|
|
85
|
+
if (!awardToken || awardToken.length < 32) {
|
|
86
|
+
throw new Error('AWARD_TOKEN must contain at least 32 characters')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const app = createGameServerGate({
|
|
90
|
+
pool,
|
|
91
|
+
sessions,
|
|
92
|
+
claims,
|
|
93
|
+
discovery,
|
|
94
|
+
settlement,
|
|
95
|
+
gameId: 'my-game',
|
|
96
|
+
maxPointsPerPlayer: 10_000,
|
|
97
|
+
awardToken,
|
|
98
|
+
pointsPerDollar: 500,
|
|
99
|
+
usdPerEth: 3_000,
|
|
100
|
+
allowedOrigins: ['https://game.example'],
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Generate a random 256-bit award token, for example with `openssl rand -hex 32`. Keep it on the gate
|
|
105
|
+
and authoritative game server. Never send it to the browser or use a memorable value.
|
|
106
|
+
|
|
107
|
+
Use a stable, distinct `gameId` for each game. It scopes durable rounds and awards when games share
|
|
108
|
+
a PostgreSQL database.
|
|
109
|
+
|
|
110
|
+
The browser calls `joinTicket()` after it joins the economy and sends the opaque result as its first
|
|
111
|
+
game-server socket message. Verify it before accepting gameplay:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const playerJoin = verifyPlayerJoinTicket(message.ticket, {
|
|
115
|
+
awardToken,
|
|
116
|
+
gameId: 'my-game',
|
|
117
|
+
roundId,
|
|
118
|
+
audience: 'https://gate.example',
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
if (!playerJoin || usedTicketIds.has(playerJoin.jti)) {
|
|
122
|
+
socket.close(4401, 'invalid join ticket')
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
usedTicketIds.add(playerJoin.jti)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The default audience is the gate origin. This works when a reverse proxy serves the game WebSocket
|
|
129
|
+
from a path on that origin. For a separate origin, pass the same reviewed `gameServerOrigins` list
|
|
130
|
+
to `createGameServerGate()` and the platform registration, then use that exact origin as the ticket
|
|
131
|
+
audience. A ticket is short lived; also reject replays and supersede an earlier connection for the
|
|
132
|
+
same `playerJoin.player`.
|
|
133
|
+
|
|
134
|
+
Your server adds points with `POST /internal/rounds/:id/awards`. The gate checks the token, event
|
|
135
|
+
ID, wallet, round window and per-player limit before it changes the ledger. It also makes identical
|
|
136
|
+
retries safe.
|
|
137
|
+
|
|
138
|
+
`GET /internal/rounds/:id/players/:player` reports what one wallet has earned and how much more it
|
|
139
|
+
may earn, so your server can refuse a seat before the player takes one. That read is advisory; the
|
|
140
|
+
award route still enforces the ceiling.
|
|
141
|
+
|
|
142
|
+
Use the [external game-server steps](https://github.com/flayerlabs/gamemode-sdk/blob/main/docs/guides/run-a-gate.md#connect-an-existing-game-server)
|
|
143
|
+
for the request format, responses and deployment checks. The
|
|
144
|
+
[runnable external-server example](https://github.com/flayerlabs/gamemode-sdk/tree/main/examples/external-server)
|
|
145
|
+
shows the complete browser, WebSocket and award flow. Do not call the ledger or signer directly.
|
|
146
|
+
|
|
68
147
|
## Run the central game registry
|
|
69
148
|
|
|
70
149
|
`createGameRegistry` provides:
|
package/dist/bearer.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIpF"}
|
package/dist/bearer.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/** Compare a server-to-server bearer token without leaking how much of it matched. */
|
|
3
|
+
export function hasBearerToken(header, expected) {
|
|
4
|
+
const supplied = header?.startsWith('Bearer ') ? header.slice('Bearer '.length) : '';
|
|
5
|
+
const digest = (value) => createHash('sha256').update(value).digest();
|
|
6
|
+
return timingSafeEqual(digest(supplied), digest(expected));
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=bearer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer.js","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D,sFAAsF;AACtF,MAAM,UAAU,cAAc,CAAC,MAA0B,EAAE,QAAgB;IACzE,MAAM,QAAQ,GAAG,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;IACtF,OAAO,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC7D,CAAC"}
|
package/dist/demo.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,CAiFf"}
|
package/dist/demo.js
CHANGED
|
@@ -38,8 +38,9 @@ export async function createDemoGate(options) {
|
|
|
38
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
39
|
// The same check the real gate runs at boot. A demo that silently used an impossible economy
|
|
40
40
|
// would teach a builder that their numbers work when they do not.
|
|
41
|
+
const maxPointsPerPlayer = game.rewardBounds(config).maxPointsPerPlayer;
|
|
41
42
|
const economy = resolveEconomy({
|
|
42
|
-
maxPointsPerPlayer
|
|
43
|
+
maxPointsPerPlayer,
|
|
43
44
|
walletCapWei,
|
|
44
45
|
pointsPerDollar,
|
|
45
46
|
usdPerEth,
|
|
@@ -58,7 +59,11 @@ export async function createDemoGate(options) {
|
|
|
58
59
|
seed: Math.floor(Math.random() * 1_000_000),
|
|
59
60
|
window: { opensAt, closesAt: opensAt + roundMs },
|
|
60
61
|
weiPerPoint: economy.economy.weiPerPoint,
|
|
62
|
+
pointsPerDollar: economy.economy.pointsPerDollar,
|
|
63
|
+
usdPerEth: economy.economy.usdPerEth,
|
|
61
64
|
walletCapWei,
|
|
65
|
+
maxPointsPerPlayer,
|
|
66
|
+
scoreAuthority: 'rules',
|
|
62
67
|
poolId: DEMO_POOL_ID,
|
|
63
68
|
});
|
|
64
69
|
const app = createGate({
|
package/dist/demo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;GAaG;AAEH,wFAAwF;AACxF,MAAM,gBAAgB,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAW,CAAC;AACzD,MAAM,eAAe,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAW,CAAC;AACxD,MAAM,YAAY,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAW,CAAC;AACrD,MAAM,eAAe,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAW,CAAC;AA8BxD,MAAM,GAAG,GAAG,oBAA0B,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA0E;IAE1E,MAAM,EACJ,IAAI,EACJ,MAAM,EACN,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,uDAAuD,EACjG,OAAO,GAAG,KAAK,EACf,OAAO,GAAG,EAAE,GAAG,MAAM,EACrB,YAAY,GAAG,GAAG,GAAG,GAAG,EACxB,eAAe,GAAG,GAAG,EACrB,SAAS,GAAG,KAAK,EACjB,OAAO,GAAG,KAAK,EACf,UAAU,GAAG,uBAAuB,GACrC,GAAG,OAAO,CAAC;IAEZ,6FAA6F;IAC7F,kEAAkE;IAClE,MAAM,
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"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"}
|
package/dist/economy.d.ts
CHANGED
|
@@ -22,6 +22,15 @@ export interface Economy {
|
|
|
22
22
|
weiPerPoint: bigint;
|
|
23
23
|
/** What a flawless round pays. Always at or under `walletCapWei`, or this would not exist. */
|
|
24
24
|
maxPayoutWei: bigint;
|
|
25
|
+
/**
|
|
26
|
+
* The two inputs, returned beside the rate they produced.
|
|
27
|
+
*
|
|
28
|
+
* They travel together so a caller cannot persist a rate from one calculation and a dollar
|
|
29
|
+
* figure from another. Both are process-wide settings, so the only place they are true of a
|
|
30
|
+
* particular round is the moment that round's rate was fixed.
|
|
31
|
+
*/
|
|
32
|
+
pointsPerDollar: number;
|
|
33
|
+
usdPerEth: number;
|
|
25
34
|
}
|
|
26
35
|
export type EconomyResult = {
|
|
27
36
|
ok: true;
|
package/dist/economy.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;IACrB;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;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
CHANGED
|
@@ -30,7 +30,7 @@ export function resolveEconomy(input) {
|
|
|
30
30
|
if (!Number.isFinite(usdPerEth) || usdPerEth <= 0) {
|
|
31
31
|
return { ok: false, problem: 'the ETH price must be a positive number', suggestedPointsPerDollar: pointsPerDollar };
|
|
32
32
|
}
|
|
33
|
-
if (!Number.
|
|
33
|
+
if (!Number.isSafeInteger(maxPointsPerPlayer) || maxPointsPerPlayer <= 0) {
|
|
34
34
|
return {
|
|
35
35
|
ok: false,
|
|
36
36
|
problem: 'the game says a player can earn at most 0 points, so there is nothing to pay',
|
|
@@ -72,7 +72,7 @@ export function resolveEconomy(input) {
|
|
|
72
72
|
suggestedPointsPerDollar: minimumPointsPerDollar(input),
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
return { ok: true, economy: { weiPerPoint, maxPayoutWei } };
|
|
75
|
+
return { ok: true, economy: { weiPerPoint, maxPayoutWei, pointsPerDollar, usdPerEth } };
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* The smallest `pointsPerDollar` that keeps a flawless round inside the cap.
|
package/dist/economy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"economy.js","sourceRoot":"","sources":["../src/economy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,oBAAoB;AACpB,MAAM,WAAW,GAAG,oBAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"economy.js","sourceRoot":"","sources":["../src/economy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +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;
|
|
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;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAW/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;AAsBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CA6DhF"}
|
package/dist/game-registry.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import Fastify, {} from 'fastify';
|
|
2
2
|
import cors from '@fastify/cors';
|
|
3
|
-
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
4
3
|
import { isAddress } from 'viem';
|
|
4
|
+
import { hasBearerToken } from './bearer.js';
|
|
5
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
6
|
const routeChainId = (value) => {
|
|
12
7
|
if (!/^[1-9][0-9]*$/.test(value))
|
|
13
8
|
return null;
|
|
@@ -15,7 +10,7 @@ const routeChainId = (value) => {
|
|
|
15
10
|
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
16
11
|
};
|
|
17
12
|
const internalRoute = (request, token) => {
|
|
18
|
-
if (!
|
|
13
|
+
if (!hasBearerToken(request.headers.authorization, token)) {
|
|
19
14
|
return { ok: false, status: 401, message: 'registry writer authentication failed' };
|
|
20
15
|
}
|
|
21
16
|
const { chainId: rawChainId, coin } = request.params;
|
|
@@ -33,7 +28,7 @@ const internalRoute = (request, token) => {
|
|
|
33
28
|
export function createGameRegistry(options) {
|
|
34
29
|
if (options.writerToken.length < 32)
|
|
35
30
|
throw new RangeError('registry writer token must be at least 32 characters');
|
|
36
|
-
const app = Fastify({ bodyLimit:
|
|
31
|
+
const app = Fastify({ bodyLimit: 2_048 });
|
|
37
32
|
void app.register(cors, { origin: [...options.allowedOrigins], methods: ['GET'] });
|
|
38
33
|
app.get('/games/:chainId/:coin', async (request, reply) => {
|
|
39
34
|
const { chainId: rawChainId, coin } = request.params;
|
|
@@ -59,6 +54,9 @@ export function createGameRegistry(options) {
|
|
|
59
54
|
gameId: body.gameId,
|
|
60
55
|
gateOrigin: body.gateOrigin,
|
|
61
56
|
deployId: body.deployId,
|
|
57
|
+
...(body.gameServerOrigins === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { gameServerOrigins: body.gameServerOrigins }),
|
|
62
60
|
});
|
|
63
61
|
return reply.send(registration);
|
|
64
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-registry.js","sourceRoot":"","sources":["../src/game-registry.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAwB,MAAM,SAAS,CAAC;AACxD,OAAO,IAAI,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"game-registry.js","sourceRoot":"","sources":["../src/game-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { PlayerId } from '@flayerlabs/gamemode-spec';
|
|
3
|
+
/** The JSON body a trusted creator server sends to the gate. */
|
|
4
|
+
export interface GameServerAwardRequest {
|
|
5
|
+
eventId: string;
|
|
6
|
+
player: PlayerId;
|
|
7
|
+
points: number;
|
|
8
|
+
}
|
|
9
|
+
/** The durable receipt returned for a new award or an identical retry. */
|
|
10
|
+
export interface GameServerAwardReceipt extends GameServerAwardRequest {
|
|
11
|
+
roundId: string;
|
|
12
|
+
totalPoints: number;
|
|
13
|
+
}
|
|
14
|
+
export type GameServerAwardRefusal = 'award.invalid' | 'award.no_such_round' | 'award.wrong_authority' | 'award.not_open_yet' | 'award.window_closed' | 'award.event_mismatch' | 'award.limit_exceeded';
|
|
15
|
+
export type GameServerAwardResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
created: boolean;
|
|
18
|
+
award: GameServerAwardReceipt;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
refuse: GameServerAwardRefusal;
|
|
22
|
+
};
|
|
23
|
+
/** What one wallet has earned in a round, and how much more it is allowed to earn. */
|
|
24
|
+
export interface GameServerPlayerStanding {
|
|
25
|
+
roundId: string;
|
|
26
|
+
player: PlayerId;
|
|
27
|
+
/** This wallet's current round total, the same number an award receipt reports. */
|
|
28
|
+
totalPoints: number;
|
|
29
|
+
maxPointsPerPlayer: number;
|
|
30
|
+
/** `maxPointsPerPlayer` minus `totalPoints`, never below zero. */
|
|
31
|
+
remainingPoints: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A read cannot fail for a reason that only applies to a write, so it cannot return one.
|
|
35
|
+
*
|
|
36
|
+
* Extracted from the award vocabulary rather than restated beside it: the codes a game server
|
|
37
|
+
* switches on stay one list, and this read can only ever narrow that list, never extend it.
|
|
38
|
+
*/
|
|
39
|
+
export type GameServerPlayerRefusal = Extract<GameServerAwardRefusal, 'award.invalid' | 'award.no_such_round' | 'award.wrong_authority'>;
|
|
40
|
+
export type GameServerPlayerResult = {
|
|
41
|
+
ok: true;
|
|
42
|
+
standing: GameServerPlayerStanding;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
refuse: GameServerPlayerRefusal;
|
|
46
|
+
};
|
|
47
|
+
interface GameServerAwardInput extends GameServerAwardRequest {
|
|
48
|
+
roundId: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Credit events from one trusted creator game server.
|
|
52
|
+
*
|
|
53
|
+
* The event receipt and balance move in one transaction. A stable event id makes retries safe,
|
|
54
|
+
* while the adopted round fixes the wallet, time and reward ceilings this module enforces.
|
|
55
|
+
*/
|
|
56
|
+
export declare class GameServerAwards {
|
|
57
|
+
private readonly pool;
|
|
58
|
+
private readonly gameId;
|
|
59
|
+
constructor(pool: Pool, gameId: string);
|
|
60
|
+
record(candidate: GameServerAwardInput): Promise<GameServerAwardResult>;
|
|
61
|
+
/**
|
|
62
|
+
* What this wallet has earned so far, and how much more it may earn.
|
|
63
|
+
*
|
|
64
|
+
* Advisory. It takes no lock, so a concurrent award can move it between this answer and the next
|
|
65
|
+
* request; {@link record} remains the only arbiter of the ceiling, and a caller must still handle
|
|
66
|
+
* `award.limit_exceeded`. It lives here rather than in the route because it reads the same two
|
|
67
|
+
* tables under the same game scope as `record`, and a second copy of that scoping in the
|
|
68
|
+
* transport layer is how one of them ends up missing.
|
|
69
|
+
*/
|
|
70
|
+
pointsFor(query: {
|
|
71
|
+
roundId: string;
|
|
72
|
+
player: PlayerId;
|
|
73
|
+
}): Promise<GameServerPlayerResult>;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=game-server-awards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-server-awards.d.ts","sourceRoot":"","sources":["../src/game-server-awards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAK1D,gEAAgE;AAChE,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GACf,qBAAqB,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,GACrB,sBAAsB,GACtB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GAC7D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAElD,sFAAsF;AACtF,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC;IACjB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAC3C,sBAAsB,EACtB,eAAe,GAAG,qBAAqB,GAAG,uBAAuB,CAClE,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,wBAAwB,CAAA;CAAE,GAChD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAEnD,UAAU,oBAAqB,SAAQ,sBAAsB;IAC3D,OAAO,EAAE,MAAM,CAAC;CACjB;AAoCD;;;;;GAKG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM;IAG3B,MAAM,CAAC,SAAS,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAoH7E;;;;;;;;OAQG;IACG,SAAS,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;CAkD/F"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { isAddress } from 'viem';
|
|
2
|
+
import { inTransaction } from './store.js';
|
|
3
|
+
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
4
|
+
/** A result that must roll back any setup writes before it reaches the caller. */
|
|
5
|
+
class RollbackResult extends Error {
|
|
6
|
+
result;
|
|
7
|
+
constructor(result) {
|
|
8
|
+
super('game server award did not commit');
|
|
9
|
+
this.result = result;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const receipt = (row) => ({
|
|
13
|
+
roundId: row.round_id,
|
|
14
|
+
eventId: row.event_id,
|
|
15
|
+
player: row.player,
|
|
16
|
+
points: Number(row.points),
|
|
17
|
+
totalPoints: Number(row.total_points),
|
|
18
|
+
});
|
|
19
|
+
const matches = (stored, input) => stored.roundId === input.roundId &&
|
|
20
|
+
stored.eventId === input.eventId &&
|
|
21
|
+
stored.player === input.player &&
|
|
22
|
+
stored.points === input.points;
|
|
23
|
+
const replay = (stored, input) => matches(stored, input)
|
|
24
|
+
? { ok: true, created: false, award: stored }
|
|
25
|
+
: { ok: false, refuse: 'award.event_mismatch' };
|
|
26
|
+
/**
|
|
27
|
+
* Credit events from one trusted creator game server.
|
|
28
|
+
*
|
|
29
|
+
* The event receipt and balance move in one transaction. A stable event id makes retries safe,
|
|
30
|
+
* while the adopted round fixes the wallet, time and reward ceilings this module enforces.
|
|
31
|
+
*/
|
|
32
|
+
export class GameServerAwards {
|
|
33
|
+
pool;
|
|
34
|
+
gameId;
|
|
35
|
+
constructor(pool, gameId) {
|
|
36
|
+
this.pool = pool;
|
|
37
|
+
this.gameId = gameId;
|
|
38
|
+
}
|
|
39
|
+
async record(candidate) {
|
|
40
|
+
if (!candidate ||
|
|
41
|
+
typeof candidate.roundId !== 'string' ||
|
|
42
|
+
!IDENTIFIER.test(candidate.roundId) ||
|
|
43
|
+
typeof candidate.eventId !== 'string' ||
|
|
44
|
+
!IDENTIFIER.test(candidate.eventId) ||
|
|
45
|
+
typeof candidate.player !== 'string' ||
|
|
46
|
+
!isAddress(candidate.player, { strict: false }) ||
|
|
47
|
+
!Number.isSafeInteger(candidate.points) ||
|
|
48
|
+
candidate.points <= 0) {
|
|
49
|
+
return { ok: false, refuse: 'award.invalid' };
|
|
50
|
+
}
|
|
51
|
+
const input = { ...candidate, player: candidate.player.toLowerCase() };
|
|
52
|
+
const receivedAt = Date.now();
|
|
53
|
+
try {
|
|
54
|
+
return await inTransaction(this.pool, async (tx) => {
|
|
55
|
+
const prior = await tx.query(`select awards.round_id, awards.event_id, awards.player,
|
|
56
|
+
awards.points::text, awards.total_points::text
|
|
57
|
+
from award_events awards
|
|
58
|
+
join rounds on rounds.id = awards.round_id
|
|
59
|
+
where awards.round_id = $1 and awards.event_id = $2 and rounds.game_id = $3`, [input.roundId, input.eventId, this.gameId]);
|
|
60
|
+
if (prior.rows[0])
|
|
61
|
+
return replay(receipt(prior.rows[0]), input);
|
|
62
|
+
const round = await tx.query(`select opens_at, closes_at, max_points_per_player, score_authority, game_id
|
|
63
|
+
from rounds where id = $1`, [input.roundId]);
|
|
64
|
+
const terms = round.rows[0];
|
|
65
|
+
if (!terms || terms.game_id !== this.gameId) {
|
|
66
|
+
return { ok: false, refuse: 'award.no_such_round' };
|
|
67
|
+
}
|
|
68
|
+
if (terms.score_authority !== 'external-server') {
|
|
69
|
+
return { ok: false, refuse: 'award.wrong_authority' };
|
|
70
|
+
}
|
|
71
|
+
// Reserve the event id before checking the balance. A concurrent retry waits on this
|
|
72
|
+
// unique row and then reads the first request's receipt. Without that ordering, it could
|
|
73
|
+
// see the first request's new balance and incorrectly fail the reward ceiling check.
|
|
74
|
+
const inserted = await tx.query(`insert into award_events (round_id, event_id, player, points, total_points)
|
|
75
|
+
values ($1, $2, $3, $4, $4)
|
|
76
|
+
on conflict (round_id, event_id) do nothing
|
|
77
|
+
returning round_id, event_id, player, points::text, total_points::text`, [input.roundId, input.eventId, input.player, input.points]);
|
|
78
|
+
if (!inserted.rows[0]) {
|
|
79
|
+
const concurrent = await tx.query(`select round_id, event_id, player, points::text, total_points::text
|
|
80
|
+
from award_events where round_id = $1 and event_id = $2`, [input.roundId, input.eventId]);
|
|
81
|
+
const stored = concurrent.rows[0];
|
|
82
|
+
if (!stored)
|
|
83
|
+
throw new Error(`${input.roundId}: award conflict disappeared`);
|
|
84
|
+
throw new RollbackResult(replay(receipt(stored), input));
|
|
85
|
+
}
|
|
86
|
+
// Establish idempotency before checking the clock. A retry may arrive after closesAt while
|
|
87
|
+
// the original request is still committing; it must recover that result, not lose it.
|
|
88
|
+
if (receivedAt < Number(terms.opens_at)) {
|
|
89
|
+
throw new RollbackResult({ ok: false, refuse: 'award.not_open_yet' });
|
|
90
|
+
}
|
|
91
|
+
if (receivedAt >= Number(terms.closes_at)) {
|
|
92
|
+
throw new RollbackResult({ ok: false, refuse: 'award.window_closed' });
|
|
93
|
+
}
|
|
94
|
+
// A claim locks the same row. Creating it before locking means a first award and first claim
|
|
95
|
+
// cannot both pass through a missing row without seeing each other.
|
|
96
|
+
await tx.query(`insert into balances (round_id, player, points) values ($1, $2, 0)
|
|
97
|
+
on conflict (round_id, player) do nothing`, [input.roundId, input.player]);
|
|
98
|
+
const balance = await tx.query(`select points::text from balances where round_id = $1 and player = $2 for update`, [input.roundId, input.player]);
|
|
99
|
+
const total = BigInt(balance.rows[0]?.points ?? '0') + BigInt(input.points);
|
|
100
|
+
if (total > BigInt(terms.max_points_per_player)) {
|
|
101
|
+
throw new RollbackResult({ ok: false, refuse: 'award.limit_exceeded' });
|
|
102
|
+
}
|
|
103
|
+
await tx.query(`update balances set points = $3 where round_id = $1 and player = $2`, [
|
|
104
|
+
input.roundId,
|
|
105
|
+
input.player,
|
|
106
|
+
total.toString(),
|
|
107
|
+
]);
|
|
108
|
+
await tx.query(`update award_events set total_points = $3 where round_id = $1 and event_id = $2`, [input.roundId, input.eventId, total.toString()]);
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
created: true,
|
|
112
|
+
award: { ...input, totalPoints: Number(total) },
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof RollbackResult)
|
|
118
|
+
return error.result;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* What this wallet has earned so far, and how much more it may earn.
|
|
124
|
+
*
|
|
125
|
+
* Advisory. It takes no lock, so a concurrent award can move it between this answer and the next
|
|
126
|
+
* request; {@link record} remains the only arbiter of the ceiling, and a caller must still handle
|
|
127
|
+
* `award.limit_exceeded`. It lives here rather than in the route because it reads the same two
|
|
128
|
+
* tables under the same game scope as `record`, and a second copy of that scoping in the
|
|
129
|
+
* transport layer is how one of them ends up missing.
|
|
130
|
+
*/
|
|
131
|
+
async pointsFor(query) {
|
|
132
|
+
if (!query ||
|
|
133
|
+
typeof query.roundId !== 'string' ||
|
|
134
|
+
!IDENTIFIER.test(query.roundId) ||
|
|
135
|
+
typeof query.player !== 'string' ||
|
|
136
|
+
!isAddress(query.player, { strict: false })) {
|
|
137
|
+
return { ok: false, refuse: 'award.invalid' };
|
|
138
|
+
}
|
|
139
|
+
// Balances are keyed by the lower-cased wallet, exactly as `record` writes them. Reading with a
|
|
140
|
+
// checksummed address instead would quietly answer zero for a player who has already scored.
|
|
141
|
+
const player = query.player.toLowerCase();
|
|
142
|
+
// Scoped by game in the `where`, so another game's round reads as no such round rather than as
|
|
143
|
+
// an empty standing — the same answer `record` gives, in one round trip instead of two.
|
|
144
|
+
const result = await this.pool.query(`select rounds.max_points_per_player::text,
|
|
145
|
+
rounds.score_authority,
|
|
146
|
+
coalesce(balances.points, 0)::text as points
|
|
147
|
+
from rounds
|
|
148
|
+
left join balances on balances.round_id = rounds.id and balances.player = $2
|
|
149
|
+
where rounds.id = $1 and rounds.game_id = $3`, [query.roundId, player, this.gameId]);
|
|
150
|
+
const round = result.rows[0];
|
|
151
|
+
if (!round)
|
|
152
|
+
return { ok: false, refuse: 'award.no_such_round' };
|
|
153
|
+
if (round.score_authority !== 'external-server') {
|
|
154
|
+
return { ok: false, refuse: 'award.wrong_authority' };
|
|
155
|
+
}
|
|
156
|
+
// Safe as numbers because the ceiling is a safe integer at round creation and the award path
|
|
157
|
+
// never lets a balance pass it.
|
|
158
|
+
const maxPointsPerPlayer = Number(round.max_points_per_player);
|
|
159
|
+
const totalPoints = Number(round.points);
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
standing: {
|
|
163
|
+
roundId: query.roundId,
|
|
164
|
+
player,
|
|
165
|
+
totalPoints,
|
|
166
|
+
maxPointsPerPlayer,
|
|
167
|
+
remainingPoints: Math.max(0, maxPointsPerPlayer - totalPoints),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=game-server-awards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-server-awards.js","sourceRoot":"","sources":["../src/game-server-awards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAkEzD,kFAAkF;AAClF,MAAM,cAAe,SAAQ,KAAK;IACX;IAArB,YAAqB,MAA6B;QAChD,KAAK,CAAC,kCAAkC,CAAC,CAAC;QADvB,WAAM,GAAN,MAAM,CAAuB;IAElD,CAAC;CACF;AAED,MAAM,OAAO,GAAG,CAAC,GAAgB,EAA0B,EAAE,CAAC,CAAC;IAC7D,OAAO,EAAE,GAAG,CAAC,QAAQ;IACrB,OAAO,EAAE,GAAG,CAAC,QAAQ;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;CACtC,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,CAAC,MAA8B,EAAE,KAA2B,EAAW,EAAE,CACvF,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;IAChC,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;IAChC,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;IAC9B,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;AAEjC,MAAM,MAAM,GAAG,CAAC,MAA8B,EAAE,KAA2B,EAAyB,EAAE,CACpG,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;IACpB,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE;IAC7C,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAER;IACA;IAFnB,YACmB,IAAU,EACV,MAAc;QADd,SAAI,GAAJ,IAAI,CAAM;QACV,WAAM,GAAN,MAAM,CAAQ;IAC9B,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,SAA+B;QAC1C,IACE,CAAC,SAAS;YACV,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;YACrC,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YACnC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;YACrC,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YACnC,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;YACpC,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YAC/C,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC;YACvC,SAAS,CAAC,MAAM,IAAI,CAAC,EACrB,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,KAAK,GAAG,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACvE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE9B,IAAI,CAAC;YACH,OAAO,MAAM,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;gBACjD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,KAAK,CAC1B;;;;uFAI6E,EAC7E,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAC5C,CAAC;gBACF,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBAEhE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,KAAK,CAO1B;qCAC2B,EAC3B,CAAC,KAAK,CAAC,OAAO,CAAC,CAChB,CAAC;gBACF,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;oBAC5C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAW,CAAC;gBAC/D,CAAC;gBACD,IAAI,KAAK,CAAC,eAAe,KAAK,iBAAiB,EAAE,CAAC;oBAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAW,CAAC;gBACjE,CAAC;gBAED,qFAAqF;gBACrF,yFAAyF;gBACzF,qFAAqF;gBACrF,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;kFAGwE,EACxE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAC3D,CAAC;gBACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtB,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,KAAK,CAC/B;qEACyD,EACzD,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAC/B,CAAC;oBACF,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBAClC,IAAI,CAAC,MAAM;wBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;oBAC7E,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC3D,CAAC;gBAED,2FAA2F;gBAC3F,sFAAsF;gBACtF,IAAI,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACxC,MAAM,IAAI,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACxE,CAAC;gBACD,IAAI,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC1C,MAAM,IAAI,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC;gBACzE,CAAC;gBAED,6FAA6F;gBAC7F,oEAAoE;gBACpE,MAAM,EAAE,CAAC,KAAK,CACZ;qDAC2C,EAC3C,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAC9B,CAAC;gBACF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,KAAK,CAC5B,kFAAkF,EAClF,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAC9B,CAAC;gBACF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC5E,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC;oBAChD,MAAM,IAAI,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;gBAC1E,CAAC;gBAED,MAAM,EAAE,CAAC,KAAK,CAAC,qEAAqE,EAAE;oBACpF,KAAK,CAAC,OAAO;oBACb,KAAK,CAAC,MAAM;oBACZ,KAAK,CAAC,QAAQ,EAAE;iBACjB,CAAC,CAAC;gBACH,MAAM,EAAE,CAAC,KAAK,CACZ,iFAAiF,EACjF,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CACjD,CAAC;gBACF,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,OAAO,EAAE,IAAI;oBACb,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE;iBAChD,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAc;gBAAE,OAAO,KAAK,CAAC,MAAM,CAAC;YACzD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,SAAS,CAAC,KAA4C;QAC1D,IACE,CAAC,KAAK;YACN,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;YACjC,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAC/B,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;YAChC,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAC3C,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAChD,CAAC;QACD,gGAAgG;QAChG,6FAA6F;QAC7F,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAE1C,+FAA+F;QAC/F,wFAAwF;QACxF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAKlC;;;;;oDAK8C,EAC9C,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CACrC,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QAChE,IAAI,KAAK,CAAC,eAAe,KAAK,iBAAiB,EAAE,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QACxD,CAAC;QAED,6FAA6F;QAC7F,gCAAgC;QAChC,MAAM,kBAAkB,GAAG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC/D,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO;YACL,EAAE,EAAE,IAAI;YACR,QAAQ,EAAE;gBACR,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,MAAM;gBACN,WAAW;gBACX,kBAAkB;gBAClB,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAAC;aAC/D;SACF,CAAC;IACJ,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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';
|
|
2
|
+
export { Claims, DEFAULT_SETTLEMENT_TAIL_MS, Room, Sessions, createGate, createGameServerGate, } from './server.js';
|
|
3
|
+
export type { Admission, AdmissionRequest, Admit, ConfigForLaunch, GameServerGateOptions, GateOptions, MaxPointsForLaunch, VerifiedLaunch, } from './server.js';
|
|
4
4
|
export { createTurnstileAdmit } from './turnstile.js';
|
|
5
5
|
export type { TurnstileAdmitOptions } from './turnstile.js';
|
|
6
6
|
export { Ledger } from './ledger.js';
|
|
@@ -20,4 +20,7 @@ export { InvalidRegistration, RegistrationConflict, checkRegistration, disableGa
|
|
|
20
20
|
export type { Registration, SecurityProfile } from './registry.js';
|
|
21
21
|
export { createGameRegistry } from './game-registry.js';
|
|
22
22
|
export type { GameRegistryOptions } from './game-registry.js';
|
|
23
|
+
export type { GameServerAwardReceipt, GameServerAwardRefusal, GameServerAwardRequest, GameServerPlayerRefusal, GameServerPlayerResult, GameServerPlayerStanding, } from './game-server-awards.js';
|
|
24
|
+
export { PLAYER_JOIN_TICKET_TTL_MS, verifyPlayerJoinTicket } from './player-join-tickets.js';
|
|
25
|
+
export type { VerifiedPlayerJoin, VerifyPlayerJoinTicketOptions } from './player-join-tickets.js';
|
|
23
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,EACV,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,kBAAkB,EAClB,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;AAC9D,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,YAAY,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Public server-side surface for a production Game Mode gate. */
|
|
2
|
-
export { Claims, DEFAULT_SETTLEMENT_TAIL_MS, Room, Sessions, createGate, } from './server.js';
|
|
2
|
+
export { Claims, DEFAULT_SETTLEMENT_TAIL_MS, Room, Sessions, createGate, createGameServerGate, } from './server.js';
|
|
3
3
|
export { createTurnstileAdmit } from './turnstile.js';
|
|
4
4
|
export { Ledger } from './ledger.js';
|
|
5
5
|
export { CLAIM_AUTHORIZATION_TTL_MS } from './claims.js';
|
|
@@ -11,4 +11,5 @@ export { migrate } from './store.js';
|
|
|
11
11
|
export { createDemoGate } from './demo.js';
|
|
12
12
|
export { InvalidRegistration, RegistrationConflict, checkRegistration, disableGameRegistration, gateOrigin, pinGameRegistration, resolveGameRegistration, securityProfile, } from './registry.js';
|
|
13
13
|
export { createGameRegistry } from './game-registry.js';
|
|
14
|
+
export { PLAYER_JOIN_TICKET_TTL_MS, verifyPlayerJoinTicket } from './player-join-tickets.js';
|
|
14
15
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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,
|
|
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,EACV,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAWrB,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;AAUxD,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/ledger.d.ts
CHANGED
|
@@ -40,8 +40,6 @@ export declare class Ledger {
|
|
|
40
40
|
constructor(pool: Pool);
|
|
41
41
|
/** Convenience for tests and boot. The schema itself lives in store.ts. */
|
|
42
42
|
migrate(): Promise<void>;
|
|
43
|
-
/** Credit points. Called once per award the game emits. */
|
|
44
|
-
award(roundId: string, player: PlayerId, points: number): Promise<void>;
|
|
45
43
|
/**
|
|
46
44
|
* Authorise up to `upToWei`, or refuse.
|
|
47
45
|
*
|