@playmos/sdk 0.3.0 → 0.3.2

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 CHANGED
@@ -2,7 +2,7 @@
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 agent economies. 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%), 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.
6
6
 
7
7
  ## Install
8
8
 
@@ -27,21 +27,43 @@ The key comes wired to two demo games: `game_sandbox_iap` (IAP) and `game_sandbo
27
27
 
28
28
  ### In-app purchase — `pay()`
29
29
 
30
+ <!-- snippet:pay -->
30
31
  ```ts
31
32
  import { Playmos } from "@playmos/sdk";
32
33
 
33
- // No wallet in the sandbox Playmos signs the test payment for you.
34
+ // Public sandbox keyclient-safe, like Stripe's pk_test_.
35
+ // No wallet needed: Playmos server-settles the test payment for you.
34
36
  const playmos = new Playmos({ apiKey: "pk_test_playmos_sandbox" });
35
37
 
36
38
  const payment = await playmos.pay({
37
- gameId: "game_sandbox_iap", // the public sandbox IAP game
38
- amount: "4.99", // USD, as a string
39
+ gameId: "game_sandbox_iap", // public sandbox IAP game — include it (this key spans IAP + skill)
40
+ amount: "0.99", // USD string sandbox server-settle cap $1.00/request
39
41
  sku: "gems_500", // your product id
40
- playerId: "player_abc", // your user id
42
+ playerId: "player_abc", // your opaque user id
41
43
  });
42
44
 
45
+ payment.id; // "pay_…" — real, server-issued (ULID)
43
46
  payment.status; // "confirmed" — real, on Base Sepolia
44
- payment.txHash; // 0x… open on sepolia.basescan.org
47
+ payment.txHash; // 0x… open on sepolia.basescan.org/tx/{txHash}
48
+ ```
49
+
50
+ ### Verify before you grant
51
+
52
+ Grant items on a verified confirmation — never on the client `pay()` return alone.
53
+
54
+ <!-- snippet:verify -->
55
+ ```ts
56
+ import { Playmos } from "@playmos/sdk";
57
+
58
+ // Your server — the secret key lives here, never in a client bundle.
59
+ const server = new Playmos({ apiKey: process.env.PLAYMOS_SECRET! }); // sk_test_…
60
+
61
+ const result = await server.verify(payment.id); // on-chain read — idempotent, safe to retry
62
+
63
+ // Terminal success is exactly "confirmed" (IAP + entries) — not "settled" / "succeeded".
64
+ if (result.status === "confirmed") {
65
+ grantItem(result.playerId, result.sku);
66
+ }
45
67
  ```
46
68
 
47
69
  ### Skill-game entry — `enterRound()`
@@ -62,7 +84,7 @@ The base value-movement primitive: move USDC from one wallet to another with a c
62
84
 
63
85
  ```ts
64
86
  // NPC→NPC with a 5% fee (Playmos Town P2P). Requires sk_test_ + agents.createWallet first.
65
- // Full agent/Town walkthrough: see "Agent economies — the Playmos Town golden path" below.
87
+ // Full walkthrough: see "In-game economies — the Playmos Town golden path" below.
66
88
  // Confirm: status === "settled" && txHash, or await playmos.transfers.wait(id) if settling.
