@playmos/sdk 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, A as AgentWallet, c as AgentFundResult, T as TransferResult, E as EscrowHoldInput, d as EscrowHoldResult, e as EscrowResolveResult, M as MarketplaceListInput, L as Listing, f as MarketplaceSaleResult, g as MarketplaceGetResult, h as PayInput, i as Payment, j as EnterRoundInput, k as TransferReconcile, l as WaitOptions, m as TransferInput, n as TransferConfirmOptions, V as VerifyResult } from './errors-BVESr920.cjs';
2
- export { o as AgentEconomyConfig, p as ApiError, q as AuthError, C as ConfigError, r as ContractConfig, s as Eip1193Provider, G as GasConfig, t as GasMode, I as InsufficientGasError, u as InvalidAmountError, v as ListingStatus, w as MarketplaceItem, x as MarketplaceSale, y as MissingFieldError, z as PaymentFailedError, B as PaymentStatus, D as PayoutRule, F as PlaymosError, H as PlaymosErrorCode, J as RetryOptions, K as RoundStatus, O as WalletConfig, Q as WalletConnectionError, U as WalletConnector, X as WebhookEventType } from './errors-BVESr920.cjs';
1
+ import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as PrizeBalance, d as WithdrawResult, A as AgentWallet, e as AgentFundResult, T as TransferResult, E as EscrowHoldInput, f as EscrowHoldResult, g as EscrowResolveResult, M as MarketplaceListInput, L as Listing, h as MarketplaceSaleResult, i as MarketplaceGetResult, j as PayInput, k as Payment, l as EnterRoundInput, m as TransferReconcile, n as WaitOptions, o as TransferInput, p as TransferConfirmOptions, V as VerifyResult } from './errors-DMtMpbR6.cjs';
2
+ export { q as AgentEconomyConfig, r as ApiError, s as AuthError, C as ConfigError, t as ContractConfig, u as Eip1193Provider, G as GasConfig, v as GasMode, I as InsufficientGasError, w as InvalidAmountError, x as ListingStatus, y as MarketplaceItem, z as MarketplaceSale, B as MissingFieldError, D as NothingToWithdrawError, F as PaymentFailedError, H as PaymentStatus, J as PayoutRule, K as PlaymosError, O as PlaymosErrorCode, Q as RetryOptions, U as RoundStatus, X as WalletConfig, Y as WalletConnectionError, Z as WalletConnector, _ as WebhookEventType } from './errors-DMtMpbR6.cjs';
3
3
 
4
4
  /**
5
5
  * Environment resolution + the canonical address book.
@@ -80,6 +80,30 @@ declare class Playmos {
80
80
  get: (input: {
81
81
  roundId: string;
82
82
  }) => Promise<RoundState>;
83
+ /**
84
+ * Read a wallet's **claimable** prize balance for a round (issue #41).
85
+ * Prefers the service chain read (`GET /v1/rounds/:id/prize?wallet=`); falls
86
+ * back to a direct eth_call when `prizePoolAddress` is supplied and no wallet
87
+ * connector is needed for the read path… actually uses service first, then
88
+ * on-chain via the player's provider if the service is unavailable.
89
+ */
90
+ prize: (input: {
91
+ roundId: string;
92
+ wallet: `0x${string}`;
93
+ /** Optional override if you already hold the pool address from enterRound. */
94
+ prizePoolAddress?: `0x${string}`;
95
+ }) => Promise<PrizeBalance>;
96
+ /**
97
+ * Winner **claim** — call PrizePool.withdraw() from the player's wallet (issue #41).
98
+ * Pull-payment: credits from settle live in `withdrawable[msg.sender]`. No approve needed.
99
+ * Pass either `prizePoolAddress` (from enterRound / rounds.get) or `roundId` (service resolves).
100
+ */
101
+ withdraw: (input: {
102
+ prizePoolAddress?: `0x${string}`;
103
+ roundId?: string;
104
+ /** Pre-check claimable; default true. Set false only if you already called `prize()`. */
105
+ checkBalance?: boolean;
106
+ }) => Promise<WithdrawResult>;
83
107
  };
