@atumlabs/payment-gateway-client 2.2.0 → 3.0.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,8 +1,14 @@
1
- # Payment idempotency
1
+ # Idempotency
2
2
 
3
- Submit the same payment twice — a retry, a timeout, a network blip — and you
4
- should end up with **one** payment, not two, and receive the **same** answer both
5
- times. The key to both properties is the `request_id` you send with each payment.
3
+ Send the same request twice — a retry, a timeout, a network blip — and it should
4
+ count once, not twice, and give you the **same** answer both times. On a payment
5
+ that means you are charged once. The key to both properties is the `request_id`
6
+ you send.
7
+
8
+ One contract covers every write endpoint. Today that is `submitPayment` and
9
+ `createQuoteRequest`; they use the same key, reject the same misuse the same way,
10
+ and report a replay the same way, so what you learn on one applies to the other.
11
+ Where an endpoint genuinely differs it is called out inline.
6
12
 
7
13
  ## Using the SDK
8
14
 
@@ -12,25 +18,46 @@ With `@atumlabs/payment-gateway-client` this is automatic. Pass a stable
12
18
  deterministically from it, so both guarantees below line up on their own. You
13
19
  never manage nonces — `request_id` is the only value you control.
14
20
 
15
- Reuse the same `request_id` across attempts of the same logical payment. Use a
16
- fresh `request_id` only for a genuinely new payment.
21
+ Reuse the same `request_id` across attempts of the same logical request. Use a
22
+ fresh `request_id` only when you genuinely mean a new one.
23
+
24
+ Both builders require it: `preparePaymentRequest` and `prepareQuoteRequest` each
25
+ take `requestId` explicitly, and neither invents one. That is deliberate — an id
26
+ the library chose for you is an id you cannot reuse on a retry, so the retry would
27
+ create a second payment or open a second batch rather than recovering the first.
28
+
29
+ The CLIs are the exception, because a command invocation is a one-shot request:
30
+ `send-payment` and `request-quote` generate an id when `--request-id` is omitted
31
+ and print it back, so you can re-run with `--request-id <id>` to retry the same
32
+ one. A bare re-run, with no `--request-id`, is a **separate** payment or batch.
17
33
 
18
34
  ## The two guarantees
19
35
 
20
- ### 1. Gateway dedup — enforced for every caller
36
+ ### 1. Gateway dedup — every write endpoint, every caller
21
37
 