67
89
  const t = await playmos.transfer({
68
90
  // `from` is OPTIONAL in the sandbox — omit it and the service uses its server-held signer wallet
@@ -109,9 +131,9 @@ Fields match the table above (`from` optional → defaults to the signer; `feeBp
109
131
 
110
132
  > Sandbox only (Phase 1a): `transfer` settles server-held NPC/agent wallets on Base Sepolia. The player-signed (EIP-3009) non-custodial path is Phase 1b.
111
133
 
112
- ### Agent economies — the Playmos Town golden path
134
+ ### In-game economies — the Playmos Town golden path
113
135
 
114
- The NPCs in your game can hold and move USDC. This is the loop the [Playmos Town example](https://github.com/playmos-labs/playmos-sdk/blob/main/docs/examples/playmos-town-sdk.mjs) runs end to end: give each NPC a wallet, fund it, then move value with a per-call fee — P2P (5%), a monster bounty (0%), a shop sale (100%).
136
+ **In-game economies** = commerce between **players, NPCs, and agents** (AI or scripted). The money primitive is **`transfer()`** (entity-agnostic); NPC/agent wallets use **`agents.*`** (API names unchanged). This is the loop the [Playmos Town example](https://github.com/playmos-labs/playmos-sdk/blob/main/docs/examples/playmos-town-sdk.mjs) runs end to end: give each NPC a wallet, fund it, then move value with a per-call fee — P2P (5%), a monster bounty (0%), a shop sale (100%). Proven on **Base Sepolia** (transfer layer) — not a claim that a full MMO product is shipped.
115
137
 
116
138
  **Agents need a SECRET test key — `sk_test_`, not `pk_test_`.** Assigning NPC wallets and transferring *from* an NPC are privileged, server-side actions, so they require an `sk_test_` key kept on **your backend** — not the public `pk_test_playmos_sandbox`. `pk_test_` runs the no-wallet `pay()` / `enterRound()` demos above; `sk_test_` unlocks `agents.*` and NPC-funded `transfer`.
117
139
 
@@ -168,15 +190,6 @@ The full 5-NPC walkthrough — create → fund → P2P / shop / bounty, each con
168
190
  PLAYMOS_SK_TEST=sk_test_… PLAYMOS_FEE_SINK=0x… node docs/examples/playmos-town-sdk.mjs
169
191
  ```
170
192
 
171
- ### Verify before you grant
172
-
173
- Grant items on a verified confirmation — never on the client `pay()` return alone.
174
-
175
- ```ts
176
- const result = await playmos.verify(payment.id); // on-chain read
177
- if (result.status === "confirmed") grantItem(result.playerId, result.sku);
178
- ```
179
-
180
193
  ## Mock mode — offline, deterministic
181
194
 
182
195
  For CI and wiring checks, `mock: true` returns instant, deterministic results with **no network and no chain**. Results carry `mock: true` and use the real status union, so your handling code sees the exact production shape.
@@ -184,7 +197,7 @@ For CI and wiring checks, `mock: true` returns instant, deterministic results wi
184
197
  ```ts
185
198
  const playmos = new Playmos({ apiKey: "pk_test_playmos_sandbox", mock: true });
186
199
  const payment = await playmos.pay({
187
- gameId: "game_sandbox_iap", sku: "gems_100", amount: "4.99", playerId: "player_abc",
200
+ gameId: "game_sandbox_iap", sku: "gems_100", amount: "0.99", playerId: "player_abc",
188
201
  });
189
202
  // instant — payment.status === "confirmed", payment.mock === true
190
203
  ```
@@ -229,4 +242,4 @@ The escrow/marketplace resolve endpoints (release/refund/confirm) return a deter
229
242
 
230
243
  ## Docs
231
244
 
232
- Full documentation — skill games, webhooks, gas, payouts, agent economies, and the REST API — at [playmos.io](https://playmos.io).
245
+ Full documentation — skill games, webhooks, gas, payouts, in-game economies, and the REST API — at [playmos.io](https://playmos.io).
@@ -56,7 +56,16 @@ var ConfigError = class extends PlaymosError {
56
56
  super("config", message, detail);
57
57
  }
58
58
  };
59
+ var NothingToWithdrawError = class extends PlaymosError {
60
+ constructor(detail) {
61
+ super(
62
+ "nothing_to_withdraw",
63
+ "Nothing to withdraw \u2014 this wallet has no credited prize balance on this PrizePool (round not settled for them, or already claimed).",
64
+ detail
65
+ );
66
+ }
67
+ };
59
68
 
60
- export { ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, PaymentFailedError, PlaymosError, WalletConnectionError };
61
- //# sourceMappingURL=chunk-B7SHFZYY.js.map
62
- //# sourceMappingURL=chunk-B7SHFZYY.js.map
69
+ export { ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, NothingToWithdrawError, PaymentFailedError, PlaymosError, WalletConnectionError };
70
+ //# sourceMappingURL=chunk-TMBBEGIF.js.map
71
+ //# sourceMappingURL=chunk-TMBBEGIF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"names":[],"mappings":";AAmBO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAKtC,WAAA,CAAY,IAAA,EAAwB,OAAA,EAAiB,MAAA,EAAkC;AACrF,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,OAAO,GAAA,CAAA,MAAA,CAAW,IAAA;AACvB,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF;AAGO,IAAM,kBAAA,GAAN,cAAiC,YAAA,CAAa;AAAA,EACnD,YAAY,MAAA,EAAiB;AAC3B,IAAA,KAAA;AAAA,MACE,gBAAA;AAAA,MACA,CAAA,gBAAA,EAAmB,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA,6EAAA,CAAA;AAAA,MACzC,EAAE,MAAA;AAAO,KACX;AAAA,EACF;AACF;AAGO,IAAM,iBAAA,GAAN,cAAgC,YAAA,CAAa;AAAA,EAClD,YAAY,KAAA,EAAe;AACzB,IAAA,KAAA,CAAM,iBAAiB,CAAA,yBAAA,EAA4B,KAAK,CAAA,EAAA,CAAA,EAAM,EAAE,OAAO,CAAA;AAAA,EACzE;AACF;AAGO,IAAM,oBAAA,GAAN,cAAmC,YAAA,CAAa;AAAA,EACrD,YAAY,MAAA,EAAkC;AAC5C,IAAA,KAAA;AAAA,MACE,kBAAA;AAAA,MACA,CAAA,oHAAA,CAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AAGO,IAAM,qBAAA,GAAN,cAAoC,YAAA,CAAa;AAAA,EACtD,WAAA,CAAY,OAAA,GAAU,wCAAA,EAA0C,MAAA,EAAkC;AAChG,IAAA,KAAA,CAAM,mBAAA,EAAqB,SAAS,MAAM,CAAA;AAAA,EAC5C;AACF;AAGO,IAAM,kBAAA,GAAN,cAAiC,YAAA,CAAa;AAAA,EACnD,WAAA,CAAY,OAAA,GAAU,wCAAA,EAA0C,MAAA,EAAkC;AAChG,IAAA,KAAA,CAAM,gBAAA,EAAkB,SAAS,MAAM,CAAA;AAAA,EACzC;AACF;AAGO,IAAM,SAAA,GAAN,cAAwB,YAAA,CAAa;AAAA,EAC1C,WAAA,CAAY,OAAA,GAAU,6BAAA,EAA+B,MAAA,EAAkC;AACrF,IAAA,KAAA,CAAM,MAAA,EAAQ,SAAS,MAAM,CAAA;AAAA,EAC/B;AACF;AAGO,IAAM,QAAA,GAAN,cAAuB,YAAA,CAAa;AAAA,EACzC,WAAA,CAAY,SAAiB,MAAA,EAAkC;AAC7D,IAAA,KAAA,CAAM,WAAA,EAAa,SAAS,MAAM,CAAA;AAAA,EACpC;AACF;AAGO,IAAM,WAAA,GAAN,cAA0B,YAAA,CAAa;AAAA,EAC5C,WAAA,CAAY,SAAiB,MAAA,EAAkC;AAC7D,IAAA,KAAA,CAAM,QAAA,EAAU,SAAS,MAAM,CAAA;AAAA,EACjC;AACF;AAMO,IAAM,sBAAA,GAAN,cAAqC,YAAA,CAAa;AAAA,EACvD,YAAY,MAAA,EAAkC;AAC5C,IAAA,KAAA;AAAA,MACE,qBAAA;AAAA,MACA,0IAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF","file":"chunk-TMBBEGIF.js","sourcesContent":["/**\n * Typed, actionable errors — the Stripe bar (spec §11).\n *\n * Every error carries a stable machine-readable `code` and is thrown at the\n * EARLIEST possible layer: input errors fire client-side before any network or\n * chain call, so a studio never pays gas to discover a typo.\n */\n\nexport type PlaymosErrorCode =\n | \"invalid_amount\"\n | \"missing_field\"\n | \"insufficient_gas\"\n | \"wallet_connection\"\n | \"payment_failed\"\n | \"auth\"\n | \"api_error\"\n | \"config\"\n | \"nothing_to_withdraw\";\n\nexport class PlaymosError extends Error {\n readonly code: PlaymosErrorCode;\n /** Optional machine context (e.g. the offending field, the http status). */\n readonly detail?: Record<string, unknown>;\n\n constructor(code: PlaymosErrorCode, message: string, detail?: Record<string, unknown>) {\n super(message);\n this.name = new.target.name;\n this.code = code;\n this.detail = detail;\n // Restore prototype chain for `instanceof` across transpile targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** amount ≤ 0, non-numeric, empty, or more than 2 decimal places. */\nexport class InvalidAmountError extends PlaymosError {\n constructor(amount: unknown) {\n super(\n \"invalid_amount\",\n `Invalid amount: ${JSON.stringify(amount)}. Provide a positive USD decimal string with at most 2 decimals, e.g. \"4.99\".`,\n { amount },\n );\n }\n}\n\n/** A required field (sku, playerId, gameId, roundId, agentId…) was empty. */\nexport class MissingFieldError extends PlaymosError {\n constructor(field: string) {\n super(\"missing_field\", `Missing required field: \"${field}\".`, { field });\n }\n}\n\n/** gas mode \"player\" and the player's ETH is too low to cover gas (pre-check, §7). */\nexport class InsufficientGasError extends PlaymosError {\n constructor(detail?: Record<string, unknown>) {\n super(\n \"insufficient_gas\",\n \"The player's wallet has too little ETH to pay gas. Ask them to add a little ETH, or switch to gas.mode: \\\"sponsored\\\".\",\n detail,\n );\n }\n}\n\n/** The player closed or failed the wallet sheet, or no provider is available. */\nexport class WalletConnectionError extends PlaymosError {\n constructor(message = \"Could not connect the player's wallet.\", detail?: Record<string, unknown>) {\n super(\"wallet_connection\", message, detail);\n }\n}\n\n/** The on-chain settlement reverted, was cancelled, or timed out. */\nexport class PaymentFailedError extends PlaymosError {\n constructor(message = \"The on-chain payment did not complete.\", detail?: Record<string, unknown>) {\n super(\"payment_failed\", message, detail);\n }\n}\n\n/** Bad, missing, or wrong-environment API key (e.g. a pk_test_ key on a live route). */\nexport class AuthError extends PlaymosError {\n constructor(message = \"Invalid or missing API key.\", detail?: Record<string, unknown>) {\n super(\"auth\", message, detail);\n }\n}\n\n/** The Playmos service returned a non-2xx we don't have a more specific error for. */\nexport class ApiError extends PlaymosError {\n constructor(message: string, detail?: Record<string, unknown>) {\n super(\"api_error\", message, detail);\n }\n}\n\n/** SDK misconfiguration (e.g. a missing contract address for on-chain mode). */\nexport class ConfigError extends PlaymosError {\n constructor(message: string, detail?: Record<string, unknown>) {\n super(\"config\", message, detail);\n }\n}\n\n/**\n * PrizePool.withdraw() would transfer 0 (NothingToWithdraw) — pre-check or on-chain.\n * Issue #41: typed surface so studios never show a raw contract revert to winners.\n */\nexport class NothingToWithdrawError extends PlaymosError {\n constructor(detail?: Record<string, unknown>) {\n super(\n \"nothing_to_withdraw\",\n \"Nothing to withdraw — this wallet has no credited prize balance on this PrizePool (round not settled for them, or already claimed).\",\n detail,\n );\n }\n}\n"]}
@@ -398,6 +398,16 @@ interface Payment {
398
398
  * server's `hasEntered`.
399
399
  */
400
400
  identity?: string;
401
+ /**
402
+ * Prize-pool entries only (issue #41): the PrizePool contract this entry hit.
403
+ * Keep it — winners need it later to read claimable balance and call `withdraw()`.
404
+ */
405
+ prizePoolAddress?: `0x${string}`;
406
+ /**
407
+ * Prize-pool entries only (issue #41): the exact on-chain round key string used
408
+ * at enter (may equal `roundId`). Surface it so claim flow doesn't re-derive.
409
+ */
410
+ roundKey?: string;
401
411
  /** True only when produced by the labeled `mock: true` helper. */
402
412
  mock?: boolean;
403
413
  }
@@ -469,15 +479,42 @@ interface RoundState {
469
479
  roundKey: string;
470
480
  status: RoundStatus;
471
481
  entryAmount: string;
472
- /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). */
482
+ /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). Issue #42. */
473
483
  pool: string | null;
474
- /** Entrant count from chain after reconcile; **`null` until reconciled**. */
484
+ /** Entrant count from chain after reconcile; **`null` until reconciled**. Issue #42. */
475
485
  entrants: number | null;
476
486
  payout: PayoutRule;
477
487
  closeAt?: string;
478
488
  openTxHash?: `0x${string}`;
479
489
  lockTxHash?: `0x${string}`;
480
490
  settleTxHash?: `0x${string}`;
491
+ /**
492
+ * PrizePool contract for this round's game (issue #41). Needed to claim winnings.
493
+ * `null` only if the game has no contract address configured.
494
+ */
495
+ prizePoolAddress?: `0x${string}` | null;
496
+ }
497
+ /** What `rounds.prize({ … })` returns — claimable pull-payment balance (issue #41). */
498
+ interface PrizeBalance {
499
+ roundId: string;
500
+ prizePoolAddress: `0x${string}`;
501
+ /** Winner wallet whose claimable balance was read. */
502
+ wallet: `0x${string}`;
503
+ /** USD decimal string (full micro precision). */
504
+ claimable: string;
505
+ /** Integer micro-USDC. */
506
+ claimableMicro: string;
507
+ /** Round status when known from `rounds.get` (optional). */
508
+ roundStatus?: RoundStatus;
509
+ }
510
+ /** What `rounds.withdraw({ … })` returns after a successful claim (issue #41). */
511
+ interface WithdrawResult {
512
+ prizePoolAddress: `0x${string}`;
513
+ /** Amount withdrawn in USD (from pre-check claimable; on-chain transfer matches). */
514
+ amount: string;
515
+ amountMicro: string;
516
+ txHash?: `0x${string}`;
517
+ status: "confirmed" | "failed" | "pending";
481
518
  }
482
519
  interface RoundSettleInput {
483
520
  roundId: string;
@@ -509,7 +546,7 @@ interface SettleRoundResult {
509
546
  * EARLIEST possible layer: input errors fire client-side before any network or
510
547
  * chain call, so a studio never pays gas to discover a typo.
511
548
  */
512
- type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config";
549
+ type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config" | "nothing_to_withdraw";
513
550
  declare class PlaymosError extends Error {
514
551
  readonly code: PlaymosErrorCode;
515
552
  /** Optional machine context (e.g. the offending field, the http status). */
@@ -548,5 +585,12 @@ declare class ApiError extends PlaymosError {
548
585
  declare class ConfigError extends PlaymosError {
549
586
  constructor(message: string, detail?: Record<string, unknown>);
550
587
  }
588
+ /**
589
+ * PrizePool.withdraw() would transfer 0 (NothingToWithdraw) — pre-check or on-chain.
590
+ * Issue #41: typed surface so studios never show a raw contract revert to winners.
591
+ */
592
+ declare class NothingToWithdrawError extends PlaymosError {
593
+ constructor(detail?: Record<string, unknown>);
594
+ }
551
595
 
552
- export { type AgentWallet as A, type PaymentStatus as B, ConfigError as C, type PayoutRule as D, type EscrowHoldInput as E, PlaymosError as F, type GasConfig as G, type PlaymosErrorCode as H, InsufficientGasError as I, type RetryOptions as J, type RoundStatus as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type WalletConfig as O, type PlaymosConfig as P, WalletConnectionError as Q, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type WalletConnector as U, type VerifyResult as V, type WebhookEvent as W, type WebhookEventType as X, type RoundState as a, type RoundSettleInput as b, type AgentFundResult as c, type EscrowHoldResult as d, type EscrowResolveResult as e, type MarketplaceSaleResult as f, type MarketplaceGetResult as g, type PayInput as h, type Payment as i, type EnterRoundInput as j, type TransferReconcile as k, type WaitOptions as l, type TransferInput as m, type TransferConfirmOptions as n, type AgentEconomyConfig as o, ApiError as p, AuthError as q, type ContractConfig as r, type Eip1193Provider as s, type GasMode as t, InvalidAmountError as u, type ListingStatus as v, type MarketplaceItem as w, type MarketplaceSale as x, MissingFieldError as y, PaymentFailedError as z };
596
+ export { type AgentWallet as A, MissingFieldError as B, ConfigError as C, NothingToWithdrawError as D, type EscrowHoldInput as E, PaymentFailedError as F, type GasConfig as G, type PaymentStatus as H, InsufficientGasError as I, type PayoutRule as J, PlaymosError as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type PlaymosErrorCode as O, type PlaymosConfig as P, type RetryOptions as Q, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type RoundStatus as U, type VerifyResult as V, type WebhookEvent as W, type WalletConfig as X, WalletConnectionError as Y, type WalletConnector as Z, type WebhookEventType as _, type RoundState as a, type RoundSettleInput as b, type PrizeBalance as c, type WithdrawResult as d, type AgentFundResult as e, type EscrowHoldResult as f, type EscrowResolveResult as g, type MarketplaceSaleResult as h, type MarketplaceGetResult as i, type PayInput as j, type Payment as k, type EnterRoundInput as l, type TransferReconcile as m, type WaitOptions as n, type TransferInput as o, type TransferConfirmOptions as p, type AgentEconomyConfig as q, ApiError as r, AuthError as s, type ContractConfig as t, type Eip1193Provider as u, type GasMode as v, InvalidAmountError as w, type ListingStatus as x, type MarketplaceItem as y, type MarketplaceSale as z };
@@ -398,6 +398,16 @@ interface Payment {
398
398
  * server's `hasEntered`.
399
399
  */
400
400
  identity?: string;
401
+ /**
402
+ * Prize-pool entries only (issue #41): the PrizePool contract this entry hit.
403
+ * Keep it — winners need it later to read claimable balance and call `withdraw()`.
404
+ */
405
+ prizePoolAddress?: `0x${string}`;
406
+ /**
407
+ * Prize-pool entries only (issue #41): the exact on-chain round key string used
408
+ * at enter (may equal `roundId`). Surface it so claim flow doesn't re-derive.
409
+ */
410
+ roundKey?: string;
401
411
  /** True only when produced by the labeled `mock: true` helper. */
402
412
  mock?: boolean;
403
413
  }
@@ -469,15 +479,42 @@ interface RoundState {
469
479
  roundKey: string;
470
480
  status: RoundStatus;
471
481
  entryAmount: string;
472
- /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). */
482
+ /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). Issue #42. */
473
483
  pool: string | null;
474
- /** Entrant count from chain after reconcile; **`null` until reconciled**. */
484
+ /** Entrant count from chain after reconcile; **`null` until reconciled**. Issue #42. */
475
485
  entrants: number | null;
476
486
  payout: PayoutRule;
477
487
  closeAt?: string;
478
488
  openTxHash?: `0x${string}`;
479
489
  lockTxHash?: `0x${string}`;
480
490
  settleTxHash?: `0x${string}`;
491
+ /**
492
+ * PrizePool contract for this round's game (issue #41). Needed to claim winnings.
493
+ * `null` only if the game has no contract address configured.
494
+ */
495
+ prizePoolAddress?: `0x${string}` | null;
496
+ }
497
+ /** What `rounds.prize({ … })` returns — claimable pull-payment balance (issue #41). */
498
+ interface PrizeBalance {
499
+ roundId: string;
500
+ prizePoolAddress: `0x${string}`;
501
+ /** Winner wallet whose claimable balance was read. */
502
+ wallet: `0x${string}`;
503
+ /** USD decimal string (full micro precision). */
504
+ claimable: string;
505
+ /** Integer micro-USDC. */
506
+ claimableMicro: string;
507
+ /** Round status when known from `rounds.get` (optional). */
508
+ roundStatus?: RoundStatus;
509
+ }
510
+ /** What `rounds.withdraw({ … })` returns after a successful claim (issue #41). */
511
+ interface WithdrawResult {
512
+ prizePoolAddress: `0x${string}`;
513
+ /** Amount withdrawn in USD (from pre-check claimable; on-chain transfer matches). */
514
+ amount: string;
515
+ amountMicro: string;
516
+ txHash?: `0x${string}`;
517
+ status: "confirmed" | "failed" | "pending";
481
518
  }
482
519
  interface RoundSettleInput {
483
520
  roundId: string;
@@ -509,7 +546,7 @@ interface SettleRoundResult {
509
546
  * EARLIEST possible layer: input errors fire client-side before any network or
510
547
  * chain call, so a studio never pays gas to discover a typo.
511
548
  */
512
- type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config";
549
+ type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config" | "nothing_to_withdraw";
513
550
  declare class PlaymosError extends Error {
514
551
  readonly code: PlaymosErrorCode;
515
552
  /** Optional machine context (e.g. the offending field, the http status). */
@@ -548,5 +585,12 @@ declare class ApiError extends PlaymosError {
548
585
  declare class ConfigError extends PlaymosError {
549
586
  constructor(message: string, detail?: Record<string, unknown>);
550
587
  }
588
+ /**
589
+ * PrizePool.withdraw() would transfer 0 (NothingToWithdraw) — pre-check or on-chain.
590
+ * Issue #41: typed surface so studios never show a raw contract revert to winners.
591
+ */
592
+ declare class NothingToWithdrawError extends PlaymosError {
593
+ constructor(detail?: Record<string, unknown>);
594
+ }
551
595
 
552
- export { type AgentWallet as A, type PaymentStatus as B, ConfigError as C, type PayoutRule as D, type EscrowHoldInput as E, PlaymosError as F, type GasConfig as G, type PlaymosErrorCode as H, InsufficientGasError as I, type RetryOptions as J, type RoundStatus as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type WalletConfig as O, type PlaymosConfig as P, WalletConnectionError as Q, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type WalletConnector as U, type VerifyResult as V, type WebhookEvent as W, type WebhookEventType as X, type RoundState as a, type RoundSettleInput as b, type AgentFundResult as c, type EscrowHoldResult as d, type EscrowResolveResult as e, type MarketplaceSaleResult as f, type MarketplaceGetResult as g, type PayInput as h, type Payment as i, type EnterRoundInput as j, type TransferReconcile as k, type WaitOptions as l, type TransferInput as m, type TransferConfirmOptions as n, type AgentEconomyConfig as o, ApiError as p, AuthError as q, type ContractConfig as r, type Eip1193Provider as s, type GasMode as t, InvalidAmountError as u, type ListingStatus as v, type MarketplaceItem as w, type MarketplaceSale as x, MissingFieldError as y, PaymentFailedError as z };
596
+ export { type AgentWallet as A, MissingFieldError as B, ConfigError as C, NothingToWithdrawError as D, type EscrowHoldInput as E, PaymentFailedError as F, type GasConfig as G, type PaymentStatus as H, InsufficientGasError as I, type PayoutRule as J, PlaymosError as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type PlaymosErrorCode as O, type PlaymosConfig as P, type RetryOptions as Q, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type RoundStatus as U, type VerifyResult as V, type WebhookEvent as W, type WalletConfig as X, WalletConnectionError as Y, type WalletConnector as Z, type WebhookEventType as _, type RoundState as a, type RoundSettleInput as b, type PrizeBalance as c, type WithdrawResult as d, type AgentFundResult as e, type EscrowHoldResult as f, type EscrowResolveResult as g, type MarketplaceSaleResult as h, type MarketplaceGetResult as i, type PayInput as j, type Payment as k, type EnterRoundInput as l, type TransferReconcile as m, type WaitOptions as n, type TransferInput as o, type TransferConfirmOptions as p, type AgentEconomyConfig as q, ApiError as r, AuthError as s, type ContractConfig as t, type Eip1193Provider as u, type GasMode as v, InvalidAmountError as w, type ListingStatus as x, type MarketplaceItem as y, type MarketplaceSale as z };