@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/README.md +87 -0
- package/dist/errors-CjL85YKR.d.cts +502 -0
- package/dist/errors-CjL85YKR.d.ts +502 -0
- package/dist/index.cjs +602 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +327 -17
- package/dist/index.d.ts +327 -17
- package/dist/index.js +593 -14
- package/dist/index.js.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +3 -3
- package/dist/errors-B-85VYMv.d.cts +0 -224
- package/dist/errors-B-85VYMv.d.ts +0 -224
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @playmos/sdk
|
|
2
2
|
|
|
3
|
+
**🧪 Beta — Playmos SDK.** Runs on Base Sepolia **testnet**. The value-movement primitives (escrow / marketplace / transfer) are proven on-chain but **not yet production-hardened**, and nothing is on mainnet. Build and integrate freely against the sandbox; don't route real user funds yet.
|
|
4
|
+
|
|
3
5
|
Stablecoin payments for games on Base. One SDK for in-app purchases (1%), skill-game prize-pool entries (10%, 60/30/10), and agent economies. USD in, USDC on-chain — no crypto UX for your players.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
@@ -8,6 +10,15 @@ Stablecoin payments for games on Base. One SDK for in-app purchases (1%), skill-
|
|
|
8
10
|
npm i @playmos/sdk
|
|
9
11
|
```
|
|
10
12
|
|
|
13
|
+
Requires **Node 18 / 20 / 22 LTS** for the monorepo toolchains (Hardhat/service). See repo `docs/TOOLCHAIN.md` (issue #5).
|
|
14
|
+
|
|
15
|
+
## Browser vs server entry
|
|
16
|
+
|
|
17
|
+
- **Games (browser / Vite / Phaser):** `import { Playmos } from "@playmos/sdk"` — this entry is browser-safe and does **not** import Node `crypto` (issue #9).
|
|
18
|
+
- **Your backend (webhook receivers):** `import { verifyWebhook } from "@playmos/sdk/server"` — HMAC verification uses Node crypto and must stay off the client bundle.
|
|
19
|
+
|
|
20
|
+
The sandbox API also sends CORS headers so browser `pay()` / `enterRound()` work without a same-origin proxy (issue #10).
|
|
21
|
+
|
|
11
22
|
## Quickstart — the no-wallet sandbox
|
|
12
23
|
|
|
13
24
|
The public sandbox key `pk_test_playmos_sandbox` runs against a real Playmos test service on Base Sepolia. **You don't need a wallet:** on a `pk_test` key with no `wallet` configured, the SDK routes to a server-settle path where Playmos signs and submits the on-chain transaction for you. You still get a real, confirmed `txHash` — just signed by the service. No wallet, no gas, no crypto.
|
|
@@ -45,6 +56,59 @@ const entry = await playmos.enterRound({
|
|
|
45
56
|
// entry.status === "confirmed"; entry.split is the on-chain 60/30/10 breakdown
|
|
46
57
|
```
|
|
47
58
|
|
|
59
|
+
### Move USDC between wallets — `transfer()`
|
|
60
|
+
|
|
61
|
+
The base value-movement primitive: move USDC from one wallet to another with a configurable per-call fee. The **game logic is the authority** — you already decided the move is valid — so this is a direct, unconditional push (reach for `escrow`/`marketplace` when a trust boundary needs fair exchange). Settled through the on-chain `PlaymosTransfer` contract; the split is verifiable on BaseScan.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// NPC→NPC with a 5% fee (Playmos Town P2P). Requires sk_test_ + agents.createWallet first.
|
|
65
|
+
// Full walkthrough: docs/examples/playmos-town-sdk.mjs
|
|
66
|
+
// Confirm: status === "settled" && txHash, or poll playmos.transfers.get(id) if settling.
|
|
67
|
+
const t = await playmos.transfer({
|
|
68
|
+
// `from` is OPTIONAL in the sandbox — omit it and the service uses its server-held signer wallet
|
|
69
|
+
// (Phase 1a's only payer), so you don't need to know that address to run this example.
|
|
70
|
+
to: "0x…blacksmith",
|
|
71
|
+
amount: "0.80", // USD; USDC settles on-chain. Stay UNDER the $1.00 sandbox cap (see below).
|
|
72
|
+
feeBps: 500, // 5% — 0–10000
|
|
73
|
+
feeSink: "0x…treasury", // required when feeBps > 0
|
|
74
|
+
memo: "ore", // optional, echoed on the receipt
|
|
75
|
+
});
|
|
76
|
+
t.status; // "settled" — real, on Base Sepolia
|
|
77
|
+
t.txHash; // 0x… open on sepolia.basescan.org
|
|
78
|
+
t.net; // "0.76" (payee)
|
|
79
|
+
t.fee; // "0.04" (feeSink)
|
|
80
|
+
t.idempotentReplay; // false on the first settle
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**`feeBps: 0` is an untaxed reward/faucet transfer** (no fee taken; `feeSink` not needed) — e.g. a monster-bounty payout from a world wallet to a fighter. Set `feeBps: 10000` for a 100% first-party sale (all of it to `feeSink`).
|
|
84
|
+
|
|
85
|
+
| Field | Required | Notes |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `from` | no | Payer wallet — a `0x` address. **Omit in the sandbox** and the service defaults it to its server-held signer wallet (Phase 1a's only payer). If supplied, it must be that wallet. |
|
|
88
|
+
| `to` | yes | Payee wallet — a `0x` address. |
|
|
89
|
+
| `amount` | yes | USD decimal string ("0.80"); > 0, ≤ 2 dp. **Sandbox cap: ≤ $1.00 per transfer** (see limits below). |
|
|
90
|
+
| `feeBps` | no (default `0`) | Per-call fee in bps, **0–10000**. `0` = untaxed reward/faucet. |
|
|
91
|
+
| `feeSink` | when `feeBps > 0` | Fee destination `0x` address. |
|
|
92
|
+
| `memo` | no | Optional note, echoed on the receipt. |
|
|
93
|
+
| `idempotencyKey` | no | Pass the same key on a retry to guarantee **at most one** on-chain transfer (see below). |
|
|
94
|
+
|
|
95
|
+
**Sandbox limits (enforced before any signing).** The public sandbox signs from a shared funded testnet wallet, so the transfer path is guarded: **$1.00 max per transfer** (a larger `amount` is a `400`), **5 requests/minute** per IP and per key (`429`), and a **$20/day budget** across all sandbox transfers (`429`). These are testnet abuse controls — not product limits.
|
|
96
|
+
|
|
97
|
+
**Retries are safe (idempotent, no double-spend).** Every transfer is anchored on a requirement id derived from `idempotencyKey`. The service durably reserves the transfer *before* broadcasting, so a retry with the same key — or two concurrent calls with the same key — **broadcast at most one transaction**; the loser returns the original result with `idempotentReplay: true`. Preview the split with no network via `previewTransferSplit(amount, feeBps)`. Reusing an id with **different** terms (payTo/amount/fee/sink/from) is rejected, not silently replayed.
|
|
98
|
+
|
|
99
|
+
**REST (no SDK).** The SDK's `transfer()` is a thin wrapper over `POST /v1/transfers`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl -X POST https://<sandbox-host>/v1/transfers \
|
|
103
|
+
-H "Authorization: Bearer pk_test_playmos_sandbox" \
|
|
104
|
+
-H "Content-Type: application/json" \
|
|
105
|
+
-d '{ "to": "0x…blacksmith", "amount": "0.80", "feeBps": 500, "feeSink": "0x…treasury", "memo": "ore", "idempotencyKey": "ore-trade-1" }'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Fields match the table above (`from` optional → defaults to the signer; `feeBps`/`feeSink` as documented). Poll `GET /v1/transfers/:id` to reconcile a transfer's status against the chain (it recovers a transfer left mid-flight by a crash).
|
|
109
|
+
|
|
110
|
+
> Sandbox only (Phase 1a): `transfer` settles server-held NPC/agent wallets on Base Sepolia. The player-signed (EIP-3009) non-custodial path is Phase 1b.
|
|
111
|
+
|
|
48
112
|
### Verify before you grant
|
|
49
113
|
|
|
50
114
|
Grant items on a verified confirmation — never on the client `pay()` return alone.
|
|
@@ -81,6 +145,29 @@ const playmos = new Playmos({
|
|
|
81
145
|
|
|
82
146
|
Promote by swapping the key — the same code that passed in the sandbox works live.
|
|
83
147
|
|
|
148
|
+
## Value movement — escrow, marketplace, transfer (0.2.0, testnet)
|
|
149
|
+
|
|
150
|
+
`0.2.0` adds non-custodial USDC value-movement primitives on top of the settlement core:
|
|
151
|
+
|
|
152
|
+
- **`playmos.transfer(...)`** — a direct USDC transfer.
|
|
153
|
+
- **`playmos.escrow.hold / release / refund / get`** — hold funds against a deal, then pay the payee (fee skimmed at release) or refund the payer (100%). Every term is frozen on-chain at hold.
|
|
154
|
+
- **`playmos.marketplace.list / buy / confirm / refund / cancel / get`** — a listing packaged as an escrow deal (`buy` = hold, `confirm` = release, timeout/dispute = refund), including `buy({ deliver: true })` to hold+release in one call.
|
|
155
|
+
|
|
156
|
+
**Status quo (2026-07): testnet-sandbox only.** These paths are proven on Base Sepolia (independently reconciled on-chain) but are **not** marketed as production-hardened, and are not on mainnet. For money preflights, **point the service at a dedicated RPC** (Alchemy/QuickNode) — do not rely on a load-balanced public endpoint for read-your-write consistency.
|
|
157
|
+
|
|
158
|
+
### Resolve status-code contract
|
|
159
|
+
|
|
160
|
+
The escrow/marketplace resolve endpoints (release/refund/confirm) return a deterministic contract — handle it explicitly:
|
|
161
|
+
|
|
162
|
+
| Code | Meaning | Client action |
|
|
163
|
+
|------|---------|---------------|
|
|
164
|
+
| **404** | Unknown escrow / listing | Terminal — the id does not exist |
|
|
165
|
+
| **409** | Not resolvable (already released/refunded, or genuinely never held) | Terminal — do not retry |
|
|
166
|
+
| **503** | Transient (RPC read-lag, or a crash-orphaned hold being reconciled) | **Retry** with backoff |
|
|
167
|
+
| **400** | Malformed input | Terminal — fix the request |
|
|
168
|
+
|
|
169
|
+
**On a `503`, retry the release/confirm** — it means the on-chain state just hasn't propagated yet, not that anything failed.
|
|
170
|
+
|
|
84
171
|
## Docs
|
|
85
172
|
|
|
86
173
|
Full documentation — skill games, webhooks, gas, payouts, agent economies, and the REST API — at [playmos.io](https://playmos.io).
|
|
@@ -0,0 +1,502 @@
|
|
|
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
|
+
/**
|
|
42
|
+
* @deprecated Unused — agent routes are enabled server-side (SERVICE_MASTER_SECRET +
|
|
43
|
+
* transfer contracts). Prefer `sk_test_` + `agents.createWallet`; do not set this.
|
|
44
|
+
* Kept optional so old clients don't type-error (issue #23).
|
|
45
|
+
*/
|
|
46
|
+
interface AgentEconomyConfig {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface PlaymosConfig {
|
|
50
|
+
/** pk_test_* / sk_test_* (sandbox / Base Sepolia) or pk_live_* / sk_live_* (mainnet).
|
|
51
|
+
* Agents require **sk_test_** (secret studio key), not the public pk_test_ sandbox key. */
|
|
52
|
+
apiKey: string;
|
|
53
|
+
/** Override the network derived from the key. Explicit value wins. */
|
|
54
|
+
network?: Network;
|
|
55
|
+
wallet?: WalletConfig;
|
|
56
|
+
gas?: GasConfig;
|
|
57
|
+
/** @deprecated No-op. Server enables agents; use sk_test_ + agents.* (issue #23). */
|
|
58
|
+
agentEconomy?: AgentEconomyConfig;
|
|
59
|
+
contracts?: ContractConfig;
|
|
60
|
+
/** Escape hatch; defaults to the right env's base URL. */
|
|
61
|
+
apiBaseUrl?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Explicit, clearly-labeled offline unit-test helper — instant, deterministic,
|
|
64
|
+
* NO network, NO chain. Never the default, never conflated with sandbox. Note:
|
|
65
|
+
* even mock results use the REAL status union (never a synthetic "mocked").
|
|
66
|
+
*/
|
|
67
|
+
mock?: boolean;
|
|
68
|
+
}
|
|
69
|
+
interface PayInput {
|
|
70
|
+
/** USD decimal string ("4.99"). Rejected: ≤ 0, non-numeric, > 2 dp. */
|
|
71
|
+
amount: string;
|
|
72
|
+
/** Your product id, echoed on the receipt + webhook. */
|
|
73
|
+
sku: string;
|
|
74
|
+
/** Your opaque user id. */
|
|
75
|
+
playerId: string;
|
|
76
|
+
/**
|
|
77
|
+
* The game this IAP belongs to. Optional: when your API key maps to exactly
|
|
78
|
+
* one game the service resolves it for you (the quickstart). Supply it
|
|
79
|
+
* explicitly when your key spans multiple games.
|
|
80
|
+
*/
|
|
81
|
+
gameId?: string;
|
|
82
|
+
/** The studio wallet that receives the 99%. Falls back to the service default. */
|
|
83
|
+
studio?: `0x${string}`;
|
|
84
|
+
/** Supply your own to make retries safe; omit and the SDK generates a ULID. */
|
|
85
|
+
idempotencyKey?: string;
|
|
86
|
+
metadata?: Record<string, string>;
|
|
87
|
+
}
|
|
88
|
+
interface EnterRoundInput {
|
|
89
|
+
/** The game — selects its prize-pool contract. */
|
|
90
|
+
gameId: string;
|
|
91
|
+
/** The round being entered. */
|
|
92
|
+
roundId: string;
|
|
93
|
+
/** USD entry — grows this round's pool. */
|
|
94
|
+
amount: string;
|
|
95
|
+
playerId: string;
|
|
96
|
+
idempotencyKey?: string;
|
|
97
|
+
metadata?: Record<string, string>;
|
|
98
|
+
/**
|
|
99
|
+
* Advanced (games that manage their own on-chain rounds, e.g. the Playmos
|
|
100
|
+
* game-hub kit): the EXACT on-chain round key to enter — overrides the default
|
|
101
|
+
* `${gameId}:${roundId}` derivation. Set it to the value your game's server
|
|
102
|
+
* verifies `hasEntered` against (e.g. "bjtest:T1").
|
|
103
|
+
*/
|
|
104
|
+
roundKey?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Advanced: the EXACT on-chain identity for this paid attempt (e.g.
|
|
107
|
+
* "0xWallet#nonce"). Overrides the server-generated identity so the on-chain
|
|
108
|
+
* entry and your server's `hasEntered` check line up. One entry per identity.
|
|
109
|
+
*/
|
|
110
|
+
identity?: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Input to `playmos.transfer(...)` — the value-movement `transfer` primitive (Phase 1a): move USDC
|
|
114
|
+
* from one wallet to another, with a configurable per-call fee. The game logic is the authority; the
|
|
115
|
+
* SDK just moves the money and proves it on-chain.
|
|
116
|
+
*/
|
|
117
|
+
interface TransferInput {
|
|
118
|
+
/**
|
|
119
|
+
* The payer — either a **game NPC id** you assigned a wallet to (`playmos.agents.createWallet`, e.g.
|
|
120
|
+
* `"npc_miner"`) OR a raw `0x` wallet address. OMIT it in the sandbox and the service defaults `from` to
|
|
121
|
+
* its server-held treasury signer (the untaxed-faucet path). Transferring FROM an NPC requires a secret
|
|
122
|
+
* test key (`sk_test_`) and settles gaslessly (the NPC signs; Playmos relays + pays gas).
|
|
123
|
+
*/
|
|
124
|
+
from?: string;
|
|
125
|
+
/** The payee — a game NPC id you assigned a wallet to, OR a raw `0x` address (player or NPC). */
|
|
126
|
+
to: string;
|
|
127
|
+
/** USD decimal string ("5.00"). Rejected: ≤ 0, non-numeric, > 2 dp. */
|
|
128
|
+
amount: string;
|
|
129
|
+
/** Optional note, echoed on the receipt (carried as the requirement's `terms`). */
|
|
130
|
+
memo?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Per-call Playmos fee in bps of `amount`, 0–10000. Defaults to 0.
|
|
133
|
+
* 0 → an UNTAXED reward/faucet transfer (no fee taken; `feeSink` not needed).
|
|
134
|
+
* 500 → 5% (e.g. Playmos-Town P2P: 95% to payee, 5% to `feeSink`).
|
|
135
|
+
* 10000 → 100% (e.g. a first-party shop sale: all of it to `feeSink`).
|
|
136
|
+
*/
|
|
137
|
+
feeBps?: number;
|
|
138
|
+
/** Fee destination. REQUIRED when `feeBps > 0`; ignored when `feeBps === 0`. */
|
|
139
|
+
feeSink?: `0x${string}`;
|
|
140
|
+
/** Supply your own to make retries safe (anchors idempotency on the same requirement); omit and the SDK generates one. */
|
|
141
|
+
idempotencyKey?: string;
|
|
142
|
+
}
|
|
143
|
+
/** A wallet the SDK assigned to one of your game's NPCs (by your own id). Keys are never returned. */
|
|
144
|
+
interface AgentWallet {
|
|
145
|
+
agentId: string;
|
|
146
|
+
address: `0x${string}`;
|
|
147
|
+
chain: string;
|
|
148
|
+
}
|
|
149
|
+
/** What `agents.fund(...)` resolves to (sandbox faucet: treasury → NPC). */
|
|
150
|
+
interface AgentFundResult {
|
|
151
|
+
agent: AgentWallet;
|
|
152
|
+
funding: {
|
|
153
|
+
amount: string;
|
|
154
|
+
status: string;
|
|
155
|
+
txHash: string | null;
|
|
156
|
+
idempotentReplay: boolean;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** What `playmos.transfer(...)` resolves to — the settled result from the settlement core. */
|
|
160
|
+
interface TransferResult {
|
|
161
|
+
/** The requirement id this transfer settled (the idempotency anchor). */
|
|
162
|
+
id: string;
|
|
163
|
+
/** "settled" (on-chain confirmed) · "failed" (mined revert) · "settling" (reserved, confirmation pending). */
|
|
164
|
+
status: "settled" | "failed" | "settling";
|
|
165
|
+
/** The on-chain settlement tx (openable on BaseScan). */
|
|
166
|
+
txHash?: `0x${string}`;
|
|
167
|
+
/** Resolved payer address — omitted when it's the redacted server treasury signer. */
|
|
168
|
+
from?: `0x${string}`;
|
|
169
|
+
to: `0x${string}`;
|
|
170
|
+
/** Gross amount moved (USD). */
|
|
171
|
+
amount: string;
|
|
172
|
+
/** Fee skimmed to `feeSink` (USD). */
|
|
173
|
+
fee: string;
|
|
174
|
+
/** Amount the payee received (USD) = amount − fee. */
|
|
175
|
+
net: string;
|
|
176
|
+
feeBps: number;
|
|
177
|
+
feeSink?: `0x${string}` | null;
|
|
178
|
+
memo?: string | null;
|
|
179
|
+
/** True when a prior settle for this id was replayed instead of broadcasting again (double-spend safety). */
|
|
180
|
+
idempotentReplay: boolean;
|
|
181
|
+
}
|
|
182
|
+
/** Input to `playmos.escrow.hold(...)` — open a fair-exchange deal by locking the payer's USDC on-chain. */
|
|
183
|
+
interface EscrowHoldInput {
|
|
184
|
+
/**
|
|
185
|
+
* Payer wallet whose USDC is locked. OPTIONAL in the sandbox: omit it and the service defaults `payer`
|
|
186
|
+
* to its server-held signer wallet (Phase 2a's only payer), like `transfer.from`.
|
|
187
|
+
*/
|
|
188
|
+
payer?: `0x${string}`;
|
|
189
|
+
/** Payee wallet — where a `release` sends the funds (any identity: player or NPC). */
|
|
190
|
+
payee: `0x${string}`;
|
|
191
|
+
/** USD decimal string ("12.50"). Rejected: ≤ 0, non-numeric, > 2 dp. */
|
|
192
|
+
amount: string;
|
|
193
|
+
/**
|
|
194
|
+
* Per-call Playmos fee in bps, 0–10000, taken ONLY at `release` (a `refund` is always 100%, untaxed).
|
|
195
|
+
* Defaults to 0. Frozen at hold — the fee a deal will pay can't change once funds are locked.
|
|
196
|
+
*/
|
|
197
|
+
feeBps?: number;
|
|
198
|
+
/** Fee destination. REQUIRED when `feeBps > 0`; ignored when `feeBps === 0`. */
|
|
199
|
+
feeSink?: `0x${string}`;
|
|
200
|
+
/**
|
|
201
|
+
* The authority allowed to `release`/`refund` this deal. Defaults to the payer. This is YOUR game
|
|
202
|
+
* server's address in the common case — it verifies the deal condition off-chain, then calls release.
|
|
203
|
+
*/
|
|
204
|
+
resolver?: `0x${string}`;
|
|
205
|
+
/**
|
|
206
|
+
* On-chain auto-refund deadline as a relative duration — "30s" | "15m" | "1h" | "7d" (max 30 days).
|
|
207
|
+
* At/after it, ANYONE can trigger the refund (funds never get stuck). Defaults to "1h". This is the
|
|
208
|
+
* DEAL's life; it is distinct from the short off-chain window the hold authorization itself is valid for.
|
|
209
|
+
*/
|
|
210
|
+
expiresIn?: string;
|
|
211
|
+
/** Absolute on-chain deadline as a unix-seconds timestamp. Overrides `expiresIn` when set. */
|
|
212
|
+
deadline?: number;
|
|
213
|
+
/** Optional note, echoed on the receipt. */
|
|
214
|
+
memo?: string;
|
|
215
|
+
/** Supply your own to make retries safe (anchors idempotency on the same deal); omit and the SDK generates one. */
|
|
216
|
+
idempotencyKey?: string;
|
|
217
|
+
}
|
|
218
|
+
/** What `playmos.escrow.hold(...)` resolves to. `id` is the holdId — pass it to `release`/`refund`/`get`. */
|
|
219
|
+
interface EscrowHoldResult {
|
|
220
|
+
/** The deal id (holdId) — the reserve-before-broadcast anchor and the handle for release/refund/get. */
|
|
221
|
+
id: string;
|
|
222
|
+
/** "settled" (funds locked on-chain) · "failed" (mined revert) · "settling" (reserved, confirmation pending). */
|
|
223
|
+
status: "settled" | "failed" | "settling";
|
|
224
|
+
txHash?: `0x${string}`;
|
|
225
|
+
payee: `0x${string}`;
|
|
226
|
+
/** Gross amount locked (USD). */
|
|
227
|
+
amount: string;
|
|
228
|
+
feeBps: number;
|
|
229
|
+
feeSink?: `0x${string}` | null;
|
|
230
|
+
/** The resolver recorded for the deal; null means it defaulted to the payer. */
|
|
231
|
+
resolver?: `0x${string}` | null;
|
|
232
|
+
/** On-chain auto-refund deadline (unix seconds). */
|
|
233
|
+
deadline: number;
|
|
234
|
+
/** Fee that WOULD be skimmed on release (USD) — preview; a refund takes none. */
|
|
235
|
+
feeAtRelease: string;
|
|
236
|
+
/** Amount the payee WOULD receive on release (USD) = amount − feeAtRelease. */
|
|
237
|
+
netAtRelease: string;
|
|
238
|
+
memo?: string | null;
|
|
239
|
+
idempotentReplay: boolean;
|
|
240
|
+
}
|
|
241
|
+
/** What `release`/`refund`/`get` resolve to. */
|
|
242
|
+
interface EscrowResolveResult {
|
|
243
|
+
/** The deal id (holdId). */
|
|
244
|
+
id: string;
|
|
245
|
+
/** Which resolution this was (absent on `get`). */
|
|
246
|
+
leg?: "release" | "refund";
|
|
247
|
+
/** For release/refund: the leg's settlement status. For `get`: the HOLD's on-chain-verified state. */
|
|
248
|
+
status: string;
|
|
249
|
+
txHash?: `0x${string}` | null;
|
|
250
|
+
idempotentReplay?: boolean;
|
|
251
|
+
/** How the status was derived — chain truth, honest cache, or not-yet-configured. */
|
|
252
|
+
verifiedVia?: "chain" | "cache" | "degraded";
|
|
253
|
+
}
|
|
254
|
+
/** An item for sale. Off-chain (game DB inventory) is Phase 3a; on-chain (token) is Phase 3b. */
|
|
255
|
+
type MarketplaceItem = {
|
|
256
|
+
kind: "offchain";
|
|
257
|
+
sku: string;
|
|
258
|
+
metadata?: Record<string, unknown>;
|
|
259
|
+
} | {
|
|
260
|
+
kind: "onchain";
|
|
261
|
+
token: `0x${string}`;
|
|
262
|
+
tokenId: string;
|
|
263
|
+
};
|
|
264
|
+
interface MarketplaceListInput {
|
|
265
|
+
/** Seller wallet (the payee, paid on sale). REQUIRED — it's never defaulted to the server signer.
|
|
266
|
+
* (The buyer/payer is what defaults to the signer in the sandbox; the seller is a real counterparty.) */
|
|
267
|
+
seller: `0x${string}`;
|
|
268
|
+
/** The item being sold. Phase 3a settles `{ kind: "offchain", sku }`; on-chain items are Phase 3b. */
|
|
269
|
+
item: MarketplaceItem;
|
|
270
|
+
/** USD decimal string ("12.50"). */
|
|
271
|
+
price: string;
|
|
272
|
+
/** Per-sale Playmos take in bps, 0–10000, taken at settle (on the sale). Defaults to the service's config. */
|
|
273
|
+
feeBps?: number;
|
|
274
|
+
/** Fee destination; required when `feeBps > 0` and no service default is set. */
|
|
275
|
+
feeSink?: `0x${string}`;
|
|
276
|
+
/** On-sale delivery/escrow deadline as a duration — "30s"|"15m"|"1h"|"7d" (max 30d). Default "1h". */
|
|
277
|
+
deliveryWindow?: string;
|
|
278
|
+
/** Optional listing shelf-life — after this, the listing can't be bought (lazy `expired`). */
|
|
279
|
+
expiresIn?: string;
|
|
280
|
+
gameId?: string;
|
|
281
|
+
/** Supply your own for a deterministic listing id (retried list() never double-lists). */
|
|
282
|
+
idempotencyKey?: string;
|
|
283
|
+
}
|
|
284
|
+
type ListingStatus = "active" | "sold_pending_delivery" | "sold" | "refunded" | "cancelled" | "expired";
|
|
285
|
+
/** A marketplace listing (as the service returns it). */
|
|
286
|
+
interface Listing {
|
|
287
|
+
id: string;
|
|
288
|
+
status: ListingStatus;
|
|
289
|
+
seller: `0x${string}`;
|
|
290
|
+
buyer?: `0x${string}` | null;
|
|
291
|
+
item: MarketplaceItem;
|
|
292
|
+
price: string;
|
|
293
|
+
feeBps: number;
|
|
294
|
+
feeSink?: `0x${string}` | null;
|
|
295
|
+
/** Fee that WOULD be skimmed on a completed sale (USD). */
|
|
296
|
+
feeAtSale: string;
|
|
297
|
+
/** Amount the seller WOULD receive on a completed sale (USD) = price − feeAtSale. */
|
|
298
|
+
netAtSale: string;
|
|
299
|
+
deliveryWindowSec: number;
|
|
300
|
+
expiresAt?: string | null;
|
|
301
|
+
createdAt: string;
|
|
302
|
+
updatedAt: string;
|
|
303
|
+
}
|
|
304
|
+
/** The escrow-backed sale attached to a listing after buy/confirm/refund/get. */
|
|
305
|
+
interface MarketplaceSale {
|
|
306
|
+
/** The sale's settlement id (`<listingId>:sale`). */
|
|
307
|
+
id: string;
|
|
308
|
+
leg?: "confirm" | "refund";
|
|
309
|
+
/** "settled" (funds moved) · "settling" · "failed" — or the hold's verified state on `get`. */
|
|
310
|
+
status: string;
|
|
311
|
+
txHash?: `0x${string}` | null;
|
|
312
|
+
idempotentReplay?: boolean;
|
|
313
|
+
verifiedVia?: "chain" | "cache" | "degraded";
|
|
314
|
+
}
|
|
315
|
+
interface MarketplaceSaleResult {
|
|
316
|
+
listing: Listing;
|
|
317
|
+
sale: MarketplaceSale;
|
|
318
|
+
}
|
|
319
|
+
interface MarketplaceGetResult {
|
|
320
|
+
listing: Listing;
|
|
321
|
+
sale: MarketplaceSale | null;
|
|
322
|
+
}
|
|
323
|
+
interface Payment {
|
|
324
|
+
/** `pay_…` (IAP) or `entry_…` (prize-pool) — ULID, server-issued, unique. */
|
|
325
|
+
id: string;
|
|
326
|
+
status: PaymentStatus;
|
|
327
|
+
/** "iap" (1% external) or "entry" (60/30/10 prize-pool). */
|
|
328
|
+
kind: "iap" | "entry";
|
|
329
|
+
amount: string;
|
|
330
|
+
fee: string;
|
|
331
|
+
net: string;
|
|
332
|
+
/** Present for prize-pool entries: the 60/30/10 breakdown in USD. */
|
|
333
|
+
split?: {
|
|
334
|
+
pool: string;
|
|
335
|
+
seed: string;
|
|
336
|
+
rake: string;
|
|
337
|
+
};
|
|
338
|
+
sku?: string;
|
|
339
|
+
roundId?: string;
|
|
340
|
+
gameId?: string;
|
|
341
|
+
playerId: string;
|
|
342
|
+
txHash?: `0x${string}`;
|
|
343
|
+
chain: Network;
|
|
344
|
+
createdAt: string;
|
|
345
|
+
metadata?: Record<string, string>;
|
|
346
|
+
/**
|
|
347
|
+
* The on-chain identity used for a prize-pool entry (echo of
|
|
348
|
+
* `EnterRoundInput.identity` or the SDK-derived value) — reconcile against your
|
|
349
|
+
* server's `hasEntered`.
|
|
350
|
+
*/
|
|
351
|
+
identity?: string;
|
|
352
|
+
/** True only when produced by the labeled `mock: true` helper. */
|
|
353
|
+
mock?: boolean;
|
|
354
|
+
}
|
|
355
|
+
/** What `verify()` resolves to (server reads the chain). */
|
|
356
|
+
interface VerifyResult {
|
|
357
|
+
id: string;
|
|
358
|
+
status: PaymentStatus;
|
|
359
|
+
amount: string;
|
|
360
|
+
fee: string;
|
|
361
|
+
net: string;
|
|
362
|
+
txHash?: `0x${string}`;
|
|
363
|
+
playerId: string;
|
|
364
|
+
sku?: string;
|
|
365
|
+
roundId?: string;
|
|
366
|
+
chain: Network;
|
|
367
|
+
/** How the service derived this status: an on-chain read, the honest cache, or
|
|
368
|
+
* degraded (chain reads not configured). Lets the SDK stop polling when the
|
|
369
|
+
* service can never confirm on-chain. */
|
|
370
|
+
verifiedVia?: "chain" | "cache" | "degraded";
|
|
371
|
+
chainReads?: "enabled" | "degraded";
|
|
372
|
+
}
|
|
373
|
+
type WebhookEventType = "payment.confirmed" | "payment.failed" | "payout.settled" | "refund.processed";
|
|
374
|
+
interface WebhookEvent {
|
|
375
|
+
id: string;
|
|
376
|
+
type: WebhookEventType;
|
|
377
|
+
createdAt: string;
|
|
378
|
+
data: {
|
|
379
|
+
id: string;
|
|
380
|
+
status: PaymentStatus;
|
|
381
|
+
amount: string;
|
|
382
|
+
fee: string;
|
|
383
|
+
net: string;
|
|
384
|
+
playerId: string;
|
|
385
|
+
sku?: string;
|
|
386
|
+
txHash?: `0x${string}`;
|
|
387
|
+
chain: Network;
|
|
388
|
+
metadata?: Record<string, string>;
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
/** How the payable pool is split among ranked wallets (best-first). Never a score. */
|
|
392
|
+
type PayoutRule = {
|
|
393
|
+
kind: "winner-take-all";
|
|
394
|
+
} | {
|
|
395
|
+
kind: "top-n";
|
|
396
|
+
splitsBps: number[];
|
|
397
|
+
} | {
|
|
398
|
+
kind: "custom";
|
|
399
|
+
amounts: string[];
|
|
400
|
+
};
|
|
401
|
+
type RoundStatus = "open" | "locked" | "settled" | "cancelled" | "none";
|
|
402
|
+
interface RoundOpenInput {
|
|
403
|
+
gameId: string;
|
|
404
|
+
/** Studio-facing round id (also used as the on-chain round key unless roundKey set). */
|
|
405
|
+
roundId: string;
|
|
406
|
+
/** USD entry amount ("0.25"). Fixed for the round once opened. */
|
|
407
|
+
entryAmount: string;
|
|
408
|
+
/** Studio payout rule stored for settle-from-ranking. */
|
|
409
|
+
payout: PayoutRule;
|
|
410
|
+
/** Optional ISO-8601 when the studio intends to close entries (off-chain advisory). */
|
|
411
|
+
closeAt?: string;
|
|
412
|
+
/** Optional series key for the 60/30/10 seed flywheel; defaults to gameId. */
|
|
413
|
+
seriesKey?: string;
|
|
414
|
+
/** Advanced: exact on-chain round key string (default = roundId). */
|
|
415
|
+
roundKey?: string;
|
|
416
|
+
}
|
|
417
|
+
interface RoundState {
|
|
418
|
+
roundId: string;
|
|
419
|
+
gameId: string;
|
|
420
|
+
roundKey: string;
|
|
421
|
+
status: RoundStatus;
|
|
422
|
+
entryAmount: string;
|
|
423
|
+
/** Payable pool in USD (from chain after lock); may be "0.00" while open. */
|
|
424
|
+
pool: string;
|
|
425
|
+
entrants: number;
|
|
426
|
+
payout: PayoutRule;
|
|
427
|
+
closeAt?: string;
|
|
428
|
+
openTxHash?: `0x${string}`;
|
|
429
|
+
lockTxHash?: `0x${string}`;
|
|
430
|
+
settleTxHash?: `0x${string}`;
|
|
431
|
+
}
|
|
432
|
+
interface RoundSettleInput {
|
|
433
|
+
roundId: string;
|
|
434
|
+
gameId?: string;
|
|
435
|
+
results: {
|
|
436
|
+
ranking: `0x${string}`[];
|
|
437
|
+
} | {
|
|
438
|
+
winners: {
|
|
439
|
+
wallet: `0x${string}`;
|
|
440
|
+
amount: string;
|
|
441
|
+
}[];
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
interface SettleRoundResult {
|
|
445
|
+
roundId: string;
|
|
446
|
+
txHash: `0x${string}`;
|
|
447
|
+
poolPaid: string;
|
|
448
|
+
winners: {
|
|
449
|
+
wallet: `0x${string}`;
|
|
450
|
+
amount: string;
|
|
451
|
+
}[];
|
|
452
|
+
status: "settled";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Typed, actionable errors — the Stripe bar (spec §11).
|
|
457
|
+
*
|
|
458
|
+
* Every error carries a stable machine-readable `code` and is thrown at the
|
|
459
|
+
* EARLIEST possible layer: input errors fire client-side before any network or
|
|
460
|
+
* chain call, so a studio never pays gas to discover a typo.
|
|
461
|
+
*/
|
|
462
|
+
type PlaymosErrorCode = "invalid_amount" | "missing_field" | "insufficient_gas" | "wallet_connection" | "payment_failed" | "auth" | "api_error" | "config";
|
|
463
|
+
declare class PlaymosError extends Error {
|
|
464
|
+
readonly code: PlaymosErrorCode;
|
|
465
|
+
/** Optional machine context (e.g. the offending field, the http status). */
|
|
466
|
+
readonly detail?: Record<string, unknown>;
|
|
467
|
+
constructor(code: PlaymosErrorCode, message: string, detail?: Record<string, unknown>);
|
|
468
|
+
}
|
|
469
|
+
/** amount ≤ 0, non-numeric, empty, or more than 2 decimal places. */
|
|
470
|
+
declare class InvalidAmountError extends PlaymosError {
|
|
471
|
+
constructor(amount: unknown);
|
|
472
|
+
}
|
|
473
|
+
/** A required field (sku, playerId, gameId, roundId, agentId…) was empty. */
|
|
474
|
+
declare class MissingFieldError extends PlaymosError {
|
|
475
|
+
constructor(field: string);
|
|
476
|
+
}
|
|
477
|
+
/** gas mode "player" and the player's ETH is too low to cover gas (pre-check, §7). */
|
|
478
|
+
declare class InsufficientGasError extends PlaymosError {
|
|
479
|
+
constructor(detail?: Record<string, unknown>);
|
|
480
|
+
}
|
|
481
|
+
/** The player closed or failed the wallet sheet, or no provider is available. */
|
|
482
|
+
declare class WalletConnectionError extends PlaymosError {
|
|
483
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
484
|
+
}
|
|
485
|
+
/** The on-chain settlement reverted, was cancelled, or timed out. */
|
|
486
|
+
declare class PaymentFailedError extends PlaymosError {
|
|
487
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
488
|
+
}
|
|
489
|
+
/** Bad, missing, or wrong-environment API key (e.g. a pk_test_ key on a live route). */
|
|
490
|
+
declare class AuthError extends PlaymosError {
|
|
491
|
+
constructor(message?: string, detail?: Record<string, unknown>);
|
|
492
|
+
}
|
|
493
|
+
/** The Playmos service returned a non-2xx we don't have a more specific error for. */
|
|
494
|
+
declare class ApiError extends PlaymosError {
|
|
495
|
+
constructor(message: string, detail?: Record<string, unknown>);
|
|
496
|
+
}
|
|
497
|
+
/** SDK misconfiguration (e.g. a missing contract address for on-chain mode). */
|
|
498
|
+
declare class ConfigError extends PlaymosError {
|
|
499
|
+
constructor(message: string, detail?: Record<string, unknown>);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export { type AgentWallet as A, type PlaymosErrorCode as B, ConfigError as C, type RoundStatus as D, type EscrowHoldInput as E, type WalletConfig as F, type GasConfig as G, WalletConnectionError as H, InsufficientGasError as I, type WalletConnector as J, type WebhookEventType as K, type Listing as L, type MarketplaceListInput as M, type Network as N, type PlaymosConfig as P, type RoundOpenInput as R, type SettleRoundResult as S, type TransferResult as T, type VerifyResult as V, type WebhookEvent as W, type RoundState as a, type RoundSettleInput as b, type AgentFundResult as c, type EscrowHoldResult as d, type EscrowResolveResult as e, type MarketplaceSaleResult as f, type MarketplaceGetResult as g, type PayInput as h, type Payment as i, type EnterRoundInput as j, type TransferInput as k, type AgentEconomyConfig as l, ApiError as m, AuthError as n, type ContractConfig as o, type Eip1193Provider as p, type GasMode as q, InvalidAmountError as r, type ListingStatus as s, type MarketplaceItem as t, type MarketplaceSale as u, MissingFieldError as v, PaymentFailedError as w, type PaymentStatus as x, type PayoutRule as y, PlaymosError as z };
|