@playmos/sdk 0.2.0 → 0.3.1

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
@@ -62,8 +62,8 @@ The base value-movement primitive: move USDC from one wallet to another with a c
62
62
 
63
63
  ```ts
64
64
  // NPC→NPC with a 5% fee (Playmos Town P2P). Requires sk_test_ + agents.createWallet first.
65
- // Full walkthrough: docs/examples/playmos-town-sdk.mjs
66
- // Confirm: status === "settled" && txHash, or poll playmos.transfers.get(id) if settling.
65
+ // Full agent/Town walkthrough: see "Agent economies — the Playmos Town golden path" below.
66
+ // Confirm: status === "settled" && txHash, or await playmos.transfers.wait(id) if settling.
67
67
  const t = await playmos.transfer({
68
68
  // `from` is OPTIONAL in the sandbox — omit it and the service uses its server-held signer wallet
69
69
  // (Phase 1a's only payer), so you don't need to know that address to run this example.
@@ -109,6 +109,65 @@ Fields match the table above (`from` optional → defaults to the signer; `feeBp
109
109
 
110
110
  > 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
111
 
112
+ ### Agent economies — the Playmos Town golden path
113
+
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%).
115
+
116
+ **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
+
118
+ ```ts
119
+ import { Playmos } from "@playmos/sdk";
120
+
121
+ // Server-side only — an sk_test_ secret key, never shipped to the browser.
122
+ const playmos = new Playmos({ apiKey: process.env.PLAYMOS_SK_TEST });
123
+ const feeSink = process.env.PLAYMOS_FEE_SINK; // 0x treasury / shop wallet
124
+
125
+ // 1) Assign a wallet to an NPC your game already owns (idempotent — safe to re-call).
126
+ const miner = await playmos.agents.createWallet({ agentId: "npc_pico_miner" });
127
+ miner.address; // 0x… — the NPC's USDC wallet on Base Sepolia
128
+
129
+ // 2) Fund it from your treasury (sandbox faucet, per-NPC lifetime cap).
130
+ const funded = await playmos.agents.fund({ agentId: "npc_pico_miner", amount: "0.20" });
131
+ funded.funding.status; // "settled" | "settling"
132
+
133
+ // 3a) P2P trade — 5% fee to the treasury (feeBps: 500). NPC `from` ⇒ gasless (NPC signs, Playmos relays + pays gas).
134
+ const p2p = await playmos.transfer({
135
+ from: "npc_pico_miner",
136
+ to: "npc_bruna_blacksmith",
137
+ amount: "0.10",
138
+ feeBps: 500, // 5% P2P
139
+ feeSink, // required when feeBps > 0
140
+ });
141
+
142
+ // Confirm with the new ergonomics (#54): wait for a terminal state — no hand-rolled poll loop.
143
+ const settled = await playmos.transfers.wait(p2p.id);
144
+ settled.status; // "settled" — txHash on sepolia.basescan.org
145
+
146
+ // 3b) Monster-bounty payout — 0% untaxed reward from the treasury signer (omit `from`, no feeSink).
147
+ await playmos.transfer({ to: "npc_wynn_ranger", amount: "0.05", feeBps: 0 });
148
+
149
+ // 3c) First-party shop sale — 100% to the shop (feeBps: 10000).
150
+ await playmos.transfer({ from: "npc_kane_knight", to: feeSink, amount: "0.05", feeBps: 10_000, feeSink });
151
+ ```
152
+
153
+ **Prefer one call? Confirm inline.** Pass `{ confirm: true }` and `transfer` blocks until the transfer is terminal before it resolves — identical to calling `transfers.wait(id)` yourself (#54):
154
+
155
+ ```ts
156
+ const t = await playmos.transfer(
157
+ { from: "npc_pico_miner", to: "npc_bruna_blacksmith", amount: "0.10", feeBps: 500, feeSink },
158
+ { confirm: true },
159
+ );
160
+ t.status; // "settled" (or "failed") — already terminal, no follow-up call
161
+ ```
162
+
163
+ The sandbox throttles (5 req/min per key), so back-to-back legs can `429`. **Retries are automatic** — the SDK backs off and retries a `429` up to twice by default, and every write carries an idempotency key so a retry never double-broadcasts (tune with `new Playmos({ apiKey, retry: { maxRetries } })`, or `retry: false` to opt out).
164
+
165
+ The full 5-NPC walkthrough — create → fund → P2P / shop / bounty, each confirmed on-chain — is [`docs/examples/playmos-town-sdk.mjs`](https://github.com/playmos-labs/playmos-sdk/blob/main/docs/examples/playmos-town-sdk.mjs):
166
+
167
+ ```bash
168
+ PLAYMOS_SK_TEST=sk_test_… PLAYMOS_FEE_SINK=0x… node docs/examples/playmos-town-sdk.mjs
169
+ ```
170
+
112
171
  ### Verify before you grant
113
172
 
114
173
  Grant items on a verified confirmation — never on the client `pay()` return alone.
@@ -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"]}
@@ -65,6 +65,54 @@ interface PlaymosConfig {
65
65
  * even mock results use the REAL status union (never a synthetic "mocked").
66
66
  */
67
67
  mock?: boolean;
68
+ /**
69
+ * Automatic backoff/retry on HTTP 429 (throttling). Default ON (up to 2 retries,
70
+ * honoring a `Retry-After` header). Retries are safe — reads are idempotent and
71
+ * writes carry an idempotency key, so a retry never double-broadcasts.
72
+ * retry: false → opt out entirely
73
+ * retry: { maxRetries } → tune attempts / backoff (0 also opts out)
74
+ */
75
+ retry?: boolean | RetryOptions;
76
+ }
77
+ /** Tuning for the automatic 429 retry (all optional; sane defaults). */
78
+ interface RetryOptions {
79
+ /** Max automatic retries on HTTP 429. `0` disables retrying entirely. Default 2. */
80
+ maxRetries?: number;
81
+ /** Base backoff in ms; the wait grows exponentially per attempt (2^n). Default 500. */
82
+ baseDelayMs?: number;
83
+ /** Ceiling for any single backoff wait, including a server `Retry-After`. Default 20000. */
84
+ maxDelayMs?: number;
85
+ }
86
+ /**
87
+ * The on-chain-reconciled view of a `transfer`, as `playmos.transfers.get(id)` /
88
+ * `playmos.transfers.wait(id)` resolve it. `status` is the settlement lifecycle:
89
+ * "settled" (confirmed) · "settling" (reserved, confirmation pending) · "failed"
90
+ * (mined revert) · "pending" · "unknown". Terminal states are `settled`/`failed`.
91
+ */
92
+ interface TransferReconcile {
93
+ id: string;
94
+ status: string;
95
+ txHash: string | null;
96
+ to: string;
97
+ amount: string;
98
+ verifiedVia: "chain" | "cache" | "degraded";
99
+ }
100
+ /** Options for `playmos.transfers.wait(id, opts?)` — how long / how often to poll. */
101
+ interface WaitOptions {
102
+ /** Poll interval in ms between `transfers.get` calls. Default 1000. */
103
+ intervalMs?: number;
104
+ /** Max total time to wait before throwing a timeout. Default 30000. */
105
+ timeoutMs?: number;
106
+ /** Belt-and-suspenders cap on poll attempts (alongside `timeoutMs`). Default 40. */
107
+ maxAttempts?: number;
108
+ }
109
+ /** Options for the confirm-inline form of `transfer(input, opts?)`. */
110
+ interface TransferConfirmOptions extends WaitOptions {
111
+ /**
112
+ * When true and the POST comes back `settling`, block until the transfer reaches
113
+ * a terminal state (settled/failed) before resolving — no hand-rolled poll loop.
114
+ */
115
+ confirm?: boolean;
68
116
  }
69
117
  interface PayInput {
70
118
  /** USD decimal string ("4.99"). Rejected: ≤ 0, non-numeric, > 2 dp. */
@@ -150,6 +198,7 @@ interface AgentWallet {
150
198
  interface AgentFundResult {
151
199
  agent: AgentWallet;
152
200
  funding: {
201
+ id: string;
153
202
  amount: string;
154
203
  status: string;
155
204
  txHash: string | null;
@@ -349,6 +398,16 @@ interface Payment {
349
398
  * server's `hasEntered`.
350
399
  */
351
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;
352
411
  /** True only when produced by the labeled `mock: true` helper. */
353
412
  mock?: boolean;
354
413
  }
@@ -420,14 +479,42 @@ interface RoundState {
420
479
  roundKey: string;
421
480
  status: RoundStatus;
422
481
  entryAmount: string;
423
- /** Payable pool in USD (from chain after lock); may be "0.00" while open. */
424
- pool: string;
425
- entrants: number;
482
+ /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). Issue #42. */
483
+ pool: string | null;
484
+ /** Entrant count from chain after reconcile; **`null` until reconciled**. Issue #42. */
485
+ entrants: number | null;
426
486
  payout: PayoutRule;
427
487
  closeAt?: string;
428
488
  openTxHash?: `0x${string}`;
429
489
  lockTxHash?: `0x${string}`;
430
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";
431
518
  }
432
519
  interface RoundSettleInput {
433
520
  roundId: string;
@@ -459,7 +546,7 @@ interface SettleRoundResult {
459
546
  * EARLIEST possible layer: input errors fire client-side before any network or
460
547
  * chain call, so a studio never pays gas to discover a typo.
461
548
  */
462
- 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";
463
550
  declare class PlaymosError extends Error {
464
551
  readonly code: PlaymosErrorCode;
465
552
  /** Optional machine context (e.g. the offending field, the http status). */
@@ -498,5 +585,12 @@ declare class ApiError extends PlaymosError {
498
585
  declare class ConfigError extends PlaymosError {
499
586
  constructor(message: string, detail?: Record<string, unknown>);
500
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
+ }
501
595
 
502
- export { type AgentWallet as A, type PlaymosErrorCode as B, ConfigError as C, type RoundStatus as D, type EscrowHoldInput as E, type WalletConfig as F, type GasConfig as G, WalletConnectionError as H, InsufficientGasError as I, type WalletConnector as J, type WebhookEventType as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type PlaymosConfig as P, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type VerifyResult as V, type WebhookEvent as W, 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 TransferInput as k, type AgentEconomyConfig as l, ApiError as m, AuthError as n, type ContractConfig as o, type Eip1193Provider as p, type GasMode as q, InvalidAmountError as r, type ListingStatus as s, type MarketplaceItem as t, type MarketplaceSale as u, MissingFieldError as v, PaymentFailedError as w, type PaymentStatus as x, type PayoutRule as y, PlaymosError 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 };
@@ -65,6 +65,54 @@ interface PlaymosConfig {
65
65
  * even mock results use the REAL status union (never a synthetic "mocked").
66
66
  */
67
67
  mock?: boolean;
68
+ /**
69
+ * Automatic backoff/retry on HTTP 429 (throttling). Default ON (up to 2 retries,
70
+ * honoring a `Retry-After` header). Retries are safe — reads are idempotent and
71
+ * writes carry an idempotency key, so a retry never double-broadcasts.
72
+ * retry: false → opt out entirely
73
+ * retry: { maxRetries } → tune attempts / backoff (0 also opts out)
74
+ */
75
+ retry?: boolean | RetryOptions;
76
+ }
77
+ /** Tuning for the automatic 429 retry (all optional; sane defaults). */
78
+ interface RetryOptions {
79
+ /** Max automatic retries on HTTP 429. `0` disables retrying entirely. Default 2. */
80
+ maxRetries?: number;
81
+ /** Base backoff in ms; the wait grows exponentially per attempt (2^n). Default 500. */
82
+ baseDelayMs?: number;
83
+ /** Ceiling for any single backoff wait, including a server `Retry-After`. Default 20000. */
84
+ maxDelayMs?: number;
85
+ }
86
+ /**
87
+ * The on-chain-reconciled view of a `transfer`, as `playmos.transfers.get(id)` /
88
+ * `playmos.transfers.wait(id)` resolve it. `status` is the settlement lifecycle:
89
+ * "settled" (confirmed) · "settling" (reserved, confirmation pending) · "failed"
90
+ * (mined revert) · "pending" · "unknown". Terminal states are `settled`/`failed`.
91
+ */
92
+ interface TransferReconcile {
93
+ id: string;
94
+ status: string;
95
+ txHash: string | null;
96
+ to: string;
97
+ amount: string;
98
+ verifiedVia: "chain" | "cache" | "degraded";
99
+ }
100
+ /** Options for `playmos.transfers.wait(id, opts?)` — how long / how often to poll. */
101
+ interface WaitOptions {
102
+ /** Poll interval in ms between `transfers.get` calls. Default 1000. */
103
+ intervalMs?: number;
104
+ /** Max total time to wait before throwing a timeout. Default 30000. */
105
+ timeoutMs?: number;
106
+ /** Belt-and-suspenders cap on poll attempts (alongside `timeoutMs`). Default 40. */
107
+ maxAttempts?: number;
108
+ }
109
+ /** Options for the confirm-inline form of `transfer(input, opts?)`. */
110
+ interface TransferConfirmOptions extends WaitOptions {
111
+ /**
112
+ * When true and the POST comes back `settling`, block until the transfer reaches
113
+ * a terminal state (settled/failed) before resolving — no hand-rolled poll loop.
114
+ */
115
+ confirm?: boolean;
68
116
  }
69
117
  interface PayInput {
70
118
  /** USD decimal string ("4.99"). Rejected: ≤ 0, non-numeric, > 2 dp. */
@@ -150,6 +198,7 @@ interface AgentWallet {
150
198
  interface AgentFundResult {
151
199
  agent: AgentWallet;
152
200
  funding: {
201
+ id: string;
153
202
  amount: string;
154
203
  status: string;
155
204
  txHash: string | null;
@@ -349,6 +398,16 @@ interface Payment {
349
398
  * server's `hasEntered`.
350
399
  */
351
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;
352
411
  /** True only when produced by the labeled `mock: true` helper. */
353
412
  mock?: boolean;
354
413
  }
@@ -420,14 +479,42 @@ interface RoundState {
420
479
  roundKey: string;
421
480
  status: RoundStatus;
422
481
  entryAmount: string;
423
- /** Payable pool in USD (from chain after lock); may be "0.00" while open. */
424
- pool: string;
425
- entrants: number;
482
+ /** Payable pool in USD from chain after reconcile; **`null` until reconciled** (never a fabricated "0.00"). Issue #42. */
483
+ pool: string | null;
484
+ /** Entrant count from chain after reconcile; **`null` until reconciled**. Issue #42. */
485
+ entrants: number | null;
426
486
  payout: PayoutRule;
427
487
  closeAt?: string;
428
488
  openTxHash?: `0x${string}`;
429
489
  lockTxHash?: `0x${string}`;
430
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";
431
518
  }
432
519
  interface RoundSettleInput {
433
520
  roundId: string;
@@ -459,7 +546,7 @@ interface SettleRoundResult {
459
546
  * EARLIEST possible layer: input errors fire client-side before any network or
460
547
  * chain call, so a studio never pays gas to discover a typo.
461
548
  */
462
- 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";
463
550
  declare class PlaymosError extends Error {
464
551
  readonly code: PlaymosErrorCode;
465
552
  /** Optional machine context (e.g. the offending field, the http status). */
@@ -498,5 +585,12 @@ declare class ApiError extends PlaymosError {
498
585
  declare class ConfigError extends PlaymosError {
499
586
  constructor(message: string, detail?: Record<string, unknown>);
500
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
+ }
501
595
 
502
- export { type AgentWallet as A, type PlaymosErrorCode as B, ConfigError as C, type RoundStatus as D, type EscrowHoldInput as E, type WalletConfig as F, type GasConfig as G, WalletConnectionError as H, InsufficientGasError as I, type WalletConnector as J, type WebhookEventType as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type PlaymosConfig as P, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type VerifyResult as V, type WebhookEvent as W, 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 TransferInput as k, type AgentEconomyConfig as l, ApiError as m, AuthError as n, type ContractConfig as o, type Eip1193Provider as p, type GasMode as q, InvalidAmountError as r, type ListingStatus as s, type MarketplaceItem as t, type MarketplaceSale as u, MissingFieldError as v, PaymentFailedError as w, type PaymentStatus as x, type PayoutRule as y, PlaymosError 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 };