84
108
  /**
85
109
  * `agents` — assign wallets to the NPCs YOUR game already owns, so they can transact USDC in your economy.
@@ -245,6 +269,11 @@ declare class Playmos {
245
269
  /** Prefer the service's authoritative micro-USDC amount; fall back to the
246
270
  * locally-parsed value if the service didn't echo one. */
247
271
  private resolveUnits;
272
+ /**
273
+ * Read PrizePool.withdrawable[wallet] via eth_call (issue #41).
274
+ * Uses the wallet provider when present; otherwise a public Base Sepolia RPC in test.
275
+ */
276
+ private readWithdrawable;
248
277
  private sponsorUrl;
249
278
  /**
250
279
  * After the batch lands, resolve the authoritative status via the service's
@@ -514,4 +543,4 @@ declare function isWalletSignatureAuthorization(auth: Authorization): auth is Wa
514
543
  /** Narrow to the declared-but-unimplemented x402 variant. */
515
544
  declare function isX402PayloadAuthorization(auth: Authorization): auth is X402PayloadAuthorization;
516
545
 
517
- export { AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, type PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
546
+ export { AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, type PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, PrizeBalance, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, WithdrawResult, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, A as AgentWallet, c as AgentFundResult, T as TransferResult, E as EscrowHoldInput, d as EscrowHoldResult, e as EscrowResolveResult, M as MarketplaceListInput, L as Listing, f as MarketplaceSaleResult, g as MarketplaceGetResult, h as PayInput, i as Payment, j as EnterRoundInput, k as TransferReconcile, l as WaitOptions, m as TransferInput, n as TransferConfirmOptions, V as VerifyResult } from './errors-BVESr920.js';
2
- export { o as AgentEconomyConfig, p as ApiError, q as AuthError, C as ConfigError, r as ContractConfig, s as Eip1193Provider, G as GasConfig, t as GasMode, I as InsufficientGasError, u as InvalidAmountError, v as ListingStatus, w as MarketplaceItem, x as MarketplaceSale, y as MissingFieldError, z as PaymentFailedError, B as PaymentStatus, D as PayoutRule, F as PlaymosError, H as PlaymosErrorCode, J as RetryOptions, K as RoundStatus, O as WalletConfig, Q as WalletConnectionError, U as WalletConnector, X as WebhookEventType } from './errors-BVESr920.js';
1
+ import { N as Network, P as PlaymosConfig, W as WebhookEvent, R as RoundOpenInput, a as RoundState, b as RoundSettleInput, S as SettleRoundResult, c as PrizeBalance, d as WithdrawResult, A as AgentWallet, e as AgentFundResult, T as TransferResult, E as EscrowHoldInput, f as EscrowHoldResult, g as EscrowResolveResult, M as MarketplaceListInput, L as Listing, h as MarketplaceSaleResult, i as MarketplaceGetResult, j as PayInput, k as Payment, l as EnterRoundInput, m as TransferReconcile, n as WaitOptions, o as TransferInput, p as TransferConfirmOptions, V as VerifyResult } from './errors-DMtMpbR6.js';
2
+ export { q as AgentEconomyConfig, r as ApiError, s as AuthError, C as ConfigError, t as ContractConfig, u as Eip1193Provider, G as GasConfig, v as GasMode, I as InsufficientGasError, w as InvalidAmountError, x as ListingStatus, y as MarketplaceItem, z as MarketplaceSale, B as MissingFieldError, D as NothingToWithdrawError, F as PaymentFailedError, H as PaymentStatus, J as PayoutRule, K as PlaymosError, O as PlaymosErrorCode, Q as RetryOptions, U as RoundStatus, X as WalletConfig, Y as WalletConnectionError, Z as WalletConnector, _ as WebhookEventType } from './errors-DMtMpbR6.js';
3
3
 
4
4
  /**
5
5
  * Environment resolution + the canonical address book.
@@ -80,6 +80,30 @@ declare class Playmos {
80
80
  get: (input: {
81
81
  roundId: string;
82
82
  }) => Promise<RoundState>;
83
+ /**
84
+ * Read a wallet's **claimable** prize balance for a round (issue #41).
85
+ * Prefers the service chain read (`GET /v1/rounds/:id/prize?wallet=`); falls
86
+ * back to a direct eth_call when `prizePoolAddress` is supplied and no wallet
87
+ * connector is needed for the read path… actually uses service first, then
88
+ * on-chain via the player's provider if the service is unavailable.
89
+ */
90
+ prize: (input: {
91
+ roundId: string;
92
+ wallet: `0x${string}`;
93
+ /** Optional override if you already hold the pool address from enterRound. */
94
+ prizePoolAddress?: `0x${string}`;
95
+ }) => Promise<PrizeBalance>;
96
+ /**
97
+ * Winner **claim** — call PrizePool.withdraw() from the player's wallet (issue #41).
98
+ * Pull-payment: credits from settle live in `withdrawable[msg.sender]`. No approve needed.
99
+ * Pass either `prizePoolAddress` (from enterRound / rounds.get) or `roundId` (service resolves).
100
+ */
101
+ withdraw: (input: {
102
+ prizePoolAddress?: `0x${string}`;
103
+ roundId?: string;
104
+ /** Pre-check claimable; default true. Set false only if you already called `prize()`. */
105
+ checkBalance?: boolean;
106
+ }) => Promise<WithdrawResult>;
83
107
  };