22
38
  The gateway records your `request_id` (scoped to your source account and signing
23
- identity). If the same `request_id` arrives again for the same payment, the
24
- gateway returns the original payment — same `payment_id`, same status and receipt
25
- — and never creates a second payment or broadcasts a second deposit. This holds
26
- whether you use the SDK or call the API directly, as long as you send a stable
27
- `request_id` and reuse it on retry.
28
-
29
- "For the same payment" is the one condition: an id re-used for *different*
30
- economics is rejected rather than replayed. See
39
+ identity). If the same `request_id` arrives again, the gateway returns what it
40
+ created the first time and creates nothing new:
41
+
42
+ | Endpoint | A repeat returns | And does not |
43
+ | -- | -- | -- |
44
+ | `submitPayment` | the original payment — same `payment_id`, status and receipt | create a second payment or broadcast a second deposit |
45
+ | `createQuoteRequest` | the original batch — same `quote_request_id` and its quotes | open a second batch or re-broadcast to settlement agents |
46
+
47
+ This holds whether you use the SDK or call the API directly, as long as you send a
48
+ stable `request_id` and reuse it on retry.
49
+
50
+ "The same request" is the one condition: an id re-used for *different* economics
51
+ is rejected rather than replayed. See
31
52
  [Reusing an id for different terms is rejected](#reusing-an-id-for-different-terms-is-rejected).
32
53
 
33
- ### 2. On-chain single-settlement — enforced by the blockchain
54
+ ### 2. On-chain single-settlement — payments only, enforced by the blockchain
55
+
56
+ This second guarantee applies where funds move, which today means `submitPayment`.
57
+ A quote-request authorizes no spend — it carries no `sender_auth` and no deposit
58
+ nonce — so guarantee 1 is the whole mechanism there. Nothing is charged either
59
+ way; what a retry protects on that endpoint is your ability to find the batch
60
+ again.
34
61
 
35
62
  Each deposit authorization carries a nonce, and the chain guarantees a given
36
63
  nonce can settle at most once. This ties your retries together on-chain **only
@@ -39,53 +66,70 @@ if** each retry carries the **same** nonce. The SDK derives that nonce from your
39
66
  themselves in the signed permit.
40
67
 
41
68
  A changing `request_id` defeats both guarantees, because each attempt is then
42
- treated as a different payment — keep it stable and reuse it on retry.
69
+ treated as a different request — keep it stable and reuse it on retry.
43
70
 
44
71
  ## Reusing an id for different terms is rejected
45
72
 
46
- One `request_id` identifies one payment, so it cannot be pointed at different
47
- economics. Re-submit an id you have already used with a changed amount, token,
48
- account or destination and the gateway answers **`409`** with
49
- `IDEMPOTENCY_TERMS_MISMATCH` rather than paying, and rather than quietly serving
50
- you the original payment.
73
+ One `request_id` identifies one payment, or one quote-request batch, so it cannot
74
+ be pointed at different economics. Re-submit an id you have already used with a
75
+ changed amount, token, account or destination and the gateway answers **`409`**
76
+ with `IDEMPOTENCY_TERMS_MISMATCH` rather than acting on it, and rather than
77
+ quietly serving you the original.
51
78
 
52
79
  What counts as "different economics" is who pays, who receives, in which assets,
53
80
  and for how much — the terms that decide where the money goes. Change any of them
54
81
  and you get the `409`; the error's `message` names the category that differed. A
55
- genuinely different payment needs a new `request_id`, which is what the error is
82
+ genuinely different request needs a new `request_id`, which is what the error is
56
83
  telling you.
57
84
 
58
- Anything outside that list is taken from the payment that already exists, and the
59
- value you sent is not applied. So a re-submission is not a way to amend a payment
60
- in flight; it is a way to ask for the one you already made.
85
+ On a quote-request the `409` is doing the same job one step earlier: without it
86
+ you would silently receive the batch you opened *earlier*, whose quotes price the
87
+ corridor you asked for then — and awarding one of those settles a payment on that
88
+ corridor, not the one you meant.
89
+
90
+ Anything outside that list is taken from what already exists, and the value you
91
+ sent is not applied. So a re-submission is not a way to amend something in flight;
92
+ it is a way to ask for the one you already made.
61
93
 
62
94
  ### Deadlines and the signature are exempt
63
95
 
64
96
  Two things you may freely change on a re-submission, because neither identifies
65
- the payment:
97
+ the request:
66
98
 
67
99
  - **Deadlines.** Re-send the original bytes unchanged and the deadlines will
68
- usually have expired by then: `quote_deadline` bounds the auction and is short,
69
- and `POST /v1/payments` can hold a submission for longer than that. An expired
70
- deadline on a re-submission is **not** an error — the request resolves onto the
71
- payment that already exists, so nothing is scheduled against it.
100
+ usually have expired by then: `quote_deadline` is short — it bounds the auction
101
+ on a payment and the collection window on a quote-request — and either endpoint
102
+ can take longer than that to answer. An expired deadline on a re-submission is
103
+ **not** an error; the request resolves onto what already exists, so nothing is
104
+ scheduled against it.
72
105
  - **The signature.** Re-signing is fine. So is re-sending the original signature.
73
106
 
74
107
  Which means both retry styles work: keep the exact bytes from the first attempt,
75
- or rebuild and re-sign the same payment. What must not change is the
76
- `request_id`.
108
+ or rebuild and re-sign. What must not change is the `request_id`.
109
+
110
+ One consequence is specific to quote-requests: **the collection window belongs to
111
+ the batch, not to your request.** Asking again with a later `quote_deadline` does
112
+ not extend a window already open. The `quote_deadline` you get back is the
113
+ batch's own, and `status` tells you whether collection is still running. This is
114
+ the one place where a re-submission returns something that may not match what you
115
+ just sent — by design, because the response has to describe the batch you
116
+ actually received.
77
117
 
78
118
  ## Telling a retry from a first attempt
79
119
 
80
- `PaymentResponse.idempotent_replay` is `true` when you have been handed a payment
81
- that already existed, and `false` when this request created one. Read it if you
82
- need to distinguish "the payment went through just now" from "the payment was
83
- already there" — for logging, for reconciliation, or to avoid double-counting a
120
+ `idempotent_replay` is `true` when you have been handed something that already
121
+ existed, and `false` when this request created it. Every write response carries
122
+ it — `PaymentResponse`, `QuoteRequestResponse` and `QuoteRequestAccepted`. Read it
123
+ if you need to distinguish "this went through just now" from "this was already
124
+ there" — for logging, for reconciliation, or to avoid double-counting a
84
125
  conversion.
85
126
 
86
- ## Reading the outcome
127
+ It is always present, so `false` means `false`. A response missing the field
128
+ altogether is from a gateway predating it.
129
+
130
+ ## Reading the outcome (payments)
87
131
 
88
- `status` is on every response, and the three endings mean different things for a
132
+ `status` is on every payment response, and the three endings mean different things for a
89
133
  retry:
90
134
 
91
135
  | `status` | What it means | What to do |
@@ -121,6 +165,18 @@ holds the connection open for the whole settlement window. That constraint is
121
165
  specific to sitting in the middle of someone else's request, and does not apply
122
166
  to a backend calling this SDK directly.
123
167
 
168
+ ## Recovering a lost quote-request response
169
+
170
+ The rules above are all you need for payments. On `createQuoteRequest` they add up
171
+ to one workflow worth spelling out, because it is the reason the endpoint behaves
172
+ this way.
173
+
174
+ If you lose the create response, re-send the request. You get your
175
+ `quote_request_id` back and can read the batch's quotes — rather than being told
176
+ your `quote_deadline` has expired while holding no id to look the batch up with.
177
+ The collection window is short, so by the time you retry it usually *has* expired;
178
+ that is exactly the case the deadline exemption exists to cover.
179
+
124
180
  ## Chain-specific behavior
125
181
 
126
182
  The SDK gives you the same guarantee on every chain; the on-chain half works
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atumlabs/payment-gateway-client",
3
- "version": "2.2.0",
3
+ "version": "3.0.0",
4
4
  "description": "TypeScript client for Atum Payment Gateway API",
5
5
  "author": "Atum Labs, Inc.",
6
6
  "homepage": "https://github.com/Atum-Labs/protocol/tree/main/payment-gateway-client#readme",
@@ -35,16 +35,18 @@
35
35
  "send-payment": "./dist/cli/send-payment.js",
36
36
  "request-quote": "./dist/cli/request-quote.js",
37
37
  "quote-status": "./dist/cli/quote-status.js",
38
- "payment-status": "./dist/cli/payment-status.js"
38
+ "payment-status": "./dist/cli/payment-status.js",
39
+ "approve-permit2": "./dist/cli/approve-permit2.js"
39
40
  },
