@playmos/sdk 0.1.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.cjs +732 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +383 -0
- package/dist/index.d.ts +383 -0
- package/dist/index.js +706 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types — pruned to WIRED capability only (fixes #344/I and #335/E).
|
|
3
|
+
*
|
|
4
|
+
* The old vendored surface advertised four wallet connectors that silently
|
|
5
|
+
* returned {connected:false} and config fields the code never read. Here the
|
|
6
|
+
* types expose exactly what V1 does: two connectors, the fields `pay()` /
|
|
7
|
+
* `enterRound()` actually use, and no more.
|
|
8
|
+
*/
|
|
9
|
+
type Network = "base" | "base-sepolia";
|
|
10
|
+
/** Only the connectors that are actually implemented. */
|
|
11
|
+
type WalletConnector = "base-account" | "injected";
|
|
12
|
+
type GasMode = "sponsored" | "player";
|
|
13
|
+
type PaymentStatus = "created" | "pending" | "confirmed" | "failed";
|
|
14
|
+
/** Minimal EIP-1193 provider shape (what the wallet must expose). */
|
|
15
|
+
interface Eip1193Provider {
|
|
16
|
+
request(args: {
|
|
17
|
+
method: string;
|
|
18
|
+
params?: unknown[] | object;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
interface GasConfig {
|
|
22
|
+
mode?: GasMode;
|
|
23
|
+
/** CDP paymaster URL — required only for `mode: "sponsored"`. */
|
|
24
|
+
paymasterUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
interface WalletConfig {
|
|
27
|
+
connector?: WalletConnector;
|
|
28
|
+
/**
|
|
29
|
+
* Pre-built EIP-1193 provider. If omitted, `injected` uses globalThis.ethereum
|
|
30
|
+
* and `base-account` expects a provider supplied by the Base Account SDK host.
|
|
31
|
+
*/
|
|
32
|
+
provider?: Eip1193Provider;
|
|
33
|
+
}
|
|
34
|
+
/** Per-game contract addresses. On testnet these come from the service intent,
|
|
35
|
+
* but a studio may pin them explicitly (matches the dogfood adapter). */
|
|
36
|
+
interface ContractConfig {
|
|
37
|
+
usdc?: `0x${string}`;
|
|
38
|
+
playmosPay?: `0x${string}`;
|
|
39
|
+
prizePool?: `0x${string}`;
|
|
40
|
+
}
|
|
41
|
+
interface AgentEconomyConfig {
|
|
42
|
+
enabled?: boolean;
|
|
43
|
+
/** Tax skimmed to Playmos on each agent↔agent transfer. 100 = 1% (default). */
|
|
44
|
+
taxBps?: number;
|
|
45
|
+
}
|
|
46
|
+
interface PlaymosConfig {
|
|
47
|
+
/** pk_test_* (sandbox / Base Sepolia) or pk_live_* (production / Base mainnet).
|
|
48
|
+
* The key prefix selects the environment. */
|
|
49
|
+
apiKey: string;
|
|
50
|
+
/** Override the network derived from the key. Explicit value wins. */
|
|
51
|
+
network?: Network;
|
|
52
|
+
wallet?: WalletConfig;
|
|
53
|
+
gas?: GasConfig;
|
|
54
|
+
agentEconomy?: AgentEconomyConfig;
|
|
55
|
+
contracts?: ContractConfig;
|
|
56
|
+
/** Escape hatch; defaults to the right env's base URL. */
|
|
57
|
+
apiBaseUrl?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Explicit, clearly-labeled offline unit-test helper — instant, deterministic,
|
|
60
|
+
* NO network, NO chain. Never the default, never conflated with sandbox. Note:
|
|
61
|
+
* even mock results use the REAL status union (never a synthetic "mocked").
|
|
62
|
+
*/
|
|
63
|
+
mock?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface PayInput {
|
|
66
|
+
/** USD decimal string ("4.99"). Rejected: ≤ 0, non-numeric, > 2 dp. */
|
|
67
|
+
amount: string;
|
|
68
|
+
/** Your product id, echoed on the receipt + webhook. */
|
|
69
|
+
sku: string;
|
|
70
|
+
/** Your opaque user id. */
|
|
71
|
+
playerId: string;
|
|
72
|
+
/**
|
|
73
|
+
* The game this IAP belongs to. Optional: when your API key maps to exactly
|
|
74
|
+
* one game the service resolves it for you (the quickstart). Supply it
|
|
75
|
+
* explicitly when your key spans multiple games.
|
|
76
|
+
*/
|
|
77
|
+
gameId?: string;
|
|
78
|
+
/** The studio wallet that receives the 99%. Falls back to the service default. */
|
|
79
|
+
studio?: `0x${string}`;
|
|
80
|
+
/** Supply your own to make retries safe; omit and the SDK generates a ULID. */
|
|
81
|
+
idempotencyKey?: string;
|
|
82
|
+
metadata?: Record<string, string>;
|
|
83
|
+
}
|
|
84
|
+
interface EnterRoundInput {
|
|
85
|
+
/** The game — selects its prize-pool contract. */
|
|
86
|
+
gameId: string;
|
|
87
|
+
/** The round being entered. */
|
|
88
|
+
roundId: string;
|
|
89
|
+
/** USD entry — grows this round's pool. */
|
|
90
|
+
amount: string;
|
|
91
|
+
playerId: string;
|
|
92
|
+
idempotencyKey?: string;
|
|
93
|
+
metadata?: Record<string, string>;
|
|
94
|
+
}
|
|
95
|
+
interface Payment {
|
|
96
|
+
/** `pay_…` (IAP) or `entry_…` (prize-pool) — ULID, server-issued, unique. */
|
|
97
|
+
id: string;
|
|
98
|
+
status: PaymentStatus;
|
|
99
|
+
/** "iap" (1% external) or "entry" (60/30/10 prize-pool). */
|
|
100
|
+
kind: "iap" | "entry";
|
|
101
|
+
amount: string;
|
|
102
|
+
fee: string;
|
|
103
|
+
net: string;
|
|
104
|
+
/** Present for prize-pool entries: the 60/30/10 breakdown in USD. */
|
|
105
|
+
split?: {
|
|
106
|
+
pool: string;
|
|
107
|
+
seed: string;
|
|
108
|
+
rake: string;
|
|
109
|
+
};
|
|
110
|
+
sku?: string;
|
|
111
|
+
roundId?: string;
|
|
112
|
+
gameId?: string;
|
|
113
|
+
playerId: string;
|
|
114
|
+
txHash?: `0x${string}`;
|
|
115
|
+
chain: Network;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
metadata?: Record<string, string>;
|
|
118
|
+
/** True only when produced by the labeled `mock: true` helper. */
|
|
119
|
+
mock?: boolean;
|
|
120
|
+
}
|
|
121
|
+
/** What `verify()` resolves to (server reads the chain). */
|
|
122
|
+
interface VerifyResult {
|
|
123
|
+
id: string;
|
|
124
|
+
status: PaymentStatus;
|
|
125
|
+
amount: string;
|
|
126
|
+
fee: string;
|
|
127
|
+
net: string;
|
|
128
|
+
txHash?: `0x${string}`;
|
|
129
|
+
playerId: string;
|
|
130
|
+
sku?: string;
|
|
131
|
+
roundId?: string;
|
|
132
|
+
chain: Network;
|
|
133
|
+
/** How the service derived this status: an on-chain read, the honest cache, or
|
|
134
|
+
* degraded (chain reads not configured). Lets the SDK stop polling when the
|
|
135
|
+
* service can never confirm on-chain. */
|
|
136
|
+
verifiedVia?: "chain" | "cache" | "degraded";
|
|
137
|
+
chainReads?: "enabled" | "degraded";
|
|
138
|
+
}
|
|
139
|
+
type WebhookEventType = "payment.confirmed" | "payment.failed" | "payout.settled" | "refund.processed";
|
|
140
|
+
interface WebhookEvent {
|
|
141
|
+
id: string;
|
|
142
|
+
type: WebhookEventType;
|
|
143
|
+
createdAt: string;
|
|
144
|
+
data: {
|
|
145
|
+
id: string;
|
|
146
|
+
status: PaymentStatus;
|
|
147
|
+
amount: string;
|
|
148
|
+
fee: string;
|
|
149
|
+
net: string;
|
|
150
|
+
playerId: string;
|
|
151
|
+
sku?: string;
|
|
152
|
+
txHash?: `0x${string}`;
|
|
153
|
+
chain: Network;
|
|
154
|
+
metadata?: Record<string, string>;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Environment resolution + the canonical address book.
|
|
160
|
+
*
|
|
161
|
+
* The API key prefix selects the environment (Stripe's ergonomic): pk_test_* →
|
|
162
|
+
* Base Sepolia + the sandbox service; pk_live_* → Base mainnet + production. An
|
|
163
|
+
* explicit `network` override always wins.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
declare const CHAIN_ID: Record<Network, number>;
|
|
167
|
+
/**
|
|
168
|
+
* Circle USDC. Sepolia value fixes #336 (the vendored SDK shipped 0x0 here).
|
|
169
|
+
* Verified against the game-hub deploy scripts + kit config.
|
|
170
|
+
*/
|
|
171
|
+
declare const USDC_ADDRESS: Record<Network, `0x${string}`>;
|
|
172
|
+
declare const DEFAULT_API_BASE_URL: Record<Network, string>;
|
|
173
|
+
interface ResolvedEnv {
|
|
174
|
+
network: Network;
|
|
175
|
+
chainId: number;
|
|
176
|
+
apiBaseUrl: string;
|
|
177
|
+
isTest: boolean;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The Playmos client — `new Playmos({ apiKey })` → `pay()`, `enterRound()`,
|
|
182
|
+
* `verify()`, `webhooks`, `payouts`, `agents`.
|
|
183
|
+
*
|
|
184
|
+
* Real behavior, no `"mocked"` status anywhere. The chain calls use the proven
|
|
185
|
+
* EIP-5792 approve+call batch; `verify()` resolves by the service's on-chain
|
|
186
|
+
* read; ids are ULIDs; inputs are validated client-side; idempotency is honored.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
declare class Playmos {
|
|
190
|
+
readonly config: PlaymosConfig;
|
|
191
|
+
readonly env: ResolvedEnv;
|
|
192
|
+
private readonly http;
|
|
193
|
+
readonly webhooks: {
|
|
194
|
+
/** Verify a webhook signature and return the parsed event (server-side). */
|
|
195
|
+
verify: (rawBody: string | Buffer, signatureHeader: string | string[] | undefined, secret: string) => WebhookEvent;
|
|
196
|
+
};
|
|
197
|
+
readonly payouts: {
|
|
198
|
+
/** Choose how the studio is paid: "usdc" (default) or "fiat" (Bridge). */
|
|
199
|
+
setMode: (mode: "usdc" | "fiat") => Promise<{
|
|
200
|
+
mode: "usdc" | "fiat";
|
|
201
|
+
}>;
|
|
202
|
+
/** Create a Bridge KYC onboarding link (fiat payout). */
|
|
203
|
+
createOnboardingLink: () => Promise<{
|
|
204
|
+
url: string;
|
|
205
|
+
}>;
|
|
206
|
+
};
|
|
207
|
+
readonly agents: {
|
|
208
|
+
/** Assign a wallet to any identity (incl. an AI NPC). Idempotent by agentId. */
|
|
209
|
+
createWallet: (input: {
|
|
210
|
+
agentId: string;
|
|
211
|
+
}) => Promise<{
|
|
212
|
+
agentId: string;
|
|
213
|
+
address: `0x${string}`;
|
|
214
|
+
chain: string;
|
|
215
|
+
}>;
|
|
216
|
+
/** Agent↔agent USDC transfer; the configured taxBps is skimmed to Playmos. */
|
|
217
|
+
pay: (input: {
|
|
218
|
+
from: string;
|
|
219
|
+
to: string;
|
|
220
|
+
amount: string;
|
|
221
|
+
}) => Promise<{
|
|
222
|
+
id: string;
|
|
223
|
+
taxUSD: string;
|
|
224
|
+
status: string;
|
|
225
|
+
}>;
|
|
226
|
+
};
|
|
227
|
+
constructor(config: PlaymosConfig);
|
|
228
|
+
/** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
|
|
229
|
+
pay(input: PayInput): Promise<Payment>;
|
|
230
|
+
/** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
|
|
231
|
+
enterRound(input: EnterRoundInput): Promise<Payment>;
|
|
232
|
+
/** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
|
|
233
|
+
verify(paymentId: string): Promise<VerifyResult>;
|
|
234
|
+
/** Prefer the service's authoritative micro-USDC amount; fall back to the
|
|
235
|
+
* locally-parsed value if the service didn't echo one. */
|
|
236
|
+
private resolveUnits;
|
|
237
|
+
private sponsorUrl;
|
|
238
|
+
/**
|
|
239
|
+
* After the batch lands, resolve the authoritative status via the service's
|
|
240
|
+
* on-chain read. Polls briefly; returns the merged Payment with the real txHash.
|
|
241
|
+
*/
|
|
242
|
+
private settle;
|
|
243
|
+
}
|
|
244
|
+
/** Local preview of a split without any network — handy for UIs. */
|
|
245
|
+
declare function previewIapSplit(amount: string): {
|
|
246
|
+
amount: string;
|
|
247
|
+
fee: string;
|
|
248
|
+
net: string;
|
|
249
|
+
};
|
|
250
|
+
/** Local preview of the 60/30/10 entry split without any network. */
|
|
251
|
+
declare function previewPoolSplit(amount: string): {
|
|
252
|
+
amount: string;
|
|
253
|
+
pool: string;
|
|
254
|
+
seed: string;
|
|
255
|
+
rake: string;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Typed, actionable errors — the Stripe bar (spec §11).
|
|
260
|
+
*
|
|
261
|
+
* Every error carries a stable machine-readable `code` and is thrown at the
|
|
262
|
+
* EARLIEST possible layer: input errors fire client-side before any network or
|
|
263
|
+
* chain call, so a studio never pays gas to discover a typo.
|
|
264
|
+
*/
|
|
265
|
+
type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config";
|
|
266
|
+
declare class PlaymosError extends Error {
|
|
267
|
+
readonly code: PlaymosErrorCode;
|
|
268
|
+
/** Optional machine context (e.g. the offending field, the http status). */
|
|
269
|
+
readonly detail?: Record<string, unknown>;
|
|
270
|
+
constructor(code: PlaymosErrorCode, message: string, detail?: Record<string, unknown>);
|
|
271
|
+
}
|
|
272
|
+
/** amount ≤ 0, non-numeric, empty, or more than 2 decimal places. */
|
|
273
|
+
declare class InvalidAmountError extends PlaymosError {
|
|
274
|
+
constructor(amount: unknown);
|
|
275
|
+
}
|
|
276
|
+
/** A required field (sku, playerId, gameId, roundId, agentId…) was empty. */
|
|
277
|
+
declare class MissingFieldError extends PlaymosError {
|
|
278
|
+
constructor(field: string);
|
|
279
|
+
}
|
|
280
|
+
/** gas mode "player" and the player's ETH is too low to cover gas (pre-check, §7). */
|
|
281
|
+
declare class InsufficientGasError extends PlaymosError {
|
|
282
|
+
constructor(detail?: Record<string, unknown>);
|
|
283
|
+
}
|
|
284
|
+
/** The player closed or failed the wallet sheet, or no provider is available. */
|
|
285
|
+
declare class WalletConnectionError extends PlaymosError {
|
|
286
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
287
|
+
}
|
|
288
|
+
/** The on-chain settlement reverted, was cancelled, or timed out. */
|
|
289
|
+
declare class PaymentFailedError extends PlaymosError {
|
|
290
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
291
|
+
}
|
|
292
|
+
/** Bad, missing, or wrong-environment API key (e.g. a pk_test_ key on a live route). */
|
|
293
|
+
declare class AuthError extends PlaymosError {
|
|
294
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
295
|
+
}
|
|
296
|
+
/** The Playmos service returned a non-2xx we don't have a more specific error for. */
|
|
297
|
+
declare class ApiError extends PlaymosError {
|
|
298
|
+
constructor(message: string, detail?: Record<string, unknown>);
|
|
299
|
+
}
|
|
300
|
+
/** SDK misconfiguration (e.g. a missing contract address for on-chain mode). */
|
|
301
|
+
declare class ConfigError extends PlaymosError {
|
|
302
|
+
constructor(message: string, detail?: Record<string, unknown>);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Webhook signature verification (spec §6.2) — server-side only.
|
|
307
|
+
*
|
|
308
|
+
* Signature scheme (mirrors the Playmos service byte-for-byte):
|
|
309
|
+
* header `X-Playmos-Signature: t=<unixSeconds>,v1=<hex>`
|
|
310
|
+
* where <hex> = HMAC_SHA256(secret, `${t}.${rawBody}`)
|
|
311
|
+
*
|
|
312
|
+
* Verify BEFORE trusting an event. Uses node:crypto (constant-time compare) and
|
|
313
|
+
* rejects stale timestamps to blunt replay. Throws on any mismatch.
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
declare class WebhookSignatureError extends PlaymosError {
|
|
317
|
+
constructor(message: string);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Verify a webhook and return the parsed event. `rawBody` MUST be the exact raw
|
|
321
|
+
* request bytes (use express.raw / fastify rawBody) — a re-serialized JSON body
|
|
322
|
+
* will not match the signature.
|
|
323
|
+
*/
|
|
324
|
+
declare function verifyWebhook(rawBody: string | Buffer, signatureHeader: string | string[] | undefined, secret: string, opts?: {
|
|
325
|
+
toleranceSeconds?: number;
|
|
326
|
+
}): WebhookEvent;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Money math — exact, integer-only, in USDC micro-units (6 decimals).
|
|
330
|
+
*
|
|
331
|
+
* USD amounts are decimal strings ("4.99") to avoid float corruption. Internally
|
|
332
|
+
* every value is a BigInt count of micro-USDC (1 USDC = 1_000_000 micro), so the
|
|
333
|
+
* split is bit-exact and always sums back to the input — no wei is ever lost.
|
|
334
|
+
*
|
|
335
|
+
* The split arithmetic MIRRORS the contracts (floor division, exactly like
|
|
336
|
+
* Solidity `amount * bps / 10000`), so the SDK receipt matches what settles
|
|
337
|
+
* on-chain. The contract remains the source of truth; these numbers are the
|
|
338
|
+
* faithful preview.
|
|
339
|
+
*/
|
|
340
|
+
declare const USDC_DECIMALS = 6;
|
|
341
|
+
declare const MICRO_PER_USDC = 1000000n;
|
|
342
|
+
/**
|
|
343
|
+
* Parse a USD decimal string (≤ 2 dp, > 0) into micro-USDC.
|
|
344
|
+
* Throws {@link InvalidAmountError} on anything that isn't a clean positive price.
|
|
345
|
+
*/
|
|
346
|
+
declare function parseUsdToMicro(amount: string): bigint;
|
|
347
|
+
/**
|
|
348
|
+
* Format micro-USDC back to a USD decimal string. Keeps full precision (up to 6
|
|
349
|
+
* dp) but trims trailing zeros to a minimum of 2 dp — so "$4.99" stays "4.99"
|
|
350
|
+
* while a 1% fee of $4.99 renders its exact "0.0499", not a lossy "0.05".
|
|
351
|
+
*/
|
|
352
|
+
declare function formatMicroToUsd(micro: bigint): string;
|
|
353
|
+
/** External-studio IAP split (99/1 by default). Floor-matches `PlaymosPay.pay`. */
|
|
354
|
+
declare function computeIapSplit(amountMicro: bigint, feeBps: number): {
|
|
355
|
+
feeMicro: bigint;
|
|
356
|
+
netMicro: bigint;
|
|
357
|
+
};
|
|
358
|
+
/**
|
|
359
|
+
* Skill-game prize-pool split (60/30/10 by default). Floor-matches the on-chain
|
|
360
|
+
* `EconomyConfig`. Any 1–2 micro rounding remainder is assigned to the pool so
|
|
361
|
+
* the three parts sum EXACTLY to the entry (invariant asserted below).
|
|
362
|
+
*/
|
|
363
|
+
declare function computePoolSplit(amountMicro: bigint, poolBps: number, seedBps: number, rakeBps: number): {
|
|
364
|
+
poolMicro: bigint;
|
|
365
|
+
seedMicro: bigint;
|
|
366
|
+
rakeMicro: bigint;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* ULID generation (fixes #341/C — the `pay_mock_${Date.now()}` collision bug).
|
|
371
|
+
*
|
|
372
|
+
* A ULID is a 26-char Crockford-base32 string: 48 bits of millisecond timestamp
|
|
373
|
+
* (lexicographically sortable) + 80 bits of cryptographic randomness. Within the
|
|
374
|
+
* same millisecond we increment the random component monotonically, so even 50+
|
|
375
|
+
* ids minted in a tight loop are unique and ordered. Uses Web Crypto
|
|
376
|
+
* (`crypto.getRandomValues`), available in browsers and Node ≥ 18.
|
|
377
|
+
*/
|
|
378
|
+
/** Generate a monotonic ULID (26 chars, uppercase Crockford base32). */
|
|
379
|
+
declare function ulid(seedTime?: number): string;
|
|
380
|
+
/** `${prefix}_${ulid()}` — e.g. `pay_01J…`, `entry_01J…`, `idem_01J…`. */
|
|
381
|
+
declare function prefixedId(prefix: string): string;
|
|
382
|
+
|
|
383
|
+
export { type AgentEconomyConfig, ApiError, AuthError, CHAIN_ID, ConfigError, type ContractConfig, DEFAULT_API_BASE_URL, type Eip1193Provider, type EnterRoundInput, type GasConfig, type GasMode, InsufficientGasError, InvalidAmountError, MICRO_PER_USDC, MissingFieldError, type Network, type PayInput, type Payment, PaymentFailedError, type PaymentStatus, Playmos, type PlaymosConfig, PlaymosError, type PlaymosErrorCode, USDC_ADDRESS, USDC_DECIMALS, type VerifyResult, type WalletConfig, WalletConnectionError, type WalletConnector, type WebhookEvent, type WebhookEventType, WebhookSignatureError, computeIapSplit, computePoolSplit, formatMicroToUsd, parseUsdToMicro, prefixedId, previewIapSplit, previewPoolSplit, ulid, verifyWebhook };
|