@playmos/sdk 0.1.6 → 0.2.0

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Network, P as PlaymosConfig, W as WebhookEvent, a as PayInput, b as Payment, E as EnterRoundInput, V as VerifyResult } from './errors-B-85VYMv.js';
2
- export { A as AgentEconomyConfig, c as ApiError, d as AuthError, C as ConfigError, e as ContractConfig, f as Eip1193Provider, G as GasConfig, g as GasMode, I as InsufficientGasError, h as InvalidAmountError, M as MissingFieldError, i as PaymentFailedError, j as PaymentStatus, k as PlaymosError, l as PlaymosErrorCode, m as WalletConfig, n as WalletConnectionError, o as WalletConnector, p as WebhookEventType } from './errors-B-85VYMv.js';
1
+ import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, A as AgentWallet, c as AgentFundResult, T as TransferResult, E as EscrowHoldInput, d as EscrowHoldResult, e as EscrowResolveResult, M as MarketplaceListInput, L as Listing, f as MarketplaceSaleResult, g as MarketplaceGetResult, h as PayInput, i as Payment, j as EnterRoundInput, k as TransferInput, V as VerifyResult } from './errors-CjL85YKR.js';
2
+ export { l as AgentEconomyConfig, m as ApiError, n as AuthError, C as ConfigError, o as ContractConfig, p as Eip1193Provider, G as GasConfig, q as GasMode, I as InsufficientGasError, r as InvalidAmountError, s as ListingStatus, t as MarketplaceItem, u as MarketplaceSale, v as MissingFieldError, w as PaymentFailedError, x as PaymentStatus, y as PayoutRule, z as PlaymosError, B as PlaymosErrorCode, D as RoundStatus, F as WalletConfig, H as WalletConnectionError, J as WalletConnector, K as WebhookEventType } from './errors-CjL85YKR.js';
3
3
 
4
4
  /**
5
5
  * Environment resolution + the canonical address book.
@@ -36,11 +36,18 @@ declare class Playmos {
36
36
  readonly config: PlaymosConfig;
37
37
  readonly env: ResolvedEnv;
38
38
  private readonly http;
39
+ /**
40
+ * Webhook helpers. Signature verification is **server-only** (Node `crypto`) and
41
+ * lives on `@playmos/sdk/server` so browser bundlers never see `node:crypto` (#9).
42
+ *
43
+ * import { verifyWebhook } from "@playmos/sdk/server";
44
+ * const event = verifyWebhook(rawBody, req.headers["x-playmos-signature"], secret);
45
+ */
39
46
  readonly webhooks: {
40
47
  /**
41
- * Webhook verification is server-only (it uses node:crypto) and no longer
42
- * ships in the browser entry (issue #9). On a backend, import it directly:
43
- * import { verifyWebhook } from "@playmos/sdk/server";
48
+ * @deprecated Use `import { verifyWebhook } from "@playmos/sdk/server"` instead.
49
+ * Throws if called kept as a discoverable pointer so call sites fail loudly
50
+ * with a fix instruction rather than a silent missing method.
44
51
  */
45
52
  verify: (_rawBody: string | Buffer, _signatureHeader: string | string[] | undefined, _secret: string) => WebhookEvent;
46
53
  };
@@ -54,25 +61,104 @@ declare class Playmos {
54
61
  url: string;
55
62
  }>;
56
63
  };
