@playmos/sdk 0.3.10 → 0.3.11

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/dist/index.cjs CHANGED
@@ -267,10 +267,10 @@ function validateMetadata(metadata) {
267
267
 
268
268
  // src/payout.ts
269
269
  var PayoutError = class extends Error {
270
- constructor(message) {
270
+ constructor(message, code = "payout_invalid") {
271
271
  super(message);
272
- this.code = "payout_invalid";
273
272
  this.name = "PayoutError";
273
+ this.code = code;
274
274
  }
275
275
  };
276
276
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
@@ -315,7 +315,8 @@ function computePayout(pool, ranking, rule) {
315
315
  }
316
316
  if (splits.length > wallets.length) {
317
317
  throw new PayoutError(
318
- `top-n needs ${splits.length} ranked wallets but ranking only has ${wallets.length}`
318
+ `ranking_too_short: a top-n rule cannot be settled from a ranking shorter than splitsBps.length (need ${splits.length}, got ${wallets.length}) \u2014 open with winner-take-all, or settle with explicit winners[{wallet,amount}]`,
319
+ "ranking_too_short"
319
320
  );
320
321
  }
321
322
  let sumBps = 0;
@@ -628,7 +629,7 @@ function resolveRawProvider(wallet) {
628
629
  }
629
630
  if (injected) return injected;
630
631
  throw new WalletConnectionError(
631
- "base-account connector needs a provider. In the Base App it is injected automatically; elsewhere, create one with @base-org/account and pass it as wallet.provider."
632
+ 'base-account connector needs a provider \u2014 this is not a built-in Base Account/passkey flow in @playmos/sdk. In the Base App a provider is often injected; elsewhere install @base-org/account, construct a provider, and pass it as wallet.provider (or use connector: "injected" in a wallet browser).'
632
633
  );
633
634
  }
634
635
  function resolveProvider(wallet, opts) {
@@ -2037,8 +2038,16 @@ var Playmos = class {
2037
2038
  /**
2038
2039
  * Offline mock payments for this client instance — so `verify(id)` after
2039
2040
  * `mock: true` pay/enterRound does not hit the live API (#204).
2041
+ * Keyed by payment id for verify.
2040
2042
  */
2041
2043
  this.mockPayments = /* @__PURE__ */ new Map();
2044
+ /**
2045
+ * sdk#513 / residual B1–B2 — held idempotencyKey offline (live contract rehearsal).
2046
+ * Keyed like service: `${gameId}:${key}` (IAP without gameId uses empty gameId).
2047
+ * Stores terms so same key + different money throws (live IdempotencyConflict), not silent replay.
2048
+ * Auto-minted keys still unique per call (not stored for cross-call replay).
2049
+ */
2050
+ this.mockByIdempotencyKey = /* @__PURE__ */ new Map();
2042
2051
  /**
2043
2052
  * `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
2044
2053
  * `get` reconciles once against chain truth; `wait` polls it to a terminal state
@@ -2223,18 +2232,62 @@ var Playmos = class {
2223
2232
  }
2224
2233
  };
2225
2234
  }
2226
- rememberMock(payment) {
2227
- if (payment.mock) this.mockPayments.set(payment.id, payment);
2235
+ mockIdemScopeKey(gameId, heldKey) {
2236
+ return `${gameId ?? ""}:${heldKey}`;
2237
+ }
2238
+ rememberMock(payment, held) {
2239
+ if (payment.mock) {
2240
+ this.mockPayments.set(payment.id, payment);
2241
+ if (held) {
2242
+ this.mockByIdempotencyKey.set(this.mockIdemScopeKey(held.terms.gameId, held.key), {
2243
+ payment,
2244
+ terms: held.terms
2245
+ });
2246
+ }
2247
+ }
2228
2248
  return payment;
2229
2249
  }
2250
+ /** Live-shaped conflict when held mock key is reused with different money terms (sdk#513 residual B1). */
2251
+ mockIdemReplayOrThrow(heldKey, terms) {
2252
+ const prior = this.mockByIdempotencyKey.get(this.mockIdemScopeKey(terms.gameId, heldKey));
2253
+ if (!prior) return null;
2254
+ const t = prior.terms;
2255
+ const same = t.kind === terms.kind && t.amount === terms.amount && t.gameId === terms.gameId && t.playerId === terms.playerId && t.roundId === terms.roundId && t.identity === terms.identity && t.sku === terms.sku;
2256
+ if (!same) {
2257
+ throw new ApiError(
2258
+ `idempotencyKey was already used for a payment with DIFFERENT terms (kind/amount/gameId/playerId/roundId/identity/sku); use a fresh key (mock:true mirrors live conflict \u2014 sdk#513).`,
2259
+ {
2260
+ code: "idempotency_conflict",
2261
+ idempotencyKey: heldKey,
2262
+ priorKind: t.kind,
2263
+ priorAmount: t.amount
2264
+ }
2265
+ );
2266
+ }
2267
+ return prior.payment;
2268
+ }
2230
2269
  /** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
2231
2270
  async pay(input) {
2232
2271
  const amountMicro = validateAmount(input.amount);
2233
2272
  requireField(input.sku, "sku");
2234
2273
  requireField(input.playerId, "playerId");
2235
2274
  const metadata = validateMetadata(input.metadata);
2236
- const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
2275
+ const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
2276
+ const idempotencyKey = heldIdem ?? prefixedId("idem");
2237
2277
  if (this.config.mock) {
2278
+ const terms = {
2279
+ kind: "iap",
2280
+ amount: formatMicroToUsd(amountMicro),
2281
+ gameId: input.gameId ?? "",
2282
+ playerId: input.playerId,
2283
+ roundId: "",
2284
+ identity: "",
2285
+ sku: input.sku.trim()
2286
+ };
2287
+ if (heldIdem) {
2288
+ const replay = this.mockIdemReplayOrThrow(heldIdem, terms);
2289
+ if (replay) return replay;
2290
+ }
2238
2291
  return this.rememberMock(
2239
2292
  mockIapPayment({
2240
2293
  amountMicro,
@@ -2243,7 +2296,8 @@ var Playmos = class {
2243
2296
  playerId: input.playerId,
2244
2297
  chain: this.env.network,
2245
2298
  metadata
2246
- })
2299
+ }),
2300
+ heldIdem ? { key: heldIdem, terms } : void 0
2247
2301
  );
2248
2302
  }
2249
2303
  if (this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy())) {
@@ -2265,7 +2319,13 @@ var Playmos = class {
2265
2319
  );
2266
2320
  let payment = this.mapServerPayment(res.payment, "iap", amountMicro);
2267
2321
  if (payment.status === "pending" || payment.status === "created") {
2268
- payment = await this.waitForServerSettle(payment.id, "iap", amountMicro);
2322
+ payment = await this.waitForServerSettle(
2323
+ payment.id,
2324
+ "iap",
2325
+ amountMicro,
2326
+ input.settleTimeoutMs ?? 6e4,
2327
+ input.signal
2328
+ );
2269
2329
  }
2270
2330
  return payment;
2271
2331
  }
@@ -2310,8 +2370,22 @@ var Playmos = class {
2310
2370
  requireField(input.roundId, "roundId");
2311
2371
  requireField(input.playerId, "playerId");
2312
2372
  const metadata = validateMetadata(input.metadata);
2313
- const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
2373
+ const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
2374
+ const idempotencyKey = heldIdem ?? prefixedId("idem");
2314
2375
  if (this.config.mock) {
2376
+ const terms = {
2377
+ kind: "entry",
2378
+ amount: formatMicroToUsd(amountMicro),
2379
+ gameId: input.gameId,
2380
+ playerId: input.playerId,
2381
+ roundId: input.roundId,
2382
+ identity: typeof input.identity === "string" ? input.identity.trim() : "",
2383
+ sku: ""
2384
+ };
2385
+ if (heldIdem) {
2386
+ const replay = this.mockIdemReplayOrThrow(heldIdem, terms);
2387
+ if (replay) return replay;
2388
+ }
2315
2389
  this.mockAccumulateEntry(input.roundId, amountMicro);
2316
2390
  return this.rememberMock(
2317
2391
  mockEntryPayment({
@@ -2327,7 +2401,8 @@ var Playmos = class {
2327
2401
  // B2 pins — must survive mock path for hub hasEntered parity (#204).
2328
2402
  roundKey: input.roundKey,
2329
2403
  identity: input.identity
2330
- })
2404
+ }),
2405
+ heldIdem ? { key: heldIdem, terms } : void 0
2331
2406
  );
2332
2407
  }
2333
2408
  const serverSettleNoWallet = this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy());
@@ -2360,7 +2435,13 @@ var Playmos = class {
2360
2435
  );
2361
2436
  let payment2 = this.mapServerPayment(res.payment, "entry", amountMicro);
2362
2437
  if (payment2.status === "pending" || payment2.status === "created") {
2363
- payment2 = await this.waitForServerSettle(payment2.id, "entry", amountMicro);
2438
+ payment2 = await this.waitForServerSettle(
2439
+ payment2.id,
2440
+ "entry",
2441
+ amountMicro,
2442
+ input.settleTimeoutMs ?? 6e4,
2443
+ input.signal
2444
+ );
2364
2445
  }
2365
2446
  if (input.identity) payment2.identity = input.identity;
2366
2447
  const pool = res.clientParams?.contractAddress ?? this.config.contracts?.prizePool;
@@ -2543,10 +2624,16 @@ var Playmos = class {
2543
2624
  * until chain verify catches PaymentSettled (RPC lag) — poll so pay() matches
2544
2625
  * the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
2545
2626
  */
2546
- async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4) {
2627
+ async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4, signal) {
2547
2628
  const start = Date.now();
2548
2629
  let delayMs = 400;
2549
2630
  while (Date.now() - start < timeoutMs) {
2631
+ if (signal?.aborted) {
2632
+ throw new ApiError(
2633
+ `Aborted waiting for server-settle of ${paymentId}. Re-check with playmos.verify("${paymentId}").`,
2634
+ { paymentId, aborted: true, asyncSettle: true }
2635
+ );
2636
+ }
2550
2637
  const raw = await this.http.get(
2551
2638
  `/payments/${encodeURIComponent(paymentId)}`
2552
2639
  );
@@ -2561,12 +2648,46 @@ var Playmos = class {
2561
2648
  { paymentId, timeout: true, asyncSettle: true }
2562
2649
  );
2563
2650
  }
2651
+ /**
2652
+ * sdk#509 — recover a payment after a lost response by the idempotency key you held.
2653
+ * Requires secret sk_test_ (same as list). Returns null if not found.
2654
+ */
2655
+ async findPaymentByIdempotencyKey(idempotencyKey, opts) {
2656
+ requireField(idempotencyKey, "idempotencyKey");
2657
+ if (this.config.mock) {
2658
+ return null;
2659
+ }
2660
+ const q = new URLSearchParams({ idempotencyKey });
2661
+ if (opts?.gameId) q.set("gameId", opts.gameId);
2662
+ try {
2663
+ const res = await this.http.get(
2664
+ `/payments?${q.toString()}`
2665
+ );
2666
+ const row = res.data?.[0];
2667
+ if (!row || typeof row.id !== "string") return null;
2668
+ const kind = row.kind === "entry" ? "entry" : "iap";
2669
+ const amountMicro = validateAmount(String(row.amount ?? "0.01"));
2670
+ return this.mapServerPayment(
2671
+ row,
2672
+ kind,
2673
+ amountMicro
2674
+ );
2675
+ } catch (e) {
2676
+ if (e instanceof ApiError && (e.detail?.status === 404 || e.detail?.code === "not_found" || /not found/i.test(e.message))) {
2677
+ return null;
2678
+ }
2679
+ throw e;
2680
+ }
2681
+ }
2564
2682
  /**
2565
2683
  * Map a server-settled payment (from the `settle: "server"` response) into the
2566
2684
  * SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
2567
- * pass its authoritative `status`/`txHash`/amounts straight through and, for an
2568
- * entry, derive the 60/30/10 USD `split` locally (exactly as the mock + the
2569
- * contract compute it) from the parsed amount.
2685
+ * pass its authoritative `status`/`txHash`/amounts straight through.
2686
+ *
2687
+ * For entry, `Payment.split` is a **first-party 60/30/10 projection** from the
2688
+ * request amount using SDK BPS constants (same integer math as mock) — not a
2689
+ * chain read of pool constructor bps or post-lock payable pot (sdk#514 residual N1).
2690
+ * Prefer `chainAmount` / on-chain `getRound` for money truth.
2570
2691
  */
2571
2692
  mapServerPayment(p, kind, amountMicro) {
2572
2693
  const payment = {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as RoundCancelInput, C as CancelRoundResult, d as RoundGetResult, A as ActiveSeriesRound, e as PrizeBalance, f as WithdrawResult, g as AgentWallet, h as AgentFundResult, T as TransferResult, E as EscrowHoldInput, i as EscrowHoldResult, j as EscrowResolveResult, M as MarketplaceListInput, L as Listing, k as MarketplaceSaleResult, l as MarketplaceGetResult, m as PayInput, n as Payment, o as EnterRoundInput, p as TransferReconcile, q as WaitOptions, r as TransferInput, s as TransferConfirmOptions, V as VerifyResult, t as PayoutRule } from './errors-Chizbb96.cjs';
2
- export { u as ActiveForSeriesResult, v as AgentEconomyConfig, w as AlreadyEnteredError, x as ApiError, y as AuthError, z as ConfigError, B as ContractConfig, D as Eip1193Provider, G as GasConfig, F as GasMode, I as InsufficientGasError, H as InvalidAmountError, J as ListingStatus, K as MarketplaceItem, O as MarketplaceSale, Q as MissingFieldError, U as NothingToWithdrawError, X as PaymentFailedError, Y as PaymentStatus, Z as PlaymosError, _ as PlaymosErrorCode, $ as RetryOptions, a0 as RoundGetVia, a1 as RoundStatus, a2 as WalletConfig, a3 as WalletConnectionError, a4 as WalletConnector, a5 as WalletTimeoutError, a6 as WebhookEventType } from './errors-Chizbb96.cjs';
1
+ import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as RoundCancelInput, C as CancelRoundResult, d as RoundGetResult, A as ActiveSeriesRound, e as PrizeBalance, f as WithdrawResult, g as AgentWallet, h as AgentFundResult, T as TransferResult, E as EscrowHoldInput, i as EscrowHoldResult, j as EscrowResolveResult, M as MarketplaceListInput, L as Listing, k as MarketplaceSaleResult, l as MarketplaceGetResult, m as PayInput, n as Payment, o as EnterRoundInput, p as TransferReconcile, q as WaitOptions, r as TransferInput, s as TransferConfirmOptions, V as VerifyResult, t as PayoutRule } from './errors-Dpeesyop.cjs';
2
+ export { u as ActiveForSeriesResult, v as AgentEconomyConfig, w as AlreadyEnteredError, x as ApiError, y as AuthError, z as ConfigError, B as ContractConfig, D as Eip1193Provider, G as GasConfig, F as GasMode, I as InsufficientGasError, H as InvalidAmountError, J as ListingStatus, K as MarketplaceItem, O as MarketplaceSale, Q as MissingFieldError, U as NothingToWithdrawError, X as PaymentFailedError, Y as PaymentStatus, Z as PlaymosError, _ as PlaymosErrorCode, $ as RetryOptions, a0 as RoundGetVia, a1 as RoundStatus, a2 as WalletConfig, a3 as WalletConnectionError, a4 as WalletConnector, a5 as WalletTimeoutError, a6 as WebhookEventType } from './errors-Dpeesyop.cjs';
3
3
 
4
4
  /**
5
5
  * Environment resolution + the canonical address book.
@@ -655,8 +655,16 @@ declare class Playmos {
655
655
  /**
656
656
  * Offline mock payments for this client instance — so `verify(id)` after
657
657
  * `mock: true` pay/enterRound does not hit the live API (#204).
658
+ * Keyed by payment id for verify.
658
659
  */
659
660
  private readonly mockPayments;
661
+ /**
662
+ * sdk#513 / residual B1–B2 — held idempotencyKey offline (live contract rehearsal).
663
+ * Keyed like service: `${gameId}:${key}` (IAP without gameId uses empty gameId).
664
+ * Stores terms so same key + different money throws (live IdempotencyConflict), not silent replay.
665
+ * Auto-minted keys still unique per call (not stored for cross-call replay).
666
+ */
667
+ private readonly mockByIdempotencyKey;
660
668
  constructor(config: PlaymosConfig);
661
669
  /**
662
670
  * Wallet timeout policy (#462 / PR #463 residual).
@@ -695,7 +703,10 @@ declare class Playmos {
695
703
  identity?: string;
696
704
  }>;
697
705
  };
706
+ private mockIdemScopeKey;
698
707
  private rememberMock;
708
+ /** Live-shaped conflict when held mock key is reused with different money terms (sdk#513 residual B1). */
709
+ private mockIdemReplayOrThrow;
699
710
  /** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
700
711
  pay(input: PayInput): Promise<Payment>;
701
712
  /** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
@@ -759,12 +770,22 @@ declare class Playmos {
759
770
  * the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
760
771
  */
761
772
  private waitForServerSettle;
773
+ /**
774
+ * sdk#509 — recover a payment after a lost response by the idempotency key you held.
775
+ * Requires secret sk_test_ (same as list). Returns null if not found.
776
+ */
777
+ findPaymentByIdempotencyKey(idempotencyKey: string, opts?: {
778
+ gameId?: string;
779
+ }): Promise<Payment | null>;
762
780
  /**
763
781
  * Map a server-settled payment (from the `settle: "server"` response) into the
764
782
  * SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
765
- * pass its authoritative `status`/`txHash`/amounts straight through and, for an
766
- * entry, derive the 60/30/10 USD `split` locally (exactly as the mock + the
767
- * contract compute it) from the parsed amount.
783
+ * pass its authoritative `status`/`txHash`/amounts straight through.
784
+ *
785
+ * For entry, `Payment.split` is a **first-party 60/30/10 projection** from the
786
+ * request amount using SDK BPS constants (same integer math as mock) — not a
787
+ * chain read of pool constructor bps or post-lock payable pot (sdk#514 residual N1).
788
+ * Prefer `chainAmount` / on-chain `getRound` for money truth.
768
789
  */
769
790
  private mapServerPayment;
770
791
  /** Prefer the service's authoritative micro-USDC amount; fall back to the
@@ -838,8 +859,8 @@ declare function previewPoolSplit(amount: string): {
838
859
  */
839
860
 
840
861
  declare class PayoutError extends Error {
841
- code: "payout_invalid";
842
- constructor(message: string);
862
+ code: string;
863
+ constructor(message: string, code?: string);
843
864
  }
844
865
  /**
845
866
  * Apply the studio's payout rule to a payable pool and ranking (best-first).
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as RoundCancelInput, C as CancelRoundResult, d as RoundGetResult, A as ActiveSeriesRound, e as PrizeBalance, f as WithdrawResult, g as AgentWallet, h as AgentFundResult, T as TransferResult, E as EscrowHoldInput, i as EscrowHoldResult, j as EscrowResolveResult, M as MarketplaceListInput, L as Listing, k as MarketplaceSaleResult, l as MarketplaceGetResult, m as PayInput, n as Payment, o as EnterRoundInput, p as TransferReconcile, q as WaitOptions, r as TransferInput, s as TransferConfirmOptions, V as VerifyResult, t as PayoutRule } from './errors-Chizbb96.js';
2
- export { u as ActiveForSeriesResult, v as AgentEconomyConfig, w as AlreadyEnteredError, x as ApiError, y as AuthError, z as ConfigError, B as ContractConfig, D as Eip1193Provider, G as GasConfig, F as GasMode, I as InsufficientGasError, H as InvalidAmountError, J as ListingStatus, K as MarketplaceItem, O as MarketplaceSale, Q as MissingFieldError, U as NothingToWithdrawError, X as PaymentFailedError, Y as PaymentStatus, Z as PlaymosError, _ as PlaymosErrorCode, $ as RetryOptions, a0 as RoundGetVia, a1 as RoundStatus, a2 as WalletConfig, a3 as WalletConnectionError, a4 as WalletConnector, a5 as WalletTimeoutError, a6 as WebhookEventType } from './errors-Chizbb96.js';
1
+ import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as RoundCancelInput, C as CancelRoundResult, d as RoundGetResult, A as ActiveSeriesRound, e as PrizeBalance, f as WithdrawResult, g as AgentWallet, h as AgentFundResult, T as TransferResult, E as EscrowHoldInput, i as EscrowHoldResult, j as EscrowResolveResult, M as MarketplaceListInput, L as Listing, k as MarketplaceSaleResult, l as MarketplaceGetResult, m as PayInput, n as Payment, o as EnterRoundInput, p as TransferReconcile, q as WaitOptions, r as TransferInput, s as TransferConfirmOptions, V as VerifyResult, t as PayoutRule } from './errors-Dpeesyop.js';
2
+ export { u as ActiveForSeriesResult, v as AgentEconomyConfig, w as AlreadyEnteredError, x as ApiError, y as AuthError, z as ConfigError, B as ContractConfig, D as Eip1193Provider, G as GasConfig, F as GasMode, I as InsufficientGasError, H as InvalidAmountError, J as ListingStatus, K as MarketplaceItem, O as MarketplaceSale, Q as MissingFieldError, U as NothingToWithdrawError, X as PaymentFailedError, Y as PaymentStatus, Z as PlaymosError, _ as PlaymosErrorCode, $ as RetryOptions, a0 as RoundGetVia, a1 as RoundStatus, a2 as WalletConfig, a3 as WalletConnectionError, a4 as WalletConnector, a5 as WalletTimeoutError, a6 as WebhookEventType } from './errors-Dpeesyop.js';
3
3
 
4
4
  /**
5
5
  * Environment resolution + the canonical address book.
@@ -655,8 +655,16 @@ declare class Playmos {
655
655
  /**
656
656
  * Offline mock payments for this client instance — so `verify(id)` after
657
657
  * `mock: true` pay/enterRound does not hit the live API (#204).
658
+ * Keyed by payment id for verify.
658
659
  */
659
660
  private readonly mockPayments;
661
+ /**
662
+ * sdk#513 / residual B1–B2 — held idempotencyKey offline (live contract rehearsal).
663
+ * Keyed like service: `${gameId}:${key}` (IAP without gameId uses empty gameId).
664
+ * Stores terms so same key + different money throws (live IdempotencyConflict), not silent replay.
665
+ * Auto-minted keys still unique per call (not stored for cross-call replay).
666
+ */
667
+ private readonly mockByIdempotencyKey;
660
668
  constructor(config: PlaymosConfig);
661
669
  /**
662
670
  * Wallet timeout policy (#462 / PR #463 residual).
@@ -695,7 +703,10 @@ declare class Playmos {
695
703
  identity?: string;
696
704
  }>;
697
705
  };
706
+ private mockIdemScopeKey;
698
707
  private rememberMock;
708
+ /** Live-shaped conflict when held mock key is reused with different money terms (sdk#513 residual B1). */
709
+ private mockIdemReplayOrThrow;
699
710
  /** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
700
711
  pay(input: PayInput): Promise<Payment>;
701
712
  /** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
@@ -759,12 +770,22 @@ declare class Playmos {
759
770
  * the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
760
771
  */
761
772
  private waitForServerSettle;
773
+ /**
774
+ * sdk#509 — recover a payment after a lost response by the idempotency key you held.
775
+ * Requires secret sk_test_ (same as list). Returns null if not found.
776
+ */
777
+ findPaymentByIdempotencyKey(idempotencyKey: string, opts?: {
778
+ gameId?: string;
779
+ }): Promise<Payment | null>;
762
780
  /**
763
781
  * Map a server-settled payment (from the `settle: "server"` response) into the
764
782
  * SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
765
- * pass its authoritative `status`/`txHash`/amounts straight through and, for an
766
- * entry, derive the 60/30/10 USD `split` locally (exactly as the mock + the
767
- * contract compute it) from the parsed amount.
783
+ * pass its authoritative `status`/`txHash`/amounts straight through.
784
+ *
785
+ * For entry, `Payment.split` is a **first-party 60/30/10 projection** from the
786
+ * request amount using SDK BPS constants (same integer math as mock) — not a
787
+ * chain read of pool constructor bps or post-lock payable pot (sdk#514 residual N1).
788
+ * Prefer `chainAmount` / on-chain `getRound` for money truth.
768
789
  */
769
790
  private mapServerPayment;
770
791
  /** Prefer the service's authoritative micro-USDC amount; fall back to the
@@ -838,8 +859,8 @@ declare function previewPoolSplit(amount: string): {
838
859
  */
839
860
 
840
861
  declare class PayoutError extends Error {
841
- code: "payout_invalid";
842
- constructor(message: string);
862
+ code: string;
863
+ constructor(message: string, code?: string);
843
864
  }
844
865
  /**
845
866
  * Apply the studio's payout rule to a payable pool and ranking (best-first).