@atumlabs/mppx-atum-escrow 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # @atumlabs/mppx-atum-escrow
2
2
 
3
- > **Proprietary software - not open source.**
3
+ > Copyright 2026 Atum Labs, Inc.
4
4
  >
5
- > Copyright (c) 2026 Atum Labs, Inc. All rights reserved. Access to and use of `@atumlabs/mppx-atum-escrow` are limited to entities expressly authorized by Atum and are governed by the Atum MPP Escrow SDK Proprietary License Agreement in the LICENSE file included with this package. Do not redistribute, publish, mirror, sublicense, or provide this package to any unauthorized person. By accessing, installing, copying, or using the package, you agree to the license terms.
5
+ > Licensed under the Apache License, Version 2.0. See the `LICENSE` file
6
+ > distributed with this package.
6
7
 
7
8
  An [`mppx`](https://www.npmjs.com/package/mppx) payment method that lets a merchant accept
8
9
  **cross-chain stablecoin payments** through Atum, over the Machine Payments Protocol (MPP).
10
+ Full documentation: [docs.atum.xyz](https://docs.atum.xyz).
9
11
 
10
12
  - The **payer** funds the payment in any supported source asset and chain (e.g. USDC on Base,
11
13
  USDT on Tron, or USDC on Solana).
@@ -81,9 +83,6 @@ The merchant examples below also use
81
83
  [`@atumlabs/payment-gateway-client`](https://www.npmjs.com/package/@atumlabs/payment-gateway-client)
82
84
  for the gateway connection and corridor defaults.
83
85
 
84
- > The snippets below are illustrative. For complete, runnable, end-to-end integrations
85
- > (payer + merchant), see the examples repository: **https://github.com/Atum-Labs/examples**.
86
-
87
86
  ## Key concepts
88
87
 
89
88
  | Term | What it means |
@@ -101,6 +100,18 @@ for the gateway connection and corridor defaults.
101
100
  All addresses and amounts are strings; all amounts are **atomic units** (never floats). Addresses
102
101
  are in each chain's native form — `0x…` hex for EVM, base58 for Tron and Solana.
103
102
 
103
+ ### Which chains and assets can a corridor use?
104
+
105
+ Atum supports many corridors. The identifier for every supported token is listed under
106
+ [supported assets](https://docs.atum.xyz/get-started/reference/supported-assets), and the chain ids
107
+ under [supported networks](https://docs.atum.xyz/get-started/reference/supported-networks). Assets
108
+ are named with [CAIP-19](https://chainagnostic.org/CAIPs/caip-19) identifiers, for example
109
+ `eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e` for USDC on Base Sepolia.
110
+
111
+ Copy identifiers exactly: base58 values, such as Solana token mints and account addresses, are
112
+ case-sensitive. The escrow and role addresses for each chain are filled in by
113
+ `corridorFromDefaults`, so you never paste those by hand.
114
+
104
115
  ## Quick start — merchant (server)
105
116
 
106
117
  ```ts
@@ -231,11 +242,66 @@ await ensureSourceApproval({
231
242
  owner: wallet.address,
232
243
  signer: wallet,
233
244
  // Pass the source cap this charge needs (from the challenge). A leftover smaller allowance
234
- // then won't be mistaken for enough. Omit it to ensure an unlimited approval instead.
245
+ // then won't be mistaken for enough.
235
246
  requiredAllowance: BigInt(challenge.request.source.amount),
236
247
  });
237
248
  ```
238
249
 
250
+ Paying from Tron takes a TronWeb instance instead of an ethers signer; everything else is the
251
+ same, and the Permit2 address for the network is resolved for you:
252
+
253
+ ```ts
254
+ await ensureSourceApproval({ network: "tron:mainnet", token, owner, tronWeb });
255
+ ```
256
+
257
+ The approval waits to be mined before returning, bounded by `confirmation.timeoutMs` (one minute
258
+ by default); a timeout is reported as unconfirmed rather than failed, since the transaction may
259
+ still land. `onSubmitted` hands you the hash the moment it is broadcast.
260
+
261
+ `isUnconfirmed(error)` tells the two apart. It matters because the responses are opposite: a
262
+ failed approval needs another one, an unconfirmed approval needs a look at the transaction and
263
+ nothing else. Sending a second one on top of the first only pays for an allowance you are already
264
+ getting.
265
+
266
+ ```ts
267
+ try {
268
+ await ensureSourceApproval({ network, token, owner, signer });
269
+ } catch (error) {
270
+ if (isUnconfirmed(error)) {
271
+ // Broadcast, not yet seen to confirm. Check the hash from onSubmitted; do not re-send.
272
+ } else {
273
+ throw error;
274
+ }
275
+ }
276
+ ```
277
+
278
+ On EVM, tokens that refuse to overwrite a non-zero allowance — mainnet USDT most notably — are
279
+ reset to zero first, automatically and only where the token demands it; the result then carries a
280
+ `resetTxHash` too. Tron does not do this, because an ordinary TRC-20 accepts the change in place.
281
+
282
+ The approval sent is unlimited, so later charges on the same token need no further transaction.
283
+ To bound the approval that gets sent, pass `approvalAmount`. On its own the amount is treated as
284
+ its own requirement, so an allowance still holding the full bound is left alone rather than
285
+ re-approved — the right answer for a one-off approval. Across repeated payments, pair it with
286
+ `requiredAllowance`: Permit2 decrements the allowance on every payment, so a bound measured
287
+ against itself stops covering itself the moment the first charge lands, and every later call
288
+ would send another approval. Either way a bounded approval is consumed as it is spent and
289
+ eventually has to be granted again.
290
+
291
+ These helpers raise an allowance to what a payment needs; they never lower one. A wallet already
292
+ holding more than `approvalAmount` is left exactly as it is and reports `alreadySufficient`
293
+ without sending anything. Reducing or revoking an allowance is a separate operation, and not one
294
+ these perform.
295
+
296
+ `needsSourceApproval(params)` takes the same arguments and answers the same question without
297
+ sending anything, so it is safe to call before every charge:
298
+
299
+ ```ts
300
+ if (await needsSourceApproval({ network, token, owner, signer })) {
301
+ // tell the payer a one-off approval transaction is coming, before asking them to sign
302
+ }
303
+ ```
304
+
239
305
  ## What the merchant `verify()` guarantees
240
306
 
241
307
  `verify()` fails fast (no chain I/O) before submitting, and throws if any check fails. The Atum
@@ -274,6 +340,9 @@ On success you get an `AtumEscrowReceipt` — the MPP receipt plus the full sett
274
340
  }
275
341
  ```
276
342
 
343
+ `verify()` resolves with this type, so `fulfillmentConfirmation` is available straight off the
344
+ result. `AtumEscrowReceipt` is exported, so you can name it in your own function signatures.
345
+
277
346
  ## Retries and idempotency
278
347
 
279
348
  A retry must never become a second charge, and two different purchases must never collapse into
@@ -465,7 +534,7 @@ Import everything from the root, or from the role-specific entry points (which e
465
534
  each side needs):
466
535
 
467
536
  - `@atumlabs/mppx-atum-escrow` — everything below
468
- - `@atumlabs/mppx-atum-escrow/client` — `registerClient`, `ensureSourceApproval` + payer types
537
+ - `@atumlabs/mppx-atum-escrow/client` — `registerClient`, `ensureSourceApproval`, `needsSourceApproval` + payer types
469
538
  - `@atumlabs/mppx-atum-escrow/server` — `registerServer`, `buildChargeChallenge`,
470
539
  `buildChargeRequest`, `validateCorridor`, `corridorFromDefaults`, the settlement errors +
471
540
  merchant types
@@ -473,12 +542,13 @@ each side needs):
473
542
  | Export | Description |
474
543
  | --- | --- |
475
544
  | `registerClient(config)` | Payer-side method. `config`: `{ signer, account, now?, solanaClockReader? }`. |
476
- | `registerServer(config)` | Merchant-side method. `config`: `{ submitter, now? }`. |
545
+ | `registerServer(config)` | Merchant-side method. `config`: `{ submitter, now? }`. Returns an `AtumEscrowServer`, whose `verify()` resolves with an `AtumEscrowReceipt`. |
477
546
  | `buildChargeChallenge(corridor, select, fulfillmentAmount, { intentId, issuedAt? })` | Builds a `charge` challenge — the payment terms plus the per-purchase identifier that makes a retry safe. **Use this.** Returns `{ request, meta }`. |
478
547
  | `buildChargeRequest(corridor, select, fulfillmentAmount, options?)` | The payment terms alone, without the identifier. For supplying challenge metadata by hand. |
479
548
  | `validateCorridor(corridor)` | Validates a corridor's shape and per-source addresses (run automatically by both builders). |
480
549
  | `corridorFromDefaults(defaults, params)` | Builds a corridor by fetching escrow/role/proxy addresses from the gateway `/defaults`. |
481
- | `ensureSourceApproval(params)` | Ensures the payer has approved Permit2 to move the source token (EVM/Tron; no-op on Solana). |
550
+ | `ensureSourceApproval(params)` | Ensures the payer has approved Permit2 to move the source token. `signer` for EVM, `tronWeb` for Tron; no-op on Solana. |
551
+ | `needsSourceApproval(params)` | Whether `ensureSourceApproval` would send a transaction. Read-only, spends no gas. |
482
552
  | `SettlementPendingError`, `SettlementFailedError`, `PaymentRejectedError` | The non-receipt outcomes of `verify()`. See [Settlement outcomes](#settlement-outcomes). |
483
553
  | `isSettlementPending`, `isSettlementFailed`, `isPaymentRejected` | Guards for the above. |
484
554
  | `atumEscrowChargeMethod` | The base `mppx` method (advanced/custom wiring). |
@@ -488,10 +558,17 @@ each side needs):
488
558
 
489
559
  Key types: `AtumEscrowCorridor`, `AtumEscrowSource`, `SenderSigner`, `SenderSignerOptions`,
490
560
  `PaymentSubmitter`, `PaymentSubmitResult`, `PaymentSettlementStatus`, `AtumEscrowClientConfig`,
491
- `AtumEscrowServerConfig`, `ChainDefaultsSource`, `EnsureApprovalResult`, `AtumEscrowChallenge`,
492
- `AtumEscrowCredential`, `AtumEscrowReceipt`, `ChargeChallenge`, `ChargeRequest`,
561
+ `AtumEscrowServer`, `AtumEscrowServerConfig`, `ChainDefaultsSource`, `EnsureApprovalResult`,
562
+ `AtumEscrowChallenge`, `AtumEscrowCredential`, `AtumEscrowReceipt`, `ChargeChallenge`, `ChargeRequest`,
493
563
  `SettlementErrorDetails`, `PaymentRequest`, `FulfillmentConfirmation`.
494
564
 
495
565
  > **EVM implementation note:** on EVM the deposit authorization is a Permit2
496
566
  > `PermitWitnessTransferFrom` signature; Tron uses the equivalent TIP-712 typed data, and Solana
497
567
  > uses an ed25519-signed deposit. The public API is the same across all three.
568
+
569
+ ## Further reading
570
+
571
+ - [Accepting and making MPP payments with Atum](https://docs.atum.xyz/payment-protocols/mpp/overview)
572
+ - [Supported assets](https://docs.atum.xyz/get-started/reference/supported-assets) and [supported networks](https://docs.atum.xyz/get-started/reference/supported-networks)
573
+ - [MPP](https://mpp.dev)
574
+ - [Atum documentation](https://docs.atum.xyz)
@@ -2,6 +2,12 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
2
2
  ==============================================================================
3
3
  @atumlabs/mppx-atum-escrow
4
4
 
5
+ @atumlabs/mppx-atum-escrow is licensed under the Apache License, Version 2.0, as stated
6
+ in the package-root LICENSE file. THIS file is not that license. This
7
+ package includes the third-party software identified below; the licenses and
8
+ notices reproduced here apply only to those third-party components. They do not
9
+ modify the Apache-2.0 license governing this package or any Atum-authored code.
10
+
5
11
  The published artifact of this package (the compiled code under dist/) statically
6
12
  bundles the third-party open-source components listed below; their source is
7
13
  redistributed within this package, so their license and copyright notices are
@@ -11,9 +17,9 @@ as (peer)dependencies of this package (for example @solana/web3.js, ethers, bs58
11
17
  tweetnacl, zod, and mppx) — those ship under their own licenses and are not
12
18
  redistributed here.
13
19
 
14
- LICENSE SUMMARY (27 components)
20
+ LICENSE SUMMARY (28 components)
15
21
  ------------------------------------------------------------------------------
16
- 27 MIT
22
+ 28 MIT
17
23
  ==============================================================================
18
24
 
19
25
  ------------------------------------------------------------------------------
@@ -43,6 +49,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
49
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
44
50
  THE SOFTWARE.
45
51
 
52
+ ------------------------------------------------------------------------------
53
+ agent-base 6.0.2 (MIT)
54
+ Author: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)
55
+ ------------------------------------------------------------------------------
56
+
57
+ [No license file is distributed inside this package. Declared license: MIT; author: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/).]
58
+
46
59
  ------------------------------------------------------------------------------
47
60
  asynckit 0.4.0 (MIT)
48
61
  Author: Alex Indigo <iam@alexindigo.com>
@@ -71,7 +84,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
71
84
  SOFTWARE.
72
85
 
73
86
  ------------------------------------------------------------------------------
74
- axios 1.13.2 (MIT)
87
+ axios 1.20.0 (MIT)
75
88
  Author: Matt Zabriskie
76
89
  ------------------------------------------------------------------------------
77
90
 
@@ -556,6 +569,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
556
569
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
557
570
  SOFTWARE.
558
571
 
572
+ ------------------------------------------------------------------------------
573
+ https-proxy-agent 5.0.1 (MIT)
574
+ Author: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)
575
+ ------------------------------------------------------------------------------
576
+
577
+ [No license file is distributed inside this package. Declared license: MIT; author: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/).]
578
+
559
579
  ------------------------------------------------------------------------------
560
580
  math-intrinsics 1.1.0 (MIT)
561
581
  Author: Jordan Harband <ljharb@gmail.com>
@@ -665,32 +685,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
665
685
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
666
686
  SOFTWARE.
667
687
 
668
- ------------------------------------------------------------------------------
669
- proxy-from-env 1.1.0 (MIT)
670
- Author: Rob Wu <rob@robwu.nl> (https://robwu.nl/)
671
- ------------------------------------------------------------------------------
672
-
673
- The MIT License
674
-
675
- Copyright (C) 2016-2018 Rob Wu <rob@robwu.nl>
676
-
677
- Permission is hereby granted, free of charge, to any person obtaining a copy of
678
- this software and associated documentation files (the "Software"), to deal in
679
- the Software without restriction, including without limitation the rights to
680
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
681
- of the Software, and to permit persons to whom the Software is furnished to do
682
- so, subject to the following conditions:
683
-
684
- The above copyright notice and this permission notice shall be included in all
685
- copies or substantial portions of the Software.
686
-
687
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
688
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
689
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
690
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
691
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
692
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
693
-
694
688
  ------------------------------------------------------------------------------
695
689
  supports-color 10.2.2 (MIT)
696
690
  Author: Sindre Sorhus <sindresorhus@gmail.com>
@@ -11,8 +11,9 @@ import {
11
11
  intentIdOf,
12
12
  namespaceOf,
13
13
  require_dist2 as require_dist,
14
- sameAddress
15
- } from "./chunk-Z3AUNEU5.js";
14
+ sameAddress,
15
+ sameAssetIdentifier
16
+ } from "./chunk-LAWFMGYD.js";
16
17
 
17
18
  // src/server.ts
18
19
  var import_payment_request_sender_auth2 = __toESM(require_dist(), 1);
@@ -156,8 +157,7 @@ function registerServer(config) {
156
157
  throw new Error("atum-escrow: destination account does not match the challenge");
157
158
  }
158
159
  const expectedDestAsset = assetIdentifier(extra.destination.network, extra.destination.asset);
159
- const destAssetMatches = namespaceOf(extra.destination.network) === "solana" ? dest.asset_identifier === expectedDestAsset : dest.asset_identifier.toLowerCase() === expectedDestAsset.toLowerCase();
160
- if (!destAssetMatches) {
160
+ if (!sameAssetIdentifier(dest.asset_identifier, expectedDestAsset)) {
161
161
  throw new Error("atum-escrow: destination asset does not match the challenge");
162
162
  }
163
163
  if (pr.fulfillment_amount !== extra.fulfillmentAmount) {
@@ -179,7 +179,9 @@ function registerServer(config) {
179
179
  throw new Error("atum-escrow: fulfillment_proxy does not match the challenge");
180
180
  }
181
181
  if (!sameAddress(pr.fulfillment_verifier?.account ?? "", expectedVerifier.account)) {
182
- 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
+ );
183
185
  }
184
186
  if ((pr.fulfillment_verifier?.endpoint ?? "") !== expectedVerifier.endpoint) {
185
187
  throw new Error("atum-escrow: fulfillment_verifier.endpoint does not match the challenge");
@@ -209,7 +211,7 @@ function registerServer(config) {
209
211
  const result = await config.submitter.submit(pr);
210
212
  const fc = result.fulfillment_confirmation;
211
213
  if (!fc) {
212
- const terminal = result.status === "failed" || result.status === "cancelled";
214
+ const terminal = result.status === "failed";
213
215
  if (terminal) {
214
216
  throw new SettlementFailedError({ paymentId: result.payment_id, state: result.status });
215
217
  }
@@ -247,13 +249,21 @@ function validateCorridor(corridor) {
247
249
  throw new Error("atum-escrow: corridor must configure at least one source");
248
250
  }
249
251
  for (const src of corridor.sources) {
250
- 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
+ ]) {
251
259
  if (!src[field]) {
252
260
  throw new Error(`atum-escrow: source ${src.network ?? "?"} is missing '${field}'`);
253
261
  }
254
262
  }
255
263
  if (!src.assets || src.assets.length === 0 || src.assets.some((a) => !a)) {
256
- 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
+ );
257
267
  }
258
268
  if (namespaceOf(src.network) === "tron") {
259
269
  (0, import_payment_request_sender_auth.resolveTronPermit2)(src.network);