64
+ /**
65
+ * Skill/contest **round lifecycle** (issue #13) — closes the enterRound loop.
66
+ *
67
+ * Scores never leave the studio. Flow:
68
+ * rounds.open → players enterRound → studio scores → rounds.lock →
69
+ * rounds.settle({ ranking }) → contract pays winners.
70
+ *
71
+ * Operator txs are signed by the Playmos service (OPERATOR_ROLE key).
72
+ */
73
+ readonly rounds: {
74
+ open: (input: RoundOpenInput) => Promise<RoundState>;
75
+ lock: (input: {
76
+ roundId: string;
77
+ gameId?: string;
78
+ }) => Promise<RoundState>;
79
+ settle: (input: RoundSettleInput) => Promise<SettleRoundResult>;
80
+ get: (input: {
81
+ roundId: string;
82
+ }) => Promise<RoundState>;
83
+ };
84
+ /**
85
+ * `agents` — assign wallets to the NPCs YOUR game already owns, so they can transact USDC in your economy.
86
+ * The game creates the NPCs; the SDK only creates the WALLET for a game-supplied id. Engine-agnostic
87
+ * (Unity/Unreal/Godot/web all call the same REST). Requires a secret test key (`sk_test_`) on the sandbox.
88
+ */
57
89
  readonly agents: {
58
- /** Assign a wallet to any identity (incl. an AI NPC). Idempotent by agentId. */
90
+ /** Assign (or return) the wallet for a game NPC id. Idempotent safe to call wherever your NPCs spawn. */
59
91
  createWallet: (input: {
60
92
  agentId: string;
61
- }) => Promise<{
93
+ }) => Promise<AgentWallet>;
94
+ /** Resolve one NPC's wallet by your id. */
95
+ wallet: (agentId: string) => Promise<AgentWallet>;
96
+ /** List the NPC wallets you've assigned in this studio. */
97
+ list: () => Promise<AgentWallet[]>;
98
+ /** Sandbox faucet: fund an NPC with USDC from your treasury (per-NPC lifetime cap). */
99
+ fund: (input: {
62
100
  agentId: string;
63
- address: `0x${string}`;
64
- chain: string;
65
- }>;
66
- /** Agent↔agent USDC transfer; the configured taxBps is skimmed to Playmos. */
101
+ amount: string;
102
+ }) => Promise<AgentFundResult>;
103
+ /** NPC→NPC (or →player) USDC transfer, by id. A thin alias of `playmos.transfer` (which is canonical). */
67
104
  pay: (input: {
68
105
  from: string;
69
106
  to: string;
70
107
  amount: string;
71
- }) => Promise<{
72
- id: string;
73
- taxUSD: string;
74
- status: string;
75
- }>;
108
+ feeBps?: number;
109
+ feeSink?: `0x${string}`;
110
+ }) => Promise<TransferResult>;
111
+ };
112
+ /**
113
+ * `escrow` — the fair-exchange primitive (Phase 2): lock the payer's USDC on-chain the instant a deal
114
+ * opens (`hold`), then move it exactly once — `release` (→ payee, fee skimmed) XOR `refund` (→ payer,
115
+ * 100%). The contract holds the funds trustlessly; YOUR game decides the rule (who resolves, and when).
116
+ * A `deadline` auto-refund guarantees funds never get stuck. Pass the `hold` result's `id` to the rest.
117
+ */
118
+ readonly escrow: {
119
+ /** Open a deal: lock `amount` of the payer's USDC into the on-chain escrow. Retries are idempotent.
120
+ * `async` so client-side validation surfaces as a rejected promise, not a synchronous throw. */
121
+ hold: (input: EscrowHoldInput) => Promise<EscrowHoldResult>;
122
+ /** Release a held deal to the payee (fee skimmed). `escrowId` is the `hold` result's `id`. */
123
+ release: (input: {
124
+ escrowId: string;
125
+ }) => Promise<EscrowResolveResult>;
126
+ /** Refund a held deal to the payer (100%, untaxed). `escrowId` is the `hold` result's `id`. */
127
+ refund: (input: {
128
+ escrowId: string;
129
+ }) => Promise<EscrowResolveResult>;
130
+ /** Verify a deal's on-chain state (reconciles a `settling` hold wedged by a crash). */
131
+ get: (escrowId: string) => Promise<EscrowResolveResult>;
132
+ };
133
+ /**
134
+ * `marketplace` — list an item, and the seller is paid ONLY when it's bought (Phase 3a, off-chain items).
135
+ * Built on `escrow`: `buy` locks the buyer's USDC on-chain, `confirm` (after your game server delivers the
136
+ * item) pays the seller, and a no-delivery/timeout `refund`s the buyer. `deliver: true` on `buy` collapses
137
+ * lock+pay into one call when your server delivers synchronously. On-chain items are Phase 3b.
138
+ */
139
+ readonly marketplace: {
140
+ /** List an off-chain item for sale. No money moves. Idempotent on `idempotencyKey`. */
141
+ list: (input: MarketplaceListInput) => Promise<Listing>;
142
+ /** Buy a listing: lock the buyer's USDC in escrow. Pass `deliver: true` to also pay the seller in one call. */
143
+ buy: (input: {
144
+ listingId: string;
145
+ buyer?: `0x${string}`;
146
+ deliver?: boolean;
147
+ }) => Promise<MarketplaceSaleResult>;
148
+ /** Confirm delivery → the seller is paid (release), fee skimmed. */
149
+ confirm: (input: {
150
+ listingId: string;
151
+ }) => Promise<MarketplaceSaleResult>;
152
+ /** Refund the buyer 100% (seller couldn't deliver / dispute / timeout). */
153
+ refund: (input: {
154
+ listingId: string;
155
+ }) => Promise<MarketplaceSaleResult>;
156
+ /** Delist an unsold listing (pure DB, no tx). */
157
+ cancel: (input: {
158
+ listingId: string;
159
+ }) => Promise<Listing>;
160
+ /** Fetch a listing + its on-chain-verified sale status. */
161
+ get: (listingId: string) => Promise<MarketplaceGetResult>;
76
162
  };
