@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 +197 -0
- package/README.md +6 -2
- package/dist/chunk-2CW4U5YB.js +445 -0
- package/dist/{errors-Chizbb96.d.cts → errors-CYKgt2VU.d.cts} +112 -6
- package/dist/{errors-Chizbb96.d.ts → errors-CYKgt2VU.d.ts} +112 -6
- package/dist/index.cjs +1412 -24
- package/dist/index.d.cts +688 -21
- package/dist/index.d.ts +688 -21
- package/dist/index.js +1202 -189
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +26 -2
- package/dist/server.d.ts +26 -2
- package/dist/server.js +110 -2
- package/package.json +7 -11
- package/dist/chunk-UZECDT6F.js +0 -83
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%),
|
|
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
|
|
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 };
|