@piprail/sdk 1.21.0 → 1.22.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/dist/index.d.cts CHANGED
@@ -5079,8 +5079,13 @@ interface SpendRemaining {
5079
5079
  remainingFormatted?: string;
5080
5080
  }
5081
5081
  interface PipRailClientOptions {
5082
- /** Wallet for the chosen chain family. */
5083
- wallet: WalletInput;
5082
+ /**
5083
+ * Wallet for the chosen chain family. **Optional** — omit it for a READ-ONLY
5084
+ * client that can `quote`, `discover`, `estimateCost`, and `register` (402 Index)
5085
+ * with no key. Paying, planning, or signing then throws {@link WalletRequiredError}
5086
+ * until a wallet is provided. Supplying a wallet is byte-identical to before.
5087
+ */
5088
+ wallet?: WalletInput;
5084
5089
  /** Which chain to pay on. EVM ('bnb'|'base'|…), 'solana', 'ton', 'stellar',
5085
5090
  * 'xrpl', 'tron', 'sui', 'near', 'aptos', or 'algorand'. */
5086
5091
  chain: ChainSelector;
@@ -5545,7 +5550,7 @@ declare function formatSpendReport(summary: SpendSummary): string;
5545
5550
  * literally, so a wrong name or order actively misleads. A test pins the load-
5546
5551
  * bearing phrases.
5547
5552
  */
5548
- declare const PIPRAIL_AGENT_GUIDE = "# Paying with PipRail \u2014 the agent contract\n\nYou can pay for x402 \"402 Payment Required\" resources autonomously. Money moves\nstraight from your wallet to the server; PipRail custodies nothing. Follow this.\n\n## The loop: quote \u2192 plan \u2192 pay\n1. piprail_quote_payment(url) \u2014 PRICE it. Returns the amount, token, chain, and\n whether it is within your spend policy. No funds move. Use it to decide if a\n resource is worth buying.\n2. piprail_plan_payment(url) \u2014 can I afford it NOW? Reads your balance, native gas,\n and recipient-readiness across every rail, and returns { payable, best,\n fundingHint, session? }. If payable is false, do NOT attempt the payment \u2014\n fundingHint says exactly what to fix.\n3. piprail_pay_request(url, method?, body?) \u2014 PAY (only if the plan was payable)\n and return the result.\nAlways plan before you pay so you never commit to a payment you cannot finish.\n\n## Reading a refusal \u2014 never crash, never double-spend\nA failed pay returns a STRUCTURED object, never a thrown error you must catch:\n { ok:false, code, reason, explain, ref?, reasonCode?, declined? }\nBranch on `code` (always reliable). Key cases:\n- declined:true with reasonCode:'SESSION_EXPIRED' \u2014 your time budget is over. This\n is TERMINAL: STOP. Do not retry ANY payment this process; it cannot be undone\n without a restart / a longer TTL.\n- declined:true with reasonCode:'APPROVAL' \u2014 a human (or hook) declined this\n payment. Terminal for this pay: do NOT auto-retry \u2014 they said no, or no one\n answered.\n- declined:true with reasonCode:'OUTSIDE_WINDOW' \u2014 your rolling rate-limit is\n exhausted. Wait for it to free, then retry; do not raise the amount.\n- declined:true with reasonCode:'POLICY' or 'BUDGET' \u2014 a spend cap or allowlist\n refused it. Don't retry the same payment; pick a cheaper/allowed one.\n- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.\n- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the\n payment may ALREADY be on-chain. Recover using the proof on `.ref` (re-verify\n or re-submit it); never re-pay \u2014 a fresh payment would double-spend.\n- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on\n your chain/scheme; `explain` says whether it's the wrong chain or a scheme to enable.\n\n## Knowing your leash \u2014 call piprail_budget\npiprail_budget tells you how much budget and time you have left, per\n(network, asset), plus your spend so far. Read-only; moves no funds. Use it in\nMode A to self-check before paying.\n\n## Two modes\n- Mode A (headless, default): you run FREE inside a pre-set budget + time\n envelope. The policy IS the consent \u2014 there is no per-payment prompt. Stay\n inside it; piprail_budget shows what's left.\n- Mode B (supervised): the host may ask a human to approve each payment. A\n decline/cancel/timeout comes back as declined:true (reasonCode:'APPROVAL') \u2014\n do NOT retry it as if it were a transient error.\n\n## Hard facts\n- Spend caps are PER (network, asset). There is no single cross-token dollar cap \u2014\n budgets aren't summed across tokens (no price oracle).\n- Spend totals and the time envelope live IN-MEMORY for THIS process; they reset on restart\n (a convenience, not a durable ledger).\n";
5553
+ declare const PIPRAIL_AGENT_GUIDE = "# Paying with PipRail \u2014 the agent contract\n\nYou can pay for x402 \"402 Payment Required\" resources autonomously. Money moves\nstraight from your wallet to the server; PipRail custodies nothing. Follow this.\n\n## The loop: quote \u2192 plan \u2192 pay\n1. piprail_quote_payment(url) \u2014 PRICE it. Returns the amount, token, chain, and\n whether it is within your spend policy. No funds move. Use it to decide if a\n resource is worth buying.\n2. piprail_plan_payment(url) \u2014 can I afford it NOW? Reads your balance, native gas,\n and recipient-readiness across every rail, and returns { payable, best,\n fundingHint, session? }. If payable is false, do NOT attempt the payment \u2014\n fundingHint says exactly what to fix.\n3. piprail_pay_request(url, method?, body?) \u2014 PAY (only if the plan was payable)\n and return the result.\nAlways plan before you pay so you never commit to a payment you cannot finish.\n\n## Gasless \u2014 the exact rail (zero gas for you)\nA 402 may offer up to two rails; you don't choose per payment \u2014 the client does, automatically:\n- onchain-proof (PipRail's default): you broadcast the payment yourself and pay the network gas\n (the native coin \u2014 ETH/SOL/\u2026). Works on every chain.\n- exact (the ratified x402 rail, opt-in): you only SIGN; the server \u2014 or a facilitator it chose\n (e.g. PayAI) \u2014 broadcasts it, so you pay ZERO gas (you need only the token, no native coin). It\n works on EVM + Solana, and the on-chain method (EIP-3009 / Permit2 / SVM) is picked automatically.\nWhen the exact scheme is enabled AND balance-aware routing is on, paying picks the cheapest\nsettleable rail \u2014 i.e. the gasless exact one. Nothing changes in your loop: quote \u2192 plan \u2192 pay is\nidentical. The exact scheme is OPT-IN by the operator (MCP: PIPRAIL_SCHEMES=onchain-proof,exact);\nyou can't enable it yourself, but you can report when a 402 needs it (see UNSUPPORTED_SCHEME below).\n\n## Reading a refusal \u2014 never crash, never double-spend\nA failed pay returns a STRUCTURED object, never a thrown error you must catch:\n { ok:false, code, reason, explain, ref?, reasonCode?, declined? }\nBranch on `code` (always reliable). Key cases:\n- declined:true with reasonCode:'SESSION_EXPIRED' \u2014 your time budget is over. This\n is TERMINAL: STOP. Do not retry ANY payment this process; it cannot be undone\n without a restart / a longer TTL.\n- declined:true with reasonCode:'APPROVAL' \u2014 a human (or hook) declined this\n payment. Terminal for this pay: do NOT auto-retry \u2014 they said no, or no one\n answered.\n- declined:true with reasonCode:'OUTSIDE_WINDOW' \u2014 your rolling rate-limit is\n exhausted. Wait for it to free, then retry; do not raise the amount.\n- declined:true with reasonCode:'POLICY' or 'BUDGET' \u2014 a spend cap or allowlist\n refused it. Don't retry the same payment; pick a cheaper/allowed one.\n- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.\n- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the\n payment may ALREADY be on-chain. Recover using the proof on `.ref` (re-verify\n or re-submit it); never re-pay \u2014 a fresh payment would double-spend. On a gasless\n exact rail `.ref` is the authorization NONCE, not a tx hash: re-present the SAME\n signed authorization, never sign a fresh one (that would risk a double-spend).\n- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on\n your chain/scheme; `explain` says whether it's the wrong chain or a scheme to enable.\n If it's a standard x402 server offering an exact rail, that's a config fix the operator makes\n once (enable the exact scheme); report it, don't retry the same call blindly.\n\n## Knowing your leash \u2014 call piprail_budget\npiprail_budget tells you how much budget and time you have left, per\n(network, asset), plus your spend so far. Read-only; moves no funds. Use it in\nMode A to self-check before paying.\n\n## Two modes\n- Mode A (headless, default): you run FREE inside a pre-set budget + time\n envelope. The policy IS the consent \u2014 there is no per-payment prompt. Stay\n inside it; piprail_budget shows what's left.\n- Mode B (supervised): the host may ask a human to approve each payment. A\n decline/cancel/timeout comes back as declined:true (reasonCode:'APPROVAL') \u2014\n do NOT retry it as if it were a transient error.\n\n## Hard facts\n- Spend caps are PER (network, asset). There is no single cross-token dollar cap \u2014\n budgets aren't summed across tokens (no price oracle).\n- Spend totals and the time envelope live IN-MEMORY for THIS process; they reset on restart\n (a convenience, not a durable ledger).\n";
5549
5554
  /** Returns {@link PIPRAIL_AGENT_GUIDE} (a parity accessor for callers that prefer a function). */
5550
5555
  declare function agentGuide(): string;
5551
5556
 
@@ -6392,6 +6397,14 @@ declare class UnsupportedSchemeError extends PipRailError {
6392
6397
  declare class NonReplayableBodyError extends PipRailError {
6393
6398
  readonly code = "NON_REPLAYABLE_BODY";
6394
6399
  }
6400
+ /**
6401
+ * A wallet-bound operation was called on a READ-ONLY client — one built with no
6402
+ * `wallet`. Read-only clients still `quote`, `discover`, and `register` (402 Index);
6403
+ * paying, planning, or signing needs a wallet. Pass `wallet` to enable them.
6404
+ */
6405
+ declare class WalletRequiredError extends PipRailError {
6406
+ readonly code = "WALLET_REQUIRED";
6407
+ }
6395
6408
  /**
6396
6409
  * The chosen chain belongs to one family (EVM, Solana, TON, Stellar, XRPL, Tron,
6397
6410
  * Sui, NEAR, Aptos, Algorand) but the wallet, payTo, or token was given in another family's form
@@ -6780,4 +6793,4 @@ declare const PERMIT2_WITNESS_TYPES: {
6780
6793
  }];
6781
6794
  };
6782
6795
 
6783
- export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
6796
+ export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
package/dist/index.d.ts CHANGED
@@ -5079,8 +5079,13 @@ interface SpendRemaining {
5079
5079
  remainingFormatted?: string;
5080
5080
  }
5081
5081
  interface PipRailClientOptions {
5082
- /** Wallet for the chosen chain family. */
5083
- wallet: WalletInput;
5082
+ /**
5083
+ * Wallet for the chosen chain family. **Optional** — omit it for a READ-ONLY
5084
+ * client that can `quote`, `discover`, `estimateCost`, and `register` (402 Index)
5085
+ * with no key. Paying, planning, or signing then throws {@link WalletRequiredError}
5086
+ * until a wallet is provided. Supplying a wallet is byte-identical to before.
5087
+ */
5088
+ wallet?: WalletInput;
5084
5089
  /** Which chain to pay on. EVM ('bnb'|'base'|…), 'solana', 'ton', 'stellar',
5085
5090
  * 'xrpl', 'tron', 'sui', 'near', 'aptos', or 'algorand'. */
5086
5091
  chain: ChainSelector;
@@ -5545,7 +5550,7 @@ declare function formatSpendReport(summary: SpendSummary): string;
5545
5550
  * literally, so a wrong name or order actively misleads. A test pins the load-
5546
5551
  * bearing phrases.
5547
5552
  */
5548
- declare const PIPRAIL_AGENT_GUIDE = "# Paying with PipRail \u2014 the agent contract\n\nYou can pay for x402 \"402 Payment Required\" resources autonomously. Money moves\nstraight from your wallet to the server; PipRail custodies nothing. Follow this.\n\n## The loop: quote \u2192 plan \u2192 pay\n1. piprail_quote_payment(url) \u2014 PRICE it. Returns the amount, token, chain, and\n whether it is within your spend policy. No funds move. Use it to decide if a\n resource is worth buying.\n2. piprail_plan_payment(url) \u2014 can I afford it NOW? Reads your balance, native gas,\n and recipient-readiness across every rail, and returns { payable, best,\n fundingHint, session? }. If payable is false, do NOT attempt the payment \u2014\n fundingHint says exactly what to fix.\n3. piprail_pay_request(url, method?, body?) \u2014 PAY (only if the plan was payable)\n and return the result.\nAlways plan before you pay so you never commit to a payment you cannot finish.\n\n## Reading a refusal \u2014 never crash, never double-spend\nA failed pay returns a STRUCTURED object, never a thrown error you must catch:\n { ok:false, code, reason, explain, ref?, reasonCode?, declined? }\nBranch on `code` (always reliable). Key cases:\n- declined:true with reasonCode:'SESSION_EXPIRED' \u2014 your time budget is over. This\n is TERMINAL: STOP. Do not retry ANY payment this process; it cannot be undone\n without a restart / a longer TTL.\n- declined:true with reasonCode:'APPROVAL' \u2014 a human (or hook) declined this\n payment. Terminal for this pay: do NOT auto-retry \u2014 they said no, or no one\n answered.\n- declined:true with reasonCode:'OUTSIDE_WINDOW' \u2014 your rolling rate-limit is\n exhausted. Wait for it to free, then retry; do not raise the amount.\n- declined:true with reasonCode:'POLICY' or 'BUDGET' \u2014 a spend cap or allowlist\n refused it. Don't retry the same payment; pick a cheaper/allowed one.\n- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.\n- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the\n payment may ALREADY be on-chain. Recover using the proof on `.ref` (re-verify\n or re-submit it); never re-pay \u2014 a fresh payment would double-spend.\n- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on\n your chain/scheme; `explain` says whether it's the wrong chain or a scheme to enable.\n\n## Knowing your leash \u2014 call piprail_budget\npiprail_budget tells you how much budget and time you have left, per\n(network, asset), plus your spend so far. Read-only; moves no funds. Use it in\nMode A to self-check before paying.\n\n## Two modes\n- Mode A (headless, default): you run FREE inside a pre-set budget + time\n envelope. The policy IS the consent \u2014 there is no per-payment prompt. Stay\n inside it; piprail_budget shows what's left.\n- Mode B (supervised): the host may ask a human to approve each payment. A\n decline/cancel/timeout comes back as declined:true (reasonCode:'APPROVAL') \u2014\n do NOT retry it as if it were a transient error.\n\n## Hard facts\n- Spend caps are PER (network, asset). There is no single cross-token dollar cap \u2014\n budgets aren't summed across tokens (no price oracle).\n- Spend totals and the time envelope live IN-MEMORY for THIS process; they reset on restart\n (a convenience, not a durable ledger).\n";
5553
+ declare const PIPRAIL_AGENT_GUIDE = "# Paying with PipRail \u2014 the agent contract\n\nYou can pay for x402 \"402 Payment Required\" resources autonomously. Money moves\nstraight from your wallet to the server; PipRail custodies nothing. Follow this.\n\n## The loop: quote \u2192 plan \u2192 pay\n1. piprail_quote_payment(url) \u2014 PRICE it. Returns the amount, token, chain, and\n whether it is within your spend policy. No funds move. Use it to decide if a\n resource is worth buying.\n2. piprail_plan_payment(url) \u2014 can I afford it NOW? Reads your balance, native gas,\n and recipient-readiness across every rail, and returns { payable, best,\n fundingHint, session? }. If payable is false, do NOT attempt the payment \u2014\n fundingHint says exactly what to fix.\n3. piprail_pay_request(url, method?, body?) \u2014 PAY (only if the plan was payable)\n and return the result.\nAlways plan before you pay so you never commit to a payment you cannot finish.\n\n## Gasless \u2014 the exact rail (zero gas for you)\nA 402 may offer up to two rails; you don't choose per payment \u2014 the client does, automatically:\n- onchain-proof (PipRail's default): you broadcast the payment yourself and pay the network gas\n (the native coin \u2014 ETH/SOL/\u2026). Works on every chain.\n- exact (the ratified x402 rail, opt-in): you only SIGN; the server \u2014 or a facilitator it chose\n (e.g. PayAI) \u2014 broadcasts it, so you pay ZERO gas (you need only the token, no native coin). It\n works on EVM + Solana, and the on-chain method (EIP-3009 / Permit2 / SVM) is picked automatically.\nWhen the exact scheme is enabled AND balance-aware routing is on, paying picks the cheapest\nsettleable rail \u2014 i.e. the gasless exact one. Nothing changes in your loop: quote \u2192 plan \u2192 pay is\nidentical. The exact scheme is OPT-IN by the operator (MCP: PIPRAIL_SCHEMES=onchain-proof,exact);\nyou can't enable it yourself, but you can report when a 402 needs it (see UNSUPPORTED_SCHEME below).\n\n## Reading a refusal \u2014 never crash, never double-spend\nA failed pay returns a STRUCTURED object, never a thrown error you must catch:\n { ok:false, code, reason, explain, ref?, reasonCode?, declined? }\nBranch on `code` (always reliable). Key cases:\n- declined:true with reasonCode:'SESSION_EXPIRED' \u2014 your time budget is over. This\n is TERMINAL: STOP. Do not retry ANY payment this process; it cannot be undone\n without a restart / a longer TTL.\n- declined:true with reasonCode:'APPROVAL' \u2014 a human (or hook) declined this\n payment. Terminal for this pay: do NOT auto-retry \u2014 they said no, or no one\n answered.\n- declined:true with reasonCode:'OUTSIDE_WINDOW' \u2014 your rolling rate-limit is\n exhausted. Wait for it to free, then retry; do not raise the amount.\n- declined:true with reasonCode:'POLICY' or 'BUDGET' \u2014 a spend cap or allowlist\n refused it. Don't retry the same payment; pick a cheaper/allowed one.\n- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.\n- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the\n payment may ALREADY be on-chain. Recover using the proof on `.ref` (re-verify\n or re-submit it); never re-pay \u2014 a fresh payment would double-spend. On a gasless\n exact rail `.ref` is the authorization NONCE, not a tx hash: re-present the SAME\n signed authorization, never sign a fresh one (that would risk a double-spend).\n- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on\n your chain/scheme; `explain` says whether it's the wrong chain or a scheme to enable.\n If it's a standard x402 server offering an exact rail, that's a config fix the operator makes\n once (enable the exact scheme); report it, don't retry the same call blindly.\n\n## Knowing your leash \u2014 call piprail_budget\npiprail_budget tells you how much budget and time you have left, per\n(network, asset), plus your spend so far. Read-only; moves no funds. Use it in\nMode A to self-check before paying.\n\n## Two modes\n- Mode A (headless, default): you run FREE inside a pre-set budget + time\n envelope. The policy IS the consent \u2014 there is no per-payment prompt. Stay\n inside it; piprail_budget shows what's left.\n- Mode B (supervised): the host may ask a human to approve each payment. A\n decline/cancel/timeout comes back as declined:true (reasonCode:'APPROVAL') \u2014\n do NOT retry it as if it were a transient error.\n\n## Hard facts\n- Spend caps are PER (network, asset). There is no single cross-token dollar cap \u2014\n budgets aren't summed across tokens (no price oracle).\n- Spend totals and the time envelope live IN-MEMORY for THIS process; they reset on restart\n (a convenience, not a durable ledger).\n";
5549
5554
  /** Returns {@link PIPRAIL_AGENT_GUIDE} (a parity accessor for callers that prefer a function). */
5550
5555
  declare function agentGuide(): string;
5551
5556
 
@@ -6392,6 +6397,14 @@ declare class UnsupportedSchemeError extends PipRailError {
6392
6397
  declare class NonReplayableBodyError extends PipRailError {
6393
6398
  readonly code = "NON_REPLAYABLE_BODY";
6394
6399
  }
6400
+ /**
6401
+ * A wallet-bound operation was called on a READ-ONLY client — one built with no
6402
+ * `wallet`. Read-only clients still `quote`, `discover`, and `register` (402 Index);
6403
+ * paying, planning, or signing needs a wallet. Pass `wallet` to enable them.
6404
+ */
6405
+ declare class WalletRequiredError extends PipRailError {
6406
+ readonly code = "WALLET_REQUIRED";
6407
+ }
6395
6408
  /**
6396
6409
  * The chosen chain belongs to one family (EVM, Solana, TON, Stellar, XRPL, Tron,
6397
6410
  * Sui, NEAR, Aptos, Algorand) but the wallet, payTo, or token was given in another family's form
@@ -6780,4 +6793,4 @@ declare const PERMIT2_WITNESS_TYPES: {
6780
6793
  }];
6781
6794
  };
6782
6795
 
6783
- export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
6796
+ export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  UnknownTokenError,
15
15
  UnsupportedNetworkError,
16
16
  UnsupportedSchemeError,
17
+ WalletRequiredError,
17
18
  WrongChainError,
18
19
  WrongFamilyError,
19
20
  floorUnits,
@@ -22,7 +23,7 @@ import {
22
23
  parseUnits,
23
24
  rejectForeignToken,
24
25
  toInsufficientFundsError
25
- } from "./chunk-ILPABTI2.js";
26
+ } from "./chunk-L6WQRHEZ.js";
26
27
 
27
28
  // src/drivers/registry.ts
28
29
  var byFamily = /* @__PURE__ */ new Map();
@@ -666,10 +667,14 @@ async function payExactEvm(input) {
666
667
  `exact buyer rail requires an EOA signer; ${account.address} is a contract / EIP-1271 / EIP-7702-delegated account (no recoverable ECDSA signature). Pay via onchain-proof.`
667
668
  );
668
669
  }
669
- const domain = await readExactDomain(publicClient, accept.asset);
670
+ let domain = await readExactDomain(publicClient, accept.asset);
671
+ if (!domain) {
672
+ await new Promise((r) => setTimeout(r, 300));
673
+ domain = await readExactDomain(publicClient, accept.asset);
674
+ }
670
675
  if (!domain) {
671
676
  throw new UnsupportedSchemeError(
672
- `exact: ${accept.asset} on ${accept.network} isn't an EIP-3009 token (USDT needs Permit2; native coin and plain ERC-20s aren't exact-payable). Pay via onchain-proof.`
677
+ `exact: couldn't derive the EIP-712 domain for ${accept.asset} on ${accept.network}. Either it isn't an EIP-3009 token (USDT needs Permit2; native/plain ERC-20 aren't exact-payable) \u2014 pay via onchain-proof \u2014 OR your RPC couldn't read it (transient): if the gate advertised eip3009, pass a reliable rpcUrl and retry.`
673
678
  );
674
679
  }
675
680
  const g = globalThis.crypto;
@@ -1791,7 +1796,7 @@ var loaders = {
1791
1796
  solana: async () => {
1792
1797
  let mod;
1793
1798
  try {
1794
- mod = await import("./solana-4EMMGGDR.js");
1799
+ mod = await import("./solana-KWNRY5NR.js");
1795
1800
  } catch (cause) {
1796
1801
  throw new MissingDriverError(
1797
1802
  `Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
@@ -1803,7 +1808,7 @@ var loaders = {
1803
1808
  ton: async () => {
1804
1809
  let mod;
1805
1810
  try {
1806
- mod = await import("./ton-CHJ26BVA.js");
1811
+ mod = await import("./ton-QN5GTOCS.js");
1807
1812
  } catch (cause) {
1808
1813
  throw new MissingDriverError(
1809
1814
  `TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
@@ -1815,7 +1820,7 @@ var loaders = {
1815
1820
  stellar: async () => {
1816
1821
  let mod;
1817
1822
  try {
1818
- mod = await import("./stellar-FIJPQZVW.js");
1823
+ mod = await import("./stellar-YB7JXKK4.js");
1819
1824
  } catch (cause) {
1820
1825
  throw new MissingDriverError(
1821
1826
  `Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
@@ -1827,7 +1832,7 @@ var loaders = {
1827
1832
  xrpl: async () => {
1828
1833
  let mod;
1829
1834
  try {
1830
- mod = await import("./xrpl-GTUPP6SK.js");
1835
+ mod = await import("./xrpl-ECHK3GIX.js");
1831
1836
  } catch (cause) {
1832
1837
  throw new MissingDriverError(
1833
1838
  `XRPL selected, but its package isn't installed. Run: npm install xrpl`,
@@ -1839,7 +1844,7 @@ var loaders = {
1839
1844
  tron: async () => {
1840
1845
  let mod;
1841
1846
  try {
1842
- mod = await import("./tron-DD3JDROV.js");
1847
+ mod = await import("./tron-QSNCDYRB.js");
1843
1848
  } catch (cause) {
1844
1849
  throw new MissingDriverError(
1845
1850
  `Tron selected, but its package isn't installed. Run: npm install tronweb`,
@@ -1851,7 +1856,7 @@ var loaders = {
1851
1856
  sui: async () => {
1852
1857
  let mod;
1853
1858
  try {
1854
- mod = await import("./sui-B7AVN7NK.js");
1859
+ mod = await import("./sui-IODKU2MA.js");
1855
1860
  } catch (cause) {
1856
1861
  throw new MissingDriverError(
1857
1862
  `Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
@@ -1863,7 +1868,7 @@ var loaders = {
1863
1868
  near: async () => {
1864
1869
  let mod;
1865
1870
  try {
1866
- mod = await import("./near-LM7S3WUD.js");
1871
+ mod = await import("./near-TWA4PYOD.js");
1867
1872
  } catch (cause) {
1868
1873
  throw new MissingDriverError(
1869
1874
  `NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
@@ -1875,7 +1880,7 @@ var loaders = {
1875
1880
  aptos: async () => {
1876
1881
  let mod;
1877
1882
  try {
1878
- mod = await import("./aptos-SUXOVP7B.js");
1883
+ mod = await import("./aptos-3TSKTI4D.js");
1879
1884
  } catch (cause) {
1880
1885
  throw new MissingDriverError(
1881
1886
  `Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
@@ -1887,7 +1892,7 @@ var loaders = {
1887
1892
  algorand: async () => {
1888
1893
  let mod;
1889
1894
  try {
1890
- mod = await import("./algorand-F3OYB534.js");
1895
+ mod = await import("./algorand-677ILBQS.js");
1891
1896
  } catch (cause) {
1892
1897
  throw new MissingDriverError(
1893
1898
  `Algorand selected, but its package isn't installed. Run: npm install algosdk`,
@@ -2622,7 +2627,7 @@ var PipRailClient = class {
2622
2627
  chain: this.opts.chain,
2623
2628
  rpcUrl: this.opts.rpcUrl
2624
2629
  });
2625
- const wallet = net.bindWallet(this.opts.wallet);
2630
+ const wallet = this.opts.wallet !== void 0 ? net.bindWallet(this.opts.wallet) : void 0;
2626
2631
  return { net, wallet };
2627
2632
  })();
2628
2633
  }
@@ -2788,6 +2793,11 @@ var PipRailClient = class {
2788
2793
  throw new InvalidEnvelopeError("402 response did not include a parseable x402 challenge.");
2789
2794
  }
2790
2795
  const { net, wallet } = await this.ensure();
2796
+ if (!wallet) {
2797
+ throw new WalletRequiredError(
2798
+ "planPayment needs a wallet (it checks YOUR balance, gas, and recipient-readiness). This client is read-only \u2014 construct it with a `wallet` to plan or pay."
2799
+ );
2800
+ }
2791
2801
  return this.planFromChallenge(net, wallet, challenge, url, this.resolveSchemes());
2792
2802
  }
2793
2803
  /**
@@ -2939,6 +2949,7 @@ var PipRailClient = class {
2939
2949
  */
2940
2950
  async discoverySigner() {
2941
2951
  const { net, wallet } = await this.ensure();
2952
+ if (!wallet) return null;
2942
2953
  return net.discoverySigner ? net.discoverySigner(wallet) : null;
2943
2954
  }
2944
2955
  /**
@@ -2960,6 +2971,11 @@ var PipRailClient = class {
2960
2971
  const schemes = this.resolveSchemes(init?.schemes);
2961
2972
  const resolved = await this.resolveChallenge(url, firstResponse, schemes);
2962
2973
  const { net, wallet, challenge } = resolved;
2974
+ if (!wallet) {
2975
+ throw new WalletRequiredError(
2976
+ "Paying a 402 needs a wallet to sign + settle. This client is read-only \u2014 construct it with a `wallet` to pay (quote/discover/register still work without one)."
2977
+ );
2978
+ }
2963
2979
  let accept = resolved.accept;
2964
2980
  let quote = resolved.quote;
2965
2981
  const autoRoute = init?.autoRoute ?? this.opts.autoRoute ?? false;
@@ -3647,6 +3663,18 @@ straight from your wallet to the server; PipRail custodies nothing. Follow this.
3647
3663
  and return the result.
3648
3664
  Always plan before you pay so you never commit to a payment you cannot finish.
3649
3665
 
3666
+ ## Gasless \u2014 the exact rail (zero gas for you)
3667
+ A 402 may offer up to two rails; you don't choose per payment \u2014 the client does, automatically:
3668
+ - onchain-proof (PipRail's default): you broadcast the payment yourself and pay the network gas
3669
+ (the native coin \u2014 ETH/SOL/\u2026). Works on every chain.
3670
+ - exact (the ratified x402 rail, opt-in): you only SIGN; the server \u2014 or a facilitator it chose
3671
+ (e.g. PayAI) \u2014 broadcasts it, so you pay ZERO gas (you need only the token, no native coin). It
3672
+ works on EVM + Solana, and the on-chain method (EIP-3009 / Permit2 / SVM) is picked automatically.
3673
+ When the exact scheme is enabled AND balance-aware routing is on, paying picks the cheapest
3674
+ settleable rail \u2014 i.e. the gasless exact one. Nothing changes in your loop: quote \u2192 plan \u2192 pay is
3675
+ identical. The exact scheme is OPT-IN by the operator (MCP: PIPRAIL_SCHEMES=onchain-proof,exact);
3676
+ you can't enable it yourself, but you can report when a 402 needs it (see UNSUPPORTED_SCHEME below).
3677
+
3650
3678
  ## Reading a refusal \u2014 never crash, never double-spend
3651
3679
  A failed pay returns a STRUCTURED object, never a thrown error you must catch:
3652
3680
  { ok:false, code, reason, explain, ref?, reasonCode?, declined? }
@@ -3664,9 +3692,13 @@ Branch on \`code\` (always reliable). Key cases:
3664
3692
  - code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.
3665
3693
  - code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the
3666
3694
  payment may ALREADY be on-chain. Recover using the proof on \`.ref\` (re-verify
3667
- or re-submit it); never re-pay \u2014 a fresh payment would double-spend.
3695
+ or re-submit it); never re-pay \u2014 a fresh payment would double-spend. On a gasless
3696
+ exact rail \`.ref\` is the authorization NONCE, not a tx hash: re-present the SAME
3697
+ signed authorization, never sign a fresh one (that would risk a double-spend).
3668
3698
  - code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on
3669
3699
  your chain/scheme; \`explain\` says whether it's the wrong chain or a scheme to enable.
3700
+ If it's a standard x402 server offering an exact rail, that's a config fix the operator makes
3701
+ once (enable the exact scheme); report it, don't retry the same call blindly.
3670
3702
 
3671
3703
  ## Knowing your leash \u2014 call piprail_budget
3672
3704
  piprail_budget tells you how much budget and time you have left, per
@@ -4199,6 +4231,7 @@ function createPaymentGate(options) {
4199
4231
  if (resolved) return resolved;
4200
4232
  const p = (async () => {
4201
4233
  const accepts = normaliseAccepts(options);
4234
+ const exactSkips = [];
4202
4235
  const specs = await Promise.all(
4203
4236
  accepts.map(async (a) => {
4204
4237
  const net = await resolveNetwork2({ chain: a.chain, rpcUrl: a.rpcUrl ?? options.rpcUrl });
@@ -4212,13 +4245,17 @@ function createPaymentGate(options) {
4212
4245
  const { asset, decimals, symbol } = net.resolveToken(a.token);
4213
4246
  const amountBase = parseUnits(a.amount, decimals);
4214
4247
  const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
4215
- if (options.exact) spec.exact = await resolveExactRail(net, asset);
4248
+ if (options.exact) {
4249
+ const outcome = await resolveExactRail(net, asset);
4250
+ if (outcome.rail) spec.exact = outcome.rail;
4251
+ else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
4252
+ }
4216
4253
  return spec;
4217
4254
  })
4218
4255
  );
4219
4256
  if (options.exact && !specs.some((s) => s.exact)) {
4220
4257
  throw new Error(
4221
- "requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM ERC-20 (EIP-3009 \u2014 USDC / EURC \u2014 or Permit2, e.g. Binance-Peg USDC on BNB) or a Solana SPL token (SVM) \u2014 NOT native coins, NOT families without a standard `exact` scheme. Offer an EVM ERC-20 / Solana SPL token, or drop `exact`."
4258
+ "requirePayment: `exact` was requested but none of the offered rails support it. " + (exactSkips.length > 0 ? exactSkips.join(" ") : "The standard `exact` rail is EVM ERC-20 (EIP-3009 \u2014 USDC / EURC \u2014 or Permit2, e.g. Binance-Peg USDC on BNB) or a Solana SPL token (SVM) \u2014 NOT native coins, NOT families without a standard `exact` scheme. Offer an EVM ERC-20 / Solana SPL token, or drop `exact`.")
4222
4259
  );
4223
4260
  }
4224
4261
  return specs;
@@ -4231,10 +4268,11 @@ function createPaymentGate(options) {
4231
4268
  }
4232
4269
  async function resolveExactRail(net, asset) {
4233
4270
  const cfg = options.exact;
4234
- if (!net.resolveExactRail) return void 0;
4271
+ const settle = cfg.settle;
4272
+ if (!net.resolveExactRail) return {};
4235
4273
  let relayer;
4236
4274
  let feePayer;
4237
- if (cfg.settle === "self") {
4275
+ if (settle === "self") {
4238
4276
  if (cfg.relayer === void 0) {
4239
4277
  throw new Error(
4240
4278
  "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
@@ -4242,21 +4280,36 @@ function createPaymentGate(options) {
4242
4280
  }
4243
4281
  relayer = net.bindWallet(cfg.relayer);
4244
4282
  } else {
4245
- feePayer = cfg.settle.feePayer;
4283
+ feePayer = settle.feePayer;
4246
4284
  }
4247
4285
  const method = cfg.method ?? "auto";
4248
4286
  let info = await net.resolveExactRail({ asset, method, relayer, feePayer });
4249
- if (!info && cfg.settle !== "self" && !feePayer) {
4250
- const discovered = await fetchFacilitatorFeePayer(cfg.settle.facilitator, net.network);
4251
- if (discovered) info = await net.resolveExactRail({ asset, method, relayer, feePayer: discovered });
4287
+ if (!info && settle !== "self" && !feePayer && asset !== "native") {
4288
+ const discovered = await fetchFacilitatorFeePayer(settle.facilitator, net.network);
4289
+ if (!discovered) {
4290
+ return {
4291
+ skipReason: `${net.network}: couldn't read a fee payer from the facilitator (${settle.facilitator}/supported) \u2014 it may be down, or may not sponsor this network. Set \`exact.settle.feePayer\` explicitly to remove the runtime dependency, switch to \`settle: 'self'\` with your own relayer, or retry.`
4292
+ };
4293
+ }
4294
+ info = await net.resolveExactRail({ asset, method, relayer, feePayer: discovered });
4295
+ }
4296
+ if (!info) return {};
4297
+ if (settle !== "self" && info.method === "permit2") {
4298
+ if (cfg.method === "permit2") {
4299
+ throw new Error(
4300
+ "requirePayment: exact `method: 'permit2'` can't be settled by a third-party facilitator \u2014 facilitators settle the standard EIP-3009 (EVM) / SVM (Solana) schemes, not PipRail\u2019s Permit2 proxy. Use an EIP-3009 token (USDC / EURC) with the facilitator, or `settle: 'self'` (your own relayer) to settle Permit2 yourself."
4301
+ );
4302
+ }
4303
+ return {
4304
+ skipReason: `${net.network}: this token isn't EIP-3009 (auto-selected Permit2), which a third-party facilitator can't settle \u2014 it would serve onchain-proof only here. Use an EIP-3009 token (USDC / EURC) with the facilitator, or \`settle: 'self'\` to settle Permit2 yourself.`
4305
+ };
4252
4306
  }
4253
- if (!info) return void 0;
4254
- const mode = cfg.settle === "self" ? { kind: "self", relayer } : {
4307
+ const mode = settle === "self" ? { kind: "self", relayer } : {
4255
4308
  kind: "facilitator",
4256
- url: cfg.settle.facilitator,
4257
- ...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
4309
+ url: settle.facilitator,
4310
+ ...settle.authHeaders ? { authHeaders: settle.authHeaders } : {}
4258
4311
  };
4259
- return { method: info.method, ...info.extra ? { extra: info.extra } : {}, mode };
4312
+ return { rail: { method: info.method, ...info.extra ? { extra: info.extra } : {}, mode } };
4260
4313
  }
4261
4314
  const hasCustomStore = Boolean(options.isUsed || options.markUsed);
4262
4315
  const localUsed = /* @__PURE__ */ new Map();
@@ -4719,6 +4772,7 @@ export {
4719
4772
  UnknownTokenError,
4720
4773
  UnsupportedNetworkError,
4721
4774
  UnsupportedSchemeError,
4775
+ WalletRequiredError,
4722
4776
  WrongChainError,
4723
4777
  WrongFamilyError,
4724
4778
  X402_EXACT_PERMIT2_PROXY,
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkPA6YD3HLcjs = require('./chunk-PA6YD3HL.cjs');
10
+ var _chunkU35MG4TFcjs = require('./chunk-U35MG4TF.cjs');
11
11
 
12
12
  // src/drivers/near/index.ts
13
13
  var _nearapijs = require('near-api-js');
@@ -53,18 +53,18 @@ async function payNear(params) {
53
53
  return res.hash;
54
54
  } catch (err) {
55
55
  if (isNearRegistrationError(err)) {
56
- throw new (0, _chunkPA6YD3HLcjs.RecipientNotReadyError)(
56
+ throw new (0, _chunkU35MG4TFcjs.RecipientNotReadyError)(
57
57
  `NEAR recipient ${accept.payTo} isn't registered on token ${accept.asset} (NEP-145 storage_deposit) \u2014 register it once (\u22480.00125 NEAR) before it can receive. (NEAR: not registered)`,
58
58
  { cause: err }
59
59
  );
60
60
  }
61
61
  if (isNearAffordability(err)) {
62
- throw new (0, _chunkPA6YD3HLcjs.InsufficientFundsError)(
62
+ throw new (0, _chunkU35MG4TFcjs.InsufficientFundsError)(
63
63
  err instanceof Error ? err.message : "Insufficient NEAR balance for the payment.",
64
64
  { cause: err }
65
65
  );
66
66
  }
67
- throw _nullishCoalesce(_chunkPA6YD3HLcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
67
+ throw _nullishCoalesce(_chunkU35MG4TFcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
68
68
  }
69
69
  }
70
70
  async function payNearNative(params) {
@@ -74,12 +74,12 @@ async function payNearNative(params) {
74
74
  return res.hash;
75
75
  } catch (err) {
76
76
  if (isNearAffordability(err)) {
77
- throw new (0, _chunkPA6YD3HLcjs.InsufficientFundsError)(
77
+ throw new (0, _chunkU35MG4TFcjs.InsufficientFundsError)(
78
78
  err instanceof Error ? err.message : "Insufficient NEAR balance for the payment.",
79
79
  { cause: err }
80
80
  );
81
81
  }
82
- throw _nullishCoalesce(_chunkPA6YD3HLcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
82
+ throw _nullishCoalesce(_chunkU35MG4TFcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
83
83
  }
84
84
  }
85
85
  function isNearRegistrationError(err) {
@@ -216,22 +216,22 @@ function txNotFound(hash) {
216
216
 
217
217
  function assertNearWallet(wallet, network) {
218
218
  if (typeof wallet !== "object" || wallet === null) {
219
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
219
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
220
220
  `chain ${network} is NEAR; wallet must be { accountId, privateKey } (privateKey = ed25519:\u2026).`
221
221
  );
222
222
  }
223
223
  if ("walletClient" in wallet) {
224
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
224
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
225
225
  `chain ${network} is NEAR; a viem { walletClient } can't be used \u2014 pass { accountId, privateKey }.`
226
226
  );
227
227
  }
228
228
  if ("secretKey" in wallet || "signer" in wallet || "mnemonic" in wallet || "keyPair" in wallet || "secret" in wallet || "seed" in wallet || "keypair" in wallet) {
229
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
229
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
230
230
  `chain ${network} is NEAR; that looks like another family's wallet \u2014 pass { accountId, privateKey }.`
231
231
  );
232
232
  }
233
233
  if (!("accountId" in wallet) || !("privateKey" in wallet)) {
234
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
234
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
235
235
  `chain ${network} is NEAR; wallet must be { accountId, privateKey } (privateKey = ed25519:\u2026).`
236
236
  );
237
237
  }
@@ -239,13 +239,13 @@ function assertNearWallet(wallet, network) {
239
239
  }
240
240
  function resolveNearWallet(config) {
241
241
  if (!config.accountId || !config.privateKey) {
242
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)("NEAR wallet needs { accountId, privateKey } (privateKey = ed25519:\u2026).");
242
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)("NEAR wallet needs { accountId, privateKey } (privateKey = ed25519:\u2026).");
243
243
  }
244
244
  let signer;
245
245
  try {
246
246
  signer = _nearapijs.KeyPairSigner.fromSecretKey(config.privateKey);
247
247
  } catch (cause) {
248
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)("NEAR wallet { privateKey } is not a valid ed25519:\u2026 secret key.", {
248
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)("NEAR wallet { privateKey } is not a valid ed25519:\u2026 secret key.", {
249
249
  cause
250
250
  });
251
251
  }
@@ -312,16 +312,16 @@ function makeNearNetwork(preset, rpcUrl) {
312
312
  const info = preset.tokens[token.toUpperCase()];
313
313
  if (!info) {
314
314
  const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
315
- throw new (0, _chunkPA6YD3HLcjs.UnknownTokenError)(
315
+ throw new (0, _chunkU35MG4TFcjs.UnknownTokenError)(
316
316
  `token "${token}" isn't built in for NEAR (known: ${known}). Pass { contractId, decimals } for a custom NEP-141.`
317
317
  );
318
318
  }
319
319
  return { asset: info.contractId, decimals: info.decimals, symbol: info.symbol };
320
320
  }
321
- _chunkPA6YD3HLcjs.rejectForeignToken.call(void 0, token, "near", network);
321
+ _chunkU35MG4TFcjs.rejectForeignToken.call(void 0, token, "near", network);
322
322
  const t = token;
323
323
  if (!t.contractId || typeof t.decimals !== "number") {
324
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
324
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
325
325
  `chain ${network} is NEAR; a custom token must be { contractId, decimals }.`
326
326
  );
327
327
  }
@@ -340,12 +340,12 @@ function makeNearNetwork(preset, rpcUrl) {
340
340
  },
341
341
  assertValidPayTo(payTo) {
342
342
  if (payTo.startsWith("0x")) {
343
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
343
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
344
344
  `chain ${network} is NEAR, but payTo "${payTo}" looks like an EVM/Sui 0x address.`
345
345
  );
346
346
  }
347
347
  if (!isValidNearAccountId(payTo)) {
348
- throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
348
+ throw new (0, _chunkU35MG4TFcjs.WrongFamilyError)(
349
349
  `chain ${network} is NEAR, but payTo "${payTo}" is not a valid NEAR account id.`
350
350
  );
351
351
  }
@@ -389,10 +389,10 @@ function makeNearNetwork(preset, rpcUrl) {
389
389
  if (tx && tx.success) return { height: "0" };
390
390
  } catch (e7) {
391
391
  }
392
- throw new (0, _chunkPA6YD3HLcjs.ConfirmationTimeoutError)(`NEAR tx ${hash} not confirmed in time.`);
392
+ throw new (0, _chunkU35MG4TFcjs.ConfirmationTimeoutError)(`NEAR tx ${hash} not confirmed in time.`);
393
393
  },
394
394
  async estimateCost() {
395
- return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
395
+ return _chunkU35MG4TFcjs.nativeCost.call(void 0, {
396
396
  symbol: "NEAR",
397
397
  decimals: NEAR_DECIMALS,
398
398
  fee: 1500000000000000000000n,
@@ -7,7 +7,7 @@ import {
7
7
  nativeCost,
8
8
  rejectForeignToken,
9
9
  toInsufficientFundsError
10
- } from "./chunk-ILPABTI2.js";
10
+ } from "./chunk-L6WQRHEZ.js";
11
11
 
12
12
  // src/drivers/near/index.ts
13
13
  import { JsonRpcProvider, Account, actions } from "near-api-js";
@@ -7,7 +7,7 @@ import {
7
7
  nativeCost,
8
8
  rejectForeignToken,
9
9
  toInsufficientFundsError
10
- } from "./chunk-ILPABTI2.js";
10
+ } from "./chunk-L6WQRHEZ.js";
11
11
 
12
12
  // src/drivers/solana/index.ts
13
13
  import { Connection, PublicKey as PublicKey3 } from "@solana/web3.js";
@@ -624,6 +624,15 @@ function makeSolanaNetwork(preset, rpcUrl) {
624
624
  return { height: String(info.slot) };
625
625
  },
626
626
  async estimateCost(accept) {
627
+ if (accept.scheme === "exact") {
628
+ return nativeCost({
629
+ symbol: "SOL",
630
+ decimals: SOL_DECIMALS,
631
+ fee: 0n,
632
+ basis: "estimated",
633
+ detail: "gasless \u2014 the fee payer (facilitator/relayer) broadcasts and pays the SOL fee"
634
+ });
635
+ }
627
636
  const base = 5000n;
628
637
  if (accept.asset === "native") {
629
638
  return nativeCost({