@playmos/sdk 0.3.10 → 0.3.12

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/CHANGELOG.md ADDED
@@ -0,0 +1,197 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@playmos/sdk`. This project adheres to [Semantic Versioning](https://semver.org/).
4
+
5
+ ## Unreleased
6
+
7
+ ## [0.3.12] — 2026-08-27
8
+
9
+ ### Added
10
+ - **Operator-signed epoch settlement (E1b / sdk#664 / PR #670):** `epochs.getAttestation`, `epochs.executeSettlement`, `epochs.settlementTypedData`. Service `POST`/`GET` `/v1/epochs/:epochId/attestation`. GET is a public board (any authenticated key). POST is `sk_` + studio-gated. Amounts stay micro-USDC strings. Refs #664.
11
+
12
+ ### Notes
13
+ - Hub CI installs from npm, not the monorepo. This tag is what E1c (hub auto-pin before broadcast) consumes.
14
+
15
+ ## [0.3.11] — 2026-08-09
16
+
17
+ ### Added
18
+ - **`hasEntered` on `@playmos/sdk/server`** (sdk#510 / PR #541): server-side PrizePool admit read — required `rpcUrl`, plain-string pins, throws on RPC fail/timeout (never `false` on outage). Default `timeoutMs` 8s + optional `AbortSignal`.
19
+ - Service **`GET /v1/rounds/:id/entered`** (sk_ only): store tenancy + pure-chain query params for server-settle studios.
20
+
21
+ ### Notes
22
+ - Third parties need this tag on npm for Option A; Option B needs service deploy of main after #541.
23
+ - Related carries: timeout REVERT-RED (#542), residual money-path #538 C1–C4 (service).
24
+
25
+
26
+ ### Fixed (service; next tag)
27
+ - **#530** — `rounds.open` refuses IAP catalog ids / playmosPay games (no PrizePool calldata at PlaymosPay).
28
+ - **#533** — self-serve global settle cap releases on settle failure (no permanent shared-ceiling burn).
29
+ - **#532** — mutation-proof coverage for #528 R2 open gate, #529 burn payout, #509 idempotency lookup.
30
+ - **#528 / #529 / #508 / #509** — shipped on main via PR #531 (open-mined fence, IAP payout, settle wait, idem recovery).
31
+
32
+ ### Docs
33
+ - **#511** — `rounds.cancel` is real (docs no longer claim “no wrapper in V1”).
34
+ - **#516** — CHANGELOG in npm tarball; 0.3.8 identity breaking called out below.
35
+
36
+ ## [0.3.10] — 2026-08-06
37
+
38
+ ### Added
39
+ - **`rounds.getWithMeta`** (#501): returns `{ round, via?, chainReadFailed? }` for service read-path honesty after #499. `rounds.get` still returns `RoundState` only (no transport meta on the record — hub-safe). Mock omits `via`.
40
+
41
+ ### Fixed
42
+ - **Mock `rounds.withdraw`** (#495): requires `wallet`; debits only that wallet’s credit; zero balance → `NothingToWithdrawError` (was paying first non-zero credit to any caller).
43
+
44
+ ### Ops / service
45
+ - **Mainnet refuse non-dedicated RPC + Ankr free-path honesty** (#505 / #502 residual).
46
+ - **`/health` RPC redaction host-only** + **`chain.rpcKind`** allowlist classification (#502 partial on this tag). Warn on public RPC for Sepolia; hard-fail public RPC on mainnet chainId (further #505 refuse-unknown if merged before tag).
47
+
48
+ ## [0.3.9] — 2026-08-06
49
+
50
+ ### Fixed
51
+ - **Mock `rounds.settle` payout economics** (#490 / #493): offline settle applies the stored payout rule (`computePayout`) and accumulates the PrizePool pot (rake off top, then `POOL/(POOL+SEED)` at lock) instead of winner-takes-all on `entryAmount`. Credits `rounds.prize` from integer micro (not 2dp re-parse). Re-settle replays stored winners (claimable unchanged). Ranking validation matches live (400 `payout_invalid`). **Behaviour change:** mock settle now rejects non-address / short rankings that previously returned fake numbers.
52
+
53
+ ### Docs
54
+ - Mock pool is a **floor** (no series seed bank offline) — not a live-series forecast (#490 C6).
55
+ - Public operator note: `pk_` on `/v1/rounds*` → clean **403** (not an opaque 504) (#496 / #500).
56
+ - Public docs auto-deploy pipeline unblocked (#498).
57
+
58
+ ## [0.3.8] — 2026-08-05
59
+
60
+ ### Breaking
61
+ - **`identity` required on the player-wallet (client-signed) path** (#466 / #374). `enterRound` with a wallet no longer invents `wallet#idempotencyKey`. Missing `identity` → typed `ConfigError`. Pass the same identity your server will use for `hasEntered` / hub `/entry/confirm`. No-wallet server-settle smoke may still omit it (service derives). Studios on `^0.3.x` that auto-bump must pin identity before upgrading past 0.3.7.
62
+
63
+ ### Packaging
64
+ - **homepage / bugs** point at public docs (`https://playmos-docs-public.vercel.app/docs`) so npm sidebar links resolve for outsiders (#481 / #488).
65
+
66
+ ## [0.3.7] — 2026-08-04
67
+
68
+ ### Fixed
69
+ - **Mock operator lifecycle with public key** (#435 / #456): `rounds.open` / `lock` / `settle` / `cancel` work offline when `mock: true` with a `pk_` key. Live operator path still requires a provisioned `sk_`.
70
+
71
+ ### Docs
72
+ - **Issa residual honesty** (#435 / #457): skill entry ids are `entry_…` (not `pay_…`); Base Stack uses `basestack` / `game_sandbox_skill` (not `game_sandbox_base_stack`); shared sandbox pool callout.
73
+
74
+ ## [0.3.6] — 2026-07-25
75
+
76
+ **Packaging only. No API change, no runtime change, no behaviour change** — code under `sdk/src`
77
+ is byte-identical to `0.3.5`. Upgrading gains you nothing functionally, and nothing breaks.
78
+
79
+ This release exists to prove the release path itself. It is the **first `@playmos/sdk` version
80
+ published by CI from a version tag** rather than by a person running `npm publish` by hand.
81
+
82
+ ### Packaging
83
+ - **`repository`** — required by npm Trusted Publishing (OIDC), and it fails at publish time
84
+ rather than at setup time when absent.
85
+ - **`publishConfig: { access: "public", provenance: true }`** — a scoped package defaults to
86
+ **restricted**; a silently-private publish of a package studios must install is a failure that
87
+ looks like success. Making it explicit means `--access` can never be omitted.
88
+
89
+ ### Release path (context, not a code change)
90
+ `.github/workflows/publish-npm.yml` now publishes on a `v*` tag, behind two guards that run
91
+ **before** any credential is touched: the tag version must equal `sdk/package.json`, and the
92
+ tagged commit must be an ancestor of `origin/main`. The workflow then **reads the version back
93
+ off the registry** — the publish command's own exit code is not treated as evidence.
94
+
95
+ Why it was built: three npm tokens expired silently, each taking publishing down with it, and an
96
+ expired token returns **E404, not 401**, so the 2026-07-18 outage was misdiagnosed as a missing
97
+ package. `publish-pypi.yml` and `publish-nuget.yml` already existed; npm — the actual product —
98
+ had no publish workflow at all.
99
+
100
+ ### Supersedes
101
+ - The **"npm publish is Boz-run"** note in `0.3.5` below is **no longer accurate.** Publishing is
102
+ automated from a version tag whose commit is on `main` and passed CI. *The machine may release;
103
+ it may not release unreviewed, untagged, or off-main work.*
104
+
105
+ ### Known, recorded, not fixed here
106
+ - **The SDK's test suite cannot run standalone** — three suites import across into `service/src`,
107
+ so a clean checkout of `sdk/` alone cannot resolve them. This means **the published package has
108
+ never been validated in isolation from the monorepo**, which is the only way a studio ever
109
+ installs it. Carried to the Frozen Plan.
110
+
111
+ ## [0.3.5] — 2026-07-18
112
+
113
+ **Authoritative series latch** (issue **#351**). Unblocks hub PR #57 probe-subsystem deletion.
114
+
115
+ ### Added
116
+ - **`rounds.activeForSeries({ seriesKey, gameId })`** → `ActiveSeriesRound | null`.
117
+ `null` is a **proof of free** (service `GET /v1/series/:seriesKey/active`), not a failed probe.
118
+ - Types: `ActiveSeriesRound`, `ActiveForSeriesResult`; optional `seriesKey` on `RoundState`.
119
+
120
+ ### Fixed
121
+ - **Mock `rounds.open` persists `seriesKey`** (was discarded). Without this, mock
122
+ `activeForSeries` would always return free — the money-strand class.
123
+
124
+ ### Service (same release train)
125
+ - `GET /v1/series/:seriesKey/active?gameId=` — chain `activeRound` + studio-scoped hash reverse.
126
+ - `activeRound` on PrizePool ABI; `readActiveRound` / `readPrizeRoundByHash`.
127
+ - `serializeRound` emits `seriesKey`; `/health` capabilities include `series.active`.
128
+ - `listRoundsForStudio` on store for tenancy-safe reverse lookup.
129
+
130
+ ### Note
131
+ - **npm publish is Boz-run.** Hub pins `^0.3.4` until dual-path ships, then bumps to `^0.3.5`.
132
+ - Hub probe/memory **deletion is a separate PR** after live verify.
133
+ - **Hub dual-path capability detection:** read `ApiError.detail.status` / `detail.code` (not
134
+ top-level `.status`/`.code`). Prefer `/health` `capabilities.rounds.capabilities` includes
135
+ `"series.active"` over inferring from 404 shape.
136
+
137
+ ## [0.3.3] — 2026-07-15
138
+
139
+ 3-game dogfood convergence (Path B / kit). **Testnet only.**
140
+
141
+ ### Fixed
142
+ - **`exports["./package.json"]`** — resolvable from npm (tools that import package metadata no longer fail on 0.3.2). **#215**
143
+ - **`entryProvider().payEntry` status** — always **lowercase** (`confirmed` | `failed` | `pending`), matching `Payment.status` (was `CONFIRMED` / kit-style). **#217**
144
+ - **`entryProvider` + offline `verify`** — payEntry path keeps the mock store (`rememberMock`) so `verify(payEntry.paymentId)` is offline like `enterRound` (**#219** completes **#207** for the kit path). Same `Playmos` instance required (store is per client).
145
+
146
+ ### Added
147
+ - **Mock `rounds.open` / `lock` / `settle` / `get`** when `mock: true` — offline Path B operator lifecycle for unit tests and dogfood without sandbox. **#216**
148
+
149
+ ### Note
150
+ - Kits that compare `status === "CONFIRMED"` should use case-insensitive checks.
151
+ - No #161 interop claim language.
152
+
153
+ ## [0.3.2] — 2026-07-15
154
+
155
+ Path B mock / dogfood seam (issues **#204** / **#207**). **Testnet only.**
156
+
157
+ ### Fixed
158
+ - **`mock: true` `connect()`** works offline (no Base App / base-account provider required).
159
+ - **`enterRound` mock honors `roundKey` + `identity` pins**.
160
+ - **`verify(id)` offline for mock payments**.
161
+ - **`entryProvider` mock receipts set `onchain: false`**.
162
+
163
+ ### Docs / publish
164
+ - CI: `sdk/dist` must match a fresh `npm run build` (#211).
165
+
166
+ ## [0.3.1] — 2026-07-11
167
+
168
+ Winner claim ergonomics (issue #41). **Proven on Base Sepolia** (issue #62 — `service/scripts/prove-withdraw-claim.ts`).
169
+
170
+ ### Added
171
+ - **`enterRound` returns `prizePoolAddress` + `roundKey`** on the entry payment — keep them for later claim.
172
+ - **`rounds.prize({ roundId, wallet })`** — read claimable pull-payment balance (+ PrizePool address).
173
+ - **`rounds.withdraw({ prizePoolAddress | roundId })`** — player-signed `PrizePool.withdraw()`; pre-check claimable; typed **`NothingToWithdrawError`**.
174
+ - Service: **`GET /v1/rounds/:id/prize?wallet=`**, `RoundState.prizePoolAddress` on open/lock/get.
175
+ - Docs: claim flow marked **proven** after live settle→prize→withdraw→balance proof.
176
+
177
+ ## [0.3.0] — 2026-07-11
178
+
179
+ Agent-economy DX hardening from the Playmos Town live stress-test (Base Sepolia).
180
+
181
+ ### Added
182
+ - **`transfers.wait(id, opts?)`** and **`transfer(input, { confirm: true })`** — first-class settlement confirmation, so you no longer hand-roll a poll loop. `wait` resolves on a terminal state (`settled`/`failed` — a `failed` transfer *resolves*, inspect `status`; it does not throw) and throws a typed `ApiError` (`detail.timeout`) past `timeoutMs` (default 30s) / `maxAttempts` (40). (#47)
183
+ - **Automatic `429` retry** — the client retries throttled requests with exponential backoff + jitter, honoring `Retry-After` (default ≤ 2 retries). Opt out with `new Playmos({ apiKey, retry: false })`; tune with `retry: { maxRetries, baseDelayMs, maxDelayMs }`. Safe: reads are idempotent and writes carry an idempotency key. (#50)
184
+ - **`agents.fund` now returns `funding.id`** and auto-sends an `idempotencyKey` (body + header) like `transfer`, so a retry re-hits the same requirement (`preq_<key>`) — no double-fund. The id is pollable for recovery after a 502/timeout. (#48, #54)
185
+
186
+ ### Changed
187
+ - **`fee` / `net` strings now carry full micro precision** — a sub-cent fee shows its real value (e.g. `"0.005"`, never `"0.00"`), and **`fee + net === amount`** holds exactly. (#45)
188
+ - **`feeSink` is now optional on the sandbox** for `transfer` and `escrow.hold` (previously the client hard-rejected an omitted sink when `feeBps > 0`). The sandbox service supplies a default sink (advertised on `/health`), so agent scripts move value without wiring a treasury; on **live** an omitted sink still 400s at the service (server authority). A supplied sink is always shape-validated client-side. Now consistent with `marketplace.list`. (#49, #58)
189
+
190
+ ### Fixed
191
+ - **`RoundState.pool` / `RoundState.entrants` are now `string | null` / `number | null`** — matching the wire, which returns `null` until a round is reconciled against chain (the types previously claimed non-null). (#42)
192
+
193
+ ### Docs
194
+ - README now includes the **Playmos Town agent golden path** (`createWallet` → `fund` → `transfer` → confirm) and the `sk_test_` vs `pk_test_` distinction. (#38)
195
+
196
+ ## [0.2.0]
197
+ - Value-movement primitives: `transfer`, `escrow`, `marketplace`, agent wallets (`agents.*`), and contest-settle round lifecycle (`rounds.*`).
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  **🧪 Beta — Playmos SDK.** Runs on Base Sepolia **testnet**. The value-movement primitives (escrow / marketplace / transfer) are proven on-chain but **not yet production-hardened**, and nothing is on mainnet. Build and integrate freely against the sandbox; don't route real user funds yet.
4
4
 
5
- Stablecoin payments for games on Base. One SDK for in-app purchases (1%), skill-game prize-pool entries (10%, 60/30/10), and **in-game economies** (player · NPC · agent commerce via `transfer()`). USD in, USDC on-chain — no crypto UX for your players.
5
+ Stablecoin payments for games on Base. One SDK for in-app purchases (**1%** Playmos — you keep 99%), **first-party** skill contests (**60/30/10**), and **in-game economies** (player · NPC · agent commerce via `transfer()`). Independent studio contests: Playmos take is **1%** and **cannot be changed**; default studio-side split is **60/30/9** — **not live yet**. Live sandbox skill entry still uses the first-party 60/30/10 shape. USD in, USDC on-chain — no crypto UX for your players.
6
6
 
7
+ **Changelog:** see [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball from the next publish). **Breaking in 0.3.8:** player-wallet `enterRound` requires `identity` — no silent invent.
7
8
 
8
9
  ## Install
9
10
 
@@ -76,7 +77,8 @@ const entry = await playmos.enterRound({
76
77
  amount: "1.00", // grows this round's pool
77
78
  playerId: "player_abc",
78
79
  });
79
- // entry.status === "confirmed"; entry.split is the on-chain 60/30/10 breakdown
80
+ // entry.status === "confirmed"; entry.split is a first-party 60/30/10 *projection*
81
+ // (sandbox / Playmos Lab — not a studio 60/30/9+1% split, not a chain read)
80
82
  ```
81
83
 
82
84
  ### Move USDC between wallets — `transfer()`
@@ -246,6 +248,8 @@ Outside the sandbox (a `pk_live` key), or whenever you want the player to sign t
246
248
  ```ts
247
249
  const playmos = new Playmos({
248
250
  apiKey: "pk_live_…",
251
+ // base-account (default) is NOT a built-in Base Account package — pass wallet.provider
252
+ // from @base-org/account when not in Base App / no injected ethereum (sdk#512)
249
253
  wallet: { connector: "base-account" }, // or "injected" in a wallet browser
250
254
  });
251
255
  ```
@@ -0,0 +1,445 @@
1
+ import { keccak256, toHex, numberToHex, encodeFunctionData, toBytes, getAddress } from 'viem';
2
+
3
+ // src/errors.ts
4
+ var PlaymosError = class extends Error {
5
+ constructor(code, message, detail) {
6
+ super(message);
7
+ this.name = new.target.name;
8
+ this.code = code;
9
+ this.detail = detail;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ }
12
+ };
13
+ var InvalidAmountError = class extends PlaymosError {
14
+ constructor(amount) {
15
+ super(
16
+ "invalid_amount",
17
+ `Invalid amount: ${JSON.stringify(amount)}. Provide a positive USD decimal string with at most 2 decimals, e.g. "4.99".`,
18
+ { amount }
19
+ );
20
+ }
21
+ };
22
+ var MissingFieldError = class extends PlaymosError {
23
+ constructor(field) {
24
+ super("missing_field", `Missing required field: "${field}".`, { field });
25
+ }
26
+ };
27
+ var InsufficientGasError = class extends PlaymosError {
28
+ constructor(detail) {
29
+ super(
30
+ "insufficient_gas",
31
+ `The player's wallet has too little ETH to pay gas. Ask them to add a little ETH, or switch to gas.mode: "sponsored".`,
32
+ detail
33
+ );
34
+ }
35
+ };
36
+ var WalletConnectionError = class extends PlaymosError {
37
+ constructor(message = "Could not connect the player's wallet.", detail) {
38
+ super("wallet_connection", message, detail);
39
+ }
40
+ };
41
+ var WalletTimeoutError = class extends PlaymosError {
42
+ constructor(message = "The wallet did not respond in time. Ask the player to approve the prompt, or retry.", detail) {
43
+ super("wallet_timeout", message, detail);
44
+ }
45
+ };
46
+ var PaymentFailedError = class extends PlaymosError {
47
+ constructor(message = "The on-chain payment did not complete.", detail) {
48
+ super("payment_failed", message, detail);
49
+ }
50
+ };
51
+ var AlreadyEnteredError = class extends PlaymosError {
52
+ constructor(detail) {
53
+ super(
54
+ "already_entered",
55
+ "This identity already entered this round on-chain. For pay-per-play, pass a unique identity per attempt (not just the wallet address).",
56
+ detail
57
+ );
58
+ }
59
+ };
60
+ var AuthError = class extends PlaymosError {
61
+ constructor(message = "Invalid or missing API key.", detail) {
62
+ super("auth", message, detail);
63
+ }
64
+ };
65
+ var ApiError = class extends PlaymosError {
66
+ constructor(message, detail) {
67
+ super("api_error", message, detail);
68
+ }
69
+ };
70
+ var ConfigError = class extends PlaymosError {
71
+ constructor(message, detail) {
72
+ super("config", message, detail);
73
+ }
74
+ };
75
+ var NothingToWithdrawError = class extends PlaymosError {
76
+ constructor(detail) {
77
+ super(
78
+ "nothing_to_withdraw",
79
+ "Nothing to withdraw \u2014 this wallet has no credited prize balance on this PrizePool (round not settled for them, or already claimed).",
80
+ detail
81
+ );
82
+ }
83
+ };
84
+ function seriesToBytes32(series) {
85
+ const s = series.trim();
86
+ if (/^0x[0-9a-fA-F]{64}$/.test(s)) return s.toLowerCase();
87
+ return keccak256(toHex(s));
88
+ }
89
+ var identityToBytes32 = seriesToBytes32;
90
+
91
+ // src/chain/abis.ts
92
+ var erc20Abi = [
93
+ {
94
+ type: "function",
95
+ name: "approve",
96
+ stateMutability: "nonpayable",
97
+ inputs: [
98
+ { name: "spender", type: "address" },
99
+ { name: "amount", type: "uint256" }
100
+ ],
101
+ outputs: [{ type: "bool" }]
102
+ }
103
+ ];
104
+ var playmosPayAbi = [
105
+ {
106
+ type: "function",
107
+ name: "pay",
108
+ stateMutability: "nonpayable",
109
+ inputs: [
110
+ { name: "paymentId", type: "bytes32" },
111
+ { name: "studio", type: "address" },
112
+ { name: "amount", type: "uint256" }
113
+ ],
114
+ outputs: []
115
+ }
116
+ ];
117
+ var prizePoolAbi = [
118
+ {
119
+ type: "function",
120
+ name: "enter",
121
+ stateMutability: "nonpayable",
122
+ inputs: [
123
+ { name: "roundId", type: "bytes32" },
124
+ { name: "identity", type: "bytes32" }
125
+ ],
126
+ outputs: []
127
+ },
128
+ {
129
+ type: "function",
130
+ name: "withdrawable",
131
+ stateMutability: "view",
132
+ inputs: [{ name: "account", type: "address" }],
133
+ outputs: [{ type: "uint256" }]
134
+ },
135
+ {
136
+ type: "function",
137
+ name: "withdraw",
138
+ stateMutability: "nonpayable",
139
+ inputs: [],
140
+ outputs: [{ name: "amount", type: "uint256" }]
141
+ }
142
+ ];
143
+ async function sendCalls(provider, from, chainId, calls, paymasterUrl) {
144
+ const params = {
145
+ version: "2.0.0",
146
+ from,
147
+ chainId: numberToHex(chainId),
148
+ atomicRequired: true,
149
+ calls
150
+ };
151
+ if (paymasterUrl) {
152
+ params.capabilities = { paymasterService: { url: paymasterUrl } };
153
+ }
154
+ let result;
155
+ try {
156
+ result = await provider.request({ method: "wallet_sendCalls", params: [params] });
157
+ } catch (e) {
158
+ if (e instanceof WalletTimeoutError) throw e;
159
+ const msg = e?.message ?? String(e);
160
+ if (/reject|denied|cancel|closed/i.test(msg)) {
161
+ throw new WalletConnectionError("The player cancelled or closed the payment sheet.", { cause: msg });
162
+ }
163
+ throw new PaymentFailedError("wallet_sendCalls failed.", { cause: msg });
164
+ }
165
+ const id = typeof result === "string" ? result : result?.id ?? "";
166
+ if (!id) throw new PaymentFailedError("Wallet returned no calls id.");
167
+ return { id };
168
+ }
169
+ async function waitForCalls(provider, id, timeoutMs = 6e4) {
170
+ if (!id) return { status: "FAILED" };
171
+ const deadline = Date.now() + timeoutMs;
172
+ while (Date.now() < deadline) {
173
+ try {
174
+ const res = await provider.request({
175
+ method: "wallet_getCallsStatus",
176
+ params: [id]
177
+ });
178
+ const s = String(res?.status ?? "").toUpperCase();
179
+ const txHash = res?.receipts?.[0]?.transactionHash;
180
+ if (s === "200" || s === "CONFIRMED" || s === "SUCCESS") return { status: "CONFIRMED", txHash };
181
+ if (s === "400" || s === "500" || s === "FAILED" || s === "REVERTED") return { status: "FAILED", txHash };
182
+ } catch (e) {
183
+ if (e instanceof WalletTimeoutError) {
184
+ await new Promise((r) => setTimeout(r, 900));
185
+ continue;
186
+ }
187
+ throw e;
188
+ }
189
+ await new Promise((r) => setTimeout(r, 900));
190
+ }
191
+ return { status: "PENDING" };
192
+ }
193
+ function encodeApprove(spender, amountUnits) {
194
+ return encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [spender, amountUnits] });
195
+ }
196
+ var GAS_FLOOR_WEI_DEFAULT = 200000000000000n;
197
+ var GAS_FLOOR_WEI_BASE_SEPOLIA = 50000000000000n;
198
+ var CHAIN_ID_BASE_SEPOLIA = 84532;
199
+ function gasFloorWei(chainId) {
200
+ return chainId === CHAIN_ID_BASE_SEPOLIA ? GAS_FLOOR_WEI_BASE_SEPOLIA : GAS_FLOOR_WEI_DEFAULT;
201
+ }
202
+ async function assertEnoughGas(provider, from, minWei) {
203
+ let floor = minWei;
204
+ if (floor === void 0) {
205
+ let chainId = 0;
206
+ try {
207
+ const hex = await provider.request({ method: "eth_chainId", params: [] });
208
+ chainId = Number(BigInt(hex));
209
+ } catch {
210
+ }
211
+ floor = gasFloorWei(chainId);
212
+ }
213
+ let balance;
214
+ try {
215
+ const hex = await provider.request({ method: "eth_getBalance", params: [from, "latest"] });
216
+ balance = BigInt(hex);
217
+ } catch {
218
+ return;
219
+ }
220
+ if (balance < floor) {
221
+ throw new InsufficientGasError({
222
+ balanceWei: balance.toString(),
223
+ minWei: floor.toString()
224
+ });
225
+ }
226
+ }
227
+
228
+ // src/chain/epochPrizePoolAbi.ts
229
+ var epochPrizePoolViewAbi = [
230
+ {
231
+ type: "function",
232
+ name: "currentEpochId",
233
+ stateMutability: "view",
234
+ inputs: [{ name: "series", type: "bytes32" }],
235
+ outputs: [{ type: "uint256" }]
236
+ },
237
+ {
238
+ type: "function",
239
+ name: "epochIdAt",
240
+ stateMutability: "view",
241
+ inputs: [
242
+ { name: "series", type: "bytes32" },
243
+ { name: "timestamp", type: "uint256" }
244
+ ],
245
+ outputs: [{ type: "uint256" }]
246
+ },
247
+ {
248
+ type: "function",
249
+ name: "epochPool",
250
+ stateMutability: "view",
251
+ inputs: [
252
+ { name: "series", type: "bytes32" },
253
+ { name: "epochId", type: "uint256" }
254
+ ],
255
+ outputs: [{ type: "uint256" }]
256
+ },
257
+ {
258
+ type: "function",
259
+ name: "epochOutgoingSeed",
260
+ stateMutability: "view",
261
+ inputs: [
262
+ { name: "series", type: "bytes32" },
263
+ { name: "epochId", type: "uint256" }
264
+ ],
265
+ outputs: [{ type: "uint256" }]
266
+ },
267
+ {
268
+ type: "function",
269
+ name: "epochIncomingSeed",
270
+ stateMutability: "view",
271
+ inputs: [
272
+ { name: "series", type: "bytes32" },
273
+ { name: "epochId", type: "uint256" }
274
+ ],
275
+ outputs: [{ type: "uint256" }]
276
+ },
277
+ {
278
+ type: "function",
279
+ name: "epochEntryCount",
280
+ stateMutability: "view",
281
+ inputs: [
282
+ { name: "series", type: "bytes32" },
283
+ { name: "epochId", type: "uint256" }
284
+ ],
285
+ outputs: [{ type: "uint256" }]
286
+ },
287
+ {
288
+ type: "function",
289
+ name: "getSeries",
290
+ stateMutability: "view",
291
+ inputs: [{ name: "series", type: "bytes32" }],
292
+ outputs: [
293
+ { name: "created", type: "bool" },
294
+ { name: "genesis", type: "uint256" },
295
+ { name: "epochDuration", type: "uint256" },
296
+ { name: "entry", type: "uint256" },
297
+ { name: "feeBps", type: "uint16" },
298
+ { name: "poolBps", type: "uint16" },
299
+ { name: "seedBps", type: "uint16" }
300
+ ]
301
+ },
302
+ {
303
+ type: "function",
304
+ name: "getEpoch",
305
+ stateMutability: "view",
306
+ inputs: [
307
+ { name: "series", type: "bytes32" },
308
+ { name: "epochId", type: "uint256" }
309
+ ],
310
+ outputs: [
311
+ { name: "pool", type: "uint256" },
312
+ { name: "outgoingSeed", type: "uint256" },
313
+ { name: "incomingSeed", type: "uint256" },
314
+ { name: "entryCount_", type: "uint256" },
315
+ { name: "terminal", type: "uint8" }
316
+ ]
317
+ },
318
+ {
319
+ type: "function",
320
+ name: "entryCount",
321
+ stateMutability: "view",
322
+ inputs: [
323
+ { name: "series", type: "bytes32" },
324
+ { name: "epochId", type: "uint256" },
325
+ { name: "identity", type: "bytes32" }
326
+ ],
327
+ outputs: [{ type: "uint256" }]
328
+ },
329
+ {
330
+ type: "function",
331
+ name: "claimableRefund",
332
+ stateMutability: "view",
333
+ inputs: [
334
+ { name: "series", type: "bytes32" },
335
+ { name: "epochId", type: "uint256" },
336
+ { name: "payer", type: "address" }
337
+ ],
338
+ outputs: [{ type: "uint256" }]
339
+ },
340
+ {
341
+ type: "function",
342
+ name: "withdrawable",
343
+ stateMutability: "view",
344
+ inputs: [{ name: "account", type: "address" }],
345
+ outputs: [{ type: "uint256" }]
346
+ }
347
+ ];
348
+ var epochPrizePoolEnterAbi = [
349
+ {
350
+ type: "function",
351
+ name: "enter",
352
+ stateMutability: "nonpayable",
353
+ inputs: [
354
+ { name: "series", type: "bytes32" },
355
+ { name: "identity", type: "bytes32" }
356
+ ],
357
+ outputs: []
358
+ }
359
+ ];
360
+ var epochPrizePoolRefundAbi = [
361
+ {
362
+ type: "function",
363
+ name: "claimRefund",
364
+ stateMutability: "nonpayable",
365
+ inputs: [
366
+ { name: "series", type: "bytes32" },
367
+ { name: "epochId", type: "uint256" },
368
+ { name: "payer", type: "address" }
369
+ ],
370
+ outputs: [{ name: "amount", type: "uint256" }]
371
+ },
372
+ {
373
+ type: "function",
374
+ name: "withdraw",
375
+ stateMutability: "nonpayable",
376
+ inputs: [],
377
+ outputs: []
378
+ }
379
+ ];
380
+ var epochPrizePoolExecuteSettlementAbi = [
381
+ {
382
+ type: "function",
383
+ name: "executeSignedSettlement",
384
+ stateMutability: "nonpayable",
385
+ inputs: [
386
+ { name: "series", type: "bytes32" },
387
+ { name: "epochId", type: "uint256" },
388
+ { name: "winners", type: "address[]" },
389
+ { name: "amounts", type: "uint256[]" },
390
+ { name: "signature", type: "bytes" }
391
+ ],
392
+ outputs: []
393
+ }
394
+ ];
395
+
396
+ // src/chain/calls.ts
397
+ var toBytes32 = (s) => keccak256(toBytes(s));
398
+ function buildIapCalls(args) {
399
+ const payData = encodeFunctionData({
400
+ abi: playmosPayAbi,
401
+ functionName: "pay",
402
+ args: [toBytes32(args.paymentId), args.studio, args.amountUnits]
403
+ });
404
+ return [
405
+ { to: args.usdc, data: encodeApprove(args.playmosPay, args.amountUnits) },
406
+ { to: args.playmosPay, data: payData }
407
+ ];
408
+ }
409
+ function buildEntryCalls(args) {
410
+ const enterData = encodeFunctionData({
411
+ abi: prizePoolAbi,
412
+ functionName: "enter",
413
+ args: [toBytes32(args.roundKey), toBytes32(args.identity)]
414
+ });
415
+ return [
416
+ { to: args.usdc, data: encodeApprove(args.prizePool, args.amountUnits) },
417
+ { to: args.prizePool, data: enterData }
418
+ ];
419
+ }
420
+ function buildWithdrawCall(prizePool) {
421
+ const data = encodeFunctionData({
422
+ abi: prizePoolAbi,
423
+ functionName: "withdraw",
424
+ args: []
425
+ });
426
+ return { to: prizePool, data };
427
+ }
428
+ function buildEpochExecuteSettlementCall(args) {
429
+ return {
430
+ to: args.epochPrizePool,
431
+ data: encodeFunctionData({
432
+ abi: epochPrizePoolExecuteSettlementAbi,
433
+ functionName: "executeSignedSettlement",
434
+ args: [
435
+ seriesToBytes32(args.series),
436
+ args.epochId,
437
+ args.winners.map((w) => getAddress(w.toLowerCase())),
438
+ args.amounts,
439
+ args.signature
440
+ ]
441
+ })
442
+ };
443
+ }
444
+
445
+ export { AlreadyEnteredError, ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, NothingToWithdrawError, PaymentFailedError, PlaymosError, WalletConnectionError, WalletTimeoutError, assertEnoughGas, buildEntryCalls, buildEpochExecuteSettlementCall, buildIapCalls, buildWithdrawCall, encodeApprove, epochPrizePoolEnterAbi, epochPrizePoolRefundAbi, epochPrizePoolViewAbi, identityToBytes32, prizePoolAbi, sendCalls, seriesToBytes32, toBytes32, waitForCalls };