@playmos/sdk 0.1.6 β 0.3.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 +146 -0
- package/dist/errors-BVESr920.d.cts +552 -0
- package/dist/errors-BVESr920.d.ts +552 -0
- package/dist/index.cjs +682 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +335 -17
- package/dist/index.d.ts +335 -17
- package/dist/index.js +674 -19
- 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,118 @@ 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 agent/Town walkthrough: see "Agent economies β the Playmos Town golden path" below.
|
|
66
|
+
// Confirm: status === "settled" && txHash, or await playmos.transfers.wait(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
|
+
|
|
112
|
+
### Agent economies β the Playmos Town golden path
|
|
113
|
+
|
|
114
|
+
The NPCs in your game can hold and move USDC. This is the loop the [Playmos Town example](https://github.com/playmos-labs/playmos-sdk/blob/main/docs/examples/playmos-town-sdk.mjs) runs end to end: give each NPC a wallet, fund it, then move value with a per-call fee β P2P (5%), a monster bounty (0%), a shop sale (100%).
|
|
115
|
+
|
|
116
|
+
**Agents need a SECRET test key β `sk_test_`, not `pk_test_`.** Assigning NPC wallets and transferring *from* an NPC are privileged, server-side actions, so they require an `sk_test_` key kept on **your backend** β not the public `pk_test_playmos_sandbox`. `pk_test_` runs the no-wallet `pay()` / `enterRound()` demos above; `sk_test_` unlocks `agents.*` and NPC-funded `transfer`.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Playmos } from "@playmos/sdk";
|
|
120
|
+
|
|
121
|
+
// Server-side only β an sk_test_ secret key, never shipped to the browser.
|
|
122
|
+
const playmos = new Playmos({ apiKey: process.env.PLAYMOS_SK_TEST });
|
|
123
|
+
const feeSink = process.env.PLAYMOS_FEE_SINK; // 0x treasury / shop wallet
|
|
124
|
+
|
|
125
|
+
// 1) Assign a wallet to an NPC your game already owns (idempotent β safe to re-call).
|
|
126
|
+
const miner = await playmos.agents.createWallet({ agentId: "npc_pico_miner" });
|
|
127
|
+
miner.address; // 0xβ¦ β the NPC's USDC wallet on Base Sepolia
|
|
128
|
+
|
|
129
|
+
// 2) Fund it from your treasury (sandbox faucet, per-NPC lifetime cap).
|
|
130
|
+
const funded = await playmos.agents.fund({ agentId: "npc_pico_miner", amount: "0.20" });
|
|
131
|
+
funded.funding.status; // "settled" | "settling"
|
|
132
|
+
|
|
133
|
+
// 3a) P2P trade β 5% fee to the treasury (feeBps: 500). NPC `from` β gasless (NPC signs, Playmos relays + pays gas).
|
|
134
|
+
const p2p = await playmos.transfer({
|
|
135
|
+
from: "npc_pico_miner",
|
|
136
|
+
to: "npc_bruna_blacksmith",
|
|
137
|
+
amount: "0.10",
|
|
138
|
+
feeBps: 500, // 5% P2P
|
|
139
|
+
feeSink, // required when feeBps > 0
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Confirm with the new ergonomics (#54): wait for a terminal state β no hand-rolled poll loop.
|
|
143
|
+
const settled = await playmos.transfers.wait(p2p.id);
|
|
144
|
+
settled.status; // "settled" β txHash on sepolia.basescan.org
|
|
145
|
+
|
|
146
|
+
// 3b) Monster-bounty payout β 0% untaxed reward from the treasury signer (omit `from`, no feeSink).
|
|
147
|
+
await playmos.transfer({ to: "npc_wynn_ranger", amount: "0.05", feeBps: 0 });
|
|
148
|
+
|
|
149
|
+
// 3c) First-party shop sale β 100% to the shop (feeBps: 10000).
|
|
150
|
+
await playmos.transfer({ from: "npc_kane_knight", to: feeSink, amount: "0.05", feeBps: 10_000, feeSink });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Prefer one call? Confirm inline.** Pass `{ confirm: true }` and `transfer` blocks until the transfer is terminal before it resolves β identical to calling `transfers.wait(id)` yourself (#54):
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const t = await playmos.transfer(
|
|
157
|
+
{ from: "npc_pico_miner", to: "npc_bruna_blacksmith", amount: "0.10", feeBps: 500, feeSink },
|
|
158
|
+
{ confirm: true },
|
|
159
|
+
);
|
|
160
|
+
t.status; // "settled" (or "failed") β already terminal, no follow-up call
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The sandbox throttles (5 req/min per key), so back-to-back legs can `429`. **Retries are automatic** β the SDK backs off and retries a `429` up to twice by default, and every write carries an idempotency key so a retry never double-broadcasts (tune with `new Playmos({ apiKey, retry: { maxRetries } })`, or `retry: false` to opt out).
|
|
164
|
+
|
|
165
|
+
The full 5-NPC walkthrough β create β fund β P2P / shop / bounty, each confirmed on-chain β is [`docs/examples/playmos-town-sdk.mjs`](https://github.com/playmos-labs/playmos-sdk/blob/main/docs/examples/playmos-town-sdk.mjs):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
PLAYMOS_SK_TEST=sk_test_⦠PLAYMOS_FEE_SINK=0x⦠node docs/examples/playmos-town-sdk.mjs
|
|
169
|
+
```
|
|
170
|
+
|
|
48
171
|
### Verify before you grant
|
|
49
172
|
|
|
50
173
|
Grant items on a verified confirmation β never on the client `pay()` return alone.
|
|
@@ -81,6 +204,29 @@ const playmos = new Playmos({
|
|
|
81
204
|
|
|
82
205
|
Promote by swapping the key β the same code that passed in the sandbox works live.
|
|
83
206
|
|
|
207
|
+
## Value movement β escrow, marketplace, transfer (0.2.0, testnet)
|
|
208
|
+
|
|
209
|
+
`0.2.0` adds non-custodial USDC value-movement primitives on top of the settlement core:
|
|
210
|
+
|
|
211
|
+
- **`playmos.transfer(...)`** β a direct USDC transfer.
|
|
212
|
+
- **`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.
|
|
213
|
+
- **`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.
|
|
214
|
+
|
|
215
|
+
**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.
|
|
216
|
+
|
|
217
|
+
### Resolve status-code contract
|
|
218
|
+
|
|
219
|
+
The escrow/marketplace resolve endpoints (release/refund/confirm) return a deterministic contract β handle it explicitly:
|
|
220
|
+
|
|
221
|
+
| Code | Meaning | Client action |
|
|
222
|
+
|------|---------|---------------|
|
|
223
|
+
| **404** | Unknown escrow / listing | Terminal β the id does not exist |
|
|
224
|
+
| **409** | Not resolvable (already released/refunded, or genuinely never held) | Terminal β do not retry |
|
|
225
|
+
| **503** | Transient (RPC read-lag, or a crash-orphaned hold being reconciled) | **Retry** with backoff |
|
|
226
|
+
| **400** | Malformed input | Terminal β fix the request |
|
|
227
|
+
|
|
228
|
+
**On a `503`, retry the release/confirm** β it means the on-chain state just hasn't propagated yet, not that anything failed.
|
|
229
|
+
|
|
84
230
|
## Docs
|
|
85
231
|
|
|
86
232
|
Full documentation β skill games, webhooks, gas, payouts, agent economies, and the REST API β at [playmos.io](https://playmos.io).
|