84
108
  /**
85
109
  * `agents` — assign wallets to the NPCs YOUR game already owns, so they can transact USDC in your economy.
@@ -245,6 +269,11 @@ declare class Playmos {
245
269
  /** Prefer the service's authoritative micro-USDC amount; fall back to the
246
270
  * locally-parsed value if the service didn't echo one. */
247
271
  private resolveUnits;
272
+ /**
273
+ * Read PrizePool.withdrawable[wallet] via eth_call (issue #41).
274
+ * Uses the wallet provider when present; otherwise a public Base Sepolia RPC in test.
275
+ */
276
+ private readWithdrawable;
248
277
  private sponsorUrl;
249
278
  /**
250
279
  * After the batch lands, resolve the authoritative status via the service's
@@ -514,4 +543,4 @@ declare function isWalletSignatureAuthorization(auth: Authorization): auth is Wa
514
543
  /** Narrow to the declared-but-unimplemented x402 variant. */
515
544
  declare function isX402PayloadAuthorization(auth: Authorization): auth is X402PayloadAuthorization;
516
545
 
517
- export { AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, type PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
546
+ export { AgentFundResult, AgentWallet, type Authorization, CHAIN_ID, type CreatePaymentRequirementInput, DEFAULT_API_BASE_URL, EnterRoundInput, EscrowHoldInput, EscrowHoldResult, EscrowResolveResult, Listing, MICRO_PER_USDC, MarketplaceGetResult, MarketplaceListInput, MarketplaceSaleResult, Network, PayInput, Payment, type PaymentRequirement, PayoutError, type PayoutRule as PayoutRuleCompute, Playmos, PlaymosConfig, PrizeBalance, RoundOpenInput, RoundSettleInput, RoundState, SettleRoundResult, type SettlementAsset, TransferConfirmOptions, TransferInput, TransferReconcile, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, WaitOptions, type WalletSignatureAuthorization, WebhookEvent, WithdrawResult, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { InvalidAmountError, ConfigError, ApiError, MissingFieldError, AuthError, WalletConnectionError, InsufficientGasError, PaymentFailedError } from './chunk-B7SHFZYY.js';
2
- export { ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, PaymentFailedError, PlaymosError, WalletConnectionError } from './chunk-B7SHFZYY.js';
3
- import { encodeFunctionData, numberToHex, keccak256, toBytes } from 'viem';
1
+ import { InvalidAmountError, ConfigError, NothingToWithdrawError, PaymentFailedError, ApiError, MissingFieldError, WalletConnectionError, InsufficientGasError, AuthError } from './chunk-TMBBEGIF.js';
2
+ export { ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, NothingToWithdrawError, PaymentFailedError, PlaymosError, WalletConnectionError } from './chunk-TMBBEGIF.js';
3
+ import { encodeFunctionData, decodeFunctionResult, numberToHex, keccak256, toBytes } from 'viem';
4
4
 
5
5
  // src/config.ts
6
6
  var CHAIN_ID = {
@@ -403,6 +403,13 @@ var prizePoolAbi = [
403
403
  ],
404
404
  outputs: [{ type: "bool" }]
405
405
  },
406
+ {
407
+ type: "function",
408
+ name: "withdrawable",
409
+ stateMutability: "view",
410
+ inputs: [{ name: "account", type: "address" }],
411
+ outputs: [{ type: "uint256" }]
412
+ },
406
413
  {
407
414
  type: "function",
408
415
  name: "withdraw",
@@ -490,6 +497,14 @@ function buildEntryCalls(args) {
490
497
  { to: args.prizePool, data: enterData }
491
498
  ];
492
499
  }
500
+ function buildWithdrawCall(prizePool) {
501
+ const data = encodeFunctionData({
502
+ abi: prizePoolAbi,
503
+ functionName: "withdraw",
504
+ args: []
505
+ });
506
+ return { to: prizePool, data };
507
+ }
493
508
 
494
509
  // src/mock.ts
495
510
  var MOCK_TX = "0x000000000000000000000000000000000000000000000000000000000000mock";
@@ -534,6 +549,9 @@ function mockEntryPayment(args) {
534
549
  },
535
550
  gameId: args.gameId,
536
551
  roundId: args.roundId,
552
+ roundKey: args.roundId,
553
+ // Deterministic mock pool address so claim-flow unit tests can assert shape (#41).
554
+ prizePoolAddress: "0x0000000000000000000000000000000000000001",
537
555
  playerId: args.playerId,
538
556
  txHash: MOCK_TX,
539
557
  chain: args.chain,
@@ -646,6 +664,112 @@ var Playmos = class {
646
664
  `/rounds/${encodeURIComponent(input.roundId)}`
647
665
  );
648
666
  return round;
667
+ },
668
+ /**
669
+ * Read a wallet's **claimable** prize balance for a round (issue #41).
670
+ * Prefers the service chain read (`GET /v1/rounds/:id/prize?wallet=`); falls
671
+ * back to a direct eth_call when `prizePoolAddress` is supplied and no wallet
672
+ * connector is needed for the read path… actually uses service first, then
673
+ * on-chain via the player's provider if the service is unavailable.
674
+ */
675
+ prize: async (input) => {
676
+ requireField(input?.roundId, "roundId");
677
+ const wallet = requireAddressField(input?.wallet, "wallet");
678
+ try {
679
+ const res = await this.http.get(
680
+ `/rounds/${encodeURIComponent(input.roundId)}/prize?wallet=${encodeURIComponent(wallet)}`
681
+ );
682
+ return res.prize;
683
+ } catch (e) {
684
+ if (!input.prizePoolAddress && !(e instanceof ApiError)) throw e;
685
+ if (!input.prizePoolAddress) throw e;
686
+ }
687
+ const prizePoolAddress = requireAddressField(input.prizePoolAddress, "prizePoolAddress");
688
+ const claimableMicro = await this.readWithdrawable(prizePoolAddress, wallet);
689
+ return {
690
+ roundId: input.roundId,
691
+ prizePoolAddress,
692
+ wallet,
693
+ claimable: formatMicroToUsd(claimableMicro),
694
+ claimableMicro: claimableMicro.toString()
695
+ };
696
+ },
697
+ /**
698
+ * Winner **claim** — call PrizePool.withdraw() from the player's wallet (issue #41).
699
+ * Pull-payment: credits from settle live in `withdrawable[msg.sender]`. No approve needed.
700
+ * Pass either `prizePoolAddress` (from enterRound / rounds.get) or `roundId` (service resolves).
701
+ */
702
+ withdraw: async (input) => {
703
+ if (this.config.mock) {
704
+ return {
705
+ prizePoolAddress: input.prizePoolAddress ?? "0x0000000000000000000000000000000000000001",
706
+ amount: "0.00",
707
+ amountMicro: "0",
708
+ txHash: "0x000000000000000000000000000000000000000000000000000000000000mock",
709
+ status: "confirmed"
710
+ };
711
+ }
712
+ let prizePool = input.prizePoolAddress ? requireAddressField(input.prizePoolAddress, "prizePoolAddress") : void 0;
713
+ if (!prizePool) {
714
+ requireField(input?.roundId, "roundId");
715
+ const round = await this.rounds.get({ roundId: input.roundId });
716
+ if (!round.prizePoolAddress) {
717
+ throw new ConfigError(
718
+ "rounds.get did not return prizePoolAddress \u2014 pass prizePoolAddress from enterRound or configure contracts.prizePool"
719
+ );
720
+ }
721
+ prizePool = round.prizePoolAddress;
722
+ }
723
+ const provider = resolveProvider(this.config.wallet);
724
+ if (this.config.gas?.mode === "player") {
725
+ const from0 = await getAccount(provider);
726
+ await assertEnoughGas(provider, from0);
727
+ }
728
+ const from = await getAccount(provider);
729
+ let amountMicro = 0n;
730
+ if (input.checkBalance !== false) {
731
+ amountMicro = await this.readWithdrawable(prizePool, from);
732
+ if (amountMicro === 0n) {
733
+ throw new NothingToWithdrawError({ prizePoolAddress: prizePool, wallet: from });
734
+ }
735
+ }
736
+ const call = buildWithdrawCall(prizePool);
737
+ const paymasterUrl = this.config.gas?.mode === "sponsored" ? this.config.gas.paymasterUrl : void 0;
738
+ let txHash;
739
+ let status = "pending";
740
+ try {
741
+ const { id: callsId } = await sendCalls(
742
+ provider,
743
+ from,
744
+ this.env.chainId,
745
+ [call],
746
+ paymasterUrl
747
+ );
748
+ const waited = await waitForCalls(provider, callsId);
749
+ txHash = waited.txHash;
750
+ status = waited.status === "CONFIRMED" ? "confirmed" : waited.status === "FAILED" ? "failed" : "pending";
751
+ } catch (e) {
752
+ const msg = e?.message ?? String(e);
753
+ if (/NothingToWithdraw|nothing to withdraw/i.test(msg)) {
754
+ throw new NothingToWithdrawError({ prizePoolAddress: prizePool, wallet: from, cause: msg });
755
+ }
756
+ if (e instanceof NothingToWithdrawError || e instanceof PaymentFailedError) throw e;
757
+ throw new PaymentFailedError("PrizePool.withdraw() failed.", { cause: msg });
758
+ }
759
+ if (status === "failed") {
760
+ throw new PaymentFailedError("PrizePool.withdraw() did not confirm on-chain.", {
761
+ prizePoolAddress: prizePool,
762
+ txHash
763
+ });
764
+ }
765
+ if (amountMicro === 0n && input.checkBalance === false) ;
766
+ return {
767
+ prizePoolAddress: prizePool,
768
+ amount: formatMicroToUsd(amountMicro),
769
+ amountMicro: amountMicro.toString(),
770
+ txHash,
771
+ status
772
+ };
649
773
  }
650
774
  };
651
775
  /**
@@ -975,6 +1099,9 @@ var Playmos = class {
975
1099
  );
976
1100
  const payment2 = this.mapServerPayment(res.payment, "entry", amountMicro);
977
1101
  if (input.identity) payment2.identity = input.identity;
1102
+ const pool = res.clientParams?.contractAddress ?? this.config.contracts?.prizePool;
1103
+ if (pool) payment2.prizePoolAddress = pool.toLowerCase();
1104
+ payment2.roundKey = res.clientParams?.roundKey ?? res.clientParams?.roundId ?? payment2.roundId ?? input.roundId;
978
1105
  return payment2;
979
1106
  }
980
1107
  const intent = await this.http.post(
@@ -1002,6 +1129,8 @@ var Playmos = class {
1002
1129
  const { txHash } = await waitForCalls(provider, callsId);
1003
1130
  const payment = await this.settle(intent.payment.id, txHash);
1004
1131
  payment.identity = identity;
1132
+ payment.prizePoolAddress = prizePool.toLowerCase();
1133
+ payment.roundKey = roundKey;
1005
1134
  return payment;
1006
1135
  }
1007
1136
  /**
@@ -1111,6 +1240,58 @@ var Playmos = class {
1111
1240
  const fromServer = intent.clientParams?.amountUnits ?? intent.clientParams?.amountMicro;
1112
1241
  return fromServer ? BigInt(fromServer) : local;
1113
1242
  }
1243
+ /**
1244
+ * Read PrizePool.withdrawable[wallet] via eth_call (issue #41).
1245
+ * Uses the wallet provider when present; otherwise a public Base Sepolia RPC in test.
1246
+ */
1247
+ async readWithdrawable(prizePool, wallet) {
1248
+ const data = encodeFunctionData({
1249
+ abi: prizePoolAbi,
1250
+ functionName: "withdrawable",
1251
+ args: [wallet]
1252
+ });
1253
+ let raw;
1254
+ try {
1255
+ if (await walletAvailable(this.config.wallet)) {
1256
+ const provider = resolveProvider(this.config.wallet);
1257
+ raw = await provider.request({
1258
+ method: "eth_call",
1259
+ params: [{ to: prizePool, data }, "latest"]
1260
+ });
1261
+ } else {
1262
+ const rpc = this.env.network === "base" ? "https://mainnet.base.org" : "https://sepolia.base.org";
1263
+ const res = await fetch(rpc, {
1264
+ method: "POST",
1265
+ headers: { "content-type": "application/json" },
1266
+ body: JSON.stringify({
1267
+ jsonrpc: "2.0",
1268
+ id: 1,
1269
+ method: "eth_call",
1270
+ params: [{ to: prizePool, data }, "latest"]
1271
+ })
1272
+ });
1273
+ const json = await res.json();
1274
+ if (!json.result) {
1275
+ throw new ApiError(
1276
+ `eth_call withdrawable failed: ${json.error?.message ?? "no result"}`,
1277
+ { prizePool, wallet }
1278
+ );
1279
+ }
1280
+ raw = json.result;
1281
+ }
1282
+ } catch (e) {
1283
+ if (e instanceof ApiError) throw e;
1284
+ throw new ApiError(`Could not read withdrawable balance: ${e.message}`, {
1285
+ prizePool,
1286
+ wallet
1287
+ });
1288
+ }
1289
+ return decodeFunctionResult({
1290
+ abi: prizePoolAbi,
1291
+ functionName: "withdrawable",
1292
+ data: raw
1293
+ });
1294
+ }
1114
1295
  sponsorUrl(intent) {
1115
1296
  if (this.config.gas?.mode === "player") return void 0;
1116
1297
  return this.config.gas?.paymasterUrl ?? intent.clientParams?.paymasterUrl;