@atumlabs/mppx-atum-escrow 0.2.2 → 0.4.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.
@@ -1,19 +1,102 @@
1
1
  import { createRequire as __atumCreateRequire } from 'module'; const require = __atumCreateRequire(import.meta.url);
2
2
  import {
3
+ INTENT_ID_META_KEY,
3
4
  METHOD_NAME,
4
5
  __toESM,
5
6
  assetIdentifier,
6
7
  atumEscrowChargeMethod,
8
+ chargeRequestId,
7
9
  contractOverridesFromExtra,
8
10
  import_payment_request_sender_auth,
11
+ intentIdOf,
9
12
  namespaceOf,
10
13
  require_dist2 as require_dist,
11
- sameAddress
12
- } from "./chunk-4P34CLTO.js";
14
+ sameAddress,
15
+ sameAssetIdentifier
16
+ } from "./chunk-OEEC5P3E.js";
13
17
 
14
18
  // src/server.ts
15
19
  var import_payment_request_sender_auth2 = __toESM(require_dist(), 1);
16
- import { Method } from "mppx";
20
+ import { Errors as Errors2, Method } from "mppx";
21
+
22
+ // src/errors.ts
23
+ import { Errors } from "mppx";
24
+ function hasMarker(err, marker) {
25
+ return typeof err === "object" && err !== null && err[marker] === true;
26
+ }
27
+ function isSettlementPending(err) {
28
+ return hasMarker(err, "isSettlementPending");
29
+ }
30
+ function isSettlementFailed(err) {
31
+ return hasMarker(err, "isSettlementFailed");
32
+ }
33
+ function isPaymentRejected(err) {
34
+ return hasMarker(err, "isPaymentRejected");
35
+ }
36
+ var SettlementPendingError = class extends Errors.PaymentActionRequiredError {
37
+ /** Discriminant; test it with {@link isSettlementPending}. */
38
+ isSettlementPending = true;
39
+ paymentId;
40
+ constructor(options = {}) {
41
+ super({
42
+ reason: "the payment is still settling" + (options.paymentId ? ` (payment ${options.paymentId})` : "") + "; re-attempt the purchase to collect the result \u2014 request a fresh challenge and sign it again, keeping the SAME intent id, and it resolves onto this payment rather than charging again. Do not re-present the credential already signed: its deadlines are fixed"
43
+ });
44
+ this.paymentId = options.paymentId;
45
+ }
46
+ /**
47
+ * Adds `paymentId` as its own field, not just interpolated into `detail`'s prose. A merchant
48
+ * catching this in-process already has `.paymentId`; a real cross-network payer only ever
49
+ * sees this JSON body, and without a dedicated field would have to parse it out of a sentence
50
+ * to reconcile the attempt against the gateway. The return type is declared (not left to
51
+ * inference) so a TypeScript merchant that re-serializes through the base type still sees
52
+ * `paymentId` in intellisense.
53
+ */
54
+ toProblemDetails(challengeId) {
55
+ return {
56
+ ...super.toProblemDetails(challengeId),
57
+ ...this.paymentId !== void 0 ? { paymentId: this.paymentId } : {}
58
+ };
59
+ }
60
+ };
61
+ var SettlementFailedError = class extends Errors.VerificationFailedError {
62
+ /** Discriminant; test it with {@link isSettlementFailed}. */
63
+ isSettlementFailed = true;
64
+ paymentId;
65
+ /** The terminal state the gateway reported. */
66
+ state;
67
+ constructor(options) {
68
+ super({
69
+ reason: `the payment ${options.state}` + (options.paymentId ? ` (payment ${options.paymentId})` : "") + "; retrying this purchase resolves to the same failed payment, so recovering requires a new purchase under a new identifier"
70
+ });
71
+ this.paymentId = options.paymentId;
72
+ this.state = options.state;
73
+ }
74
+ /**
75
+ * Adds `paymentId` and `state` as their own fields, not just interpolated into `detail`'s
76
+ * prose — see {@link SettlementPendingError.toProblemDetails} for why a payer needs this on
77
+ * the wire, not only a same-process merchant. Return type declared for the same reason: so a
78
+ * TypeScript merchant sees `paymentId`/`state` in intellisense, not only at runtime.
79
+ */
80
+ toProblemDetails(challengeId) {
81
+ return {
82
+ ...super.toProblemDetails(challengeId),
83
+ ...this.paymentId !== void 0 ? { paymentId: this.paymentId } : {},
84
+ state: this.state
85
+ };
86
+ }
87
+ };
88
+ var PaymentRejectedError = class extends Errors.BadRequestError {
89
+ /** Discriminant; test it with {@link isPaymentRejected}. */
90
+ isPaymentRejected = true;
91
+ /** The gateway's error code, for programmatic handling. */
92
+ code;
93
+ constructor(options) {
94
+ super({ reason: `${options.detail} (${options.code})` });
95
+ this.code = options.code;
96
+ }
97
+ };
98
+
99
+ // src/server.ts
17
100
  var DEADLINE_SKEW_TOLERANCE_MS = 6e4;
