@atumlabs/mppx-atum-escrow 0.3.0 → 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.
- package/CHANGELOG.md +31 -0
- package/README.md +85 -9
- package/THIRD-PARTY-NOTICES.txt +7 -0
- package/dist/{chunk-ZFA5SSUP.js → chunk-KRSFEITH.js} +3364 -409
- package/dist/{chunk-Z3AUNEU5.js → chunk-OEEC5P3E.js} +1295 -282
- package/dist/{chunk-4YD6566T.js → chunk-TPLD2L6B.js} +17 -7
- package/dist/client.d.ts +180 -42
- package/dist/client.js +9 -4
- package/dist/index.d.ts +3 -77
- package/dist/index.js +10 -5
- package/dist/{internal-9tB7y-A7.d.ts → internal-CJEu9yUF.d.ts} +113 -24
- package/dist/server.d.ts +38 -42
- package/dist/server.js +2 -2
- package/package.json +10 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Clarified that the approval helpers raise an allowance and never lower one: a wallet already holding more than `approvalAmount` is left as it is and reports `alreadySufficient`. The wording said `approvalAmount` caps what the escrow can ever move, which is only true when no larger allowance already exists. Reducing or revoking an allowance is a separate operation these do not perform.
|
|
14
|
+
- `ensureSourceApproval` refuses to send when the client that would sign is not the `owner` whose allowance it read. An approval only ever applies to the account that sends it, so a mismatched signer approved that account instead, returned a transaction hash that read as success, and left the owner exactly as unable to pay as before — a silent failure that surfaced only as a settlement revert. Read-only `needsSourceApproval` is unaffected: asking whether a payer is ready without holding their keys is a legitimate preflight, and a client that cannot report which account it signs as is not treated as a mismatch.
|
|
15
|
+
- Tron approvals wait for confirmation before returning, so the hash they report means mined rather than broadcast — a payer that deposited immediately afterwards would otherwise race their own approval. The EVM path already waited; what is new there is a bound on how long it will wait (`confirmation.timeoutMs`, one minute by default) instead of waiting forever, and an `onSubmitted` callback that hands you the hash the moment it is broadcast so a stuck approval can still be looked up.
|
|
16
|
+
- On EVM, `ensureSourceApproval` resets an allowance to zero before setting a new one, but only where the token demands it. Some tokens — mainnet USDT most notably — refuse to move an allowance straight from one non-zero value to another, so a payer holding any leftover allowance could not be approved at all and saw a bare on-chain revert. The check is a simulated call that costs nothing and sends nothing, and a wallet with no allowance skips it entirely, so an ordinary approval still takes exactly one transaction. Tron does not do this: an ordinary TRC-20 accepts the change in place, and guessing otherwise would burn an extra transaction at Tron's fee limit — if a Tron token ever does refuse, the error names the manual remedy.
|
|
17
|
+
- The approval helpers are now one implementation shared by this package, the x402 scheme and the payment-gateway client, so the three cannot drift apart on what counts as a sufficient allowance. The exported names and their behaviour are unchanged.
|
|
18
|
+
- `ChargeRequest` declares the Solana `issuedAt` on its `extra` itself, instead of inheriting it
|
|
19
|
+
from the shared x402 request type. That type dropped the field — an x402 resource server rebuilds
|
|
20
|
+
its payment requirements on the paid request and compares them against what the payer echoed, and
|
|
21
|
+
a clock reading cannot survive that comparison. MPP has no such rebuild, so a merchant-stamped
|
|
22
|
+
issue time stays valid here and `buildChargeRequest` still emits it exactly as before. Type-only:
|
|
23
|
+
the emitted JavaScript is byte-identical, so nothing changes at runtime and no code needs editing
|
|
24
|
+
on upgrade.
|
|
25
|
+
- A destination identifier whose CAIP namespaces are not lowercase, such as `SOLANA:` or `SPL:`, is now accepted on Solana. Previously that chain compared the whole identifier byte for byte and refused it. CAIP pins both namespaces lowercase, so this only forgives a spelling the schema already treats as equivalent.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`isUnconfirmed`**, for telling a broadcast-but-unseen approval apart from one that failed. The wait for a confirmation is bounded, and reaching that bound is not a failure: the transaction may confirm a moment later. The two need opposite responses — a failure needs another approval, an unconfirmed one needs the transaction checked and nothing else — and a caller that treats them alike pays for an allowance they are already getting. Previously the distinction existed only as an undocumented property on the error.
|
|
29
|
+
- **`needsSourceApproval`**, the read-only counterpart to `ensureSourceApproval`. It takes the same arguments and answers the same question, but only reads the allowance: no transaction, no gas. Use it to tell a payer that a one-off approval is due before asking them to sign, instead of letting them find out when one is broadcast.
|
|
30
|
+
- **`ensureSourceApproval` now works on Tron.** It previously documented Tron support it did not have — the implementation reached the chain through ethers, which cannot talk to Tron, so a Tron source failed either for want of a `spender` or somewhere inside ethers. Pass a TronWeb instance as `tronWeb` in place of the ethers `signer`, and the Permit2 address for the network is resolved for you rather than demanded as `spender`. `tronweb` is not a dependency of this package; the caller constructs the instance, exactly as they already construct the ethers signer.
|
|
31
|
+
- **`approvalAmount`**, for granting a bounded approval instead of an unlimited one, capping what the escrow can ever move. On its own the amount is its own requirement, so an allowance still holding the full bound is left alone rather than re-approved — the right answer for a one-off approval. Across repeated payments, pair it with `requiredAllowance`: a bound measured against itself stops covering itself the moment the first charge is decremented from it, and every later call would send another approval. The trade-off to weigh is that Permit2 decrements the allowance on every payment, so a bounded approval is consumed and eventually has to be granted again. Asking to approve less than an explicit `requiredAllowance` is refused outright, since that approval could never satisfy the charge it was granted for.
|
|
32
|
+
- `AtumEscrowServer`, the type `registerServer` returns, for naming the method where it is built in one place and registered in another. Its `verify()` now resolves with an `AtumEscrowReceipt`, so `fulfillmentConfirmation` is available directly on the result: any cast you added to reach it can be removed. Casts already in place continue to compile, so this needs no change on upgrade.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **`ensureSourceApproval` no longer sends a fresh approval before every payment after the first.** Permit2 decrements the payer's allowance on each transfer it makes, so an allowance granted as unlimited stops being exactly `MaxUint256` the moment the first payment settles. Sufficiency was measured against that exact value, so from the second payment onward the check never short-circuited again and every charge was preceded by another approval transaction. The payments were correct; the payer was buying an allowance they already held. Sufficiency is now measured against half of `MaxUint256`, which separates an unlimited approval that has been partly spent from one a payer bounded deliberately — crossing that floor would take more token units than any supply contains. Nothing changes for a caller passing `requiredAllowance`: that comparison was already right, and only the unlimited case was wrong.
|
|
36
|
+
- A payer whose `signer` option carries a blank `pinnedAddress` falls back to the payer's own account. Blank covers an empty string, which is how an unset variable arrives, and a whitespace only value, which is how one read from a file or a padded shell variable arrives. It previously passed the value straight through, which the payment-gateway client now refuses instead of reading as "no pin", and the two blank forms took different paths.
|
|
37
|
+
- `verify()` compares the payer's destination asset identifier against the merchant's advertised one part by part, instead of lowercasing the whole string for every chain except Solana. A Tron `trc20:` reference is base58, where capitalisation is part of the value, so the old fold treated two distinct tokens as the same one on a field the deposit signature does not cover. The token reference and a Solana chain reference are now compared the way the neighbouring address comparison already documented, and only the two CAIP namespaces fold.
|
|
38
|
+
|
|
8
39
|
## [0.3.0] - 2026-07-30
|
|
9
40
|
|
|
10
41
|
### Added
|
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
An [`mppx`](https://www.npmjs.com/package/mppx) payment method that lets a merchant accept
|
|
8
8
|
**cross-chain stablecoin payments** through Atum, over the Machine Payments Protocol (MPP).
|
|
9
|
+
Full documentation: [docs.atum.xyz](https://docs.atum.xyz).
|
|
9
10
|
|
|
10
11
|
- The **payer** funds the payment in any supported source asset and chain (e.g. USDC on Base,
|
|
11
12
|
USDT on Tron, or USDC on Solana).
|
|
@@ -81,9 +82,6 @@ The merchant examples below also use
|
|
|
81
82
|
[`@atumlabs/payment-gateway-client`](https://www.npmjs.com/package/@atumlabs/payment-gateway-client)
|
|
82
83
|
for the gateway connection and corridor defaults.
|
|
83
84
|
|
|
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
85
|
## Key concepts
|
|
88
86
|
|
|
89
87
|
| Term | What it means |
|
|
@@ -101,6 +99,18 @@ for the gateway connection and corridor defaults.
|
|
|
101
99
|
All addresses and amounts are strings; all amounts are **atomic units** (never floats). Addresses
|
|
102
100
|
are in each chain's native form — `0x…` hex for EVM, base58 for Tron and Solana.
|
|
103
101
|
|
|
102
|
+
### Which chains and assets can a corridor use?
|
|
103
|
+
|
|
104
|
+
Atum supports many corridors. The identifier for every supported token is listed under
|
|
105
|
+
[supported assets](https://docs.atum.xyz/get-started/reference/supported-assets), and the chain ids
|
|
106
|
+
under [supported networks](https://docs.atum.xyz/get-started/reference/supported-networks). Assets
|
|
107
|
+
are named with [CAIP-19](https://chainagnostic.org/CAIPs/caip-19) identifiers, for example
|
|
108
|
+
`eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e` for USDC on Base Sepolia.
|
|
109
|
+
|
|
110
|
+
Copy identifiers exactly: base58 values, such as Solana token mints and account addresses, are
|
|
111
|
+
case-sensitive. The escrow and role addresses for each chain are filled in by
|
|
112
|
+
`corridorFromDefaults`, so you never paste those by hand.
|
|
113
|
+
|
|
104
114
|
## Quick start — merchant (server)
|
|
105
115
|
|
|
106
116
|
```ts
|
|
@@ -231,11 +241,66 @@ await ensureSourceApproval({
|
|
|
231
241
|
owner: wallet.address,
|
|
232
242
|
signer: wallet,
|
|
233
243
|
// Pass the source cap this charge needs (from the challenge). A leftover smaller allowance
|
|
234
|
-
// then won't be mistaken for enough.
|
|
244
|
+
// then won't be mistaken for enough.
|
|
235
245
|
requiredAllowance: BigInt(challenge.request.source.amount),
|
|
236
246
|
});
|
|
237
247
|
```
|
|
238
248
|
|
|
249
|
+
Paying from Tron takes a TronWeb instance instead of an ethers signer; everything else is the
|
|
250
|
+
same, and the Permit2 address for the network is resolved for you:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
await ensureSourceApproval({ network: "tron:mainnet", token, owner, tronWeb });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The approval waits to be mined before returning, bounded by `confirmation.timeoutMs` (one minute
|
|
257
|
+
by default); a timeout is reported as unconfirmed rather than failed, since the transaction may
|
|
258
|
+
still land. `onSubmitted` hands you the hash the moment it is broadcast.
|
|
259
|
+
|
|
260
|
+
`isUnconfirmed(error)` tells the two apart. It matters because the responses are opposite: a
|
|
261
|
+
failed approval needs another one, an unconfirmed approval needs a look at the transaction and
|
|
262
|
+
nothing else. Sending a second one on top of the first only pays for an allowance you are already
|
|
263
|
+
getting.
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
try {
|
|
267
|
+
await ensureSourceApproval({ network, token, owner, signer });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (isUnconfirmed(error)) {
|
|
270
|
+
// Broadcast, not yet seen to confirm. Check the hash from onSubmitted; do not re-send.
|
|
271
|
+
} else {
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
On EVM, tokens that refuse to overwrite a non-zero allowance — mainnet USDT most notably — are
|
|
278
|
+
reset to zero first, automatically and only where the token demands it; the result then carries a
|
|
279
|
+
`resetTxHash` too. Tron does not do this, because an ordinary TRC-20 accepts the change in place.
|
|
280
|
+
|
|
281
|
+
The approval sent is unlimited, so later charges on the same token need no further transaction.
|
|
282
|
+
To bound the approval that gets sent, pass `approvalAmount`. On its own the amount is treated as
|
|
283
|
+
its own requirement, so an allowance still holding the full bound is left alone rather than
|
|
284
|
+
re-approved — the right answer for a one-off approval. Across repeated payments, pair it with
|
|
285
|
+
`requiredAllowance`: Permit2 decrements the allowance on every payment, so a bound measured
|
|
286
|
+
against itself stops covering itself the moment the first charge lands, and every later call
|
|
287
|
+
would send another approval. Either way a bounded approval is consumed as it is spent and
|
|
288
|
+
eventually has to be granted again.
|
|
289
|
+
|
|
290
|
+
These helpers raise an allowance to what a payment needs; they never lower one. A wallet already
|
|
291
|
+
holding more than `approvalAmount` is left exactly as it is and reports `alreadySufficient`
|
|
292
|
+
without sending anything. Reducing or revoking an allowance is a separate operation, and not one
|
|
293
|
+
these perform.
|
|
294
|
+
|
|
295
|
+
`needsSourceApproval(params)` takes the same arguments and answers the same question without
|
|
296
|
+
sending anything, so it is safe to call before every charge:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
if (await needsSourceApproval({ network, token, owner, signer })) {
|
|
300
|
+
// tell the payer a one-off approval transaction is coming, before asking them to sign
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
239
304
|
## What the merchant `verify()` guarantees
|
|
240
305
|
|
|
241
306
|
`verify()` fails fast (no chain I/O) before submitting, and throws if any check fails. The Atum
|
|
@@ -274,6 +339,9 @@ On success you get an `AtumEscrowReceipt` — the MPP receipt plus the full sett
|
|
|
274
339
|
}
|
|
275
340
|
```
|
|
276
341
|
|
|
342
|
+
`verify()` resolves with this type, so `fulfillmentConfirmation` is available straight off the
|
|
343
|
+
result. `AtumEscrowReceipt` is exported, so you can name it in your own function signatures.
|
|
344
|
+
|
|
277
345
|
## Retries and idempotency
|
|
278
346
|
|
|
279
347
|
A retry must never become a second charge, and two different purchases must never collapse into
|
|
@@ -465,7 +533,7 @@ Import everything from the root, or from the role-specific entry points (which e
|
|
|
465
533
|
each side needs):
|
|
466
534
|
|
|
467
535
|
- `@atumlabs/mppx-atum-escrow` — everything below
|
|
468
|
-
- `@atumlabs/mppx-atum-escrow/client` — `registerClient`, `ensureSourceApproval` + payer types
|
|
536
|
+
- `@atumlabs/mppx-atum-escrow/client` — `registerClient`, `ensureSourceApproval`, `needsSourceApproval` + payer types
|
|
469
537
|
- `@atumlabs/mppx-atum-escrow/server` — `registerServer`, `buildChargeChallenge`,
|
|
470
538
|
`buildChargeRequest`, `validateCorridor`, `corridorFromDefaults`, the settlement errors +
|
|
471
539
|
merchant types
|
|
@@ -473,12 +541,13 @@ each side needs):
|
|
|
473
541
|
| Export | Description |
|
|
474
542
|
| --- | --- |
|
|
475
543
|
| `registerClient(config)` | Payer-side method. `config`: `{ signer, account, now?, solanaClockReader? }`. |
|
|
476
|
-
| `registerServer(config)` | Merchant-side method. `config`: `{ submitter, now? }`. |
|
|
544
|
+
| `registerServer(config)` | Merchant-side method. `config`: `{ submitter, now? }`. Returns an `AtumEscrowServer`, whose `verify()` resolves with an `AtumEscrowReceipt`. |
|
|
477
545
|
| `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
546
|
| `buildChargeRequest(corridor, select, fulfillmentAmount, options?)` | The payment terms alone, without the identifier. For supplying challenge metadata by hand. |
|
|
479
547
|
| `validateCorridor(corridor)` | Validates a corridor's shape and per-source addresses (run automatically by both builders). |
|
|
480
548
|
| `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
|
|
549
|
+
| `ensureSourceApproval(params)` | Ensures the payer has approved Permit2 to move the source token. `signer` for EVM, `tronWeb` for Tron; no-op on Solana. |
|
|
550
|
+
| `needsSourceApproval(params)` | Whether `ensureSourceApproval` would send a transaction. Read-only, spends no gas. |
|
|
482
551
|
| `SettlementPendingError`, `SettlementFailedError`, `PaymentRejectedError` | The non-receipt outcomes of `verify()`. See [Settlement outcomes](#settlement-outcomes). |
|
|
483
552
|
| `isSettlementPending`, `isSettlementFailed`, `isPaymentRejected` | Guards for the above. |
|
|
484
553
|
| `atumEscrowChargeMethod` | The base `mppx` method (advanced/custom wiring). |
|
|
@@ -488,10 +557,17 @@ each side needs):
|
|
|
488
557
|
|
|
489
558
|
Key types: `AtumEscrowCorridor`, `AtumEscrowSource`, `SenderSigner`, `SenderSignerOptions`,
|
|
490
559
|
`PaymentSubmitter`, `PaymentSubmitResult`, `PaymentSettlementStatus`, `AtumEscrowClientConfig`,
|
|
491
|
-
`AtumEscrowServerConfig`, `ChainDefaultsSource`, `EnsureApprovalResult`,
|
|
492
|
-
`AtumEscrowCredential`, `AtumEscrowReceipt`, `ChargeChallenge`, `ChargeRequest`,
|
|
560
|
+
`AtumEscrowServer`, `AtumEscrowServerConfig`, `ChainDefaultsSource`, `EnsureApprovalResult`,
|
|
561
|
+
`AtumEscrowChallenge`, `AtumEscrowCredential`, `AtumEscrowReceipt`, `ChargeChallenge`, `ChargeRequest`,
|
|
493
562
|
`SettlementErrorDetails`, `PaymentRequest`, `FulfillmentConfirmation`.
|
|
494
563
|
|
|
495
564
|
> **EVM implementation note:** on EVM the deposit authorization is a Permit2
|
|
496
565
|
> `PermitWitnessTransferFrom` signature; Tron uses the equivalent TIP-712 typed data, and Solana
|
|
497
566
|
> uses an ed25519-signed deposit. The public API is the same across all three.
|
|
567
|
+
|
|
568
|
+
## Further reading
|
|
569
|
+
|
|
570
|
+
- [Accepting and making MPP payments with Atum](https://docs.atum.xyz/payment-protocols/mpp/overview)
|
|
571
|
+
- [Supported assets](https://docs.atum.xyz/get-started/reference/supported-assets) and [supported networks](https://docs.atum.xyz/get-started/reference/supported-networks)
|
|
572
|
+
- [MPP](https://mpp.dev)
|
|
573
|
+
- [Atum documentation](https://docs.atum.xyz)
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -2,6 +2,13 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
|
|
|
2
2
|
==============================================================================
|
|
3
3
|
@atumlabs/mppx-atum-escrow
|
|
4
4
|
|
|
5
|
+
@atumlabs/mppx-atum-escrow is proprietary software, governed by the license
|
|
6
|
+
agreement 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 proprietary license governing this package or any Atum-authored code,
|
|
10
|
+
and they do not make it open source.
|
|
11
|
+
|
|
5
12
|
The published artifact of this package (the compiled code under dist/) statically
|
|
6
13
|
bundles the third-party open-source components listed below; their source is
|
|
7
14
|
redistributed within this package, so their license and copyright notices are
|