40
41
  "scripts": {
41
- "generate": "openapi-ts --input ../schemas/apis/payment-gateway/v1/openapi.yaml --output ./src/generated --client axios --name PaymentGatewayClient --useOptions",
42
- "prebuild:deps": "node -e \"if(!require('fs').existsSync('../key-management/common/typescript/tsconfig.json')){console.error('protocol key-management packages not found at ../key-management. Is this checked out inside the protocol monorepo?');process.exit(1)}\"",
43
- "build:deps": "tsc -p ../key-management/common/typescript/tsconfig.json && tsc -p ../key-management/providers/turnkey/typescript/tsconfig.json && tsc -p ../contracts/svm/escrow-encoding/typescript/tsconfig.json && tsc -p ../contracts/evm/escrow-encoding/typescript/tsconfig.json && tsc -p ../contracts/tvm/escrow-encoding/typescript/tsconfig.json && tsc -p ../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/tsconfig.json",
44
- "build": "pnpm run build:deps && tsup && chmod +x dist/cli/*.js && node scripts/stamp-entry-headers.mjs",
45
- "pretest": "pnpm run build:deps",
46
- "test": "jest",
47
- "clean": "rm -rf dist ../key-management/common/typescript/dist ../key-management/providers/turnkey/typescript/dist ../contracts/svm/escrow-encoding/typescript/dist ../contracts/evm/escrow-encoding/typescript/dist ../contracts/tvm/escrow-encoding/typescript/dist ../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist",
42
+ "generate": "openapi-ts --input ../schemas/apis/payment-gateway/v1/openapi.yaml --output \"${PGC_GEN_OUT:-./src/generated}\" --client axios --name PaymentGatewayClient --useOptions",
43
+ "lint": "eslint . --max-warnings 0",
44
+ "prebuild:deps": "node -e \"if(!require('fs').existsSync('../pnpm-workspace.yaml')){console.error('protocol workspace not found at ../pnpm-workspace.yaml. Is this checked out inside the protocol monorepo?');process.exit(1)}\"",
45
+ "build:deps": "pnpm --filter \"@atum-labs/payment-gateway-client^...\" run build",
46
+ "build": "tsup && chmod +x dist/cli/*.js && node scripts/stamp-entry-headers.mjs",
47
+ "typecheck:tests": "tsc -p tsconfig.test.json",
48
+ "test": "pnpm run typecheck:tests && jest",
49
+ "clean": "rm -rf dist && pnpm --filter \"@atum-labs/payment-gateway-client^...\" run clean",
48
50
  "check:publishable": "node scripts/check-publishable.mjs",
49
51
  "gen:notices": "node scripts/gen-third-party-notices.mjs",
50
52
  "prepare": "pnpm run build",
@@ -61,9 +63,11 @@
61
63
  "zod": "^3.25.0"
62
64
  },
63
65
  "devDependencies": {
66
+ "@atum-labs/chain-address": "workspace:*",
64
67
  "@atum-labs/evm-escrow-encoding": "workspace:*",
65
68
  "@atum-labs/km-core": "workspace:*",
66
69
  "@atum-labs/payment-request-sender-auth": "workspace:*",
70
+ "@atum-labs/payment-request-token-approval": "workspace:*",
67
71
  "@atum-labs/publish-guard": "workspace:*",
68
72
  "@atum-labs/solana-escrow-encoding": "workspace:*",
69
73
  "@atum-labs/turnkey-signer": "workspace:*",
@@ -71,7 +75,10 @@
71
75
  "@hey-api/openapi-ts": "^0.45.1",
72
76
  "@types/jest": "^29.5.5",
73
77
  "@types/node": "^20.0.0",
78
+ "ajv": "^8.17.1",
79
+ "ajv-formats": "^3.0.1",
74
80
  "jest": "^29.7.0",
81
+ "tronweb": "^6.0.0",
75
82
  "ts-jest": "^29.1.1",
76
83
  "tsup": "^8.5.1",
77
84
  "typescript": "^5.0.0",
@@ -88,5 +95,13 @@
88
95
  "license": "SEE LICENSE IN LICENSE",
89
96
  "publishConfig": {
90
97
  "access": "restricted"
98
+ },
99
+ "peerDependencies": {
100
+ "tronweb": "^6.0.0"
101
+ },
102
+ "peerDependenciesMeta": {
103
+ "tronweb": {
104
+ "optional": true
105
+ }
91
106
  }
92
107
  }