@atumlabs/payment-gateway-client 2.1.0 → 2.2.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 +25 -0
- package/README.md +91 -11
- package/dist/cli/payment-status.js +100 -8
- package/dist/cli/quote-status.js +60 -7
- package/dist/cli/request-quote.js +60 -7
- package/dist/cli/send-payment.js +274 -57
- package/dist/index.d.ts +116 -17
- package/dist/index.js +219 -56
- package/docs/idempotency.md +91 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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
|
+
## [2.2.0] - 2026-08-04
|
|
9
|
+
|
|
10
|
+
> **Read this before upgrading if you script `send-payment`.** Its exit codes now
|
|
11
|
+
> distinguish a settled payment from one still settling and one that failed, where every
|
|
12
|
+
> accepted submission previously exited `0`. See the first entry under Changed.
|
|
13
|
+
>
|
|
14
|
+
> One typed-API change: three fields on `PaymentResponse` are no longer optional. This makes
|
|
15
|
+
> reading a response simpler, not harder — see Changed.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `send-payment --wait` keeps checking a submitted payment until it settles or fails, instead of reporting it as still pending. `--wait-seconds <n>` sets how long (default 60). With `--wait` the JSON on stdout is printed once the wait ends and reports the payment as of the last check — so it carries the settled `status` and the `fulfillment_confirmation`, rather than the `pending` the submit returned. Without `--wait`, output is unchanged. The bound is a client-side convenience: giving up does not affect the payment, and re-submitting the same `request_id` or reading the status endpoint picks up wherever it got to. A settlement that stalls never becomes terminal on its own, so no bound can promise a terminal answer.
|
|
19
|
+
- `send-payment` and `payment-status` now print a one-line outcome summary to stderr saying what the payment's state means for a re-attempt: a pending payment is collected by re-submitting the **same** `request_id`, while a terminally failed one keeps its id and needs a **new** one. The JSON on stdout is unchanged.
|
|
20
|
+
- Documented in `docs/idempotency.md` and the README: what the `409` compares, that deadlines and the signature are exempt from it, `idempotent_replay`, how to read `status`, and that a direct caller may either poll the status endpoint or re-submit.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Behaviour change — `send-payment` exit codes.** Previously any accepted submission exited `0`. Now `0` means `completed`, `2` means `pending`, and `3` means `failed`/`cancelled`; `1` continues to mean the payment could not be submitted. Previously a still-settling payment and a terminally failed one both exited `0`, so `send-payment ... && <next step>` ran the next step for a payment that had not settled — including when re-submitting a `request_id` whose payment had already failed. Scripts that treat `0` as "submitted" should treat `0` and `2` as "submitted"; scripts that mean "settled" now get that from `0` alone. `payment-status` is unaffected: as a lookup, its exit code reports whether the lookup worked.
|
|
24
|
+
- `--request-id`'s help text now states the consequence of omitting it: an id is generated, which makes every bare re-run a separate payment rather than a retry.
|
|
25
|
+
- **`PaymentResponse.payment_id`, `.status` and `.idempotent_replay` are now required** in the typed API, matching what the gateway has always sent. They were typed optional, so TypeScript reported them as possibly `undefined` and reading `idempotent_replay` meant writing `=== true` to satisfy the compiler for a value that is always present. Existing code keeps compiling — a narrowed type only removes checks — unless it constructs a `PaymentResponse` itself, as a test fixture might, in which case all three now have to be supplied.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Corrected the idempotency documentation. The 2.1.0 note below said that reusing a `request_id` with different terms returns the original payment and silently drops the new terms. The gateway rejects it with `409` `IDEMPOTENCY_TERMS_MISMATCH`, so an integrator following the old text would neither expect nor handle that error.
|
|
31
|
+
- `IDEMPOTENCY_TERMS_MISMATCH`'s message described the compared terms as "source, destination, or fulfillment_amount", which had become incomplete — `max_source_amount` is compared too. It now names the categories ("accounts, assets, or amounts") rather than enumerating fields, so it stays accurate as the payment schema grows. The docs describe the comparison the same way, and point at the error's own `message` for which category differed.
|
|
32
|
+
|
|
8
33
|
## [2.1.0] - 2026-07-31
|
|
9
34
|
|
|
10
35
|
### Added
|
package/README.md
CHANGED
|
@@ -51,10 +51,39 @@ send-payment [options] <sender> <amount> <sourceAsset> <receiver> <destinationAs
|
|
|
51
51
|
|
|
52
52
|
- `-V, --version` - Output the version number
|
|
53
53
|
- `--gateway <url>` - Payment gateway URL (default: testnet)
|
|
54
|
-
- `--request-id <id>` - Idempotency key; seeds the on-chain deposit nonce.
|
|
54
|
+
- `--request-id <id>` - Idempotency key; seeds the on-chain deposit nonce. Re-run with the same id to re-attempt one payment. Omitted, an id is generated and printed, which makes every bare re-run a separate payment (see [Idempotency and retries](#idempotency-and-retries))
|
|
55
55
|
- `--request-id-prefix <prefix>` - Prefix for the auto-generated request id (default: `pmt-gw-cli`)
|
|
56
|
+
- `--wait` - After submitting, keep checking until the payment settles or fails instead of reporting it as still pending
|
|
57
|
+
- `--wait-seconds <n>` - How long `--wait` keeps checking (default: `60`). A convenience bound only; giving up does not affect the payment
|
|
56
58
|
- `-h, --help` - Display help
|
|
57
59
|
|
|
60
|
+
### Exit codes
|
|
61
|
+
|
|
62
|
+
`send-payment` reports the payment's ending, so `send-payment ... && <next step>`
|
|
63
|
+
runs the next step only for a settled payment:
|
|
64
|
+
|
|
65
|
+
| Code | Meaning |
|
|
66
|
+
| ---- | --------------------------------------------------------------------------- |
|
|
67
|
+
| `0` | `completed` — settled |
|
|
68
|
+
| `2` | `pending` — accepted, no result yet |
|
|
69
|
+
| `3` | `failed` / `cancelled` — terminal; a fresh attempt needs a new `--request-id` |
|
|
70
|
+
| `1` | no outcome obtained (rejected, gateway unreachable, bad arguments) |
|
|
71
|
+
|
|
72
|
+
On `2`, follow the payment with **`payment-status <payment_id>`** — the id is in the
|
|
73
|
+
output — or use `--wait` to have `send-payment` do it for you. Re-running
|
|
74
|
+
`send-payment` with the same `--request-id` also returns the current state, but it
|
|
75
|
+
rebuilds and re-sends the payment to do so, which is more work than a status
|
|
76
|
+
lookup.
|
|
77
|
+
|
|
78
|
+
`1` is the case that reverses that order. It means the outcome is **unknown**, not
|
|
79
|
+
that nothing happened: a request that timed out may still have been accepted, and
|
|
80
|
+
you may not have a `payment_id` to look up. Re-run with the same `--request-id`,
|
|
81
|
+
which resolves to the payment if one was created and creates it if not. The id is
|
|
82
|
+
printed on failure for exactly this.
|
|
83
|
+
|
|
84
|
+
`payment-status` is a lookup, so its exit code reports whether the lookup worked
|
|
85
|
+
(`0`) or not (`1`), never the payment's outcome — read `status` for that.
|
|
86
|
+
|
|
58
87
|
### CAIP-19 Asset Format Examples
|
|
59
88
|
|
|
60
89
|
- **EVM ERC20**: `eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831`
|
|
@@ -209,11 +238,34 @@ async function sendPayment() {
|
|
|
209
238
|
requestBody: paymentRequest
|
|
210
239
|
}) as PaymentResponse;
|
|
211
240
|
|
|
212
|
-
|
|
241
|
+
// A 2xx means the payment was accepted, NOT that it settled. Branch on `status`:
|
|
242
|
+
// cross-chain settlement can outrun the gateway's synchronous window, so an accepted
|
|
243
|
+
// payment may still have no result yet. See "Idempotency and retries" below.
|
|
213
244
|
console.log('Payment ID:', response.payment_id);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
245
|
+
if (response.idempotent_replay) {
|
|
246
|
+
// This request_id had been used before, so this is the payment your earlier attempt
|
|
247
|
+
// created. Nothing additional was charged.
|
|
248
|
+
console.log('Handed back an existing payment');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
switch (response.status) {
|
|
252
|
+
case 'completed':
|
|
253
|
+
console.log('Settled.');
|
|
254
|
+
console.log('Source TX:', response.fulfillment_confirmation?.source_tx_hash);
|
|
255
|
+
console.log('Destination TX:', response.fulfillment_confirmation?.destination_tx_hash);
|
|
256
|
+
break;
|
|
257
|
+
case 'failed':
|
|
258
|
+
case 'cancelled':
|
|
259
|
+
// Terminal, and this request_id stays bound to it. A fresh attempt needs a NEW id.
|
|
260
|
+
console.error('Failed:', response.error?.code, response.error?.message);
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
// Still settling. Do not release goods, and do not pay again under a new id.
|
|
264
|
+
// Follow it with getPaymentStatus(payment_id); re-submitting this same request
|
|
265
|
+
// also returns the current state, but it re-sends the payment to do so.
|
|
266
|
+
console.log('Still settling — follow it with getPaymentStatus.');
|
|
267
|
+
}
|
|
268
|
+
|
|
217
269
|
return response;
|
|
218
270
|
} catch (error) {
|
|
219
271
|
const gwError = getErrorResponse(error);
|
|
@@ -256,6 +308,26 @@ try {
|
|
|
256
308
|
}
|
|
257
309
|
```
|
|
258
310
|
|
|
311
|
+
A thrown error does **not** mean no payment was created — a request that timed out
|
|
312
|
+
may still have been accepted. Treat it as unknown rather than as a failure:
|
|
313
|
+
|
|
314
|
+
- If you already hold the `payment_id` from an earlier attempt, read
|
|
315
|
+
`getPaymentStatus` — the cheapest way to find out what happened.
|
|
316
|
+
- If you do not, re-submit the same `request_id`. It resolves to the payment if one
|
|
317
|
+
was created and creates it if not, which is the only recovery that works without
|
|
318
|
+
an id.
|
|
319
|
+
|
|
320
|
+
One code is worth handling by name:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const gwError = getErrorResponse(error);
|
|
324
|
+
if (gwError?.code === 'IDEMPOTENCY_TERMS_MISMATCH') {
|
|
325
|
+
// 409 — this request_id already identifies a payment with different economics.
|
|
326
|
+
// Re-submitting cannot amend that payment; a genuinely different one needs a new
|
|
327
|
+
// request_id. See "Idempotency and retries".
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
259
331
|
### Asset Identifiers
|
|
260
332
|
|
|
261
333
|
Assets are identified using the [CAIP-19](https://chainagnostic.org/CAIPs/caip-19) format:
|
|
@@ -273,15 +345,23 @@ with one payment, not two — and you get the same answer each time.
|
|
|
273
345
|
(`preparePaymentRequest`), and it is the only thing you manage: the SDK derives
|
|
274
346
|
the on-chain deposit nonce deterministically from it, so retries line up on
|
|
275
347
|
their own. Reuse the same `request_id` to retry safely.
|
|
276
|
-
- On the CLI, `--request-id` is optional; when omitted
|
|
277
|
-
|
|
278
|
-
payment.
|
|
348
|
+
- On the CLI, `--request-id` is optional; when omitted an id is generated and
|
|
349
|
+
printed, so you can re-run with `--request-id <id>` to re-attempt the same
|
|
350
|
+
payment. A bare re-run, with no `--request-id`, is a **separate** payment.
|
|
279
351
|
- A genuinely new payment needs a **new** `request_id`. Reusing one with different
|
|
280
|
-
|
|
281
|
-
|
|
352
|
+
economics (a corrected amount, a different token) is rejected with `409`
|
|
353
|
+
`IDEMPOTENCY_TERMS_MISMATCH` — one id identifies one payment.
|
|
354
|
+
- Deadlines and the signature are exempt, so a re-submission of the original
|
|
355
|
+
bytes is fine even though its deadlines have expired by then.
|
|
356
|
+
- `idempotent_replay` on the response tells you whether you were handed a payment
|
|
357
|
+
that already existed.
|
|
358
|
+
- `pending` is not success. It means accepted and still settling: re-submit the
|
|
359
|
+
same `request_id` to collect the result. A `failed` payment is terminal and
|
|
360
|
+
keeps its id, so recovering from one needs a new id.
|
|
282
361
|
|
|
283
362
|
See [docs/idempotency.md](docs/idempotency.md) for the full model: the two
|
|
284
|
-
guarantees and the per-chain
|
|
363
|
+
guarantees, when the `409` applies, how to read the outcome, and the per-chain
|
|
364
|
+
differences (EVM/Tron vs Solana).
|
|
285
365
|
|
|
286
366
|
## What is in the package
|
|
287
367
|
|
|
@@ -249,12 +249,14 @@ var require_utils = __commonJS({
|
|
|
249
249
|
"../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
|
|
250
250
|
"use strict";
|
|
251
251
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
252
|
-
exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
252
|
+
exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
253
253
|
exports2.resolvePermitDeadline = resolvePermitDeadline;
|
|
254
254
|
exports2.generatePermitNonce = generatePermitNonce;
|
|
255
255
|
exports2.resolvePermitNonce = resolvePermitNonce;
|
|
256
|
+
exports2.canonicalDepositor = canonicalDepositor;
|
|
256
257
|
exports2.deterministicPermitNonce = deterministicPermitNonce2;
|
|
257
258
|
exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
|
|
259
|
+
exports2.deterministicRequestId = deterministicRequestId;
|
|
258
260
|
exports2.sameAddress = sameAddress;
|
|
259
261
|
exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
|
|
260
262
|
exports2.assertToken = assertToken;
|
|
@@ -299,6 +301,20 @@ var require_utils = __commonJS({
|
|
|
299
301
|
const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
|
|
300
302
|
return bucket << 32n | slot;
|
|
301
303
|
}
|
|
304
|
+
exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
|
|
305
|
+
function deterministicRequestId(params) {
|
|
306
|
+
if (!params.anchor) {
|
|
307
|
+
throw new Error("sender_auth: a per-intent anchor is required to derive a deterministic request_id (a random one would silently break idempotency and let a retry double-charge)");
|
|
308
|
+
}
|
|
309
|
+
if (!params.account) {
|
|
310
|
+
throw new Error("sender_auth: account is required to derive a deterministic request_id");
|
|
311
|
+
}
|
|
312
|
+
if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
|
|
313
|
+
throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
|
|
314
|
+
}
|
|
315
|
+
const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor(params.account)}`));
|
|
316
|
+
return `${params.prefix}${digest.slice(2, 26)}`;
|
|
317
|
+
}
|
|
302
318
|
function sameAddress(a, b) {
|
|
303
319
|
if (a.startsWith("0x") && b.startsWith("0x")) {
|
|
304
320
|
return a.toLowerCase() === b.toLowerCase();
|
|
@@ -890,7 +906,7 @@ var require_evm = __commonJS({
|
|
|
890
906
|
exports2.evmAdapter = {
|
|
891
907
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
892
908
|
if (nonce === void 0) {
|
|
893
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
909
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
894
910
|
}
|
|
895
911
|
if (deadline === void 0) {
|
|
896
912
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -918,6 +934,10 @@ var require_evm = __commonJS({
|
|
|
918
934
|
throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
|
|
919
935
|
}
|
|
920
936
|
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
937
|
+
const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
|
|
938
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
939
|
+
throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
940
|
+
}
|
|
921
941
|
const wallet = new ethers_1.Wallet(privateKey);
|
|
922
942
|
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
923
943
|
},
|
|
@@ -1004,7 +1024,7 @@ var require_tron = __commonJS({
|
|
|
1004
1024
|
exports2.tronAdapter = {
|
|
1005
1025
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
1006
1026
|
if (nonce === void 0) {
|
|
1007
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
1027
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
1008
1028
|
}
|
|
1009
1029
|
if (deadline === void 0) {
|
|
1010
1030
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -1027,9 +1047,19 @@ var require_tron = __commonJS({
|
|
|
1027
1047
|
const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
|
|
1028
1048
|
return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
|
|
1029
1049
|
},
|
|
1050
|
+
// Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
|
|
1051
|
+
// authorizing (Turnkey policies read `eth.eip_712.message[...]`).
|
|
1030
1052
|
async sign(signedMessage, privateKey) {
|
|
1053
|
+
if (!signedMessage.message_prehash) {
|
|
1054
|
+
throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
|
|
1055
|
+
}
|
|
1056
|
+
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
1057
|
+
const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
|
|
1058
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
1059
|
+
throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
1060
|
+
}
|
|
1031
1061
|
const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
|
|
1032
|
-
return wallet.
|
|
1062
|
+
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
1033
1063
|
},
|
|
1034
1064
|
recoverSigner(signed, source) {
|
|
1035
1065
|
let recovered;
|
|
@@ -2867,8 +2897,26 @@ var PaymentsService = class {
|
|
|
2867
2897
|
* 2. Find optimal settlement routes across chains/assets
|
|
2868
2898
|
* 3. Secure funds in escrow
|
|
2869
2899
|
* 4. Coordinate with settlement agents for execution
|
|
2870
|
-
* 5. Return a real-time result if
|
|
2871
|
-
* otherwise return a payment ID
|
|
2900
|
+
* 5. Return a real-time result if settlement completes inside the synchronous window,
|
|
2901
|
+
* otherwise return a payment ID and status for polling
|
|
2902
|
+
*
|
|
2903
|
+
* **Idempotency.** `request_id` is the idempotency key, scoped to the source account
|
|
2904
|
+
* and originator. Submitting the same `request_id` again returns the ORIGINAL payment
|
|
2905
|
+
* instead of charging a second time, so a client whose request timed out or failed
|
|
2906
|
+
* in transit can safely re-submit.
|
|
2907
|
+
*
|
|
2908
|
+
* Because a re-submission must be admissible on its own, some fields legitimately
|
|
2909
|
+
* differ between attempts and are excluded from the comparison: `quote_deadline` and
|
|
2910
|
+
* `fulfillment_deadline` (both must be in the future, so a later attempt has to move
|
|
2911
|
+
* them), and the `sender_auth` signature they are signed into.
|
|
2912
|
+
*
|
|
2913
|
+
* What must NOT differ is the payment's economics — source account and asset,
|
|
2914
|
+
* destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
|
|
2915
|
+
* payer's own risk cap; lowering it on a retry describes a different payment, not the
|
|
2916
|
+
* same one with less headroom). Re-using one `request_id` for a genuinely different
|
|
2917
|
+
* payment is a client bug that would otherwise be silently collapsed onto the first
|
|
2918
|
+
* payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
|
|
2919
|
+
* distinct `request_id` per payment.
|
|
2872
2920
|
*
|
|
2873
2921
|
* @param data The data for the request.
|
|
2874
2922
|
* @param data.requestBody
|
|
@@ -2885,6 +2933,11 @@ var PaymentsService = class {
|
|
|
2885
2933
|
400: "Invalid request",
|
|
2886
2934
|
401: "Signature or sender authorization could not be verified",
|
|
2887
2935
|
403: "Payment declined during counterparty authorization",
|
|
2936
|
+
409: `The \`request_id\` has already been used for a payment with different economics
|
|
2937
|
+
(\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
|
|
2938
|
+
made safe, so it must identify one payment; use a distinct \`request_id\` per
|
|
2939
|
+
payment.
|
|
2940
|
+
`,
|
|
2888
2941
|
413: "Payment request too large to process",
|
|
2889
2942
|
500: "Internal server error",
|
|
2890
2943
|
503: "A dependency is temporarily unavailable"
|
|
@@ -3704,7 +3757,7 @@ var PaymentGatewayClient2 = class extends PaymentGatewayClient {
|
|
|
3704
3757
|
};
|
|
3705
3758
|
|
|
3706
3759
|
// src/version.ts
|
|
3707
|
-
var PACKAGE_VERSION = true ? "2.
|
|
3760
|
+
var PACKAGE_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
3708
3761
|
|
|
3709
3762
|
// src/cli/load-env.ts
|
|
3710
3763
|
var import_dotenv = require("dotenv");
|
|
@@ -3712,6 +3765,34 @@ function loadCliEnv() {
|
|
|
3712
3765
|
(0, import_dotenv.config)({ override: false, quiet: true });
|
|
3713
3766
|
}
|
|
3714
3767
|
|
|
3768
|
+
// src/cli/payment-outcome.ts
|
|
3769
|
+
var EXIT_COMPLETED = 0;
|
|
3770
|
+
var EXIT_PENDING = 2;
|
|
3771
|
+
var EXIT_TERMINAL_FAILURE = 3;
|
|
3772
|
+
function describePaymentOutcome(input) {
|
|
3773
|
+
const { status, paymentId, requestId, idempotentReplay, errorCode } = input;
|
|
3774
|
+
const payment = paymentId ? `payment ${paymentId}` : "the payment";
|
|
3775
|
+
const replay = idempotentReplay ? " This is the payment your earlier attempt created \u2014 nothing additional was charged." : "";
|
|
3776
|
+
if (status === "completed") {
|
|
3777
|
+
return {
|
|
3778
|
+
exitCode: EXIT_COMPLETED,
|
|
3779
|
+
summary: `completed: ${payment} settled.${replay}`
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
if (status === "failed" || status === "cancelled") {
|
|
3783
|
+
const reason = errorCode ? ` (${errorCode})` : "";
|
|
3784
|
+
return {
|
|
3785
|
+
exitCode: EXIT_TERMINAL_FAILURE,
|
|
3786
|
+
summary: `${status}: ${payment} ended without settling${reason}. This is final \u2014 re-submitting ${requestId ? `request id ${requestId}` : "the same request id"} returns this same failed payment, so a fresh attempt needs a NEW request id.`
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
const resubmit = requestId ? `request id ${requestId}` : "the same request id";
|
|
3790
|
+
return {
|
|
3791
|
+
exitCode: EXIT_PENDING,
|
|
3792
|
+
summary: `pending: ${payment} was accepted and is still settling.${replay} To collect the result, keep checking its status, or re-submit ${resubmit}. Do not start a new payment under a different request id \u2014 that would pay twice.`
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3715
3796
|
// src/cli/payment-status.ts
|
|
3716
3797
|
loadCliEnv();
|
|
3717
3798
|
var program = new import_commander.Command();
|
|
@@ -3722,11 +3803,22 @@ Example:
|
|
|
3722
3803
|
Status values: pending | completed | failed | cancelled
|
|
3723
3804
|
|
|
3724
3805
|
Output:
|
|
3725
|
-
Response JSON
|
|
3806
|
+
Response JSON -> stdout (for parsing/automation, e.g. | jq)
|
|
3807
|
+
What it means -> stderr
|
|
3808
|
+
|
|
3809
|
+
Exit code reports whether the lookup worked (0) or not (1), not the payment's
|
|
3810
|
+
outcome \u2014 read \`status\` for that, or use send-payment's exit code.`).action(async (paymentId, options) => {
|
|
3726
3811
|
try {
|
|
3727
3812
|
const client = new PaymentGatewayClient2({ BASE: options.gateway });
|
|
3728
3813
|
const response = await client.payments.getPaymentStatus({ paymentId });
|
|
3729
3814
|
console.log(JSON.stringify(response, null, 2));
|
|
3815
|
+
console.error(
|
|
3816
|
+
describePaymentOutcome({
|
|
3817
|
+
status: response.status,
|
|
3818
|
+
paymentId: response.payment_id ?? paymentId,
|
|
3819
|
+
errorCode: response.error?.code
|
|
3820
|
+
}).summary
|
|
3821
|
+
);
|
|
3730
3822
|
process.exit(0);
|
|
3731
3823
|
} catch (error) {
|
|
3732
3824
|
if (error instanceof ApiError && error.body) {
|
package/dist/cli/quote-status.js
CHANGED
|
@@ -249,12 +249,14 @@ var require_utils = __commonJS({
|
|
|
249
249
|
"../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
|
|
250
250
|
"use strict";
|
|
251
251
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
252
|
-
exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
252
|
+
exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
253
253
|
exports2.resolvePermitDeadline = resolvePermitDeadline;
|
|
254
254
|
exports2.generatePermitNonce = generatePermitNonce;
|
|
255
255
|
exports2.resolvePermitNonce = resolvePermitNonce;
|
|
256
|
+
exports2.canonicalDepositor = canonicalDepositor;
|
|
256
257
|
exports2.deterministicPermitNonce = deterministicPermitNonce2;
|
|
257
258
|
exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
|
|
259
|
+
exports2.deterministicRequestId = deterministicRequestId;
|
|
258
260
|
exports2.sameAddress = sameAddress;
|
|
259
261
|
exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
|
|
260
262
|
exports2.assertToken = assertToken;
|
|
@@ -299,6 +301,20 @@ var require_utils = __commonJS({
|
|
|
299
301
|
const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
|
|
300
302
|
return bucket << 32n | slot;
|
|
301
303
|
}
|
|
304
|
+
exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
|
|
305
|
+
function deterministicRequestId(params) {
|
|
306
|
+
if (!params.anchor) {
|
|
307
|
+
throw new Error("sender_auth: a per-intent anchor is required to derive a deterministic request_id (a random one would silently break idempotency and let a retry double-charge)");
|
|
308
|
+
}
|
|
309
|
+
if (!params.account) {
|
|
310
|
+
throw new Error("sender_auth: account is required to derive a deterministic request_id");
|
|
311
|
+
}
|
|
312
|
+
if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
|
|
313
|
+
throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
|
|
314
|
+
}
|
|
315
|
+
const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor(params.account)}`));
|
|
316
|
+
return `${params.prefix}${digest.slice(2, 26)}`;
|
|
317
|
+
}
|
|
302
318
|
function sameAddress(a, b) {
|
|
303
319
|
if (a.startsWith("0x") && b.startsWith("0x")) {
|
|
304
320
|
return a.toLowerCase() === b.toLowerCase();
|
|
@@ -890,7 +906,7 @@ var require_evm = __commonJS({
|
|
|
890
906
|
exports2.evmAdapter = {
|
|
891
907
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
892
908
|
if (nonce === void 0) {
|
|
893
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
909
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
894
910
|
}
|
|
895
911
|
if (deadline === void 0) {
|
|
896
912
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -918,6 +934,10 @@ var require_evm = __commonJS({
|
|
|
918
934
|
throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
|
|
919
935
|
}
|
|
920
936
|
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
937
|
+
const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
|
|
938
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
939
|
+
throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
940
|
+
}
|
|
921
941
|
const wallet = new ethers_1.Wallet(privateKey);
|
|
922
942
|
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
923
943
|
},
|
|
@@ -1004,7 +1024,7 @@ var require_tron = __commonJS({
|
|
|
1004
1024
|
exports2.tronAdapter = {
|
|
1005
1025
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
1006
1026
|
if (nonce === void 0) {
|
|
1007
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
1027
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
1008
1028
|
}
|
|
1009
1029
|
if (deadline === void 0) {
|
|
1010
1030
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -1027,9 +1047,19 @@ var require_tron = __commonJS({
|
|
|
1027
1047
|
const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
|
|
1028
1048
|
return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
|
|
1029
1049
|
},
|
|
1050
|
+
// Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
|
|
1051
|
+
// authorizing (Turnkey policies read `eth.eip_712.message[...]`).
|
|
1030
1052
|
async sign(signedMessage, privateKey) {
|
|
1053
|
+
if (!signedMessage.message_prehash) {
|
|
1054
|
+
throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
|
|
1055
|
+
}
|
|
1056
|
+
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
1057
|
+
const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
|
|
1058
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
1059
|
+
throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
1060
|
+
}
|
|
1031
1061
|
const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
|
|
1032
|
-
return wallet.
|
|
1062
|
+
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
1033
1063
|
},
|
|
1034
1064
|
recoverSigner(signed, source) {
|
|
1035
1065
|
let recovered;
|
|
@@ -2867,8 +2897,26 @@ var PaymentsService = class {
|
|
|
2867
2897
|
* 2. Find optimal settlement routes across chains/assets
|
|
2868
2898
|
* 3. Secure funds in escrow
|
|
2869
2899
|
* 4. Coordinate with settlement agents for execution
|
|
2870
|
-
* 5. Return a real-time result if
|
|
2871
|
-
* otherwise return a payment ID
|
|
2900
|
+
* 5. Return a real-time result if settlement completes inside the synchronous window,
|
|
2901
|
+
* otherwise return a payment ID and status for polling
|
|
2902
|
+
*
|
|
2903
|
+
* **Idempotency.** `request_id` is the idempotency key, scoped to the source account
|
|
2904
|
+
* and originator. Submitting the same `request_id` again returns the ORIGINAL payment
|
|
2905
|
+
* instead of charging a second time, so a client whose request timed out or failed
|
|
2906
|
+
* in transit can safely re-submit.
|
|
2907
|
+
*
|
|
2908
|
+
* Because a re-submission must be admissible on its own, some fields legitimately
|
|
2909
|
+
* differ between attempts and are excluded from the comparison: `quote_deadline` and
|
|
2910
|
+
* `fulfillment_deadline` (both must be in the future, so a later attempt has to move
|
|
2911
|
+
* them), and the `sender_auth` signature they are signed into.
|
|
2912
|
+
*
|
|
2913
|
+
* What must NOT differ is the payment's economics — source account and asset,
|
|
2914
|
+
* destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
|
|
2915
|
+
* payer's own risk cap; lowering it on a retry describes a different payment, not the
|
|
2916
|
+
* same one with less headroom). Re-using one `request_id` for a genuinely different
|
|
2917
|
+
* payment is a client bug that would otherwise be silently collapsed onto the first
|
|
2918
|
+
* payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
|
|
2919
|
+
* distinct `request_id` per payment.
|
|
2872
2920
|
*
|
|
2873
2921
|
* @param data The data for the request.
|
|
2874
2922
|
* @param data.requestBody
|
|
@@ -2885,6 +2933,11 @@ var PaymentsService = class {
|
|
|
2885
2933
|
400: "Invalid request",
|
|
2886
2934
|
401: "Signature or sender authorization could not be verified",
|
|
2887
2935
|
403: "Payment declined during counterparty authorization",
|
|
2936
|
+
409: `The \`request_id\` has already been used for a payment with different economics
|
|
2937
|
+
(\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
|
|
2938
|
+
made safe, so it must identify one payment; use a distinct \`request_id\` per
|
|
2939
|
+
payment.
|
|
2940
|
+
`,
|
|
2888
2941
|
413: "Payment request too large to process",
|
|
2889
2942
|
500: "Internal server error",
|
|
2890
2943
|
503: "A dependency is temporarily unavailable"
|
|
@@ -3704,7 +3757,7 @@ var PaymentGatewayClient2 = class extends PaymentGatewayClient {
|
|
|
3704
3757
|
};
|
|
3705
3758
|
|
|
3706
3759
|
// src/version.ts
|
|
3707
|
-
var PACKAGE_VERSION = true ? "2.
|
|
3760
|
+
var PACKAGE_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
3708
3761
|
|
|
3709
3762
|
// src/cli/load-env.ts
|
|
3710
3763
|
var import_dotenv = require("dotenv");
|
|
@@ -249,12 +249,14 @@ var require_utils = __commonJS({
|
|
|
249
249
|
"../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
|
|
250
250
|
"use strict";
|
|
251
251
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
252
|
-
exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
252
|
+
exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
|
|
253
253
|
exports2.resolvePermitDeadline = resolvePermitDeadline;
|
|
254
254
|
exports2.generatePermitNonce = generatePermitNonce;
|
|
255
255
|
exports2.resolvePermitNonce = resolvePermitNonce;
|
|
256
|
+
exports2.canonicalDepositor = canonicalDepositor;
|
|
256
257
|
exports2.deterministicPermitNonce = deterministicPermitNonce2;
|
|
257
258
|
exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
|
|
259
|
+
exports2.deterministicRequestId = deterministicRequestId;
|
|
258
260
|
exports2.sameAddress = sameAddress;
|
|
259
261
|
exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
|
|
260
262
|
exports2.assertToken = assertToken;
|
|
@@ -299,6 +301,20 @@ var require_utils = __commonJS({
|
|
|
299
301
|
const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
|
|
300
302
|
return bucket << 32n | slot;
|
|
301
303
|
}
|
|
304
|
+
exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
|
|
305
|
+
function deterministicRequestId(params) {
|
|
306
|
+
if (!params.anchor) {
|
|
307
|
+
throw new Error("sender_auth: a per-intent anchor is required to derive a deterministic request_id (a random one would silently break idempotency and let a retry double-charge)");
|
|
308
|
+
}
|
|
309
|
+
if (!params.account) {
|
|
310
|
+
throw new Error("sender_auth: account is required to derive a deterministic request_id");
|
|
311
|
+
}
|
|
312
|
+
if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
|
|
313
|
+
throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
|
|
314
|
+
}
|
|
315
|
+
const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor(params.account)}`));
|
|
316
|
+
return `${params.prefix}${digest.slice(2, 26)}`;
|
|
317
|
+
}
|
|
302
318
|
function sameAddress(a, b) {
|
|
303
319
|
if (a.startsWith("0x") && b.startsWith("0x")) {
|
|
304
320
|
return a.toLowerCase() === b.toLowerCase();
|
|
@@ -890,7 +906,7 @@ var require_evm = __commonJS({
|
|
|
890
906
|
exports2.evmAdapter = {
|
|
891
907
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
892
908
|
if (nonce === void 0) {
|
|
893
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
909
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
894
910
|
}
|
|
895
911
|
if (deadline === void 0) {
|
|
896
912
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -918,6 +934,10 @@ var require_evm = __commonJS({
|
|
|
918
934
|
throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
|
|
919
935
|
}
|
|
920
936
|
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
937
|
+
const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
|
|
938
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
939
|
+
throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
940
|
+
}
|
|
921
941
|
const wallet = new ethers_1.Wallet(privateKey);
|
|
922
942
|
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
923
943
|
},
|
|
@@ -1004,7 +1024,7 @@ var require_tron = __commonJS({
|
|
|
1004
1024
|
exports2.tronAdapter = {
|
|
1005
1025
|
async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
|
|
1006
1026
|
if (nonce === void 0) {
|
|
1007
|
-
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g.
|
|
1027
|
+
throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
|
|
1008
1028
|
}
|
|
1009
1029
|
if (deadline === void 0) {
|
|
1010
1030
|
throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
|
|
@@ -1027,9 +1047,19 @@ var require_tron = __commonJS({
|
|
|
1027
1047
|
const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
|
|
1028
1048
|
return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
|
|
1029
1049
|
},
|
|
1050
|
+
// Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
|
|
1051
|
+
// authorizing (Turnkey policies read `eth.eip_712.message[...]`).
|
|
1030
1052
|
async sign(signedMessage, privateKey) {
|
|
1053
|
+
if (!signedMessage.message_prehash) {
|
|
1054
|
+
throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
|
|
1055
|
+
}
|
|
1056
|
+
const prehash = JSON.parse(signedMessage.message_prehash);
|
|
1057
|
+
const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
|
|
1058
|
+
if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
|
|
1059
|
+
throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
|
|
1060
|
+
}
|
|
1031
1061
|
const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
|
|
1032
|
-
return wallet.
|
|
1062
|
+
return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
|
|
1033
1063
|
},
|
|
1034
1064
|
recoverSigner(signed, source) {
|
|
1035
1065
|
let recovered;
|
|
@@ -2867,8 +2897,26 @@ var PaymentsService = class {
|
|
|
2867
2897
|
* 2. Find optimal settlement routes across chains/assets
|
|
2868
2898
|
* 3. Secure funds in escrow
|
|
2869
2899
|
* 4. Coordinate with settlement agents for execution
|
|
2870
|
-
* 5. Return a real-time result if
|
|
2871
|
-
* otherwise return a payment ID
|
|
2900
|
+
* 5. Return a real-time result if settlement completes inside the synchronous window,
|
|
2901
|
+
* otherwise return a payment ID and status for polling
|
|
2902
|
+
*
|
|
2903
|
+
* **Idempotency.** `request_id` is the idempotency key, scoped to the source account
|
|
2904
|
+
* and originator. Submitting the same `request_id` again returns the ORIGINAL payment
|
|
2905
|
+
* instead of charging a second time, so a client whose request timed out or failed
|
|
2906
|
+
* in transit can safely re-submit.
|
|
2907
|
+
*
|
|
2908
|
+
* Because a re-submission must be admissible on its own, some fields legitimately
|
|
2909
|
+
* differ between attempts and are excluded from the comparison: `quote_deadline` and
|
|
2910
|
+
* `fulfillment_deadline` (both must be in the future, so a later attempt has to move
|
|
2911
|
+
* them), and the `sender_auth` signature they are signed into.
|
|
2912
|
+
*
|
|
2913
|
+
* What must NOT differ is the payment's economics — source account and asset,
|
|
2914
|
+
* destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
|
|
2915
|
+
* payer's own risk cap; lowering it on a retry describes a different payment, not the
|
|
2916
|
+
* same one with less headroom). Re-using one `request_id` for a genuinely different
|
|
2917
|
+
* payment is a client bug that would otherwise be silently collapsed onto the first
|
|
2918
|
+
* payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
|
|
2919
|
+
* distinct `request_id` per payment.
|
|
2872
2920
|
*
|
|
2873
2921
|
* @param data The data for the request.
|
|
2874
2922
|
* @param data.requestBody
|
|
@@ -2885,6 +2933,11 @@ var PaymentsService = class {
|
|
|
2885
2933
|
400: "Invalid request",
|
|
2886
2934
|
401: "Signature or sender authorization could not be verified",
|
|
2887
2935
|
403: "Payment declined during counterparty authorization",
|
|
2936
|
+
409: `The \`request_id\` has already been used for a payment with different economics
|
|
2937
|
+
(\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
|
|
2938
|
+
made safe, so it must identify one payment; use a distinct \`request_id\` per
|
|
2939
|
+
payment.
|
|
2940
|
+
`,
|
|
2888
2941
|
413: "Payment request too large to process",
|
|
2889
2942
|
500: "Internal server error",
|
|
2890
2943
|
503: "A dependency is temporarily unavailable"
|
|
@@ -3714,7 +3767,7 @@ function parseSeconds(optionName) {
|
|
|
3714
3767
|
}
|
|
3715
3768
|
|
|
3716
3769
|
// src/version.ts
|
|
3717
|
-
var PACKAGE_VERSION = true ? "2.
|
|
3770
|
+
var PACKAGE_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
3718
3771
|
|
|
3719
3772
|
// src/cli/load-env.ts
|
|
3720
3773
|
var import_dotenv = require("dotenv");
|