@mooring-live/x402-client 0.0.0-stage → 0.1.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.
@@ -0,0 +1,81 @@
1
+ import { type xdr } from "@stellar/stellar-sdk";
2
+ export type DenialReason = "frozen" | "cancelled" | "expired" | "not_allowlisted" | "over_per_tx_cap" | "over_budget" | "wrong_token" | "insufficient_balance" | "bad_signature" | "wrong_context" | "unknown";
3
+ /**
4
+ * Where a payment was denied.
5
+ *
6
+ * - `precheck` — the local policy mirror, before anything is signed.
7
+ * - `simulate` — the card's `__check_auth`, in the enforcing simulation the
8
+ * client runs after signing and before handing the payload to a facilitator.
9
+ * - `verify` — the facilitator refused the payload.
10
+ * - `settle` — the settlement transaction failed on-chain.
11
+ */
12
+ export type DenialStage = "precheck" | "simulate" | "verify" | "settle";
13
+ /** Card contract error codes that mean "policy denied". */
14
+ export declare const CARD_ERROR_CODES: Record<number, DenialReason>;
15
+ /**
16
+ * Every contract error the card can raise (`CardError`, codes 1..13), with a
17
+ * short user-facing sentence for each. This is the full table: unlike
18
+ * `CARD_ERROR_CODES` (1..8, payment denials only), it also covers the owner
19
+ * operations the web app surfaces (`set_policy`, `set_label`, allowlist
20
+ * management, freeze/cancel/withdraw) so their errors can be shown directly
21
+ * without a separate mapping.
22
+ */
23
+ export declare const CARD_ERRORS: Record<number, string>;
24
+ /** Parses a Soroban `Error(Contract, #N)` from an error/diagnostic string. */
25
+ export declare function classifyContractError(text: string): {
26
+ code: number;
27
+ reason: DenialReason;
28
+ } | null;
29
+ /**
30
+ * Reads the card's own `__check_auth` verdict out of a transaction's diagnostic
31
+ * events.
32
+ *
33
+ * The card's address alone is not evidence: it appears in the `transfer`
34
+ * arguments of every diagnostic this transaction can produce, and the token's
35
+ * own error codes overlap the card's 1-8 range — a SAC balance error would be
36
+ * read as a policy decision. What identifies an auth failure is the host's
37
+ * event, which names the failing account and its error together:
38
+ *
39
+ * ["failed account authentication with error", <card>, Error(Contract, #N)]
40
+ *
41
+ * so the reason is taken from that event and nowhere else. Anything that does
42
+ * not match returns `null`, and the caller reports `unknown`.
43
+ */
44
+ export declare function cardDenialFromEvents(events: readonly xdr.DiagnosticEvent[], card: string): {
45
+ code: number;
46
+ reason: DenialReason;
47
+ } | null;
48
+ export declare class CardPolicyDenied extends Error {
49
+ readonly reason: DenialReason;
50
+ readonly stage: DenialStage;
51
+ readonly contractError?: number;
52
+ readonly detail?: string;
53
+ constructor(reason: DenialReason, stage: DenialStage, opts?: {
54
+ contractError?: number;
55
+ detail?: string;
56
+ });
57
+ }
58
+ /**
59
+ * Why a payment did not deliver the resource, when the card's policy is *not*
60
+ * the reason. `CardPolicyDenied` means exactly one thing — the card refused —
61
+ * so everything else lands here:
62
+ *
63
+ * - `rejected` — the facilitator (or the transport) refused the payment
64
+ * before anything was submitted: a malformed payload, a fee above the
65
+ * facilitator's ceiling, an expiration it will not accept, a 5xx. Nothing
66
+ * was spent.
67
+ * - `unconfirmed` — the settlement state is unknown or contradictory: the
68
+ * server reported a failure for a transaction the ledger accepted, or the
69
+ * transaction was never observed. The card may have been debited; reconcile
70
+ * `transaction` against the network before retrying.
71
+ */
72
+ export declare class PaymentError extends Error {
73
+ readonly kind: "rejected" | "unconfirmed";
74
+ readonly transaction?: string;
75
+ readonly detail?: string;
76
+ constructor(opts: {
77
+ kind: "rejected" | "unconfirmed";
78
+ transaction?: string;
79
+ detail?: string;
80
+ });
81
+ }
package/dist/denial.js ADDED
@@ -0,0 +1,116 @@
1
+ import { humanizeEvents } from "@stellar/stellar-sdk";
2
+ /** Card contract error codes that mean "policy denied". */
3
+ export const CARD_ERROR_CODES = {
4
+ 1: "bad_signature",
5
+ 2: "wrong_context",
6
+ 3: "frozen",
7
+ 4: "cancelled",
8
+ 5: "expired",
9
+ 6: "not_allowlisted",
10
+ 7: "over_per_tx_cap",
11
+ 8: "over_budget",
12
+ };
13
+ /**
14
+ * Every contract error the card can raise (`CardError`, codes 1..13), with a
15
+ * short user-facing sentence for each. This is the full table: unlike
16
+ * `CARD_ERROR_CODES` (1..8, payment denials only), it also covers the owner
17
+ * operations the web app surfaces (`set_policy`, `set_label`, allowlist
18
+ * management, freeze/cancel/withdraw) so their errors can be shown directly
19
+ * without a separate mapping.
20
+ */
21
+ export const CARD_ERRORS = {
22
+ 1: "The signature is not valid for this card.",
23
+ 2: "This action is not the one payment the card's agent key is allowed to sign.",
24
+ 3: "This card is frozen.",
25
+ 4: "This card has been cancelled.",
26
+ 5: "This card has expired.",
27
+ 6: "This merchant is not on the card's allowlist.",
28
+ 7: "This payment is over the card's per-transaction cap.",
29
+ 8: "This would put the card over its budget for the current period.",
30
+ 9: "The amount must be a positive number.",
31
+ 10: "The card's merchant allowlist is full.",
32
+ 11: "That policy is not valid (check the budget, cap and expiry values).",
33
+ 12: "This action is not allowed in the card's current state.",
34
+ 13: "That label is not valid (it must be 1 to 32 bytes).",
35
+ };
36
+ const CONTRACT_ERROR = /Error\(Contract, #(\d+)\)/;
37
+ /** Parses a Soroban `Error(Contract, #N)` from an error/diagnostic string. */
38
+ export function classifyContractError(text) {
39
+ const m = CONTRACT_ERROR.exec(text);
40
+ if (!m)
41
+ return null;
42
+ const code = Number(m[1]);
43
+ return { code, reason: CARD_ERROR_CODES[code] ?? "unknown" };
44
+ }
45
+ /** The host's phrase that attributes an authorization failure to an account. */
46
+ const AUTH_FAILURE = "failed account authentication with error";
47
+ /**
48
+ * Reads the card's own `__check_auth` verdict out of a transaction's diagnostic
49
+ * events.
50
+ *
51
+ * The card's address alone is not evidence: it appears in the `transfer`
52
+ * arguments of every diagnostic this transaction can produce, and the token's
53
+ * own error codes overlap the card's 1-8 range — a SAC balance error would be
54
+ * read as a policy decision. What identifies an auth failure is the host's
55
+ * event, which names the failing account and its error together:
56
+ *
57
+ * ["failed account authentication with error", <card>, Error(Contract, #N)]
58
+ *
59
+ * so the reason is taken from that event and nowhere else. Anything that does
60
+ * not match returns `null`, and the caller reports `unknown`.
61
+ */
62
+ export function cardDenialFromEvents(events, card) {
63
+ for (const e of humanizeEvents([...events])) {
64
+ const data = e.data;
65
+ if (!Array.isArray(data) || data.length < 3)
66
+ continue;
67
+ if (data[0] !== AUTH_FAILURE || data[1] !== card)
68
+ continue;
69
+ const err = data[2];
70
+ if (err?.type !== "contract" || typeof err.code !== "number")
71
+ continue;
72
+ return { code: err.code, reason: CARD_ERROR_CODES[err.code] ?? "unknown" };
73
+ }
74
+ return null;
75
+ }
76
+ export class CardPolicyDenied extends Error {
77
+ reason;
78
+ stage;
79
+ contractError;
80
+ detail;
81
+ constructor(reason, stage, opts = {}) {
82
+ super(`card policy denied (${reason}) at ${stage}${opts.contractError ? ` [contract error #${opts.contractError}]` : ""}${opts.detail ? `: ${opts.detail}` : ""}`);
83
+ this.name = "CardPolicyDenied";
84
+ this.reason = reason;
85
+ this.stage = stage;
86
+ this.contractError = opts.contractError;
87
+ this.detail = opts.detail;
88
+ }
89
+ }
90
+ /**
91
+ * Why a payment did not deliver the resource, when the card's policy is *not*
92
+ * the reason. `CardPolicyDenied` means exactly one thing — the card refused —
93
+ * so everything else lands here:
94
+ *
95
+ * - `rejected` — the facilitator (or the transport) refused the payment
96
+ * before anything was submitted: a malformed payload, a fee above the
97
+ * facilitator's ceiling, an expiration it will not accept, a 5xx. Nothing
98
+ * was spent.
99
+ * - `unconfirmed` — the settlement state is unknown or contradictory: the
100
+ * server reported a failure for a transaction the ledger accepted, or the
101
+ * transaction was never observed. The card may have been debited; reconcile
102
+ * `transaction` against the network before retrying.
103
+ */
104
+ export class PaymentError extends Error {
105
+ kind;
106
+ transaction;
107
+ detail;
108
+ constructor(opts) {
109
+ super(`payment ${opts.kind}${opts.transaction ? ` (tx ${opts.transaction})` : ""}${opts.detail ? `: ${opts.detail}` : ""}`);
110
+ this.name = "PaymentError";
111
+ this.kind = opts.kind;
112
+ this.transaction = opts.transaction;
113
+ this.detail = opts.detail;
114
+ }
115
+ }
116
+ //# sourceMappingURL=denial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"denial.js","sourceRoot":"","sources":["../src/denial.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export { signCardAuthEntries } from "./signer.js";
2
+ export { CARD_ERROR_CODES, CARD_ERRORS, CardPolicyDenied, PaymentError, cardDenialFromEvents, classifyContractError, } from "./denial.js";
3
+ export type { DenialReason, DenialStage } from "./denial.js";
4
+ export { reconcileSettlement } from "./reconcile.js";
5
+ export type { SettlementOutcome } from "./reconcile.js";
6
+ export { precheck } from "./precheck.js";
7
+ export type { PaymentTerms } from "./precheck.js";
8
+ export { readCardInfo, readMerchants } from "./card.js";
9
+ export type { CardInfo, CardPolicy, CardState } from "./card.js";
10
+ export { CardExactStellarScheme } from "./scheme.js";
11
+ export type { CardSchemeOptions, StellarNetwork } from "./scheme.js";
12
+ export { createMooringClient, createMooringFetch, getSettlement } from "./client.js";
13
+ export type { MooringClientOptions, Settlement } from "./client.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { signCardAuthEntries } from "./signer.js";
2
+ export { CARD_ERROR_CODES, CARD_ERRORS, CardPolicyDenied, PaymentError, cardDenialFromEvents, classifyContractError, } from "./denial.js";
3
+ export { reconcileSettlement } from "./reconcile.js";
4
+ export { precheck } from "./precheck.js";
5
+ export { readCardInfo, readMerchants } from "./card.js";
6
+ export { CardExactStellarScheme } from "./scheme.js";
7
+ export { createMooringClient, createMooringFetch, getSettlement } from "./client.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { CardInfo } from "./card.js";
2
+ import type { DenialReason } from "./denial.js";
3
+ export interface PaymentTerms {
4
+ payTo: string;
5
+ amount: string;
6
+ asset: string;
7
+ }
8
+ /**
9
+ * Local mirror of the card's on-chain policy, evaluated before signing so the agent
10
+ * never submits a payment the card will reject. Order matches the contract.
11
+ */
12
+ export declare function precheck(info: CardInfo, merchants: string[], req: PaymentTerms, nowSeconds: number): DenialReason | null;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Local mirror of the card's on-chain policy, evaluated before signing so the agent
3
+ * never submits a payment the card will reject. Order matches the contract.
4
+ */
5
+ export function precheck(info, merchants, req, nowSeconds) {
6
+ const amount = BigInt(req.amount);
7
+ if (req.asset !== info.token)
8
+ return "wrong_token";
9
+ if (info.state === 1)
10
+ return "frozen";
11
+ if (info.state === 2)
12
+ return "cancelled";
13
+ if (BigInt(nowSeconds) >= info.policy.expiry)
14
+ return "expired";
15
+ if (!merchants.includes(req.payTo))
16
+ return "not_allowlisted";
17
+ if (amount > info.policy.max_per_tx)
18
+ return "over_per_tx_cap";
19
+ if (amount > info.remaining)
20
+ return "over_budget";
21
+ if (amount > info.balance)
22
+ return "insufficient_balance";
23
+ return null;
24
+ }
25
+ //# sourceMappingURL=precheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precheck.js","sourceRoot":"","sources":["../src/precheck.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,SAAmB,EAAE,GAAiB,EAAE,UAAkB;IACjG,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;QAAE,OAAO,aAAa,CAAC;IACnD,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACtC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IACzC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC/D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAC7D,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO,iBAAiB,CAAC;IAC9D,IAAI,MAAM,GAAG,IAAI,CAAC,SAAS;QAAE,OAAO,aAAa,CAAC;IAClD,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO;QAAE,OAAO,sBAAsB,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { type xdr } from "@stellar/stellar-sdk";
2
+ import type { StellarNetwork } from "./scheme.js";
3
+ /** What the ledger says about a settlement transaction. */
4
+ export type SettlementOutcome = {
5
+ status: "SUCCESS";
6
+ } | {
7
+ status: "FAILED";
8
+ events: xdr.DiagnosticEvent[];
9
+ } | {
10
+ status: "NOT_FOUND";
11
+ };
12
+ /** Reconciliation window: ~10 s, one RPC read per second. */
13
+ export declare const RECONCILE_ATTEMPTS = 10;
14
+ export declare const RECONCILE_DELAY_MS = 1000;
15
+ /**
16
+ * Asks the network what actually happened to a settlement transaction.
17
+ *
18
+ * The facilitator answers `settle_exact_stellar_transaction_failed` both for a
19
+ * transaction the ledger rejected *and* for one it simply stopped waiting for
20
+ * (its own poll gives up after `maxTimeoutSeconds`), so its "failed" cannot be
21
+ * read as "the payment did not happen" — inclusion can follow the answer. This
22
+ * asks the RPC directly and distinguishes the three real outcomes:
23
+ *
24
+ * - `SUCCESS` — the payment settled; the card was debited even though the
25
+ * server reported a failure and withheld the resource.
26
+ * - `FAILED` — the ledger rejected it; `events` carry the reason, including
27
+ * the card's own `__check_auth` verdict when that is what refused.
28
+ * - `NOT_FOUND` — still not visible when the window closed; the settlement
29
+ * state is unknown.
30
+ *
31
+ * An RPC error is treated as "not seen yet", not as an answer: a reconciliation
32
+ * we could not complete must never be reported as a failed payment.
33
+ */
34
+ export declare function reconcileSettlement(rpcUrl: string, network: StellarNetwork, hash: string, opts?: {
35
+ attempts?: number;
36
+ delayMs?: number;
37
+ }): Promise<SettlementOutcome>;
@@ -0,0 +1,44 @@
1
+ import { rpc } from "@stellar/stellar-sdk";
2
+ /** Reconciliation window: ~10 s, one RPC read per second. */
3
+ export const RECONCILE_ATTEMPTS = 10;
4
+ export const RECONCILE_DELAY_MS = 1000;
5
+ /**
6
+ * Asks the network what actually happened to a settlement transaction.
7
+ *
8
+ * The facilitator answers `settle_exact_stellar_transaction_failed` both for a
9
+ * transaction the ledger rejected *and* for one it simply stopped waiting for
10
+ * (its own poll gives up after `maxTimeoutSeconds`), so its "failed" cannot be
11
+ * read as "the payment did not happen" — inclusion can follow the answer. This
12
+ * asks the RPC directly and distinguishes the three real outcomes:
13
+ *
14
+ * - `SUCCESS` — the payment settled; the card was debited even though the
15
+ * server reported a failure and withheld the resource.
16
+ * - `FAILED` — the ledger rejected it; `events` carry the reason, including
17
+ * the card's own `__check_auth` verdict when that is what refused.
18
+ * - `NOT_FOUND` — still not visible when the window closed; the settlement
19
+ * state is unknown.
20
+ *
21
+ * An RPC error is treated as "not seen yet", not as an answer: a reconciliation
22
+ * we could not complete must never be reported as a failed payment.
23
+ */
24
+ export async function reconcileSettlement(rpcUrl, network, hash, opts = {}) {
25
+ const attempts = opts.attempts ?? RECONCILE_ATTEMPTS;
26
+ const delayMs = opts.delayMs ?? RECONCILE_DELAY_MS;
27
+ const server = new rpc.Server(rpcUrl, { allowHttp: network === "stellar:testnet" });
28
+ for (let i = 0; i < attempts; i++) {
29
+ try {
30
+ const tx = await server.getTransaction(hash);
31
+ if (tx.status === "SUCCESS")
32
+ return { status: "SUCCESS" };
33
+ if (tx.status === "FAILED")
34
+ return { status: "FAILED", events: tx.diagnosticEventsXdr ?? [] };
35
+ }
36
+ catch {
37
+ /* not visible yet, or the RPC hiccuped: keep waiting */
38
+ }
39
+ if (i < attempts - 1)
40
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
41
+ }
42
+ return { status: "NOT_FOUND" };
43
+ }
44
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,MAAM,sBAAsB,CAAC;AASrD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAc,EACd,OAAuB,EACvB,IAAY,EACZ,OAAgD,EAAE;IAElD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,kBAAkB,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,kBAAkB,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,OAAO,KAAK,iBAAiB,EAAE,CAAC,CAAC;IAEpF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,EAAE,CAAC,MAAM,KAAK,SAAS;gBAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YAC1D,IAAI,EAAE,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,mBAAmB,IAAI,EAAE,EAAE,CAAC;QAChG,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;QACD,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { Keypair, contract } from "@stellar/stellar-sdk";
2
+ import type { PaymentPayloadResult, PaymentRequirements, SchemeNetworkClient } from "@x402/core/types";
3
+ import { CardPolicyDenied, PaymentError } from "./denial.js";
4
+ export type StellarNetwork = "stellar:testnet" | "stellar:pubnet";
5
+ /**
6
+ * How far ahead the card's auth entry may be valid.
7
+ *
8
+ * The facilitator computes `maxLedger = currentLedger + ceil(maxTimeoutSeconds
9
+ * / estimatedLedgerSeconds)` and rejects an entry more than 2 ledgers beyond
10
+ * it (`invalid_exact_stellar_signature_expiration_too_far`). Its estimate is
11
+ * sampled from Horizon, so it can be slower than the 5 s the stock client
12
+ * assumes — on pubnet it usually is. Aiming low costs nothing: the expiry only
13
+ * has to outlive settlement, and a shorter one is never rejected.
14
+ */
15
+ export declare function authExpirationLedgers(maxTimeoutSeconds: number): number;
16
+ export interface CardSchemeOptions {
17
+ /** Card contract address (C...). The card is the payer. */
18
+ card: string;
19
+ /** Agent keypair registered as the card's signer. */
20
+ agent: Keypair;
21
+ network: StellarNetwork;
22
+ /** Required on pubnet; optional on testnet. */
23
+ rpcUrl?: string;
24
+ /** Called when the card rejects the payment in the enforcing simulation. */
25
+ onDenial?: (denial: CardPolicyDenied) => void;
26
+ /** Called when the payment fails for a reason that is not the card's policy. */
27
+ onPaymentError?: (error: PaymentError) => void;
28
+ }
29
+ /**
30
+ * x402 "exact" scheme client whose payer is a Mooring card (a Soroban custom
31
+ * account). It produces exactly the same payload as `@x402/stellar`'s
32
+ * `ExactStellarScheme` — one `invokeHostFunction` calling
33
+ * `asset.transfer(card, payTo, amount)`, re-simulated after signing, returned
34
+ * as a base64 transaction envelope. Only the signing differs: the card's auth
35
+ * entries are signed by the agent key through `signCardAuthEntries`, so
36
+ * `__check_auth` enforces the budget, allowlist and expiry at settlement.
37
+ */
38
+ export declare class CardExactStellarScheme implements SchemeNetworkClient {
39
+ readonly scheme = "exact";
40
+ readonly findDefaultAsset: import("@x402/core/types").FindDefaultAsset<import("@x402/core/types").DefaultAsset>;
41
+ private readonly card;
42
+ private readonly agent;
43
+ private readonly network;
44
+ private readonly rpcUrl;
45
+ private readonly passphrase;
46
+ private readonly onDenial?;
47
+ private readonly onPaymentError?;
48
+ constructor(opts: CardSchemeOptions);
49
+ /** Throws unless the requirements are an `exact` payment this client can make. */
50
+ validateRequirements(req: PaymentRequirements): void;
51
+ createPaymentPayload(x402Version: number, req: PaymentRequirements): Promise<PaymentPayloadResult>;
52
+ /**
53
+ * Turns a failed enforcing simulation into a typed denial when it was the
54
+ * card's `__check_auth` that said no.
55
+ *
56
+ * The card's address alone is not evidence: it appears in the `transfer`
57
+ * arguments of every diagnostic this transaction can produce, and the token's
58
+ * own error codes overlap the card's 1–8 range — a SAC balance error would be
59
+ * read as a policy decision. What identifies an auth failure is the host's
60
+ * phrase, which names the failing account and its error together:
61
+ *
62
+ * ["failed account authentication with error", <card>, Error(Contract, #N)]
63
+ *
64
+ * so the reason is taken from that fragment and nowhere else. Anything that
65
+ * does not match stays an ordinary error.
66
+ */
67
+ private denialFromCard;
68
+ /**
69
+ * Refuses to build a payment whose card auth entry the deployed facilitator
70
+ * cannot decode.
71
+ *
72
+ * `buildTransfer` asks the RPC to record legacy v1 `ADDRESS` credentials
73
+ * (`useUpgradedAuth: false`) because OZ Channels rejects CAP-71 `ADDRESS_V2`
74
+ * as `invalid_exact_stellar_payload_malformed`. That flag is transitional —
75
+ * it becomes a no-op from protocol 28 — and an RPC or SDK that ignores it
76
+ * would otherwise produce a payload that is signed, sent and refused with a
77
+ * reason that says nothing about why. This checks what was actually
78
+ * recorded, and fails before the agent key signs anything.
79
+ */
80
+ private assertLegacyCardCredentials;
81
+ /** Builds and simulates `asset.transfer(from, to, amount)` (SEP-41). */
82
+ protected buildTransfer(asset: string, from: string, to: string, amount: bigint): Promise<contract.AssembledTransaction<unknown>>;
83
+ /** Signs the card's auth entries with the agent key. */
84
+ protected sign(tx: contract.AssembledTransaction<unknown>, expirationLedger: number): Promise<void>;
85
+ /** Current ledger sequence, used as the auth-entry expiration base. */
86
+ protected latestLedger(): Promise<number>;
87
+ }