@playmos/sdk 0.1.5 → 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/chunk-B7SHFZYY.js +62 -0
- package/dist/chunk-B7SHFZYY.js.map +1 -0
- package/dist/errors-CjL85YKR.d.cts +502 -0
- package/dist/errors-CjL85YKR.d.ts +502 -0
- package/dist/index.cjs +610 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +321 -250
- package/dist/index.d.ts +321 -250
- package/dist/index.js +603 -113
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +63 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +26 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.js +47 -0
- package/dist/server.js.map +1 -0
- package/package.json +8 -3
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var viem = require('viem');
|
|
4
|
-
var crypto = require('crypto');
|
|
5
4
|
|
|
6
5
|
// src/errors.ts
|
|
7
6
|
var PlaymosError = class extends Error {
|
|
@@ -361,6 +360,17 @@ var playmosPayAbi = [
|
|
|
361
360
|
}
|
|
362
361
|
];
|
|
363
362
|
var prizePoolAbi = [
|
|
363
|
+
{
|
|
364
|
+
type: "function",
|
|
365
|
+
name: "openRound",
|
|
366
|
+
stateMutability: "nonpayable",
|
|
367
|
+
inputs: [
|
|
368
|
+
{ name: "roundId", type: "bytes32" },
|
|
369
|
+
{ name: "series", type: "bytes32" },
|
|
370
|
+
{ name: "entry", type: "uint256" }
|
|
371
|
+
],
|
|
372
|
+
outputs: []
|
|
373
|
+
},
|
|
364
374
|
{
|
|
365
375
|
type: "function",
|
|
366
376
|
name: "enter",
|
|
@@ -371,6 +381,40 @@ var prizePoolAbi = [
|
|
|
371
381
|
],
|
|
372
382
|
outputs: []
|
|
373
383
|
},
|
|
384
|
+
{
|
|
385
|
+
type: "function",
|
|
386
|
+
name: "lockRound",
|
|
387
|
+
stateMutability: "nonpayable",
|
|
388
|
+
inputs: [{ name: "roundId", type: "bytes32" }],
|
|
389
|
+
outputs: []
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
type: "function",
|
|
393
|
+
name: "settle",
|
|
394
|
+
stateMutability: "nonpayable",
|
|
395
|
+
inputs: [
|
|
396
|
+
{ name: "roundId", type: "bytes32" },
|
|
397
|
+
{ name: "winners", type: "address[]" },
|
|
398
|
+
{ name: "amounts", type: "uint256[]" }
|
|
399
|
+
],
|
|
400
|
+
outputs: []
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
type: "function",
|
|
404
|
+
name: "getRound",
|
|
405
|
+
stateMutability: "view",
|
|
406
|
+
inputs: [{ name: "roundId", type: "bytes32" }],
|
|
407
|
+
outputs: [
|
|
408
|
+
{ name: "state", type: "uint8" },
|
|
409
|
+
{ name: "series", type: "bytes32" },
|
|
410
|
+
{ name: "entry", type: "uint256" },
|
|
411
|
+
{ name: "pot", type: "uint256" },
|
|
412
|
+
{ name: "entrantCount", type: "uint256" },
|
|
413
|
+
{ name: "payable_", type: "uint256" },
|
|
414
|
+
{ name: "inheritedSeed", type: "uint256" },
|
|
415
|
+
{ name: "lockedAt", type: "uint256" }
|
|
416
|
+
]
|
|
417
|
+
},
|
|
374
418
|
{
|
|
375
419
|
type: "function",
|
|
376
420
|
name: "hasEntered",
|
|
@@ -380,6 +424,13 @@ var prizePoolAbi = [
|
|
|
380
424
|
{ name: "identity", type: "bytes32" }
|
|
381
425
|
],
|
|
382
426
|
outputs: [{ type: "bool" }]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
type: "function",
|
|
430
|
+
name: "withdraw",
|
|
431
|
+
stateMutability: "nonpayable",
|
|
432
|
+
inputs: [],
|
|
433
|
+
outputs: [{ name: "amount", type: "uint256" }]
|
|
383
434
|
}
|
|
384
435
|
];
|
|
385
436
|
async function sendCalls(provider, from, chainId, calls, paymasterUrl) {
|
|
@@ -513,57 +564,51 @@ function mockEntryPayment(args) {
|
|
|
513
564
|
mock: true
|
|
514
565
|
};
|
|
515
566
|
}
|
|
516
|
-
var WebhookSignatureError = class extends PlaymosError {
|
|
517
|
-
constructor(message) {
|
|
518
|
-
super("api_error", `Webhook signature verification failed: ${message}`);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
function parseHeader(header) {
|
|
522
|
-
const parts = Object.fromEntries(
|
|
523
|
-
header.split(",").map((kv) => {
|
|
524
|
-
const [k, v] = kv.split("=");
|
|
525
|
-
return [k?.trim(), v?.trim()];
|
|
526
|
-
})
|
|
527
|
-
);
|
|
528
|
-
const t = Number(parts["t"]);
|
|
529
|
-
const v1 = parts["v1"];
|
|
530
|
-
if (!Number.isFinite(t) || !v1) throw new WebhookSignatureError("malformed signature header");
|
|
531
|
-
return { t, v1 };
|
|
532
|
-
}
|
|
533
|
-
function computeSignature(secret, t, rawBody) {
|
|
534
|
-
return crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
|
|
535
|
-
}
|
|
536
|
-
function verifyWebhook(rawBody, signatureHeader, secret, opts = {}) {
|
|
537
|
-
if (!signatureHeader || Array.isArray(signatureHeader)) {
|
|
538
|
-
throw new WebhookSignatureError("missing signature header");
|
|
539
|
-
}
|
|
540
|
-
if (!secret) throw new WebhookSignatureError("missing signing secret");
|
|
541
|
-
const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
|
|
542
|
-
const { t, v1 } = parseHeader(signatureHeader);
|
|
543
|
-
const tolerance = opts.toleranceSeconds ?? 300;
|
|
544
|
-
const nowSec = Math.floor(Date.now() / 1e3);
|
|
545
|
-
if (Math.abs(nowSec - t) > tolerance) {
|
|
546
|
-
throw new WebhookSignatureError(`timestamp outside tolerance (${tolerance}s)`);
|
|
547
|
-
}
|
|
548
|
-
const expected = computeSignature(secret, t, body);
|
|
549
|
-
const a = Buffer.from(expected, "hex");
|
|
550
|
-
const b = Buffer.from(v1, "hex");
|
|
551
|
-
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
|
|
552
|
-
throw new WebhookSignatureError("signature mismatch");
|
|
553
|
-
}
|
|
554
|
-
return JSON.parse(body);
|
|
555
|
-
}
|
|
556
567
|
|
|
557
568
|
// src/client.ts
|
|
569
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
570
|
+
function requireAddressField(value, field) {
|
|
571
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
572
|
+
throw new MissingFieldError(field);
|
|
573
|
+
}
|
|
574
|
+
if (!ADDRESS_RE.test(value)) {
|
|
575
|
+
throw new ConfigError(
|
|
576
|
+
`${field} must be a 0x-prefixed 20-byte wallet address (Phase 1a transfers settle server-held wallets), got: ${JSON.stringify(value)}`,
|
|
577
|
+
{ field, value }
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
return value.toLowerCase();
|
|
581
|
+
}
|
|
582
|
+
function partyRef(value, field) {
|
|
583
|
+
if (typeof value !== "string" || value.trim() === "") throw new MissingFieldError(field);
|
|
584
|
+
if (value.startsWith("0x")) return requireAddressField(value, field);
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
558
587
|
var IAP_FEE_BPS = 100;
|
|
559
588
|
var POOL_BPS = 6e3;
|
|
560
589
|
var SEED_BPS = 3e3;
|
|
561
590
|
var RAKE_BPS = 1e3;
|
|
562
591
|
var Playmos = class {
|
|
563
592
|
constructor(config) {
|
|
593
|
+
/**
|
|
594
|
+
* Webhook helpers. Signature verification is **server-only** (Node `crypto`) and
|
|
595
|
+
* lives on `@playmos/sdk/server` so browser bundlers never see `node:crypto` (#9).
|
|
596
|
+
*
|
|
597
|
+
* import { verifyWebhook } from "@playmos/sdk/server";
|
|
598
|
+
* const event = verifyWebhook(rawBody, req.headers["x-playmos-signature"], secret);
|
|
599
|
+
*/
|
|
564
600
|
this.webhooks = {
|
|
565
|
-
/**
|
|
566
|
-
|
|
601
|
+
/**
|
|
602
|
+
* @deprecated Use `import { verifyWebhook } from "@playmos/sdk/server"` instead.
|
|
603
|
+
* Throws if called — kept as a discoverable pointer so call sites fail loudly
|
|
604
|
+
* with a fix instruction rather than a silent missing method.
|
|
605
|
+
*/
|
|
606
|
+
verify: (_rawBody, _signatureHeader, _secret) => {
|
|
607
|
+
throw new ConfigError(
|
|
608
|
+
'playmos.webhooks.verify is server-only and no longer ships in the browser entry (issue #9). Use: import { verifyWebhook } from "@playmos/sdk/server"',
|
|
609
|
+
{ entry: "@playmos/sdk/server" }
|
|
610
|
+
);
|
|
611
|
+
}
|
|
567
612
|
};
|
|
568
613
|
this.payouts = {
|
|
569
614
|
/** Choose how the studio is paid: "usdc" (default) or "fiat" (Bridge). */
|
|
@@ -571,18 +616,236 @@ var Playmos = class {
|
|
|
571
616
|
/** Create a Bridge KYC onboarding link (fiat payout). */
|
|
572
617
|
createOnboardingLink: () => this.http.post("/payouts/onboarding_link", {})
|
|
573
618
|
};
|
|
619
|
+
/**
|
|
620
|
+
* Skill/contest **round lifecycle** (issue #13) — closes the enterRound loop.
|
|
621
|
+
*
|
|
622
|
+
* Scores never leave the studio. Flow:
|
|
623
|
+
* rounds.open → players enterRound → studio scores → rounds.lock →
|
|
624
|
+
* rounds.settle({ ranking }) → contract pays winners.
|
|
625
|
+
*
|
|
626
|
+
* Operator txs are signed by the Playmos service (OPERATOR_ROLE key).
|
|
627
|
+
*/
|
|
628
|
+
this.rounds = {
|
|
629
|
+
open: async (input) => {
|
|
630
|
+
requireField(input?.gameId, "gameId");
|
|
631
|
+
requireField(input?.roundId, "roundId");
|
|
632
|
+
requireField(input?.entryAmount, "entryAmount");
|
|
633
|
+
if (!input?.payout || typeof input.payout !== "object") {
|
|
634
|
+
throw new ConfigError("payout rule is required (winner-take-all | top-n | custom)");
|
|
635
|
+
}
|
|
636
|
+
validateAmount(input.entryAmount);
|
|
637
|
+
const { round } = await this.http.post("/rounds", {
|
|
638
|
+
gameId: input.gameId,
|
|
639
|
+
roundId: input.roundId,
|
|
640
|
+
entryAmount: input.entryAmount,
|
|
641
|
+
payout: input.payout,
|
|
642
|
+
closeAt: input.closeAt,
|
|
643
|
+
seriesKey: input.seriesKey,
|
|
644
|
+
roundKey: input.roundKey
|
|
645
|
+
});
|
|
646
|
+
return round;
|
|
647
|
+
},
|
|
648
|
+
lock: async (input) => {
|
|
649
|
+
requireField(input?.roundId, "roundId");
|
|
650
|
+
const { round } = await this.http.post(
|
|
651
|
+
`/rounds/${encodeURIComponent(input.roundId)}/lock`,
|
|
652
|
+
{ gameId: input.gameId }
|
|
653
|
+
);
|
|
654
|
+
return round;
|
|
655
|
+
},
|
|
656
|
+
settle: async (input) => {
|
|
657
|
+
requireField(input?.roundId, "roundId");
|
|
658
|
+
if (!input?.results) throw new ConfigError("results are required (ranking or winners)");
|
|
659
|
+
const { settle } = await this.http.post(
|
|
660
|
+
`/rounds/${encodeURIComponent(input.roundId)}/settle`,
|
|
661
|
+
{ gameId: input.gameId, results: input.results }
|
|
662
|
+
);
|
|
663
|
+
return settle;
|
|
664
|
+
},
|
|
665
|
+
get: async (input) => {
|
|
666
|
+
requireField(input?.roundId, "roundId");
|
|
667
|
+
const { round } = await this.http.get(
|
|
668
|
+
`/rounds/${encodeURIComponent(input.roundId)}`
|
|
669
|
+
);
|
|
670
|
+
return round;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
/**
|
|
674
|
+
* `agents` — assign wallets to the NPCs YOUR game already owns, so they can transact USDC in your economy.
|
|
675
|
+
* The game creates the NPCs; the SDK only creates the WALLET for a game-supplied id. Engine-agnostic
|
|
676
|
+
* (Unity/Unreal/Godot/web all call the same REST). Requires a secret test key (`sk_test_`) on the sandbox.
|
|
677
|
+
*/
|
|
574
678
|
this.agents = {
|
|
575
|
-
/** Assign
|
|
576
|
-
createWallet: (input) => {
|
|
679
|
+
/** Assign (or return) the wallet for a game NPC id. Idempotent — safe to call wherever your NPCs spawn. */
|
|
680
|
+
createWallet: async (input) => {
|
|
577
681
|
requireField(input?.agentId, "agentId");
|
|
578
|
-
|
|
682
|
+
const { agent } = await this.http.post("/agents/wallets", { agentId: input.agentId });
|
|
683
|
+
return agent;
|
|
684
|
+
},
|
|
685
|
+
/** Resolve one NPC's wallet by your id. */
|
|
686
|
+
wallet: async (agentId) => {
|
|
687
|
+
requireField(agentId, "agentId");
|
|
688
|
+
const { agent } = await this.http.get(`/agents/wallets/${encodeURIComponent(agentId)}`);
|
|
689
|
+
return agent;
|
|
579
690
|
},
|
|
580
|
-
/**
|
|
691
|
+
/** List the NPC wallets you've assigned in this studio. */
|
|
692
|
+
list: async () => {
|
|
693
|
+
const { agents } = await this.http.get("/agents/wallets");
|
|
694
|
+
return agents;
|
|
695
|
+
},
|
|
696
|
+
/** Sandbox faucet: fund an NPC with USDC from your treasury (per-NPC lifetime cap). */
|
|
697
|
+
fund: (input) => {
|
|
698
|
+
requireField(input?.agentId, "agentId");
|
|
699
|
+
validateAmount(input.amount);
|
|
700
|
+
return this.http.post(`/agents/wallets/${encodeURIComponent(input.agentId)}/fund`, { amount: input.amount });
|
|
701
|
+
},
|
|
702
|
+
/** NPC→NPC (or →player) USDC transfer, by id. A thin alias of `playmos.transfer` (which is canonical). */
|
|
581
703
|
pay: (input) => {
|
|
582
704
|
requireField(input?.from, "from");
|
|
583
705
|
requireField(input?.to, "to");
|
|
584
|
-
|
|
585
|
-
|
|
706
|
+
return this.transfer({ from: input.from, to: input.to, amount: input.amount, feeBps: input.feeBps, feeSink: input.feeSink });
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
/**
|
|
710
|
+
* `escrow` — the fair-exchange primitive (Phase 2): lock the payer's USDC on-chain the instant a deal
|
|
711
|
+
* opens (`hold`), then move it exactly once — `release` (→ payee, fee skimmed) XOR `refund` (→ payer,
|
|
712
|
+
* 100%). The contract holds the funds trustlessly; YOUR game decides the rule (who resolves, and when).
|
|
713
|
+
* A `deadline` auto-refund guarantees funds never get stuck. Pass the `hold` result's `id` to the rest.
|
|
714
|
+
*/
|
|
715
|
+
this.escrow = {
|
|
716
|
+
/** Open a deal: lock `amount` of the payer's USDC into the on-chain escrow. Retries are idempotent.
|
|
717
|
+
* `async` so client-side validation surfaces as a rejected promise, not a synchronous throw. */
|
|
718
|
+
hold: async (input) => {
|
|
719
|
+
const amountMicro = validateAmount(input.amount);
|
|
720
|
+
const payer = input.payer === void 0 ? void 0 : requireAddressField(input.payer, "payer");
|
|
721
|
+
const payee = requireAddressField(input.payee, "payee");
|
|
722
|
+
const feeBps = input.feeBps ?? 0;
|
|
723
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4) {
|
|
724
|
+
throw new ConfigError(
|
|
725
|
+
`feeBps must be an integer in [0, 10000] (fee is taken at release only; refunds are 100%), got: ${JSON.stringify(input.feeBps)}`,
|
|
726
|
+
{ feeBps: input.feeBps }
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
const feeSink = feeBps > 0 ? requireAddressField(input.feeSink, "feeSink") : input.feeSink === void 0 ? void 0 : requireAddressField(input.feeSink, "feeSink");
|
|
730
|
+
const resolver = input.resolver === void 0 ? void 0 : requireAddressField(input.resolver, "resolver");
|
|
731
|
+
const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
|
|
732
|
+
const feeMicro = amountMicro * BigInt(feeBps) / 10000n;
|
|
733
|
+
const { escrow } = await this.http.post(
|
|
734
|
+
"/escrows",
|
|
735
|
+
{
|
|
736
|
+
payer,
|
|
737
|
+
payee,
|
|
738
|
+
amount: input.amount,
|
|
739
|
+
feeBps,
|
|
740
|
+
feeSink,
|
|
741
|
+
resolver,
|
|
742
|
+
expiresIn: input.expiresIn,
|
|
743
|
+
deadline: input.deadline,
|
|
744
|
+
memo: input.memo,
|
|
745
|
+
idempotencyKey
|
|
746
|
+
},
|
|
747
|
+
{ idempotencyKey }
|
|
748
|
+
);
|
|
749
|
+
return {
|
|
750
|
+
...escrow,
|
|
751
|
+
payee: escrow.payee ?? payee,
|
|
752
|
+
amount: escrow.amount ?? formatMicroToUsd(amountMicro),
|
|
753
|
+
feeBps: escrow.feeBps ?? feeBps,
|
|
754
|
+
feeSink: escrow.feeSink ?? feeSink ?? null,
|
|
755
|
+
resolver: escrow.resolver ?? resolver ?? null,
|
|
756
|
+
feeAtRelease: escrow.feeAtRelease ?? formatMicroToUsd(feeMicro),
|
|
757
|
+
netAtRelease: escrow.netAtRelease ?? formatMicroToUsd(amountMicro - feeMicro),
|
|
758
|
+
idempotentReplay: Boolean(escrow.idempotentReplay)
|
|
759
|
+
};
|
|
760
|
+
},
|
|
761
|
+
/** Release a held deal to the payee (fee skimmed). `escrowId` is the `hold` result's `id`. */
|
|
762
|
+
release: async (input) => {
|
|
763
|
+
requireField(input?.escrowId, "escrowId");
|
|
764
|
+
const { escrow } = await this.http.post(`/escrows/${encodeURIComponent(input.escrowId)}/release`, {});
|
|
765
|
+
return escrow;
|
|
766
|
+
},
|
|
767
|
+
/** Refund a held deal to the payer (100%, untaxed). `escrowId` is the `hold` result's `id`. */
|
|
768
|
+
refund: async (input) => {
|
|
769
|
+
requireField(input?.escrowId, "escrowId");
|
|
770
|
+
const { escrow } = await this.http.post(`/escrows/${encodeURIComponent(input.escrowId)}/refund`, {});
|
|
771
|
+
return escrow;
|
|
772
|
+
},
|
|
773
|
+
/** Verify a deal's on-chain state (reconciles a `settling` hold wedged by a crash). */
|
|
774
|
+
get: async (escrowId) => {
|
|
775
|
+
requireField(escrowId, "escrowId");
|
|
776
|
+
const { escrow } = await this.http.get(`/escrows/${encodeURIComponent(escrowId)}`);
|
|
777
|
+
return escrow;
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
/**
|
|
781
|
+
* `marketplace` — list an item, and the seller is paid ONLY when it's bought (Phase 3a, off-chain items).
|
|
782
|
+
* Built on `escrow`: `buy` locks the buyer's USDC on-chain, `confirm` (after your game server delivers the
|
|
783
|
+
* item) pays the seller, and a no-delivery/timeout `refund`s the buyer. `deliver: true` on `buy` collapses
|
|
784
|
+
* lock+pay into one call when your server delivers synchronously. On-chain items are Phase 3b.
|
|
785
|
+
*/
|
|
786
|
+
this.marketplace = {
|
|
787
|
+
/** List an off-chain item for sale. No money moves. Idempotent on `idempotencyKey`. */
|
|
788
|
+
list: async (input) => {
|
|
789
|
+
if (!input?.item || input.item.kind !== "offchain") {
|
|
790
|
+
throw new ConfigError('marketplace.list requires item = { kind: "offchain", sku: "<your-item-id>" } (on-chain items are Phase 3b)', { item: input?.item });
|
|
791
|
+
}
|
|
792
|
+
validateAmount(input.price);
|
|
793
|
+
const seller = requireAddressField(input.seller, "seller");
|
|
794
|
+
const feeBps = input.feeBps;
|
|
795
|
+
if (feeBps !== void 0 && (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4)) {
|
|
796
|
+
throw new ConfigError(`feeBps must be an integer in [0, 10000], got: ${JSON.stringify(feeBps)}`, { feeBps });
|
|
797
|
+
}
|
|
798
|
+
const feeSink = input.feeSink === void 0 ? void 0 : requireAddressField(input.feeSink, "feeSink");
|
|
799
|
+
const { listing } = await this.http.post("/listings", {
|
|
800
|
+
seller,
|
|
801
|
+
item: input.item,
|
|
802
|
+
price: input.price,
|
|
803
|
+
feeBps,
|
|
804
|
+
feeSink,
|
|
805
|
+
deliveryWindow: input.deliveryWindow,
|
|
806
|
+
expiresIn: input.expiresIn,
|
|
807
|
+
gameId: input.gameId,
|
|
808
|
+
idempotencyKey: input.idempotencyKey
|
|
809
|
+
});
|
|
810
|
+
return listing;
|
|
811
|
+
},
|
|
812
|
+
/** Buy a listing: lock the buyer's USDC in escrow. Pass `deliver: true` to also pay the seller in one call. */
|
|
813
|
+
buy: async (input) => {
|
|
814
|
+
requireField(input?.listingId, "listingId");
|
|
815
|
+
const buyer = input.buyer === void 0 ? void 0 : requireAddressField(input.buyer, "buyer");
|
|
816
|
+
return this.http.post(`/listings/${encodeURIComponent(input.listingId)}/buy`, { buyer, deliver: input.deliver === true });
|
|
817
|
+
},
|
|
818
|
+
/** Confirm delivery → the seller is paid (release), fee skimmed. */
|
|
819
|
+
confirm: async (input) => {
|
|
820
|
+
requireField(input?.listingId, "listingId");
|
|
821
|
+
return this.http.post(`/listings/${encodeURIComponent(input.listingId)}/confirm`, {});
|
|
822
|
+
},
|
|
823
|
+
/** Refund the buyer 100% (seller couldn't deliver / dispute / timeout). */
|
|
824
|
+
refund: async (input) => {
|
|
825
|
+
requireField(input?.listingId, "listingId");
|
|
826
|
+
return this.http.post(`/listings/${encodeURIComponent(input.listingId)}/refund`, {});
|
|
827
|
+
},
|
|
828
|
+
/** Delist an unsold listing (pure DB, no tx). */
|
|
829
|
+
cancel: async (input) => {
|
|
830
|
+
requireField(input?.listingId, "listingId");
|
|
831
|
+
const { listing } = await this.http.post(`/listings/${encodeURIComponent(input.listingId)}/cancel`, {});
|
|
832
|
+
return listing;
|
|
833
|
+
},
|
|
834
|
+
/** Fetch a listing + its on-chain-verified sale status. */
|
|
835
|
+
get: async (listingId) => {
|
|
836
|
+
requireField(listingId, "listingId");
|
|
837
|
+
return this.http.get(`/listings/${encodeURIComponent(listingId)}`);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
/**
|
|
841
|
+
* `transfers` — read-back / confirmation for a prior `transfer()` (issue #27).
|
|
842
|
+
* Poll when POST returned `status: "settling"`; reconciles against chain (incl. gasless agent path).
|
|
843
|
+
*/
|
|
844
|
+
this.transfers = {
|
|
845
|
+
get: async (transferId) => {
|
|
846
|
+
requireField(transferId, "transferId");
|
|
847
|
+
const res = await this.http.get(`/transfers/${encodeURIComponent(transferId)}`);
|
|
848
|
+
return res.transfer;
|
|
586
849
|
}
|
|
587
850
|
};
|
|
588
851
|
this.config = config;
|
|
@@ -659,9 +922,11 @@ var Playmos = class {
|
|
|
659
922
|
const playmosPay = intent.clientParams?.contractAddress ?? this.config.contracts?.playmosPay;
|
|
660
923
|
const studio = intent.clientParams?.studio ?? input.studio;
|
|
661
924
|
if (!playmosPay) throw new ConfigError("No PlaymosPay contract address (service intent + config.contracts both empty).");
|
|
662
|
-
if (!studio) throw new ConfigError("No studio payout address for this payment.");
|
|
925
|
+
if (!studio) throw new ConfigError("No studio payout address for this payment (service must return clientParams.studio).");
|
|
926
|
+
if (!intent.clientParams?.studio && input.studio) ;
|
|
663
927
|
const amountUnits = this.resolveUnits(intent, amountMicro);
|
|
664
|
-
const
|
|
928
|
+
const paymentId = intent.clientParams?.paymentId ?? intent.payment.id;
|
|
929
|
+
const calls = buildIapCalls({ usdc, playmosPay, paymentId, studio, amountUnits });
|
|
665
930
|
const { id: callsId } = await sendCalls(provider, from, this.env.chainId, calls, this.sponsorUrl(intent));
|
|
666
931
|
const { txHash } = await waitForCalls(provider, callsId);
|
|
667
932
|
return this.settle(intent.payment.id, txHash);
|
|
@@ -713,8 +978,9 @@ var Playmos = class {
|
|
|
713
978
|
const usdc = intent.clientParams?.usdc ?? this.config.contracts?.usdc ?? USDC_ADDRESS[this.env.network];
|
|
714
979
|
const prizePool = intent.clientParams?.contractAddress ?? this.config.contracts?.prizePool;
|
|
715
980
|
if (!prizePool) throw new ConfigError("No PrizePool contract address for this game (service intent + config.contracts both empty).");
|
|
716
|
-
const roundKey =
|
|
717
|
-
|
|
981
|
+
const roundKey = intent.clientParams?.roundKey ?? intent.clientParams?.roundId ?? input.roundKey ?? intent.payment.roundId ?? input.roundId;
|
|
982
|
+
if (!roundKey) throw new ConfigError("No roundKey/roundId for enterRound (service clientParams missing).");
|
|
983
|
+
const identity = intent.clientParams?.identity ?? input.identity ?? intent.payment.identity ?? `${from}#${idempotencyKey}`;
|
|
718
984
|
const amountUnits = this.resolveUnits(intent, amountMicro);
|
|
719
985
|
const calls = buildEntryCalls({ usdc, prizePool, roundKey, identity, amountUnits });
|
|
720
986
|
const { id: callsId } = await sendCalls(provider, from, this.env.chainId, calls, this.sponsorUrl(intent));
|
|
@@ -723,6 +989,64 @@ var Playmos = class {
|
|
|
723
989
|
payment.identity = identity;
|
|
724
990
|
return payment;
|
|
725
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* `transfer` — the value-movement base primitive (Phase 1a): move USDC from one wallet to another,
|
|
994
|
+
* with a per-call fee. The GAME LOGIC is the authority — you already decided the move is valid — so
|
|
995
|
+
* this is a direct, unconditional push (use `escrow`/`marketplace` when a trust boundary needs fair
|
|
996
|
+
* exchange). The service settles it through the protocol-agnostic settlement core (idempotent,
|
|
997
|
+
* reserve-before-broadcast) and the on-chain PlaymosTransfer / PlaymosTransferAuth contracts.
|
|
998
|
+
*
|
|
999
|
+
* Fee is per-call: `feeBps` 0–10000 (+ `feeSink`). `feeBps: 0` is an untaxed reward/faucet transfer.
|
|
1000
|
+
* Retries are safe: pass the same `idempotencyKey` and a re-call NEVER broadcasts a second tx —
|
|
1001
|
+
* it returns the cached result (`idempotentReplay: true`).
|
|
1002
|
+
*
|
|
1003
|
+
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, call
|
|
1004
|
+
* `playmos.transfers.get(id)` until settled/failed. BaseScan: `https://sepolia.basescan.org/tx/<txHash>`.
|
|
1005
|
+
*
|
|
1006
|
+
* NPC `from` requires `sk_test_` and settles gaslessly (NPC signs; service relays).
|
|
1007
|
+
*/
|
|
1008
|
+
async transfer(input) {
|
|
1009
|
+
const amountMicro = validateAmount(input.amount);
|
|
1010
|
+
const from = input.from === void 0 ? void 0 : partyRef(input.from, "from");
|
|
1011
|
+
const to = partyRef(input.to, "to");
|
|
1012
|
+
const feeBps = input.feeBps ?? 0;
|
|
1013
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4) {
|
|
1014
|
+
throw new ConfigError(
|
|
1015
|
+
`feeBps must be an integer in [0, 10000] (0 = untaxed reward/faucet; 500 = 5%; 10000 = 100%), got: ${JSON.stringify(input.feeBps)}`,
|
|
1016
|
+
{ feeBps: input.feeBps }
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
let feeSink;
|
|
1020
|
+
if (feeBps > 0) {
|
|
1021
|
+
feeSink = requireAddressField(input.feeSink, "feeSink");
|
|
1022
|
+
} else if (input.feeSink !== void 0) {
|
|
1023
|
+
feeSink = requireAddressField(input.feeSink, "feeSink");
|
|
1024
|
+
}
|
|
1025
|
+
const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
|
|
1026
|
+
const res = await this.http.post(
|
|
1027
|
+
"/transfers",
|
|
1028
|
+
{ from, to, amount: input.amount, memo: input.memo, feeBps, feeSink, idempotencyKey },
|
|
1029
|
+
{ idempotencyKey }
|
|
1030
|
+
);
|
|
1031
|
+
const t = res.transfer;
|
|
1032
|
+
const feeMicro = amountMicro * BigInt(feeBps) / 10000n;
|
|
1033
|
+
return {
|
|
1034
|
+
id: t.id,
|
|
1035
|
+
status: t.status,
|
|
1036
|
+
txHash: t.txHash,
|
|
1037
|
+
from: t.from,
|
|
1038
|
+
// the service's resolved payer address (omitted when it's the redacted treasury signer)
|
|
1039
|
+
to: t.to,
|
|
1040
|
+
// the service's resolved payee address
|
|
1041
|
+
amount: t.amount ?? formatMicroToUsd(amountMicro),
|
|
1042
|
+
fee: t.fee ?? formatMicroToUsd(feeMicro),
|
|
1043
|
+
net: t.net ?? formatMicroToUsd(amountMicro - feeMicro),
|
|
1044
|
+
feeBps: t.feeBps ?? feeBps,
|
|
1045
|
+
feeSink: t.feeSink ?? feeSink ?? null,
|
|
1046
|
+
memo: t.memo ?? input.memo ?? null,
|
|
1047
|
+
idempotentReplay: Boolean(t.idempotentReplay)
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
726
1050
|
/** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
|
|
727
1051
|
async verify(paymentId) {
|
|
728
1052
|
requireField(paymentId, "paymentId");
|
|
@@ -814,6 +1138,30 @@ function previewIapSplit(amount) {
|
|
|
814
1138
|
const { feeMicro, netMicro } = computeIapSplit(micro, IAP_FEE_BPS);
|
|
815
1139
|
return { amount: formatMicroToUsd(micro), fee: formatMicroToUsd(feeMicro), net: formatMicroToUsd(netMicro) };
|
|
816
1140
|
}
|
|
1141
|
+
function previewTransferSplit(amount, feeBps) {
|
|
1142
|
+
const micro = validateAmount(amount);
|
|
1143
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4) {
|
|
1144
|
+
throw new ConfigError(`feeBps must be an integer in [0, 10000], got: ${JSON.stringify(feeBps)}`, { feeBps });
|
|
1145
|
+
}
|
|
1146
|
+
const feeMicro = micro * BigInt(feeBps) / 10000n;
|
|
1147
|
+
return { amount: formatMicroToUsd(micro), fee: formatMicroToUsd(feeMicro), net: formatMicroToUsd(micro - feeMicro), feeBps };
|
|
1148
|
+
}
|
|
1149
|
+
function previewEscrowFee(amount, feeBps) {
|
|
1150
|
+
const micro = validateAmount(amount);
|
|
1151
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4) {
|
|
1152
|
+
throw new ConfigError(`feeBps must be an integer in [0, 10000], got: ${JSON.stringify(feeBps)}`, { feeBps });
|
|
1153
|
+
}
|
|
1154
|
+
const feeMicro = micro * BigInt(feeBps) / 10000n;
|
|
1155
|
+
return { amount: formatMicroToUsd(micro), fee: formatMicroToUsd(feeMicro), net: formatMicroToUsd(micro - feeMicro), feeBps };
|
|
1156
|
+
}
|
|
1157
|
+
function previewMarketplaceSplit(price, feeBps) {
|
|
1158
|
+
const micro = validateAmount(price);
|
|
1159
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1e4) {
|
|
1160
|
+
throw new ConfigError(`feeBps must be an integer in [0, 10000], got: ${JSON.stringify(feeBps)}`, { feeBps });
|
|
1161
|
+
}
|
|
1162
|
+
const feeMicro = micro * BigInt(feeBps) / 10000n;
|
|
1163
|
+
return { price: formatMicroToUsd(micro), fee: formatMicroToUsd(feeMicro), net: formatMicroToUsd(micro - feeMicro), feeBps };
|
|
1164
|
+
}
|
|
817
1165
|
function previewPoolSplit(amount) {
|
|
818
1166
|
const micro = validateAmount(amount);
|
|
819
1167
|
const { poolMicro, seedMicro, rakeMicro } = computePoolSplit(micro, POOL_BPS, SEED_BPS, RAKE_BPS);
|
|
@@ -825,6 +1173,205 @@ function previewPoolSplit(amount) {
|
|
|
825
1173
|
};
|
|
826
1174
|
}
|
|
827
1175
|
|
|
1176
|
+
// src/payout.ts
|
|
1177
|
+
var PayoutError = class extends Error {
|
|
1178
|
+
constructor(message) {
|
|
1179
|
+
super(message);
|
|
1180
|
+
this.code = "payout_invalid";
|
|
1181
|
+
this.name = "PayoutError";
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
var ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
|
|
1185
|
+
var BPS = 10000n;
|
|
1186
|
+
function requireAddress(w, i) {
|
|
1187
|
+
if (!ADDRESS_RE2.test(w)) {
|
|
1188
|
+
throw new PayoutError(`ranking[${i}] must be a 0x-prefixed 20-byte address, got: ${JSON.stringify(w)}`);
|
|
1189
|
+
}
|
|
1190
|
+
return w.toLowerCase();
|
|
1191
|
+
}
|
|
1192
|
+
function parseUsdToMicroLoose(amount) {
|
|
1193
|
+
if (typeof amount !== "string" || !/^\d+(\.\d{1,6})?$/.test(amount.trim())) {
|
|
1194
|
+
throw new PayoutError(`invalid USD amount: ${JSON.stringify(amount)}`);
|
|
1195
|
+
}
|
|
1196
|
+
const [wholeRaw, frac = ""] = amount.trim().split(".");
|
|
1197
|
+
const whole = wholeRaw ?? "0";
|
|
1198
|
+
const fracPadded = (frac + "000000").slice(0, 6);
|
|
1199
|
+
const micro = BigInt(whole) * 1000000n + BigInt(fracPadded);
|
|
1200
|
+
if (micro <= 0n) throw new PayoutError(`amount must be > 0, got: ${JSON.stringify(amount)}`);
|
|
1201
|
+
return micro;
|
|
1202
|
+
}
|
|
1203
|
+
function computePayout(pool, ranking, rule) {
|
|
1204
|
+
if (typeof pool !== "bigint" || pool <= 0n) {
|
|
1205
|
+
throw new PayoutError(`pool must be a positive bigint (micro-USDC), got: ${String(pool)}`);
|
|
1206
|
+
}
|
|
1207
|
+
if (!Array.isArray(ranking) || ranking.length === 0) {
|
|
1208
|
+
throw new PayoutError("ranking must be a non-empty array of wallets (best-first)");
|
|
1209
|
+
}
|
|
1210
|
+
const wallets = ranking.map((w, i) => requireAddress(w, i));
|
|
1211
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1212
|
+
for (const w of wallets) {
|
|
1213
|
+
if (seen.has(w)) throw new PayoutError(`duplicate wallet in ranking: ${w}`);
|
|
1214
|
+
seen.add(w);
|
|
1215
|
+
}
|
|
1216
|
+
if (rule.kind === "winner-take-all") {
|
|
1217
|
+
return [{ wallet: wallets[0], amount: pool }];
|
|
1218
|
+
}
|
|
1219
|
+
if (rule.kind === "top-n") {
|
|
1220
|
+
const splits = rule.splitsBps;
|
|
1221
|
+
if (!Array.isArray(splits) || splits.length === 0) {
|
|
1222
|
+
throw new PayoutError("top-n splitsBps must be a non-empty array");
|
|
1223
|
+
}
|
|
1224
|
+
if (splits.length > wallets.length) {
|
|
1225
|
+
throw new PayoutError(
|
|
1226
|
+
`top-n needs ${splits.length} ranked wallets but ranking only has ${wallets.length}`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
let sumBps = 0;
|
|
1230
|
+
for (const b of splits) {
|
|
1231
|
+
if (!Number.isInteger(b) || b <= 0 || b > 1e4) {
|
|
1232
|
+
throw new PayoutError(`each splitsBps entry must be an integer in (0, 10000], got: ${b}`);
|
|
1233
|
+
}
|
|
1234
|
+
sumBps += b;
|
|
1235
|
+
}
|
|
1236
|
+
if (sumBps !== 1e4) {
|
|
1237
|
+
throw new PayoutError(`splitsBps must sum to 10000, got ${sumBps}`);
|
|
1238
|
+
}
|
|
1239
|
+
const out = [];
|
|
1240
|
+
let allocated = 0n;
|
|
1241
|
+
for (let i = 0; i < splits.length; i++) {
|
|
1242
|
+
const amt = pool * BigInt(splits[i]) / BPS;
|
|
1243
|
+
out.push({ wallet: wallets[i], amount: amt });
|
|
1244
|
+
allocated += amt;
|
|
1245
|
+
}
|
|
1246
|
+
const remainder = pool - allocated;
|
|
1247
|
+
if (remainder < 0n) throw new PayoutError("internal: allocated more than pool");
|
|
1248
|
+
out[0] = { wallet: out[0].wallet, amount: out[0].amount + remainder };
|
|
1249
|
+
const filtered = out.filter((x) => x.amount > 0n);
|
|
1250
|
+
if (filtered.length === 0) throw new PayoutError("payout produced no positive amounts");
|
|
1251
|
+
const total = filtered.reduce((s, x) => s + x.amount, 0n);
|
|
1252
|
+
if (total !== pool) throw new PayoutError(`internal: sum ${total} != pool ${pool}`);
|
|
1253
|
+
return filtered;
|
|
1254
|
+
}
|
|
1255
|
+
if (rule.kind === "custom") {
|
|
1256
|
+
const amounts = rule.amounts;
|
|
1257
|
+
if (!Array.isArray(amounts) || amounts.length === 0) {
|
|
1258
|
+
throw new PayoutError("custom amounts must be a non-empty array of USD strings");
|
|
1259
|
+
}
|
|
1260
|
+
if (amounts.length > wallets.length) {
|
|
1261
|
+
throw new PayoutError(
|
|
1262
|
+
`custom amounts has ${amounts.length} entries but ranking only has ${wallets.length}`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
const out = [];
|
|
1266
|
+
let total = 0n;
|
|
1267
|
+
for (let i = 0; i < amounts.length; i++) {
|
|
1268
|
+
const amt = parseUsdToMicroLoose(amounts[i]);
|
|
1269
|
+
out.push({ wallet: wallets[i], amount: amt });
|
|
1270
|
+
total += amt;
|
|
1271
|
+
}
|
|
1272
|
+
if (total !== pool) {
|
|
1273
|
+
throw new PayoutError(
|
|
1274
|
+
`custom amounts sum to ${total} micro-USDC but pool is ${pool} \u2014 must match exactly`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
return out;
|
|
1278
|
+
}
|
|
1279
|
+
throw new PayoutError(`unknown payout rule kind: ${JSON.stringify(rule.kind)}`);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/settlement.ts
|
|
1283
|
+
var ADDRESS_RE3 = /^0x[0-9a-fA-F]{40}$/;
|
|
1284
|
+
var DEFAULT_TTL_MS = 15 * 60 * 1e3;
|
|
1285
|
+
function requireAddress2(value, field) {
|
|
1286
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1287
|
+
throw new MissingFieldError(field);
|
|
1288
|
+
}
|
|
1289
|
+
if (!ADDRESS_RE3.test(value)) {
|
|
1290
|
+
throw new ConfigError(`${field} must be a 0x-prefixed 20-byte address, got: ${JSON.stringify(value)}`, {
|
|
1291
|
+
field,
|
|
1292
|
+
value
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
return value;
|
|
1296
|
+
}
|
|
1297
|
+
function requireNetwork(value) {
|
|
1298
|
+
if (value === "base" || value === "base-sepolia") return value;
|
|
1299
|
+
throw new ConfigError(`Unknown network: ${JSON.stringify(value)}. Expected one of ${Object.keys(CHAIN_ID).join(", ")}.`, {
|
|
1300
|
+
field: "network",
|
|
1301
|
+
value
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
function createPaymentRequirement(input) {
|
|
1305
|
+
const now = (input.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
1306
|
+
const payTo = requireAddress2(input.payTo, "payTo");
|
|
1307
|
+
const network = requireNetwork(input.network);
|
|
1308
|
+
const asset = input.asset ?? "USDC";
|
|
1309
|
+
if (asset !== "USDC") {
|
|
1310
|
+
throw new ConfigError(`Unsupported asset: ${JSON.stringify(asset)}. Only "USDC" is supported.`, { asset });
|
|
1311
|
+
}
|
|
1312
|
+
parseUsdToMicro(input.amount);
|
|
1313
|
+
const expiresAt = input.expiresAt ?? new Date(now.getTime() + (input.expiresInMs ?? DEFAULT_TTL_MS)).toISOString();
|
|
1314
|
+
const req = {
|
|
1315
|
+
id: input.id ?? prefixedId("preq"),
|
|
1316
|
+
payTo,
|
|
1317
|
+
amount: input.amount,
|
|
1318
|
+
asset,
|
|
1319
|
+
network,
|
|
1320
|
+
expiresAt
|
|
1321
|
+
};
|
|
1322
|
+
if (input.terms !== void 0) req.terms = input.terms;
|
|
1323
|
+
return req;
|
|
1324
|
+
}
|
|
1325
|
+
function serializePaymentRequirement(req) {
|
|
1326
|
+
const body = {
|
|
1327
|
+
id: req.id,
|
|
1328
|
+
payTo: req.payTo,
|
|
1329
|
+
amount: req.amount,
|
|
1330
|
+
asset: req.asset,
|
|
1331
|
+
network: req.network,
|
|
1332
|
+
expiresAt: req.expiresAt
|
|
1333
|
+
};
|
|
1334
|
+
if (req.terms !== void 0) body.terms = req.terms;
|
|
1335
|
+
return body;
|
|
1336
|
+
}
|
|
1337
|
+
function parsePaymentRequirement(input) {
|
|
1338
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
1339
|
+
throw new ConfigError("PaymentRequirement must be a JSON object.", { input });
|
|
1340
|
+
}
|
|
1341
|
+
const o = input;
|
|
1342
|
+
const id = o.id;
|
|
1343
|
+
if (typeof id !== "string" || id.trim() === "") throw new MissingFieldError("id");
|
|
1344
|
+
if (o.asset !== "USDC") {
|
|
1345
|
+
throw new ConfigError(`PaymentRequirement.asset must be "USDC", got: ${JSON.stringify(o.asset)}.`, {
|
|
1346
|
+
asset: o.asset
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
const payTo = requireAddress2(o.payTo, "payTo");
|
|
1350
|
+
const network = requireNetwork(o.network);
|
|
1351
|
+
if (typeof o.amount !== "string") throw new InvalidAmountError(o.amount);
|
|
1352
|
+
parseUsdToMicro(o.amount);
|
|
1353
|
+
if (typeof o.expiresAt !== "string" || o.expiresAt.trim() === "") throw new MissingFieldError("expiresAt");
|
|
1354
|
+
if (o.terms !== void 0 && typeof o.terms !== "string") {
|
|
1355
|
+
throw new ConfigError("PaymentRequirement.terms must be a string when present.", { terms: o.terms });
|
|
1356
|
+
}
|
|
1357
|
+
const req = {
|
|
1358
|
+
id,
|
|
1359
|
+
payTo,
|
|
1360
|
+
amount: o.amount,
|
|
1361
|
+
asset: "USDC",
|
|
1362
|
+
network,
|
|
1363
|
+
expiresAt: o.expiresAt
|
|
1364
|
+
};
|
|
1365
|
+
if (o.terms !== void 0) req.terms = o.terms;
|
|
1366
|
+
return req;
|
|
1367
|
+
}
|
|
1368
|
+
function isWalletSignatureAuthorization(auth) {
|
|
1369
|
+
return auth.kind === "wallet-signature";
|
|
1370
|
+
}
|
|
1371
|
+
function isX402PayloadAuthorization(auth) {
|
|
1372
|
+
return auth.kind === "x402-payload";
|
|
1373
|
+
}
|
|
1374
|
+
|
|
828
1375
|
exports.ApiError = ApiError;
|
|
829
1376
|
exports.AuthError = AuthError;
|
|
830
1377
|
exports.CHAIN_ID = CHAIN_ID;
|
|
@@ -835,20 +1382,28 @@ exports.InvalidAmountError = InvalidAmountError;
|
|
|
835
1382
|
exports.MICRO_PER_USDC = MICRO_PER_USDC;
|
|
836
1383
|
exports.MissingFieldError = MissingFieldError;
|
|
837
1384
|
exports.PaymentFailedError = PaymentFailedError;
|
|
1385
|
+
exports.PayoutError = PayoutError;
|
|
838
1386
|
exports.Playmos = Playmos;
|
|
839
1387
|
exports.PlaymosError = PlaymosError;
|
|
840
1388
|
exports.USDC_ADDRESS = USDC_ADDRESS;
|
|
841
1389
|
exports.USDC_DECIMALS = USDC_DECIMALS;
|
|
842
1390
|
exports.WalletConnectionError = WalletConnectionError;
|
|
843
|
-
exports.WebhookSignatureError = WebhookSignatureError;
|
|
844
1391
|
exports.computeIapSplit = computeIapSplit;
|
|
1392
|
+
exports.computePayout = computePayout;
|
|
845
1393
|
exports.computePoolSplit = computePoolSplit;
|
|
1394
|
+
exports.createPaymentRequirement = createPaymentRequirement;
|
|
846
1395
|
exports.formatMicroToUsd = formatMicroToUsd;
|
|
1396
|
+
exports.isWalletSignatureAuthorization = isWalletSignatureAuthorization;
|
|
1397
|
+
exports.isX402PayloadAuthorization = isX402PayloadAuthorization;
|
|
1398
|
+
exports.parsePaymentRequirement = parsePaymentRequirement;
|
|
847
1399
|
exports.parseUsdToMicro = parseUsdToMicro;
|
|
848
1400
|
exports.prefixedId = prefixedId;
|
|
1401
|
+
exports.previewEscrowFee = previewEscrowFee;
|
|
849
1402
|
exports.previewIapSplit = previewIapSplit;
|
|
1403
|
+
exports.previewMarketplaceSplit = previewMarketplaceSplit;
|
|
850
1404
|
exports.previewPoolSplit = previewPoolSplit;
|
|
1405
|
+
exports.previewTransferSplit = previewTransferSplit;
|
|
1406
|
+
exports.serializePaymentRequirement = serializePaymentRequirement;
|
|
851
1407
|
exports.ulid = ulid;
|
|
852
|
-
exports.verifyWebhook = verifyWebhook;
|
|
853
1408
|
//# sourceMappingURL=index.cjs.map
|
|
854
1409
|
//# sourceMappingURL=index.cjs.map
|