77
163
  constructor(config: PlaymosConfig);
78
164
  /** Connect the player's wallet and return their address. */
@@ -107,6 +193,37 @@ declare class Playmos {
107
193
  pay(input: PayInput): Promise<Payment>;
108
194
  /** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
109
195
  enterRound(input: EnterRoundInput): Promise<Payment>;
196
+ /**
197
+ * `transfers` — read-back / confirmation for a prior `transfer()` (issue #27).
198
+ * Poll when POST returned `status: "settling"`; reconciles against chain (incl. gasless agent path).
199
+ */
200
+ readonly transfers: {
201
+ get: (transferId: string) => Promise<{
202
+ id: string;
203
+ status: string;
204
+ txHash: string | null;
205
+ to: string;
206
+ amount: string;
207
+ verifiedVia: "chain" | "cache" | "degraded";
208
+ }>;
209
+ };
210
+ /**
211
+ * `transfer` — the value-movement base primitive (Phase 1a): move USDC from one wallet to another,
212
+ * with a per-call fee. The GAME LOGIC is the authority — you already decided the move is valid — so
213
+ * this is a direct, unconditional push (use `escrow`/`marketplace` when a trust boundary needs fair
214
+ * exchange). The service settles it through the protocol-agnostic settlement core (idempotent,
215
+ * reserve-before-broadcast) and the on-chain PlaymosTransfer / PlaymosTransferAuth contracts.
216
+ *
217
+ * Fee is per-call: `feeBps` 0–10000 (+ `feeSink`). `feeBps: 0` is an untaxed reward/faucet transfer.
218
+ * Retries are safe: pass the same `idempotencyKey` and a re-call NEVER broadcasts a second tx —
219
+ * it returns the cached result (`idempotentReplay: true`).
220
+ *
221
+ * Confirmation: treat `status === "settled" && txHash` as final. If `settling`, call
222
+ * `playmos.transfers.get(id)` until settled/failed. BaseScan: `https://sepolia.basescan.org/tx/<txHash>`.
223
+ *
224
+ * NPC `from` requires `sk_test_` and settles gaslessly (NPC signs; service relays).
225
+ */
226
+ transfer(input: TransferInput): Promise<TransferResult>;
110
227
  /** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
111
228
  verify(paymentId: string): Promise<VerifyResult>;
112
229
  /**
@@ -133,6 +250,37 @@ declare function previewIapSplit(amount: string): {
133
250
  fee: string;
134
251
  net: string;
135
252
  };
253
+ /**
254
+ * Local preview of a `transfer` fee split without any network — handy for UIs and quotes. Mirrors the
255
+ * on-chain floor math: `fee = floor(amount * feeBps / 10000)`, `net = amount − fee` (dust → payee).
256
+ */
257
+ declare function previewTransferSplit(amount: string, feeBps: number): {
258
+ amount: string;
259
+ fee: string;
260
+ net: string;
261
+ feeBps: number;
262
+ };
263
+ /**
264
+ * Local preview of what an escrow `release` would pay out, without any network. Mirrors the on-chain
265
+ * floor math: `fee = floor(amount * feeBps / 10000)`, `net = amount − fee` (dust → payee). A `refund`
266
+ * returns the full `amount` with no fee, so it needs no preview.
267
+ */
268
+ declare function previewEscrowFee(amount: string, feeBps: number): {
269
+ amount: string;
270
+ fee: string;
271
+ net: string;
272
+ feeBps: number;
273
+ };
274
+ /**
275
+ * Local preview of a marketplace sale's payout, without any network. Mirrors the on-chain floor math
276
+ * (`fee = floor(price * feeBps / 10000)`, `net = price − fee`, dust → seller). A no-sale/refund pays 0.
277
+ */
278
+ declare function previewMarketplaceSplit(price: string, feeBps: number): {
279
+ price: string;
280
+ fee: string;
281
+ net: string;
282
+ feeBps: number;
283
+ };
136
284
  /** Local preview of the 60/30/10 entry split without any network. */
137
285
  declare function previewPoolSplit(amount: string): {
138
286
  amount: string;
@@ -141,6 +289,40 @@ declare function previewPoolSplit(amount: string): {
141
289
  rake: string;
142
290
  };
143
291
 
292
+ /**
293
+ * payout.ts — pure payout math for skill/contest settlement (issue #13).
294
+ *
295
+ * Scores NEVER enter this module. The studio ranks wallets (best-first) from its
296
+ * own leaderboard; we only turn that ranking + a payout rule into integer USDC
297
+ * base-unit amounts that sum EXACTLY to the payable pool.
298
+ *
299
+ * No network, no chain — fully unit-testable.
300
+ */
301
+ type PayoutRule = {
302
+ kind: "winner-take-all";
303
+ } | {
304
+ kind: "top-n";
305
+ splitsBps: number[];
306
+ } | {
307
+ kind: "custom";
308
+ amounts: string[];
309
+ };
310
+ declare class PayoutError extends Error {
311
+ code: "payout_invalid";
312
+ constructor(message: string);
313
+ }
314
+ /**
315
+ * Apply the studio's payout rule to a payable pool and ranking (best-first).
316
+ *
317
+ * - Integer math only (bigint micro-USDC).
318
+ * - Sum of amounts == pool exactly; any remainder from floor division goes to rank 1.
319
+ * - Rejects empty ranking, bad splits, more winners than ranking, zero pool.
320
+ */
321
+ declare function computePayout(pool: bigint, ranking: `0x${string}`[], rule: PayoutRule): {
322
+ wallet: `0x${string}`;
323
+ amount: bigint;
324
+ }[];
325
+
144
326
  /**
145
327
  * Money math — exact, integer-only, in USDC micro-units (6 decimals).
146
328
  *
@@ -196,4 +378,132 @@ declare function ulid(seedTime?: number): string;
196
378
  /** `${prefix}_${ulid()}` — e.g. `pay_01J…`, `entry_01J…`, `idem_01J…`. */
197
379
  declare function prefixedId(prefix: string): string;
198
380
 
199
- export { CHAIN_ID, DEFAULT_API_BASE_URL, EnterRoundInput, MICRO_PER_USDC, Network, PayInput, Payment, Playmos, PlaymosConfig, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WebhookEvent, computeIapSplit, computePoolSplit, formatMicroToUsd, parseUsdToMicro, prefixedId, previewIapSplit, previewPoolSplit, ulid };
381
+ /**
382
+ * settlement.ts — the x402-ready settlement CORE contract (Phase 0).
383
+ *
384
+ * Two shared shapes every value-movement primitive (`transfer`, `escrow`,
385
+ * `marketplace`) AND the future x402 adapter build on. The whole point of
386
+ * naming them now is that x402 later becomes a thin HTTP-402 adapter with zero
387
+ * rework — see `docs/design/transfer-escrow-marketplace-spec.md` §"Architecture:
388
+ * x402-ready by design".
389
+ *
390
+ * PaymentRequirement — WHAT must be paid. A plain, JSON-safe object. A direct
391
+ * SDK call creates one and satisfies it immediately; an x402 "402 Payment
392
+ * Required" challenge is literally this object serialized onto the wire. So
393
+ * it is designed to round-trip to/from an HTTP 402 body with zero loss —
394
+ * `parsePaymentRequirement(serializePaymentRequirement(req))` is `req`.
395
+ *
396
+ * Authorization — HOW a settlement is authorized. A discriminated union so the
397
+ * settlement core never bakes in "direct SDK signature": today a
398
+ * `wallet-signature` (implemented), tomorrow an `x402-payload` (declared,
399
+ * not settled yet). This union is the seam that keeps x402 out of the core.
400
+ *
401
+ * Nothing here touches the network or the chain — these are the shared data
402
+ * shapes the SDK produces and the service's protocol-agnostic settlement core
403
+ * consumes.
404
+ */
405
+
406
+ /** The only settlement asset in V1. Named so an x402 challenge carries it verbatim. */
407
+ type SettlementAsset = "USDC";
408
+ /**
409
+ * The shared payment contract — WHAT must be paid, independent of HOW it was
410
+ * requested (direct SDK call today, x402 challenge tomorrow).
411
+ *
412
+ * Every field is a JSON primitive so the object is byte-stable across an HTTP
413
+ * 402 body: mint it once, and both the direct path and the x402 path settle the
414
+ * exact same requirement, idempotent on `id`.
415
+ */
416
+ interface PaymentRequirement {
417
+ /** The anchor id (`preq_<ulid>`). Settlement idempotency key + x402 challenge id. */
418
+ id: string;
419
+ /** The recipient wallet (the payee). Checksummed or lowercase 0x-address. */
420
+ payTo: `0x${string}`;
421
+ /** USD decimal string ("5.00") — same money convention as `pay()`/`enterRound()`. */
422
+ amount: string;
423
+ /** Only USDC in V1. */
424
+ asset: SettlementAsset;
425
+ /** Which chain settles this requirement. */
426
+ network: Network;
427
+ /** Optional free-form terms/memo, echoed on the receipt and in the 402 body. */
428
+ terms?: string;
429
+ /** ISO-8601 expiry. A challenge/authorization presented after this is rejected. */
430
+ expiresAt: string;
431
+ }
432
+ interface CreatePaymentRequirementInput {
433
+ /** Recipient wallet. */
434
+ payTo: `0x${string}`;
435
+ /** USD decimal string ("5.00"). */
436
+ amount: string;
437
+ /** Settlement chain. */
438
+ network: Network;
439
+ /** Defaults to "USDC". */
440
+ asset?: SettlementAsset;
441
+ /** Optional terms/memo. */
442
+ terms?: string;
443
+ /** Absolute ISO expiry. Overrides `expiresInMs`. Defaults to now + 15 min. */
444
+ expiresAt?: string;
445
+ /** Relative TTL from now, in ms. Ignored if `expiresAt` is set. */
446
+ expiresInMs?: number;
447
+ /** Supply for a deterministic id (e.g. to reuse an upstream id); else a `preq_<ulid>` is minted. */
448
+ id?: string;
449
+ /** Injectable clock for deterministic tests. */
450
+ now?: () => Date;
451
+ }
452
+ /**
453
+ * Build a validated {@link PaymentRequirement}. This is the single producer the
454
+ * direct primitives (`transfer`/`escrow`/`marketplace`) and — later — the x402
455
+ * adapter both call, so every requirement on the wire is shaped identically.
456
+ * Pure: no network, no chain, no id collisions (monotonic ULID).
457
+ */
458
+ declare function createPaymentRequirement(input: CreatePaymentRequirementInput): PaymentRequirement;
459
+ /**
460
+ * Serialize a requirement to a plain, JSON-safe object — the exact body an x402
461
+ * "402 Payment Required" response carries. Undefined optionals are omitted so
462
+ * the shape is stable across `JSON.stringify` → `JSON.parse` → re-parse.
463
+ */
464
+ declare function serializePaymentRequirement(req: PaymentRequirement): Record<string, unknown>;
465
+ /**
466
+ * Parse + validate an untrusted object (an HTTP 402 body, a queue message, a
467
+ * direct call) back into a {@link PaymentRequirement}. This is the exact decoder
468
+ * the x402 adapter reuses — it must reject anything malformed with a typed error.
469
+ */
470
+ declare function parsePaymentRequirement(input: unknown): PaymentRequirement;
471
+ /**
472
+ * A settlement authorized by a wallet — the ONLY variant implemented in Phase 0.
473
+ * Covers both signer models the primitives support:
474
+ * - server-held NPC/agent wallets (the service signs) and
475
+ * - player Base Accounts (the client signs) —
476
+ * carrying either the settlement `txHash` the client already broadcast, or a
477
+ * `signature` + `payload` (e.g. EIP-3009 / EIP-5792 batch params) the service
478
+ * submits. Phase 0 only READS `txHash`; the signed-submit path lands in Phase 1.
479
+ */
480
+ interface WalletSignatureAuthorization {
481
+ kind: "wallet-signature";
482
+ /** The wallet that authorized the move (payer / signer). */
483
+ from: `0x${string}`;
484
+ /** A settlement tx the client already broadcast (client-signed path). */
485
+ txHash?: `0x${string}`;
486
+ /** An off-chain signature the service submits on the payer's behalf. */
487
+ signature?: `0x${string}`;
488
+ /** Opaque protocol-specific authorization data (EIP-5792 calls, EIP-3009, …). */
489
+ payload?: Record<string, unknown>;
490
+ }
491
+ /**
492
+ * A settlement authorized by an x402 payment payload (the `X-PAYMENT` header).
493
+ * DECLARED for the Phase 4 HTTP-402 adapter so the core's type surface is final
494
+ * now — but NOT settled in Phase 0: `settle()` throws a clear "not implemented
495
+ * yet" for this variant. This is the seam that keeps x402 out of the core.
496
+ */
497
+ interface X402PayloadAuthorization {
498
+ kind: "x402-payload";
499
+ /** The raw, opaque x402 payment payload decoded from the request. */
500
+ payload: Record<string, unknown>;
501
+ }
502
+ /** How a settlement is authorized — protocol-agnostic by construction. */
503
+ type Authorization = WalletSignatureAuthorization | X402PayloadAuthorization;
504
+ /** Narrow to the implemented wallet-signature variant. */
505
+ declare function isWalletSignatureAuthorization(auth: Authorization): auth is WalletSignatureAuthorization;
506
+ /** Narrow to the declared-but-unimplemented x402 variant. */
507
+ declare function isX402PayloadAuthorization(auth: Authorization): auth is X402PayloadAuthorization;
508
+
509
+ export { AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, type PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferInput, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, type WalletSignatureAuthorization, WebhookEvent, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };