@playmos/sdk 0.2.0 → 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 +61 -2
- package/dist/{errors-CjL85YKR.d.cts → errors-BVESr920.d.cts} +54 -4
- package/dist/{errors-CjL85YKR.d.ts → errors-BVESr920.d.ts} +54 -4
- package/dist/index.cjs +95 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -16
- package/dist/index.d.ts +24 -16
- package/dist/index.js +96 -20
- package/dist/index.js.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +1 -1
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 TransferInput, V as VerifyResult } from './errors-
|
|
2
|
-
export {
|
|
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';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Environment resolution + the canonical address book.
|
|
@@ -99,6 +99,7 @@ declare class Playmos {
|
|
|
99
99
|
fund: (input: {
|
|
100
100
|
agentId: string;
|
|
101
101
|
amount: string;
|
|
102
|
+
idempotencyKey?: string;
|
|
102
103
|
}) => Promise<AgentFundResult>;
|
|
103
104
|
/** NPC→NPC (or →player) USDC transfer, by id. A thin alias of `playmos.transfer` (which is canonical). */
|
|
104
105
|
pay: (input: {
|
|
@@ -194,18 +195,24 @@ declare class Playmos {
|
|
|
194
195
|
/** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
|
|
195
196
|
enterRound(input: EnterRoundInput): Promise<Payment>;
|
|
196
197
|
/**
|
|
197
|
-
* `transfers` — read-back / confirmation for a prior `transfer()` (
|
|
198
|
-
*
|
|
198
|
+
* `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
|
|
199
|
+
* `get` reconciles once against chain truth; `wait` polls it to a terminal state
|
|
200
|
+
* for you (no hand-rolled loop). Both cover the gasless agent path.
|
|
199
201
|
*/
|
|
200
202
|
readonly transfers: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
/** One-shot reconcile of a transfer against chain truth. */
|
|
204
|
+
get: (transferId: string) => Promise<TransferReconcile>;
|
|
205
|
+
/**
|
|
206
|
+
* Block until a transfer reaches a terminal state — `settled` or `failed` —
|
|
207
|
+
* instead of hand-rolling a poll loop (#47). Polls `transfers.get(id)` every
|
|
208
|
+
* `intervalMs` (default 1000) until terminal, then RESOLVES with the final
|
|
209
|
+
* reconcile. Throws a typed `ApiError` (`detail.timeout`) if neither
|
|
210
|
+
* `timeoutMs` (default 30000) nor `maxAttempts` (default 40) is reached first.
|
|
211
|
+
*
|
|
212
|
+
* A `failed` transfer is a legitimate outcome, so it RESOLVES (status
|
|
213
|
+
* "failed") — inspect `result.status`; it does not throw.
|
|
214
|
+
*/
|
|
215
|
+
wait: (transferId: string, opts?: WaitOptions) => Promise<TransferReconcile>;
|
|
209
216
|
};
|
|
210
217
|
/**
|
|
211
218
|
* `transfer` — the value-movement base primitive (Phase 1a): move USDC from one wallet to another,
|
|
@@ -218,12 +225,13 @@ declare class Playmos {
|
|
|
218
225
|
* Retries are safe: pass the same `idempotencyKey` and a re-call NEVER broadcasts a second tx —
|
|
219
226
|
* it returns the cached result (`idempotentReplay: true`).
|
|
220
227
|
*
|
|
221
|
-
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, call
|
|
222
|
-
* `playmos.transfers.
|
|
228
|
+
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, either call
|
|
229
|
+
* `playmos.transfers.wait(id)`, or pass `{ confirm: true }` here to block until terminal in one
|
|
230
|
+
* call (#47). BaseScan: `https://sepolia.basescan.org/tx/<txHash>`.
|
|
223
231
|
*
|
|
224
232
|
* NPC `from` requires `sk_test_` and settles gaslessly (NPC signs; service relays).
|
|
225
233
|
*/
|
|
226
|
-
transfer(input: TransferInput): Promise<TransferResult>;
|
|
234
|
+
transfer(input: TransferInput, opts?: TransferConfirmOptions): Promise<TransferResult>;
|
|
227
235
|
/** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
|
|
228
236
|
verify(paymentId: string): Promise<VerifyResult>;
|
|
229
237
|
/**
|
|
@@ -506,4 +514,4 @@ declare function isWalletSignatureAuthorization(auth: Authorization): auth is Wa
|
|
|
506
514
|
/** Narrow to the declared-but-unimplemented x402 variant. */
|
|
507
515
|
declare function isX402PayloadAuthorization(auth: Authorization): auth is X402PayloadAuthorization;
|
|
508
516
|
|
|
509
|
-
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, TransferInput, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, type WalletSignatureAuthorization, WebhookEvent, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
|
|
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 };
|
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 TransferInput, V as VerifyResult } from './errors-
|
|
2
|
-
export {
|
|
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';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Environment resolution + the canonical address book.
|
|
@@ -99,6 +99,7 @@ declare class Playmos {
|
|
|
99
99
|
fund: (input: {
|
|
100
100
|
agentId: string;
|
|
101
101
|
amount: string;
|
|
102
|
+
idempotencyKey?: string;
|
|
102
103
|
}) => Promise<AgentFundResult>;
|
|
103
104
|
/** NPC→NPC (or →player) USDC transfer, by id. A thin alias of `playmos.transfer` (which is canonical). */
|
|
104
105
|
pay: (input: {
|
|
@@ -194,18 +195,24 @@ declare class Playmos {
|
|
|
194
195
|
/** Playmos-owned skill-game entry (10%, 60/30/10 prize pool). Closes #343. */
|
|
195
196
|
enterRound(input: EnterRoundInput): Promise<Payment>;
|
|
196
197
|
/**
|
|
197
|
-
* `transfers` — read-back / confirmation for a prior `transfer()` (
|
|
198
|
-
*
|
|
198
|
+
* `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
|
|
199
|
+
* `get` reconciles once against chain truth; `wait` polls it to a terminal state
|
|
200
|
+
* for you (no hand-rolled loop). Both cover the gasless agent path.
|
|
199
201
|
*/
|
|
200
202
|
readonly transfers: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
/** One-shot reconcile of a transfer against chain truth. */
|
|
204
|
+
get: (transferId: string) => Promise<TransferReconcile>;
|
|
205
|
+
/**
|
|
206
|
+
* Block until a transfer reaches a terminal state — `settled` or `failed` —
|
|
207
|
+
* instead of hand-rolling a poll loop (#47). Polls `transfers.get(id)` every
|
|
208
|
+
* `intervalMs` (default 1000) until terminal, then RESOLVES with the final
|
|
209
|
+
* reconcile. Throws a typed `ApiError` (`detail.timeout`) if neither
|
|
210
|
+
* `timeoutMs` (default 30000) nor `maxAttempts` (default 40) is reached first.
|
|
211
|
+
*
|
|
212
|
+
* A `failed` transfer is a legitimate outcome, so it RESOLVES (status
|
|
213
|
+
* "failed") — inspect `result.status`; it does not throw.
|
|
214
|
+
*/
|
|
215
|
+
wait: (transferId: string, opts?: WaitOptions) => Promise<TransferReconcile>;
|
|
209
216
|
};
|
|
210
217
|
/**
|
|
211
218
|
* `transfer` — the value-movement base primitive (Phase 1a): move USDC from one wallet to another,
|
|
@@ -218,12 +225,13 @@ declare class Playmos {
|
|
|
218
225
|
* Retries are safe: pass the same `idempotencyKey` and a re-call NEVER broadcasts a second tx —
|
|
219
226
|
* it returns the cached result (`idempotentReplay: true`).
|
|
220
227
|
*
|
|
221
|
-
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, call
|
|
222
|
-
* `playmos.transfers.
|
|
228
|
+
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, either call
|
|
229
|
+
* `playmos.transfers.wait(id)`, or pass `{ confirm: true }` here to block until terminal in one
|
|
230
|
+
* call (#47). BaseScan: `https://sepolia.basescan.org/tx/<txHash>`.
|
|
223
231
|
*
|
|
224
232
|
* NPC `from` requires `sk_test_` and settles gaslessly (NPC signs; service relays).
|
|
225
233
|
*/
|
|
226
|
-
transfer(input: TransferInput): Promise<TransferResult>;
|
|
234
|
+
transfer(input: TransferInput, opts?: TransferConfirmOptions): Promise<TransferResult>;
|
|
227
235
|
/** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
|
|
228
236
|
verify(paymentId: string): Promise<VerifyResult>;
|
|
229
237
|
/**
|
|
@@ -506,4 +514,4 @@ declare function isWalletSignatureAuthorization(auth: Authorization): auth is Wa
|
|
|
506
514
|
/** Narrow to the declared-but-unimplemented x402 variant. */
|
|
507
515
|
declare function isX402PayloadAuthorization(auth: Authorization): auth is X402PayloadAuthorization;
|
|
508
516
|
|
|
509
|
-
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, TransferInput, TransferResult, USDC_ADDRESS, USDC_DECIMALS, VerifyResult, type WalletSignatureAuthorization, WebhookEvent, type X402PayloadAuthorization, computeIapSplit, computePayout, computePoolSplit, createPaymentRequirement, formatMicroToUsd, isWalletSignatureAuthorization, isX402PayloadAuthorization, parsePaymentRequirement, parseUsdToMicro, prefixedId, previewEscrowFee, previewIapSplit, previewMarketplaceSplit, previewPoolSplit, previewTransferSplit, serializePaymentRequirement, ulid };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InvalidAmountError, ConfigError, MissingFieldError, AuthError, WalletConnectionError, InsufficientGasError, PaymentFailedError
|
|
1
|
+
import { InvalidAmountError, ConfigError, ApiError, MissingFieldError, AuthError, WalletConnectionError, InsufficientGasError, PaymentFailedError } from './chunk-B7SHFZYY.js';
|
|
2
2
|
export { ApiError, AuthError, ConfigError, InsufficientGasError, InvalidAmountError, MissingFieldError, PaymentFailedError, PlaymosError, WalletConnectionError } from './chunk-B7SHFZYY.js';
|
|
3
3
|
import { encodeFunctionData, numberToHex, keccak256, toBytes } from 'viem';
|
|
4
4
|
|
|
@@ -163,8 +163,37 @@ function validateMetadata(metadata) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// src/http.ts
|
|
166
|
-
function
|
|
166
|
+
function resolveRetry(retry) {
|
|
167
|
+
const off = { maxRetries: 0, baseDelayMs: 500, maxDelayMs: 2e4 };
|
|
168
|
+
if (retry === false) return off;
|
|
169
|
+
const r = retry === true || retry === void 0 ? {} : retry;
|
|
170
|
+
return {
|
|
171
|
+
maxRetries: r.maxRetries ?? 2,
|
|
172
|
+
baseDelayMs: r.baseDelayMs ?? 500,
|
|
173
|
+
maxDelayMs: r.maxDelayMs ?? 2e4
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
177
|
+
function backoffMs(res, attempt, cfg) {
|
|
178
|
+
const header = res.headers.get("retry-after");
|
|
179
|
+
if (header) {
|
|
180
|
+
const secs = Number(header);
|
|
181
|
+
let ms;
|
|
182
|
+
if (Number.isFinite(secs)) {
|
|
183
|
+
ms = secs * 1e3;
|
|
184
|
+
} else {
|
|
185
|
+
const at = Date.parse(header);
|
|
186
|
+
ms = Number.isNaN(at) ? NaN : at - Date.now();
|
|
187
|
+
}
|
|
188
|
+
if (Number.isFinite(ms) && ms >= 0) return Math.min(ms, cfg.maxDelayMs);
|
|
189
|
+
}
|
|
190
|
+
const expo = cfg.baseDelayMs * 2 ** attempt;
|
|
191
|
+
const jitter = Math.random() * cfg.baseDelayMs;
|
|
192
|
+
return Math.min(expo + jitter, cfg.maxDelayMs);
|
|
193
|
+
}
|
|
194
|
+
function createHttpClient(baseUrl, apiKey, retry) {
|
|
167
195
|
const base = baseUrl.replace(/\/+$/, "");
|
|
196
|
+
const cfg = resolveRetry(retry);
|
|
168
197
|
async function send(url, init) {
|
|
169
198
|
try {
|
|
170
199
|
return await fetch(url, init);
|
|
@@ -176,6 +205,14 @@ function createHttpClient(baseUrl, apiKey) {
|
|
|
176
205
|
);
|
|
177
206
|
}
|
|
178
207
|
}
|
|
208
|
+
async function sendWithRetry(url, init) {
|
|
209
|
+
let res = await send(url, init);
|
|
210
|
+
for (let attempt = 0; res.status === 429 && attempt < cfg.maxRetries; attempt++) {
|
|
211
|
+
await sleep(backoffMs(res, attempt, cfg));
|
|
212
|
+
res = await send(url, init);
|
|
213
|
+
}
|
|
214
|
+
return res;
|
|
215
|
+
}
|
|
179
216
|
async function handle(res) {
|
|
180
217
|
let text;
|
|
181
218
|
try {
|
|
@@ -208,11 +245,11 @@ function createHttpClient(baseUrl, apiKey) {
|
|
|
208
245
|
authorization: `Bearer ${apiKey}`
|
|
209
246
|
};
|
|
210
247
|
if (opts?.idempotencyKey) headers["idempotency-key"] = opts.idempotencyKey;
|
|
211
|
-
const res = await
|
|
248
|
+
const res = await sendWithRetry(`${base}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
212
249
|
return handle(res);
|
|
213
250
|
},
|
|
214
251
|
async get(path) {
|
|
215
|
-
const res = await
|
|
252
|
+
const res = await sendWithRetry(`${base}${path}`, {
|
|
216
253
|
method: "GET",
|
|
217
254
|
headers: { authorization: `Bearer ${apiKey}` }
|
|
218
255
|
});
|
|
@@ -638,7 +675,12 @@ var Playmos = class {
|
|
|
638
675
|
fund: (input) => {
|
|
639
676
|
requireField(input?.agentId, "agentId");
|
|
640
677
|
validateAmount(input.amount);
|
|
641
|
-
|
|
678
|
+
const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
|
|
679
|
+
return this.http.post(
|
|
680
|
+
`/agents/wallets/${encodeURIComponent(input.agentId)}/fund`,
|
|
681
|
+
{ amount: input.amount, idempotencyKey },
|
|
682
|
+
{ idempotencyKey }
|
|
683
|
+
);
|
|
642
684
|
},
|
|
643
685
|
/** NPC→NPC (or →player) USDC transfer, by id. A thin alias of `playmos.transfer` (which is canonical). */
|
|
644
686
|
pay: (input) => {
|
|
@@ -667,7 +709,7 @@ var Playmos = class {
|
|
|
667
709
|
{ feeBps: input.feeBps }
|
|
668
710
|
);
|
|
669
711
|
}
|
|
670
|
-
const feeSink =
|
|
712
|
+
const feeSink = input.feeSink === void 0 ? void 0 : requireAddressField(input.feeSink, "feeSink");
|
|
671
713
|
const resolver = input.resolver === void 0 ? void 0 : requireAddressField(input.resolver, "resolver");
|
|
672
714
|
const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
|
|
673
715
|
const feeMicro = amountMicro * BigInt(feeBps) / 10000n;
|
|
@@ -779,19 +821,51 @@ var Playmos = class {
|
|
|
779
821
|
}
|
|
780
822
|
};
|
|
781
823
|
/**
|
|
782
|
-
* `transfers` — read-back / confirmation for a prior `transfer()` (
|
|
783
|
-
*
|
|
824
|
+
* `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
|
|
825
|
+
* `get` reconciles once against chain truth; `wait` polls it to a terminal state
|
|
826
|
+
* for you (no hand-rolled loop). Both cover the gasless agent path.
|
|
784
827
|
*/
|
|
785
828
|
this.transfers = {
|
|
829
|
+
/** One-shot reconcile of a transfer against chain truth. */
|
|
786
830
|
get: async (transferId) => {
|
|
787
831
|
requireField(transferId, "transferId");
|
|
788
|
-
const res = await this.http.get(
|
|
832
|
+
const res = await this.http.get(
|
|
833
|
+
`/transfers/${encodeURIComponent(transferId)}`
|
|
834
|
+
);
|
|
789
835
|
return res.transfer;
|
|
836
|
+
},
|
|
837
|
+
/**
|
|
838
|
+
* Block until a transfer reaches a terminal state — `settled` or `failed` —
|
|
839
|
+
* instead of hand-rolling a poll loop (#47). Polls `transfers.get(id)` every
|
|
840
|
+
* `intervalMs` (default 1000) until terminal, then RESOLVES with the final
|
|
841
|
+
* reconcile. Throws a typed `ApiError` (`detail.timeout`) if neither
|
|
842
|
+
* `timeoutMs` (default 30000) nor `maxAttempts` (default 40) is reached first.
|
|
843
|
+
*
|
|
844
|
+
* A `failed` transfer is a legitimate outcome, so it RESOLVES (status
|
|
845
|
+
* "failed") — inspect `result.status`; it does not throw.
|
|
846
|
+
*/
|
|
847
|
+
wait: async (transferId, opts) => {
|
|
848
|
+
requireField(transferId, "transferId");
|
|
849
|
+
const intervalMs = opts?.intervalMs ?? 1e3;
|
|
850
|
+
const timeoutMs = opts?.timeoutMs ?? 3e4;
|
|
851
|
+
const maxAttempts = opts?.maxAttempts ?? 40;
|
|
852
|
+
const deadline = Date.now() + timeoutMs;
|
|
853
|
+
let last;
|
|
854
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
855
|
+
last = await this.transfers.get(transferId);
|
|
856
|
+
if (last.status === "settled" || last.status === "failed") return last;
|
|
857
|
+
if (Date.now() + intervalMs > deadline) break;
|
|
858
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
859
|
+
}
|
|
860
|
+
throw new ApiError(
|
|
861
|
+
`Timed out waiting for transfer ${transferId} to settle (last status: ${last?.status ?? "unknown"}). It may still settle \u2014 re-check with playmos.transfers.get("${transferId}").`,
|
|
862
|
+
{ transferId, timeout: true, lastStatus: last?.status ?? "unknown" }
|
|
863
|
+
);
|
|
790
864
|
}
|
|
791
865
|
};
|
|
792
866
|
this.config = config;
|
|
793
867
|
this.env = resolveEnv(config.apiKey, config.network, config.apiBaseUrl);
|
|
794
|
-
this.http = createHttpClient(this.env.apiBaseUrl, config.apiKey);
|
|
868
|
+
this.http = createHttpClient(this.env.apiBaseUrl, config.apiKey, config.retry);
|
|
795
869
|
}
|
|
796
870
|
/** Connect the player's wallet and return their address. */
|
|
797
871
|
async connect() {
|
|
@@ -941,12 +1015,13 @@ var Playmos = class {
|
|
|
941
1015
|
* Retries are safe: pass the same `idempotencyKey` and a re-call NEVER broadcasts a second tx —
|
|
942
1016
|
* it returns the cached result (`idempotentReplay: true`).
|
|
943
1017
|
*
|
|
944
|
-
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, call
|
|
945
|
-
* `playmos.transfers.
|
|
1018
|
+
* Confirmation: treat `status === "settled" && txHash` as final. If `settling`, either call
|
|
1019
|
+
* `playmos.transfers.wait(id)`, or pass `{ confirm: true }` here to block until terminal in one
|
|
1020
|
+
* call (#47). BaseScan: `https://sepolia.basescan.org/tx/<txHash>`.
|
|
946
1021
|
*
|
|
947
1022
|
* NPC `from` requires `sk_test_` and settles gaslessly (NPC signs; service relays).
|
|
948
1023
|
*/
|
|
949
|
-
async transfer(input) {
|
|
1024
|
+
async transfer(input, opts) {
|
|
950
1025
|
const amountMicro = validateAmount(input.amount);
|
|
951
1026
|
const from = input.from === void 0 ? void 0 : partyRef(input.from, "from");
|
|
952
1027
|
const to = partyRef(input.to, "to");
|
|
@@ -957,12 +1032,7 @@ var Playmos = class {
|
|
|
957
1032
|
{ feeBps: input.feeBps }
|
|
958
1033
|
);
|
|
959
1034
|
}
|
|
960
|
-
|
|
961
|
-
if (feeBps > 0) {
|
|
962
|
-
feeSink = requireAddressField(input.feeSink, "feeSink");
|
|
963
|
-
} else if (input.feeSink !== void 0) {
|
|
964
|
-
feeSink = requireAddressField(input.feeSink, "feeSink");
|
|
965
|
-
}
|
|
1035
|
+
const feeSink = input.feeSink === void 0 ? void 0 : requireAddressField(input.feeSink, "feeSink");
|
|
966
1036
|
const idempotencyKey = input.idempotencyKey ?? prefixedId("idem");
|
|
967
1037
|
const res = await this.http.post(
|
|
968
1038
|
"/transfers",
|
|
@@ -971,7 +1041,7 @@ var Playmos = class {
|
|
|
971
1041
|
);
|
|
972
1042
|
const t = res.transfer;
|
|
973
1043
|
const feeMicro = amountMicro * BigInt(feeBps) / 10000n;
|
|
974
|
-
|
|
1044
|
+
const out = {
|
|
975
1045
|
id: t.id,
|
|
976
1046
|
status: t.status,
|
|
977
1047
|
txHash: t.txHash,
|
|
@@ -987,6 +1057,12 @@ var Playmos = class {
|
|
|
987
1057
|
memo: t.memo ?? input.memo ?? null,
|
|
988
1058
|
idempotentReplay: Boolean(t.idempotentReplay)
|
|
989
1059
|
};
|
|
1060
|
+
if (opts?.confirm && out.status === "settling") {
|
|
1061
|
+
const final = await this.transfers.wait(out.id, opts);
|
|
1062
|
+
out.status = final.status;
|
|
1063
|
+
out.txHash = final.txHash ?? out.txHash;
|
|
1064
|
+
}
|
|
1065
|
+
return out;
|
|
990
1066
|
}
|
|
991
1067
|
/** Verify a payment by the service's on-chain read (spec §6.1). Idempotent. */
|
|
992
1068
|
async verify(paymentId) {
|