18
101
  var SOLANA_MAX_FULFILLMENT_DEADLINE_SECONDS = import_payment_request_sender_auth2.REPLAY_HORIZON_SECS - 10;
19
102
  function registerServer(config) {
@@ -55,6 +138,17 @@ function registerServer(config) {
55
138
  nowSec: Math.floor(now() / 1e3)
56
139
  }
57
140
  );
141
+ const intentId = intentIdOf(credential.challenge);
142
+ if (intentId === void 0) {
143
+ throw new Error(
144
+ `atum-escrow: the challenge carries no '${INTENT_ID_META_KEY}' metadata, so this payment cannot be de-duplicated and is refused`
145
+ );
146
+ }
147
+ if (pr.request_id !== chargeRequestId(intentId, pr.source.account)) {
148
+ throw new Error(
149
+ "atum-escrow: request_id is not derived from the challenge's purchase identifier, so a retry of this payment would not de-duplicate"
150
+ );
151
+ }
58
152
  const dest = pr.destination?.[0];
59
153
  if (!dest) {
60
154
  throw new Error("atum-escrow: credential is missing a destination");
@@ -63,8 +157,7 @@ function registerServer(config) {
63
157
  throw new Error("atum-escrow: destination account does not match the challenge");
64
158
  }
65
159
  const expectedDestAsset = assetIdentifier(extra.destination.network, extra.destination.asset);
66
- const destAssetMatches = namespaceOf(extra.destination.network) === "solana" ? dest.asset_identifier === expectedDestAsset : dest.asset_identifier.toLowerCase() === expectedDestAsset.toLowerCase();
67
- if (!destAssetMatches) {
160
+ if (!sameAssetIdentifier(dest.asset_identifier, expectedDestAsset)) {
68
161
  throw new Error("atum-escrow: destination asset does not match the challenge");
69
162
  }
70
163
  if (pr.fulfillment_amount !== extra.fulfillmentAmount) {
@@ -86,7 +179,9 @@ function registerServer(config) {
86
179
  throw new Error("atum-escrow: fulfillment_proxy does not match the challenge");
87
180
  }
88
181
  if (!sameAddress(pr.fulfillment_verifier?.account ?? "", expectedVerifier.account)) {
89
- throw new Error("atum-escrow: fulfillment_verifier.account does not match the challenge releaser");
182
+ throw new Error(
183
+ "atum-escrow: fulfillment_verifier.account does not match the challenge releaser"
184
+ );
90
185
  }
91
186
  if ((pr.fulfillment_verifier?.endpoint ?? "") !== expectedVerifier.endpoint) {
92
187
  throw new Error("atum-escrow: fulfillment_verifier.endpoint does not match the challenge");
@@ -94,11 +189,16 @@ function registerServer(config) {
94
189
  const nowMs = now();
95
190
  const quote = Date.parse(pr.quote_deadline);
96
191
  const fulfillment = Date.parse(pr.fulfillment_deadline);
97
- if (!(nowMs < quote && quote < fulfillment)) {
192
+ if (!(quote < fulfillment)) {
98
193
  throw new Error(
99
- "atum-escrow: deadline ordering violated (now < quote_deadline < fulfillment_deadline)"
194
+ "atum-escrow: quote_deadline must be before fulfillment_deadline (quote collection has to close before settlement is due)"
100
195
  );
101
196
  }
197
+ if (!(nowMs < quote)) {
198
+ throw new Errors2.PaymentActionRequiredError({
199
+ reason: "this authorization's quote window has already closed (quote_deadline is in the past), so it cannot start a payment. To re-attempt a purchase, request a fresh challenge and sign it again, keeping the SAME intent id \u2014 the re-attempt then resolves onto the payment already in flight instead of taking a second one. A stored credential cannot be re-presented: its deadlines are fixed at the moment it was signed"
200
+ });
201
+ }
102
202
  if (quote > nowMs + extra.quoteDeadlineSeconds * 1e3 + DEADLINE_SKEW_TOLERANCE_MS) {
103
203
  throw new Error("atum-escrow: quote_deadline exceeds the advertised budget");
104
204
  }
@@ -111,9 +211,11 @@ function registerServer(config) {
111
211
  const result = await config.submitter.submit(pr);
112
212
  const fc = result.fulfillment_confirmation;
113
213
  if (!fc) {
114
- throw new Error(
115
- "atum-escrow: settlement did not complete within the synchronous window (no FulfillmentConfirmation)"
116
- );
214
+ const terminal = result.status === "failed" || result.status === "cancelled";
215
+ if (terminal) {
216
+ throw new SettlementFailedError({ paymentId: result.payment_id, state: result.status });
217
+ }
218
+ throw new SettlementPendingError({ paymentId: result.payment_id });
117
219
  }
118
220
  return {
119
221
  method: METHOD_NAME,
@@ -147,13 +249,21 @@ function validateCorridor(corridor) {
147
249
  throw new Error("atum-escrow: corridor must configure at least one source");
148
250
  }
149
251
  for (const src of corridor.sources) {
150
- for (const field of ["network", "escrow", "reserver", "releaser", "fulfillmentVerifierEndpoint"]) {
252
+ for (const field of [
253
+ "network",
254
+ "escrow",
255
+ "reserver",
256
+ "releaser",
257
+ "fulfillmentVerifierEndpoint"
258
+ ]) {
151
259
  if (!src[field]) {
152
260
  throw new Error(`atum-escrow: source ${src.network ?? "?"} is missing '${field}'`);
153
261
  }
154
262
  }
155
263
  if (!src.assets || src.assets.length === 0 || src.assets.some((a) => !a)) {
156
- throw new Error(`atum-escrow: source ${src.network} must list at least one non-empty asset in 'assets'`);
264
+ throw new Error(
265
+ `atum-escrow: source ${src.network} must list at least one non-empty asset in 'assets'`
266
+ );
157
267
  }
158
268
  if (namespaceOf(src.network) === "tron") {
159
269
  (0, import_payment_request_sender_auth.resolveTronPermit2)(src.network);
@@ -217,6 +327,17 @@ function buildChargeRequest(corridor, select, fulfillmentAmount, options) {
217
327
  }
218
328
  };
219
329
  }
330
+ function buildChargeChallenge(corridor, select, fulfillmentAmount, options) {
331
+ if (typeof options.intentId !== "string" || options.intentId === "") {
332
+ throw new Error(
333
+ "atum-escrow: intentId must be a non-empty per-purchase identifier (e.g. an order id); the payer derives the payment's identity from it, so without one a retry would be charged as a second payment"
334
+ );
335
+ }
336
+ return {
337
+ request: buildChargeRequest(corridor, select, fulfillmentAmount, options),
338
+ meta: { [INTENT_ID_META_KEY]: options.intentId }
339
+ };
340
+ }
220
341
  async function corridorFromDefaults(defaults, params) {
221
342
  const [destDefaults, ...sourceDefaults] = await Promise.all([
222
343
  defaults.fetchChainDefaults(params.destination.network),
@@ -246,8 +367,15 @@ async function corridorFromDefaults(defaults, params) {
246
367
  }
247
368
 
248
369
  export {
370
+ isSettlementPending,
371
+ isSettlementFailed,
372
+ isPaymentRejected,
373
+ SettlementPendingError,
374
+ SettlementFailedError,
375
+ PaymentRejectedError,
249
376
  registerServer,
250
377
  validateCorridor,
251
378
  buildChargeRequest,
379
+ buildChargeChallenge,
252
380
  corridorFromDefaults
253
381
  };
package/dist/client.d.ts CHANGED
@@ -1,11 +1,187 @@
1
1
  import * as zod_v4_core from 'zod/v4/core';
2
2
  import * as z from 'zod/mini';
3
- import { Signer } from 'ethers';
4
- import { S as SenderSigner, f as SenderSignerOptions, h as SolanaClusterUnixTimeReader } from './internal-DzEGXm14.js';
5
- export { A as AtumEscrowChallenge, a as AtumEscrowCredential, C as ChargeCredentialPayload, c as ChargeRequest, d as ChargeRequestSchema, e as CredentialPayloadSchema, I as INTENT, M as METHOD_NAME, g as atumEscrowChargeMethod } from './internal-DzEGXm14.js';
3
+ import { S as SenderSigner, h as SenderSignerOptions, k as SolanaClusterUnixTimeReader } from './internal-CJEu9yUF.js';
4
+ export { A as AtumEscrowChallenge, a as AtumEscrowCredential, C as ChargeCredentialPayload, d as ChargeRequest, e as ChargeRequestSchema, f as CredentialPayloadSchema, I as INTENT, g as INTENT_ID_META_KEY, M as METHOD_NAME, i as atumEscrowChargeMethod } from './internal-CJEu9yUF.js';
6
5
  import { Method } from 'mppx';
6
+ import { Signer } from 'ethers';
7
7
  export { PaymentRequest } from './generated/index.js';
8
8
 
9
+ /**
10
+ * Result of an {@link ensureSourceApproval} call.
11
+ */
12
+ interface EnsureApprovalResult {
13
+ /** The existing allowance already covered the requirement; no transaction was sent. */
14
+ alreadySufficient: boolean;
15
+ /** The approval transaction hash, when one was sent. */
16
+ txHash?: string;
17
+ /**
18
+ * Set only when the token refused to overwrite a non-zero allowance and it had to be reset to
19
+ * zero first: the hash of that reset. Its absence means one transaction was sent, not two.
20
+ */
21
+ resetTxHash?: string;
22
+ }
23
+ /** What TronWeb reports about a broadcast transaction. Empty until it is confirmed. */
24
+ interface TronTransactionInfo {
25
+ receipt?: {
26
+ result?: string;
27
+ };
28
+ }
29
+ /**
30
+ * The slice of a TronWeb instance this package needs.
31
+ *
32
+ * Declared structurally rather than imported from `tronweb` on purpose: the package then
33
+ * type-checks and builds with tronweb absent, and consumers that never pay from Tron do not
34
+ * carry an 11 MB dependency they cannot reach. The caller constructs the instance and passes
35
+ * it in, exactly as the EVM path takes a caller-constructed ethers signer.
36
+ */
37
+ interface TronWebLike {
38
+ /**
39
+ * The ABI-and-address form, which builds the handle locally. The `contract().at(address)`
40
+ * form fetches the ABI from the node instead: a round trip per use, and an interface that
41
+ * depends on what the chain happens to report for that address.
42
+ *
43
+ * Returns `unknown` on purpose. TronWeb types this as a contract whose methods are produced
44
+ * dynamically from the ABI, so pinning it to {@link TronTokenContract} here would stop a real
45
+ * TronWeb instance assigning to this interface at all and force every caller to write a cast.
46
+ * The cast belongs in one place — next to the ABI we passed, which is what makes it true.
47
+ */
48
+ contract(abi: unknown, address: string): unknown;
49
+ trx: {
50
+ getTransactionInfo(txId: string): Promise<TronTransactionInfo>;
51
+ };
52
+ /**
53
+ * The account this instance signs as. Read to check it against `owner` before broadcasting,
54
+ * since an approval only ever applies to the account that sends it.
55
+ *
56
+ * Optional, and `false` is TronWeb's own "not set": both mean the instance cannot say which
57
+ * account it would use, and neither is treated as a mismatch.
58
+ */
59
+ defaultAddress?: {
60
+ base58?: string | false;
61
+ };
62
+ }
63
+ /** How long to wait for a broadcast approval to confirm before giving up, on either chain. */
64
+ interface ConfirmationOptions {
65
+ /**
66
+ * Default 60_000. On timeout the approval is reported as unconfirmed rather than failed: it
67
+ * was broadcast and may still confirm, so the caller is told to check it before sending
68
+ * another one.
69
+ */
70
+ timeoutMs?: number;
71
+ /** Tron only: how often to poll for the receipt. Default 1_500, under Tron's ~3s block time. */
72
+ pollIntervalMs?: number;
73
+ /** Tron only: default 100_000_000 SUN. Tron charges the caller for contract execution. */
74
+ feeLimit?: number;
75
+ }
76
+ /**
77
+ * Inputs to {@link ensureSourceApproval} and {@link needsSourceApproval}.
78
+ *
79
+ * `signer` and `tronWeb` are both optional here because which one is required is decided by
80
+ * `network`, a runtime string that no type can constrain. Supplying the wrong one for the
81
+ * chain fails with a message naming the one that was needed.
82
+ */
83
+ interface ApprovalParams {
84
+ /** CAIP-2 source chain id, e.g. `eip155:8453` or `tron:mainnet`. */
85
+ network: string;
86
+ /** Source token address. */
87
+ token: string;
88
+ /** The payer's account. */
89
+ owner: string;
90
+ /**
91
+ * The contract to approve. Defaults to the canonical Permit2 for the chain: the EVM
92
+ * deployment on `eip155:*`, and the pinned per-network address on `tron:*`.
93
+ */
94
+ spender?: string;
95
+ /**
96
+ * The minimum allowance this charge needs. Answers "is what I already have enough?".
97
+ * When omitted, only an unlimited approval counts as sufficient, so a smaller leftover
98
+ * allowance cannot be mistaken for enough and revert a larger later charge on-chain.
99
+ */
100
+ requiredAllowance?: bigint;
101
+ /**
102
+ * How much to approve when an approval IS sent. Defaults to unlimited, so later charges on
103
+ * the same token need no further transaction.
104
+ *
105
+ * On its own, a bounded amount is treated as its own requirement: an allowance still holding
106
+ * the full bound is left alone rather than re-approved. That is the right answer for a one-off
107
+ * approval, where nothing else in the call says what a later charge would need.
108
+ *
109
+ * Across repeated payments, PAIR IT WITH `requiredAllowance`. Permit2 decrements the allowance
110
+ * on every payment, so a bound measured against itself stops being sufficient the moment the
111
+ * first charge lands, and every later call sends another approval. `requiredAllowance` states
112
+ * what the NEXT charge needs, which is the question a partly spent bound can still answer yes
113
+ * to.
114
+ *
115
+ * Trade-off: a bounded approval limits what the escrow can ever move, but it is consumed as it
116
+ * is spent and eventually has to be granted again, whichever way it is used.
117
+ */
118
+ approvalAmount?: bigint;
119
+ /** An ethers signer connected to the source chain. Required for `eip155:*`. */
120
+ signer?: Signer;
121
+ /** A TronWeb instance configured for the source chain. Required for `tron:*`. */
122
+ tronWeb?: TronWebLike;
123
+ /** Broadcast and confirmation tuning. Applies to both chains. */
124
+ confirmation?: ConfirmationOptions;
125
+ /**
126
+ * Called with each transaction hash the moment it is broadcast, before the wait for it to
127
+ * confirm. Without this a caller has nothing to show for a transaction that is already
128
+ * spending their gas — and nothing to look up if the wait times out.
129
+ *
130
+ * `purpose` distinguishes the approval itself from the zero-reset some tokens demand first,
131
+ * so two hashes in one call read as one deliberate sequence rather than a double spend.
132
+ */
133
+ onSubmitted?: (txHash: string, purpose: "approval" | "reset") => void;
134
+ }
135
+
136
+ /** Whether an error came from a broadcast transaction that was never seen to confirm. */
137
+ declare function isUnconfirmed(error: unknown): boolean;
138
+
139
+ /**
140
+ * Payer-side Permit2 approval for the source token an escrow deposit moves.
141
+ *
142
+ * On EVM and Tron the escrow moves the source token through Permit2, so the payer must have
143
+ * approved Permit2 on that token BEFORE paying. Without it the payment is built, signed and
144
+ * accepted by the gateway, and then the deposit reverts on-chain at settlement — a late and
145
+ * misleading failure. Solana authorizes the transfer inside the signed deposit itself, so
146
+ * there is nothing to approve there.
147
+ *
148
+ * Worth arranging before the payment is signed: a deposit that reverts is a terminal
149
+ * settlement failure, and a terminal failure is bound to the request identifier that produced
150
+ * it, so re-attempting then needs a NEW identifier. Getting the allowance right up front keeps
151
+ * the identifier usable.
152
+ *
153
+ * This package reads chain state and broadcasts a transaction, which is why it is separate
154
+ * from the sender-auth SDK next door: that one is pure, offline and byte-parity-tested against
155
+ * Go. The caller supplies the connected signer, so this package never chooses an RPC endpoint.
156
+ */
157
+
158
+ /**
159
+ * Whether a payment on these terms would need an approval transaction first.
160
+ *
161
+ * Read-only: it spends no gas and sends nothing, so it is safe to call on every payment as a
162
+ * preflight. Returns false on Solana, which needs no approval at all.
163
+ *
164
+ * @remarks
165
+ * Unlike a try-and-see, this reports the answer instead of acting on it — use it to tell a
166
+ * payer what is about to happen, or to check whether a wallet is ready before asking for a
167
+ * signature.
168
+ */
169
+ declare function needsSourceApproval(params: ApprovalParams): Promise<boolean>;
170
+ /**
171
+ * Ensure the payer has approved Permit2 to move the source token, so the escrow deposit does
172
+ * not revert at settlement. Reads the current allowance and sends an approval only if it falls
173
+ * short; the approval it sends is unlimited unless `approvalAmount` bounds it.
174
+ *
175
+ * Applies to EVM (`params.signer`) and Tron (`params.tronWeb`). On Solana it reports
176
+ * `alreadySufficient` without touching the chain.
177
+ *
178
+ * @remarks
179
+ * Not concurrency-safe: two overlapping calls for the same `(owner, token, spender)` may both
180
+ * read a short allowance and both send an approval. They target the same value, so this only
181
+ * wastes gas; serialize calls per token if that matters.
182
+ */
183
+ declare function ensureSourceApproval(params: ApprovalParams): Promise<EnsureApprovalResult>;
184
+
9
185
  /** Configuration for the payer side of the method. */
10
186
  interface AtumEscrowClientConfig {
11
187
  /**
@@ -38,16 +214,18 @@ interface AtumEscrowClientConfig {
38
214
  * built through the payment-gateway client's request builder, so the same call handles
39
215
  * EVM, Tron, and Solana sources.
40
216
  *
41
- * Idempotency: for EVM and Tron sources the deposit nonce (and the `request_id`) are
42
- * derived deterministically from the challenge id, so even if a caller rebuilds the
43
- * credential for the same challenge the escrow admits at most one deposit — the reused
44
- * nonce reverts the second on-chain. This holds only when the merchant issues a stable,
45
- * per-intent challenge id: set `opaque` to a per-payment identifier (e.g. an order id),
46
- * reused on retry of that intent, and keep `expires` stable or absent per intent (both feed
47
- * the challenge-id HMAC). Solana sources use a replay-window nonce rather than a
48
- * deterministic one and rely on the gateway's content-keyed dedup (the request id, and
49
- * thus the payment id, are still derived deterministically from the challenge id).
50
- * Building once and resubmitting the identical bytes remains the simplest safe pattern.
217
+ * Idempotency: the `request_id` is derived from the challenge's per-intent identifier (see
218
+ * {@link INTENT_ID_META_KEY}) via the shared sender_auth SDK, and `preparePaymentRequest` then
219
+ * derives the deposit nonce from that `request_id` on every chain. So rebuilding the credential
220
+ * for the same purchase reproduces the same payment identity, and the gateway de-duplicates the
221
+ * retry onto the original payment instead of charging twice. On EVM/Tron the reused Permit2
222
+ * nonce is a second, on-chain backstop (the duplicate deposit reverts); on Solana the replay
223
+ * nonce only guards a ~128s window, so there the gateway's `request_id` dedup is the durable
224
+ * protection.
225
+ *
226
+ * A challenge that carries no such identifier cannot be paid safely, so this client refuses it
227
+ * rather than falling back to something per-attempt. See
228
+ * {@link ../server!buildChargeChallenge | buildChargeChallenge} for the merchant side.
51
229
  *
52
230
  * @param config - the payer's signer, source account, and options.
53
231
  */
@@ -87,43 +265,5 @@ declare function registerClient(config: AtumEscrowClientConfig): Method.Client<{
87
265
  };
88
266
  };
89
267
  }, undefined>;
90
- /** Result of an {@link ensureSourceApproval} call. */
91
- interface EnsureApprovalResult {
92
- /** The existing allowance already covered the required amount; no transaction was sent. */
93
- alreadySufficient: boolean;
94
- /** The approval transaction hash, when one was sent. */
95
- txHash?: string;
96
- }
97
- /**
98
- * Ensure the payer has approved the token-transfer contract (Permit2) to move the source
99
- * token, so the escrow deposit does not revert at settlement. Reads the current allowance
100
- * and sends an approval only if it falls short.
101
- *
102
- * Applies to EVM and Tron sources (which use a Permit2-style allowance); Solana sources
103
- * authorize the transfer in the signed deposit itself, so this is a no-op there.
104
- *
105
- * @param params.network - CAIP-2 source chain id.
106
- * @param params.token - source token address.
107
- * @param params.owner - the payer's account.
108
- * @param params.signer - an ethers signer able to send the approval on `network`.
109
- * @param params.spender - the contract to approve; defaults to the canonical EVM Permit2.
110
- * Required for Tron (its Permit2 address is chain-specific).
111
- * @param params.requiredAllowance - the minimum allowance this charge needs (pass the
112
- * source cap, `challenge.request.source.amount`). When omitted, the function ensures an
113
- * unlimited approval — a smaller leftover allowance is NOT treated as sufficient, so a
114
- * later larger charge cannot slip through and revert on-chain.
115
- *
116
- * Not concurrency-safe: two overlapping calls for the same `(owner, token, spender)` may
117
- * both read a short allowance and both send an approval. They target the same value, so
118
- * this only wastes gas; serialize calls per token if that matters.
119
- */
120
- declare function ensureSourceApproval(params: {
121
- network: string;
122
- token: string;
123
- owner: string;
124
- signer: Signer;
125
- spender?: string;
126
- requiredAllowance?: bigint;
127
- }): Promise<EnsureApprovalResult>;
128
268
 
129
- export { type AtumEscrowClientConfig, type EnsureApprovalResult, SenderSigner, SenderSignerOptions, ensureSourceApproval, registerClient };
269
+ export { type ApprovalParams, type AtumEscrowClientConfig, type ConfirmationOptions, type EnsureApprovalResult, SenderSigner, SenderSignerOptions, type TronWebLike, ensureSourceApproval, isUnconfirmed, needsSourceApproval, registerClient };
package/dist/client.js CHANGED
@@ -6,22 +6,29 @@
6
6
  */
7
7
  import { createRequire as __atumCreateRequire } from 'module'; const require = __atumCreateRequire(import.meta.url);
8
8
  import {
9
- ensureSourceApproval,
9
+ import_payment_request_token_approval,
10
10
  registerClient
11
- } from "./chunk-IPZJXELQ.js";
11
+ } from "./chunk-KRSFEITH.js";
12
12
  import {
13
13
  ChargeRequestSchema,
14
14
  CredentialPayloadSchema,
15
15
  INTENT,
16
+ INTENT_ID_META_KEY,
16
17
  METHOD_NAME,
17
18
  atumEscrowChargeMethod
18
- } from "./chunk-4P34CLTO.js";
19
+ } from "./chunk-OEEC5P3E.js";
20
+ var export_ensureSourceApproval = import_payment_request_token_approval.ensureSourceApproval;
21
+ var export_isUnconfirmed = import_payment_request_token_approval.isUnconfirmed;
22
+ var export_needsSourceApproval = import_payment_request_token_approval.needsSourceApproval;
19
23
  export {
20
24
  ChargeRequestSchema,
21
25
  CredentialPayloadSchema,
22
26
  INTENT,
27
+ INTENT_ID_META_KEY,
23
28
  METHOD_NAME,
24
29
  atumEscrowChargeMethod,
25
- ensureSourceApproval,
30
+ export_ensureSourceApproval as ensureSourceApproval,
31
+ export_isUnconfirmed as isUnconfirmed,
32
+ export_needsSourceApproval as needsSourceApproval,
26
33
  registerClient
27
34
  };
package/dist/index.d.ts CHANGED
@@ -1,82 +1,8 @@
1
- export { A as AtumEscrowChallenge, a as AtumEscrowCredential, b as AtumEscrowRequest, C as ChargeCredentialPayload, c as ChargeRequest, d as ChargeRequestSchema, e as CredentialPayloadSchema, I as INTENT, M as METHOD_NAME, S as SenderSigner, f as SenderSignerOptions, g as atumEscrowChargeMethod } from './internal-DzEGXm14.js';
2
- export { AtumEscrowClientConfig, EnsureApprovalResult, ensureSourceApproval, registerClient } from './client.js';
3
- export { AtumEscrowCorridor, AtumEscrowReceipt, AtumEscrowServerConfig, AtumEscrowSource, ChainDefaultsSource, FulfillmentConfirmation, PaymentSubmitter, buildChargeRequest, corridorFromDefaults, registerServer, validateCorridor } from './server.js';
1
+ export { A as AtumEscrowChallenge, a as AtumEscrowCredential, b as AtumEscrowExtra, c as AtumEscrowRequest, C as ChargeCredentialPayload, d as ChargeRequest, e as ChargeRequestSchema, f as CredentialPayloadSchema, I as INTENT, g as INTENT_ID_META_KEY, M as METHOD_NAME, S as SenderSigner, h as SenderSignerOptions, i as atumEscrowChargeMethod } from './internal-CJEu9yUF.js';
2
+ export { ApprovalParams, AtumEscrowClientConfig, ConfirmationOptions, EnsureApprovalResult, TronWebLike, ensureSourceApproval, isUnconfirmed, needsSourceApproval, registerClient } from './client.js';
3
+ export { AtumEscrowCorridor, AtumEscrowReceipt, AtumEscrowServer, AtumEscrowServerConfig, AtumEscrowSource, ChainDefaultsSource, ChargeChallenge, FulfillmentConfirmation, PaymentRejectedError, PaymentSettlementStatus, PaymentSubmitResult, PaymentSubmitter, SettlementErrorDetails, SettlementFailedError, SettlementPendingError, buildChargeChallenge, buildChargeRequest, corridorFromDefaults, isPaymentRejected, isSettlementFailed, isSettlementPending, registerServer, validateCorridor } from './server.js';
4
4
  export { PaymentRequest } from './generated/index.js';
5
5
  import 'zod/mini';
6
6
  import 'mppx';
7
7
  import 'zod/v4/core';
8
8
  import 'ethers';
9
-
10
- /**
11
- * This file was automatically generated by json-schema-to-typescript.
12
- * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
13
- * and run json-schema-to-typescript to regenerate this file.
14
- */
15
- /**
16
- * The `extra.atum` object carried inside an atum-escrow x402 PaymentRequirements entry (accepts[]): the merchant's receive-side plus the contract/role addresses and deadline budgets. Scheme-specific data the standard x402 `extra` bag treats as opaque, so it is owned here and shared by all role mechanisms. Addresses are chain-general: an EVM `0x`-hex address (20 bytes) or a base58-encoded address (Solana/Tron), per the field's CAIP-2 chain.
17
- */
18
- interface AtumEscrowExtra {
19
- /**
20
- * Where the merchant receives (CAIP-2 chain, token, address).
21
- */
22
- destination: {
23
- /**
24
- * CAIP-2 destination chain id (e.g. eip155:42161).
25
- */
26
- network: string;
27
- /**
28
- * Destination token contract address (EVM hex or base58, per the destination chain).
29
- */
30
- asset: string;
31
- /**
32
- * Merchant receive address (EVM hex or base58, per the destination chain).
33
- */
34
- address: string;
35
- };
36
- /**
37
- * Exact amount the merchant receives, in atomic token units.
38
- */
39
- fulfillmentAmount: string;
40
- /**
41
- * Source-chain escrow contract (the x402 payTo); EVM hex or base58, per the source chain.
42
- */
43
- escrow: string;
44
- /**
45
- * Destination-chain fulfillment proxy contract (EVM hex or base58, per the destination chain).
46
- */
47
- fulfillmentProxy: string;
48
- /**
49
- * Atum reserver role address (escrow deposit witness); EVM hex or base58, per the source chain.
50
- */
51
- reserver: string;
52
- /**
53
- * Atum releaser role address (escrow deposit witness); EVM hex or base58, per the source chain.
54
- */
55
- releaser: string;
56
- /**
57
- * Source-chain fulfillment-verifier endpoint the payment request carries. The verifier account and the quote_selector are the releaser and reserver respectively (the network derives the deposit witness roles from them), so only the endpoint is not otherwise present in this object.
58
- */
59
- fulfillmentVerifierEndpoint: string;
60
- /**
61
- * Recommended quote-deadline budget in seconds, relative to signing time.
62
- */
63
- quoteDeadlineSeconds: number;
64
- /**
65
- * Recommended fulfillment-deadline budget in seconds, relative to signing time.
66
- */
67
- fulfillmentDeadlineSeconds: number;
68
- /**
69
- * Solana source only: cluster name used to build the 32-byte cluster_id in the V3 escrow signature domain. Optional; the facilitator requires it for a Solana source.
70
- */
71
- svmSignatureClusterId?: string;
72
- /**
73
- * Solana source only: escrow signature-domain version (V3 domain separation). Optional; required for a Solana source.
74
- */
75
- svmSignatureDomainVersion?: number;
76
- /**
77
- * Solana source only: merchant-stamped issue time in epoch seconds. The merchant reads the cluster clock so the client makes no RPC and the 402 stays self-contained. Optional; required for a Solana source.
78
- */
79
- issuedAt?: string;
80
- }
81
-
82
- export type { AtumEscrowExtra };