@playmos/sdk 0.3.9 → 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/CHANGELOG.md +189 -0
- package/README.md +3 -0
- package/dist/chunk-35WJANW4.js +255 -0
- package/dist/{errors-BMlWHsMb.d.cts → errors-Dpeesyop.d.cts} +68 -5
- package/dist/{errors-BMlWHsMb.d.ts → errors-Dpeesyop.d.ts} +68 -5
- package/dist/index.cjs +172 -29
- package/dist/index.d.cts +47 -8
- package/dist/index.d.ts +47 -8
- package/dist/index.js +175 -200
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +34 -2
- package/dist/server.d.ts +34 -2
- package/dist/server.js +110 -2
- package/package.json +4 -8
- package/dist/chunk-UZECDT6F.js +0 -83
|
@@ -24,11 +24,19 @@ interface GasConfig {
|
|
|
24
24
|
paymasterUrl?: string;
|
|
25
25
|
}
|
|
26
26
|
interface WalletConfig {
|
|
27
|
+
/**
|
|
28
|
+
* Which connector name to use when {@link WalletConfig.provider} is omitted.
|
|
29
|
+
* - `"injected"` — requires `globalThis.ethereum` (wallet browser / extension).
|
|
30
|
+
* - `"base-account"` (default) — **not** a built-in Base Account package inside
|
|
31
|
+
* `@playmos/sdk`. Resolution: use injected ethereum if present; otherwise you
|
|
32
|
+
* must install `@base-org/account` yourself, construct a provider, and pass
|
|
33
|
+
* it as `wallet.provider` (sdk#512). No hard dependency on `@base-org/account`.
|
|
34
|
+
*/
|
|
27
35
|
connector?: WalletConnector;
|
|
28
36
|
/**
|
|
29
|
-
* Pre-built EIP-1193 provider.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
37
|
+
* Pre-built EIP-1193 provider (host-owned). Always preferred when set.
|
|
38
|
+
* For Base Account / passkey hosts: construct with `@base-org/account` (or the
|
|
39
|
+
* Base App injection) and pass it here. Still **timeout-wrapped** on resolve (#462).
|
|
32
40
|
*/
|
|
33
41
|
provider?: Eip1193Provider;
|
|
34
42
|
}
|
|
@@ -129,6 +137,10 @@ interface PayInput {
|
|
|
129
137
|
sku: string;
|
|
130
138
|
/** Your opaque user id. */
|
|
131
139
|
playerId: string;
|
|
140
|
+
/** sdk#508 — max wait for server-settle poll (ms). Default 60_000. */
|
|
141
|
+
settleTimeoutMs?: number;
|
|
142
|
+
/** sdk#508 — abort server-settle polling. */
|
|
143
|
+
signal?: AbortSignal;
|
|
132
144
|
/**
|
|
133
145
|
* The game this IAP belongs to. Optional: when your API key maps to exactly
|
|
134
146
|
* one game the service resolves it for you (the quickstart). Supply it
|
|
@@ -150,6 +162,13 @@ interface EnterRoundInput {
|
|
|
150
162
|
amount: string;
|
|
151
163
|
playerId: string;
|
|
152
164
|
idempotencyKey?: string;
|
|
165
|
+
/**
|
|
166
|
+
* sdk#508 — max wait for server-settle poll (ms). Default 60_000.
|
|
167
|
+
* Only applies to no-wallet sandbox server-settle path.
|
|
168
|
+
*/
|
|
169
|
+
settleTimeoutMs?: number;
|
|
170
|
+
/** sdk#508 — abort server-settle polling. */
|
|
171
|
+
signal?: AbortSignal;
|
|
153
172
|
metadata?: Record<string, string>;
|
|
154
173
|
/**
|
|
155
174
|
* Exact on-chain round key for Path B / entryProvider (#201 / #204 / #210).
|
|
@@ -387,10 +406,31 @@ interface Payment {
|
|
|
387
406
|
status: PaymentStatus;
|
|
388
407
|
/** "iap" (1% external) or "entry" (60/30/10 prize-pool). */
|
|
389
408
|
kind: "iap" | "entry";
|
|
409
|
+
/**
|
|
410
|
+
* Requested amount (USD string). For prize-pool **server-settle**, this may
|
|
411
|
+
* differ from what moved on chain — see {@link Payment.chainAmount} (sdk#507).
|
|
412
|
+
*/
|
|
390
413
|
amount: string;
|
|
391
414
|
fee: string;
|
|
392
415
|
net: string;
|
|
393
|
-
/**
|
|
416
|
+
/**
|
|
417
|
+
* On-chain entry amount (USD) when the service observed it (server-settle entry).
|
|
418
|
+
* PrizePool charges the **round open price**, not necessarily {@link Payment.amount}.
|
|
419
|
+
* Reconcile economics against this field (or a chain `getRound` read), not `amount` alone.
|
|
420
|
+
*/
|
|
421
|
+
chainAmount?: string;
|
|
422
|
+
/** On-chain entry amount in micro-USDC (6 dp integer string), when known. */
|
|
423
|
+
chainAmountMicro?: string;
|
|
424
|
+
/**
|
|
425
|
+
* First-party **60/30/10 projection** (pool/seed/rake USD strings) for prize-pool
|
|
426
|
+
* entries — bookkeeping preview, **not** a chain-derived settlement observation
|
|
427
|
+
* (sdk#514). Numbers come from SDK constants (`POOL_BPS`/`SEED_BPS`/`RAKE_BPS`),
|
|
428
|
+
* not from reading the deployed pool’s constructor bps. On-chain, entry only
|
|
429
|
+
* takes the fee off the top; pool/seed split at **lock**; payable pot also
|
|
430
|
+
* includes inherited seedBank — so `split.pool` is **not** “what this round pays.”
|
|
431
|
+
* Reconcile money with {@link Payment.chainAmount} / pool `getRound`.
|
|
432
|
+
* Field name kept for compatibility (no rename in V1).
|
|
433
|
+
*/
|
|
394
434
|
split?: {
|
|
395
435
|
pool: string;
|
|
396
436
|
seed: string;
|
|
@@ -427,9 +467,17 @@ interface Payment {
|
|
|
427
467
|
interface VerifyResult {
|
|
428
468
|
id: string;
|
|
429
469
|
status: PaymentStatus;
|
|
470
|
+
/** Requested amount (USD). May differ from {@link VerifyResult.chainAmount} on server-settle entry. */
|
|
430
471
|
amount: string;
|
|
431
472
|
fee: string;
|
|
432
473
|
net: string;
|
|
474
|
+
/**
|
|
475
|
+
* On-chain settled entry amount (USD) when known — distinct from {@link VerifyResult.amount}
|
|
476
|
+
* (sdk#507 / Vault Pop DX-05b). Prefer this for pot/split economics on shared rounds.
|
|
477
|
+
*/
|
|
478
|
+
chainAmount?: string;
|
|
479
|
+
/** On-chain settled entry amount in micro-USDC, when known. */
|
|
480
|
+
chainAmountMicro?: string;
|
|
433
481
|
txHash?: `0x${string}`;
|
|
434
482
|
playerId: string;
|
|
435
483
|
sku?: string;
|
|
@@ -440,6 +488,8 @@ interface VerifyResult {
|
|
|
440
488
|
* service can never confirm on-chain. */
|
|
441
489
|
verifiedVia?: "chain" | "cache" | "degraded";
|
|
442
490
|
chainReads?: "enabled" | "degraded";
|
|
491
|
+
/** Echo of on-chain identity when the service returns it (server-settle). */
|
|
492
|
+
identity?: string;
|
|
443
493
|
}
|
|
444
494
|
type WebhookEventType = "payment.confirmed" | "payment.failed" | "payout.settled" | "refund.processed";
|
|
445
495
|
interface WebhookEvent {
|
|
@@ -493,6 +543,19 @@ interface RoundOpenInput {
|
|
|
493
543
|
*/
|
|
494
544
|
roundKey?: string;
|
|
495
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Service GET /v1/rounds/:id read-path honesty (#497 / #501).
|
|
548
|
+
* Transport meta — not chain lifecycle. Prefer `rounds.getWithMeta` over stuffing onto RoundState.
|
|
549
|
+
*/
|
|
550
|
+
type RoundGetVia = "chain" | "cache" | "stale" | "unknown";
|
|
551
|
+
/** Full GET round response including transport meta (#501 — Claude pick C / getWithMeta). */
|
|
552
|
+
interface RoundGetResult {
|
|
553
|
+
round: RoundState;
|
|
554
|
+
/** Present on live service responses after #499. Omitted under `mock: true`. */
|
|
555
|
+
via?: RoundGetVia;
|
|
556
|
+
/** Present only when eth_call itself threw (#497 C1). */
|
|
557
|
+
chainReadFailed?: boolean;
|
|
558
|
+
}
|
|
496
559
|
interface RoundState {
|
|
497
560
|
roundId: string;
|
|
498
561
|
gameId: string;
|
|
@@ -715,4 +778,4 @@ declare class NothingToWithdrawError extends PlaymosError {
|
|
|
715
778
|
constructor(detail?: Record<string, unknown>);
|
|
716
779
|
}
|
|
717
780
|
|
|
718
|
-
export { type
|
|
781
|
+
export { type RetryOptions as $, type ActiveSeriesRound as A, type ContractConfig as B, type CancelRoundResult as C, type Eip1193Provider as D, type EscrowHoldInput as E, type GasMode as F, type GasConfig as G, InvalidAmountError as H, InsufficientGasError as I, type ListingStatus as J, type MarketplaceItem as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type MarketplaceSale as O, type PlaymosConfig as P, MissingFieldError as Q, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, NothingToWithdrawError as U, type VerifyResult as V, type WebhookEvent as W, PaymentFailedError as X, type PaymentStatus as Y, PlaymosError as Z, type PlaymosErrorCode as _, type RoundState as a, type RoundGetVia as a0, type RoundStatus as a1, type WalletConfig as a2, WalletConnectionError as a3, type WalletConnector as a4, WalletTimeoutError as a5, type WebhookEventType as a6, type RoundSettleInput as b, type RoundCancelInput as c, type RoundGetResult as d, type PrizeBalance as e, type WithdrawResult as f, type AgentWallet as g, type AgentFundResult as h, type EscrowHoldResult as i, type EscrowResolveResult as j, type MarketplaceSaleResult as k, type MarketplaceGetResult as l, type PayInput as m, type Payment as n, type EnterRoundInput as o, type TransferReconcile as p, type WaitOptions as q, type TransferInput as r, type TransferConfirmOptions as s, type PayoutRule as t, type ActiveForSeriesResult as u, type AgentEconomyConfig as v, AlreadyEnteredError as w, ApiError as x, AuthError as y, ConfigError as z };
|
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
|
|
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
|
-
|
|
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) {
|
|
@@ -1614,19 +1615,34 @@ var Playmos = class {
|
|
|
1614
1615
|
}
|
|
1615
1616
|
return { ...result, txHash: result.txHash ?? firstTx ?? null };
|
|
1616
1617
|
},
|
|
1618
|
+
/**
|
|
1619
|
+
* Round state only (stable). Prefer {@link getWithMeta} when you need service
|
|
1620
|
+
* read-path honesty (`via` / `chainReadFailed` — #497 / #501).
|
|
1621
|
+
*/
|
|
1617
1622
|
get: async (input) => {
|
|
1623
|
+
const meta = await this.rounds.getWithMeta(input);
|
|
1624
|
+
return meta.round;
|
|
1625
|
+
},
|
|
1626
|
+
/**
|
|
1627
|
+
* Round state + transport meta from GET /v1/rounds/:id (#501).
|
|
1628
|
+
* Does **not** attach `via` onto RoundState (Claude rejected option B — hub would
|
|
1629
|
+
* silently pick it up and leak into settle/cancel paths that call get()).
|
|
1630
|
+
* Under `mock: true`, meta fields are **omitted** (no eth_call; do not invent "cache").
|
|
1631
|
+
*/
|
|
1632
|
+
getWithMeta: async (input) => {
|
|
1618
1633
|
requireField(input?.roundId, "roundId");
|
|
1619
1634
|
if (this.config.mock) {
|
|
1620
1635
|
const existing = this.mockRounds.get(input.roundId);
|
|
1621
1636
|
if (!existing) {
|
|
1622
1637
|
throw new ApiError(`unknown round: ${input.roundId}`, { status: 404, code: "not_found" });
|
|
1623
1638
|
}
|
|
1624
|
-
return existing;
|
|
1639
|
+
return { round: existing };
|
|
1625
1640
|
}
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
);
|
|
1629
|
-
|
|
1641
|
+
const body = await this.http.get(`/rounds/${encodeURIComponent(input.roundId)}`);
|
|
1642
|
+
const out = { round: body.round };
|
|
1643
|
+
if (body.via != null) out.via = body.via;
|
|
1644
|
+
if (body.chainReadFailed === true) out.chainReadFailed = true;
|
|
1645
|
+
return out;
|
|
1630
1646
|
},
|
|
1631
1647
|
/**
|
|
1632
1648
|
* Authoritative series latch (#351) — `null` means **proven free**.
|
|
@@ -1731,19 +1747,26 @@ var Playmos = class {
|
|
|
1731
1747
|
*/
|
|
1732
1748
|
withdraw: async (input) => {
|
|
1733
1749
|
if (this.config.mock) {
|
|
1734
|
-
let amountMicro2 = 0n;
|
|
1735
1750
|
let prizePool2 = input.prizePoolAddress ?? "0x0000000000000000000000000000000000000001";
|
|
1736
1751
|
if (input.roundId) {
|
|
1737
1752
|
const existing = this.mockRounds.get(input.roundId);
|
|
1738
1753
|
if (existing?.prizePoolAddress) prizePool2 = existing.prizePoolAddress;
|
|
1739
|
-
for (const [k, v] of this.mockWithdrawable) {
|
|
1740
|
-
if (k.startsWith(`${input.roundId}:`) && v > 0n) {
|
|
1741
|
-
amountMicro2 = v;
|
|
1742
|
-
this.mockWithdrawable.set(k, 0n);
|
|
1743
|
-
break;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
1754
|
}
|
|
1755
|
+
if (!input.wallet || !ADDRESS_RE3.test(input.wallet)) {
|
|
1756
|
+
throw new ConfigError(
|
|
1757
|
+
"mock rounds.withdraw requires wallet (0x\u2026) \u2014 live uses the connected provider; without wallet mock would pay the first non-zero credit to the wrong player (#495)"
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
const wallet = input.wallet.toLowerCase();
|
|
1761
|
+
if (!input.roundId) {
|
|
1762
|
+
throw new ConfigError("mock rounds.withdraw requires roundId to locate claimable credits");
|
|
1763
|
+
}
|
|
1764
|
+
const key = `${input.roundId}:${wallet}`;
|
|
1765
|
+
const amountMicro2 = this.mockWithdrawable.get(key) ?? 0n;
|
|
1766
|
+
if (amountMicro2 === 0n) {
|
|
1767
|
+
throw new NothingToWithdrawError({ prizePoolAddress: prizePool2, wallet });
|
|
1768
|
+
}
|
|
1769
|
+
this.mockWithdrawable.set(key, 0n);
|
|
1747
1770
|
return {
|
|
1748
1771
|
prizePoolAddress: prizePool2,
|
|
1749
1772
|
amount: formatMicroToUsd(amountMicro2),
|
|
@@ -2015,8 +2038,16 @@ var Playmos = class {
|
|
|
2015
2038
|
/**
|
|
2016
2039
|
* Offline mock payments for this client instance — so `verify(id)` after
|
|
2017
2040
|
* `mock: true` pay/enterRound does not hit the live API (#204).
|
|
2041
|
+
* Keyed by payment id for verify.
|
|
2018
2042
|
*/
|
|
2019
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();
|
|
2020
2051
|
/**
|
|
2021
2052
|
* `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
|
|
2022
2053
|
* `get` reconciles once against chain truth; `wait` polls it to a terminal state
|
|
@@ -2201,18 +2232,62 @@ var Playmos = class {
|
|
|
2201
2232
|
}
|
|
2202
2233
|
};
|
|
2203
2234
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
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
|
+
}
|
|
2206
2248
|
return payment;
|
|
2207
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
|
+
}
|
|
2208
2269
|
/** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
|
|
2209
2270
|
async pay(input) {
|
|
2210
2271
|
const amountMicro = validateAmount(input.amount);
|
|
2211
2272
|
requireField(input.sku, "sku");
|
|
2212
2273
|
requireField(input.playerId, "playerId");
|
|
2213
2274
|
const metadata = validateMetadata(input.metadata);
|
|
2214
|
-
const
|
|
2275
|
+
const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
|
|
2276
|
+
const idempotencyKey = heldIdem ?? prefixedId("idem");
|
|
2215
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
|
+
}
|
|
2216
2291
|
return this.rememberMock(
|
|
2217
2292
|
mockIapPayment({
|
|
2218
2293
|
amountMicro,
|
|
@@ -2221,7 +2296,8 @@ var Playmos = class {
|
|
|
2221
2296
|
playerId: input.playerId,
|
|
2222
2297
|
chain: this.env.network,
|
|
2223
2298
|
metadata
|
|
2224
|
-
})
|
|
2299
|
+
}),
|
|
2300
|
+
heldIdem ? { key: heldIdem, terms } : void 0
|
|
2225
2301
|
);
|
|
2226
2302
|
}
|
|
2227
2303
|
if (this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy())) {
|
|
@@ -2243,7 +2319,13 @@ var Playmos = class {
|
|
|
2243
2319
|
);
|
|
2244
2320
|
let payment = this.mapServerPayment(res.payment, "iap", amountMicro);
|
|
2245
2321
|
if (payment.status === "pending" || payment.status === "created") {
|
|
2246
|
-
payment = await this.waitForServerSettle(
|
|
2322
|
+
payment = await this.waitForServerSettle(
|
|
2323
|
+
payment.id,
|
|
2324
|
+
"iap",
|
|
2325
|
+
amountMicro,
|
|
2326
|
+
input.settleTimeoutMs ?? 6e4,
|
|
2327
|
+
input.signal
|
|
2328
|
+
);
|
|
2247
2329
|
}
|
|
2248
2330
|
return payment;
|
|
2249
2331
|
}
|
|
@@ -2288,8 +2370,22 @@ var Playmos = class {
|
|
|
2288
2370
|
requireField(input.roundId, "roundId");
|
|
2289
2371
|
requireField(input.playerId, "playerId");
|
|
2290
2372
|
const metadata = validateMetadata(input.metadata);
|
|
2291
|
-
const
|
|
2373
|
+
const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
|
|
2374
|
+
const idempotencyKey = heldIdem ?? prefixedId("idem");
|
|
2292
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
|
+
}
|
|
2293
2389
|
this.mockAccumulateEntry(input.roundId, amountMicro);
|
|
2294
2390
|
return this.rememberMock(
|
|
2295
2391
|
mockEntryPayment({
|
|
@@ -2305,7 +2401,8 @@ var Playmos = class {
|
|
|
2305
2401
|
// B2 pins — must survive mock path for hub hasEntered parity (#204).
|
|
2306
2402
|
roundKey: input.roundKey,
|
|
2307
2403
|
identity: input.identity
|
|
2308
|
-
})
|
|
2404
|
+
}),
|
|
2405
|
+
heldIdem ? { key: heldIdem, terms } : void 0
|
|
2309
2406
|
);
|
|
2310
2407
|
}
|
|
2311
2408
|
const serverSettleNoWallet = this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy());
|
|
@@ -2338,7 +2435,13 @@ var Playmos = class {
|
|
|
2338
2435
|
);
|
|
2339
2436
|
let payment2 = this.mapServerPayment(res.payment, "entry", amountMicro);
|
|
2340
2437
|
if (payment2.status === "pending" || payment2.status === "created") {
|
|
2341
|
-
payment2 = await this.waitForServerSettle(
|
|
2438
|
+
payment2 = await this.waitForServerSettle(
|
|
2439
|
+
payment2.id,
|
|
2440
|
+
"entry",
|
|
2441
|
+
amountMicro,
|
|
2442
|
+
input.settleTimeoutMs ?? 6e4,
|
|
2443
|
+
input.signal
|
|
2444
|
+
);
|
|
2342
2445
|
}
|
|
2343
2446
|
if (input.identity) payment2.identity = input.identity;
|
|
2344
2447
|
const pool = res.clientParams?.contractAddress ?? this.config.contracts?.prizePool;
|
|
@@ -2521,10 +2624,16 @@ var Playmos = class {
|
|
|
2521
2624
|
* until chain verify catches PaymentSettled (RPC lag) — poll so pay() matches
|
|
2522
2625
|
* the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
|
|
2523
2626
|
*/
|
|
2524
|
-
async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4) {
|
|
2627
|
+
async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4, signal) {
|
|
2525
2628
|
const start = Date.now();
|
|
2526
2629
|
let delayMs = 400;
|
|
2527
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
|
+
}
|
|
2528
2637
|
const raw = await this.http.get(
|
|
2529
2638
|
`/payments/${encodeURIComponent(paymentId)}`
|
|
2530
2639
|
);
|
|
@@ -2539,12 +2648,46 @@ var Playmos = class {
|
|
|
2539
2648
|
{ paymentId, timeout: true, asyncSettle: true }
|
|
2540
2649
|
);
|
|
2541
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
|
+
}
|
|
2542
2682
|
/**
|
|
2543
2683
|
* Map a server-settled payment (from the `settle: "server"` response) into the
|
|
2544
2684
|
* SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
|
|
2545
|
-
* pass its authoritative `status`/`txHash`/amounts straight through
|
|
2546
|
-
*
|
|
2547
|
-
*
|
|
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.
|
|
2548
2691
|
*/
|
|
2549
2692
|
mapServerPayment(p, kind, amountMicro) {
|
|
2550
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, A as ActiveSeriesRound,
|
|
2
|
-
export {
|
|
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.
|
|
@@ -518,9 +518,22 @@ declare class Playmos {
|
|
|
518
518
|
* if still pending — never invents terminal success).
|
|
519
519
|
*/
|
|
520
520
|
cancel: (input: RoundCancelInput) => Promise<CancelRoundResult>;
|
|
521
|
+
/**
|
|
522
|
+
* Round state only (stable). Prefer {@link getWithMeta} when you need service
|
|
523
|
+
* read-path honesty (`via` / `chainReadFailed` — #497 / #501).
|
|
524
|
+
*/
|
|
521
525
|
get: (input: {
|
|
522
526
|
roundId: string;
|
|
523
527
|
}) => Promise<RoundState>;
|
|
528
|
+
/**
|
|
529
|
+
* Round state + transport meta from GET /v1/rounds/:id (#501).
|
|
530
|
+
* Does **not** attach `via` onto RoundState (Claude rejected option B — hub would
|
|
531
|
+
* silently pick it up and leak into settle/cancel paths that call get()).
|
|
532
|
+
* Under `mock: true`, meta fields are **omitted** (no eth_call; do not invent "cache").
|
|
533
|
+
*/
|
|
534
|
+
getWithMeta: (input: {
|
|
535
|
+
roundId: string;
|
|
536
|
+
}) => Promise<RoundGetResult>;
|
|
524
537
|
/**
|
|
525
538
|
* Authoritative series latch (#351) — `null` means **proven free**.
|
|
526
539
|
* `GET /v1/series/:seriesKey/active?gameId=…` — not a candidate probe.
|
|
@@ -550,6 +563,11 @@ declare class Playmos {
|
|
|
550
563
|
withdraw: (input: {
|
|
551
564
|
prizePoolAddress?: `0x${string}`;
|
|
552
565
|
roundId?: string;
|
|
566
|
+
/**
|
|
567
|
+
* Winner wallet (0x…). **Required under mock** (#495 Claude) — live path uses the
|
|
568
|
+
* connected provider. Without this, mock paid the first non-zero credit to anyone.
|
|
569
|
+
*/
|
|
570
|
+
wallet?: string;
|
|
553
571
|
/** Pre-check claimable; default true. Set false only if you already called `prize()`. */
|
|
554
572
|
checkBalance?: boolean;
|
|
555
573
|
}) => Promise<WithdrawResult>;
|
|
@@ -637,8 +655,16 @@ declare class Playmos {
|
|
|
637
655
|
/**
|
|
638
656
|
* Offline mock payments for this client instance — so `verify(id)` after
|
|
639
657
|
* `mock: true` pay/enterRound does not hit the live API (#204).
|
|
658
|
+
* Keyed by payment id for verify.
|
|
640
659
|
*/
|
|
641
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;
|
|
642
668
|
constructor(config: PlaymosConfig);
|
|
643
669
|
/**
|
|
644
670
|
* Wallet timeout policy (#462 / PR #463 residual).
|
|
@@ -677,7 +703,10 @@ declare class Playmos {
|
|
|
677
703
|
identity?: string;
|
|
678
704
|
}>;
|
|
679
705
|
};
|
|
706
|
+
private mockIdemScopeKey;
|
|
680
707
|
private rememberMock;
|
|
708
|
+
/** Live-shaped conflict when held mock key is reused with different money terms (sdk#513 residual B1). */
|
|
709
|
+
private mockIdemReplayOrThrow;
|
|
681
710
|
/** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
|
|
682
711
|
pay(input: PayInput): Promise<Payment>;
|
|
683
712
|
/** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
|
|
@@ -741,12 +770,22 @@ declare class Playmos {
|
|
|
741
770
|
* the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
|
|
742
771
|
*/
|
|
743
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>;
|
|
744
780
|
/**
|
|
745
781
|
* Map a server-settled payment (from the `settle: "server"` response) into the
|
|
746
782
|
* SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
|
|
747
|
-
* pass its authoritative `status`/`txHash`/amounts straight through
|
|
748
|
-
*
|
|
749
|
-
*
|
|
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.
|
|
750
789
|
*/
|
|
751
790
|
private mapServerPayment;
|
|
752
791
|
/** Prefer the service's authoritative micro-USDC amount; fall back to the
|
|
@@ -820,8 +859,8 @@ declare function previewPoolSplit(amount: string): {
|
|
|
820
859
|
*/
|
|
821
860
|
|
|
822
861
|
declare class PayoutError extends Error {
|
|
823
|
-
code:
|
|
824
|
-
constructor(message: string);
|
|
862
|
+
code: string;
|
|
863
|
+
constructor(message: string, code?: string);
|
|
825
864
|
}
|
|
826
865
|
/**
|
|
827
866
|
* Apply the studio's payout rule to a payable pool and ranking (best-first).
|
|
@@ -890,4 +929,4 @@ declare function ulid(seedTime?: number): string;
|
|
|
890
929
|
/** `${prefix}_${ulid()}` — e.g. `pay_01J…`, `entry_01J…`, `idem_01J…`. */
|
|
891
930
|
declare function prefixedId(prefix: string): string;
|
|
892
931
|
|
|
893
|
-
export { ActiveSeriesRound, AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, CancelRoundResult, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, PayoutRule, PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, PrizeBalance, RoundCancelInput, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, WithdrawResult, type X402ChallengeInput, type X402ChallengeResult, type X402FulfillAuthorization, type X402PayInput, type X402PayloadAuthorization, type X402PayloadMode, type X402PaymentRequired, type X402SettleResult, clientRuntimeSignals, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, createX402Challenge, decodePaymentHeader, encodePaymentHeader, formatMicroToUsd, isClientRuntime, isWalletSignatureAuthorization, isX402PayloadAuthorization, networkToCaip2, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, resolveEnv, serializePaymentRequirement, toX402PaymentRequired, ulid, validateX402ChallengeInput };
|
|
932
|
+
export { ActiveSeriesRound, AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, CancelRoundResult, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, PayoutRule, PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, PrizeBalance, RoundCancelInput, RoundGetResult, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, WithdrawResult, type X402ChallengeInput, type X402ChallengeResult, type X402FulfillAuthorization, type X402PayInput, type X402PayloadAuthorization, type X402PayloadMode, type X402PaymentRequired, type X402SettleResult, clientRuntimeSignals, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, createX402Challenge, decodePaymentHeader, encodePaymentHeader, formatMicroToUsd, isClientRuntime, isWalletSignatureAuthorization, isX402PayloadAuthorization, networkToCaip2, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, resolveEnv, serializePaymentRequirement, toX402PaymentRequired, ulid, validateX402ChallengeInput };
|