x402-sessions 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 madhav
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # x402-sessions
2
+
3
+ **Sign once, settle many times — session-based x402 payments on Stellar.**
4
+
5
+ A tiny TypeScript SDK that turns a single Stellar Asset Contract (SAC) `approve` into an unlimited stream of x402 micropayments. Pay $1 upfront, then every API call automatically settles $0.10 on-chain via `transfer_from`. No per-call wallet popup. No off-chain bookkeeping tricks. The cap and expiry are enforced on-chain by the SAC itself.
6
+
7
+ > Classic x402 = 1 request, 1 signature, 1 settlement.
8
+ > **x402-sessions = 1 signature, N settlements.**
9
+
10
+ ## Why
11
+
12
+ If you're building an AI agent, a dapp game, a pay-per-inference API, or anything where a user makes many small payments in a row, classic x402 becomes friction theatre — one wallet popup per request. Thirdweb built a session model for EVM using Permit2 + off-chain facilitator bookkeeping. This package is the Stellar-native equivalent, and it's arguably simpler *and* stronger:
13
+
14
+ - **On-chain enforcement of the cap** via SEP-41 `approve`. Not a facilitator-side ledger hack.
15
+ - **Zero escrow.** Unused allowance stays in the user's wallet. No refund dance when a session expires.
16
+ - **Works with existing x402 infra.** Registers as a new scheme (`session`) alongside Coinbase's `exact`. Drop-in on the resource-server side via `@x402/core`'s `x402ResourceServer.register()`.
17
+ - **Tiny wire format.** The retry `PaymentPayload.payload` is just `{ sessionId }`.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install x402-sessions @stellar/stellar-sdk
23
+ # if you're also building the resource-server side:
24
+ npm install @x402/core @x402/next
25
+ ```
26
+
27
+ Peer deps: `@stellar/stellar-sdk ^14 || ^15`, `@x402/core ^2.8.0` (optional — only if you use the server-side scheme plugin).
28
+
29
+ You also need a running **x402-sessions facilitator** — a small service that verifies sessions and performs the on-chain `transfer_from`. A reference implementation (Express + better-sqlite3 + `@stellar/stellar-sdk`) ships alongside this package. Self-host it or point your clients at one you trust.
30
+
31
+ ## 30-second quickstart
32
+
33
+ ### 1. Client side (Node)
34
+
35
+ ```ts
36
+ import { Keypair } from "@stellar/stellar-sdk";
37
+ import { createSession } from "x402-sessions";
38
+
39
+ const session = await createSession({
40
+ signer: Keypair.fromSecret(process.env.USER_SECRET!),
41
+ facilitatorUrl: "http://localhost:4021",
42
+ network: "stellar:testnet",
43
+ asset: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA", // USDC testnet SAC
44
+ spendingCap: "1.00", // $1 total
45
+ expiresIn: 3600, // 1 hour
46
+ recipient: "GBWYMS7R...", // your resource server's wallet
47
+ });
48
+
49
+ // session.fetch is a drop-in fetch that transparently pays per call.
50
+ for (let i = 0; i < 10; i++) {
51
+ const res = await session.fetch("https://yourapi.example/inference");
52
+ console.log(await res.json()); // each call settles $0.10 on-chain
53
+ }
54
+ ```
55
+
56
+ Under the hood `createSession` signs and submits **one** SAC `approve(user, facilitator, cap, expiration_ledger)` tx, registers the approval with the facilitator, and returns a `SessionHandle` whose `fetch()` method transparently handles 402 responses.
57
+
58
+ ### 2. Client side (browser with Freighter)
59
+
60
+ ```ts
61
+ import { signTransaction, requestAccess } from "@stellar/freighter-api";
62
+ import { createSession } from "x402-sessions";
63
+
64
+ const { address } = await requestAccess();
65
+
66
+ const session = await createSession({
67
+ signer: {
68
+ publicKey: () => address,
69
+ signTransaction: async (xdr, opts) => {
70
+ const r = await signTransaction(xdr, {
71
+ networkPassphrase: opts?.networkPassphrase,
72
+ address,
73
+ });
74
+ if (r.error) throw new Error(r.error.message ?? "sign failed");
75
+ return r.signedTxXdr;
76
+ },
77
+ },
78
+ facilitatorUrl: "http://localhost:4021",
79
+ network: "stellar:testnet",
80
+ asset: process.env.NEXT_PUBLIC_USDC_SAC_ID!,
81
+ spendingCap: "1.00",
82
+ expiresIn: 3600,
83
+ recipient: process.env.NEXT_PUBLIC_RECIPIENT!,
84
+ });
85
+
86
+ // Use it anywhere you'd use fetch()
87
+ await session.fetch("/api/chat", {
88
+ method: "POST",
89
+ body: JSON.stringify({ prompt: "hi" }),
90
+ headers: { "Content-Type": "application/json" },
91
+ });
92
+ ```
93
+
94
+ Any signer with `publicKey()` + either `sign(tx)` (Node `Keypair`) or `signTransaction(xdr, opts)` (browser wallet adapter) works.
95
+
96
+ ### 3. Resource-server side (Next.js + `@x402/next`)
97
+
98
+ ```ts
99
+ import { paymentProxy, x402ResourceServer } from "@x402/next";
100
+ import { HTTPFacilitatorClient } from "@x402/core/server";
101
+ import { SessionStellarScheme } from "x402-sessions";
102
+
103
+ const facilitator = new HTTPFacilitatorClient({
104
+ url: process.env.SESSION_FACILITATOR_URL ?? "http://localhost:4021",
105
+ });
106
+
107
+ const server = new x402ResourceServer(facilitator).register(
108
+ "stellar:testnet",
109
+ new SessionStellarScheme({
110
+ assetContractId: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
111
+ facilitatorUrl: process.env.SESSION_FACILITATOR_URL,
112
+ }),
113
+ );
114
+
115
+ export const handler = paymentProxy(
116
+ {
117
+ "/api/chat": {
118
+ accepts: [
119
+ {
120
+ scheme: "session" as const,
121
+ price: "0.10",
122
+ network: "stellar:testnet",
123
+ payTo: process.env.SERVER_STELLAR_ADDRESS!,
124
+ },
125
+ ],
126
+ description: "AI chat, settled per message via session",
127
+ },
128
+ },
129
+ server,
130
+ );
131
+ ```
132
+
133
+ That's it. Any request to `/api/chat` without a session payload gets a 402. With one, it passes through and $0.10 settles on-chain.
134
+
135
+ ## How it works
136
+
137
+ ```
138
+ ┌──────────────┐ ┌───────────────┐
139
+ │ user wallet │──1. approve(facilitator, $1, 1h) ─────▶│ USDC SAC │
140
+ │ (Freighter) │ │ (on-chain) │
141
+ └──────┬───────┘ └───────▲───────┘
142
+ │ │
143
+ │ 2. POST /sessions {approvalTxHash, cap, …} │
144
+ ▼ │
145
+ ┌──────────────┐ 3. sessionId │
146
+ │ x402-sessions│◀─────────────────┐ │
147
+ │ facilitator │ │ │
148
+ │ (HTTP) │ │ │
149
+ └──────┬───────┘ │ │
150
+ │ │ │
151
+ │ │ │
152
+ │ 4. POST /api/chat (per call) │
153
+ │ ┌──────────────┴───────────────┐ │
154
+ │ │ │ │
155
+ │ │ ┌──────────────┐ │ │
156
+ │ └──────▶│ resource │ │ │
157
+ │ │ server │ │ │
158
+ │ │ (@x402/next) │ │ │
159
+ │ └──────┬───────┘ │ │
160
+ │ │ │ │
161
+ │◀── 5. /verify ────────── │ │ │
162
+ │ /settle │
163
+ │──── 6. transfer_from(facilitator, user, server, $0.10) ─▶
164
+ │ │
165
+ │ 7. ok + reply │
166
+ │ └──────────────▶ user │
167
+ └──── decrement session (sqlite) ─────────────────────────┘
168
+ ```
169
+
170
+ 1. User signs **one** on-chain `approve(spender=facilitator, amount=cap, expiration_ledger)`.
171
+ 2. SDK registers the approval with the facilitator (`POST /sessions`).
172
+ 3. Facilitator verifies on-chain allowance via `allowance()`, stores the session, returns a `sessionId`.
173
+ 4. User hits protected endpoint with `session.fetch(...)`. SDK handles the 402 dance automatically:
174
+ - Reads the `PAYMENT-REQUIRED` header (x402 v2)
175
+ - Builds a payment payload `{ sessionId }`
176
+ - Retries with `PAYMENT-SIGNATURE` header
177
+ 5. Resource server's `x402ResourceServer` calls facilitator `/verify` then `/settle`.
178
+ 6. Facilitator runs `transfer_from(spender, from, to, amount)` on-chain. The SAC itself enforces the cap and expiry.
179
+ 7. Response flows back to the user. Spent counter decrements.
180
+
181
+ ## API reference
182
+
183
+ ### `createSession(options)`
184
+
185
+ Signs + submits the on-chain `approve`, registers the session with the facilitator, and returns a `SessionHandle`.
186
+
187
+ ```ts
188
+ function createSession(opts: CreateSessionOptions): Promise<SessionHandle>
189
+
190
+ interface CreateSessionOptions {
191
+ signer: ClientSigner; // Keypair or browser wallet adapter
192
+ facilitatorUrl: string; // e.g. "http://localhost:4021"
193
+ network?: "stellar:testnet" | "stellar:pubnet"; // default testnet
194
+ asset: string; // SAC contract id (C...)
195
+ spendingCap: string; // human units, e.g. "1.00"
196
+ decimals?: number; // default 7 (USDC)
197
+ expiresIn: number; // seconds; converted to ledger count
198
+ recipient: string; // payTo address (G...)
199
+ sorobanRpcUrl?: string; // override default
200
+ }
201
+
202
+ interface SessionHandle {
203
+ sessionId: string;
204
+ user: string;
205
+ spender: string;
206
+ asset: string;
207
+ recipient: string;
208
+ cap: string; // base units (stroops for 7-dec USDC)
209
+ spent: string; // base units
210
+ expirationLedger: number;
211
+ network: Network;
212
+ facilitatorUrl: string;
213
+ fetch: typeof fetch; // auto-paying fetch
214
+ }
215
+ ```
216
+
217
+ ### `wrapFetch(sessionId)`
218
+
219
+ Lower-level helper. Returns a `fetch`-compatible function that, on receiving a 402, reads the `PAYMENT-REQUIRED` header, builds a `PaymentPayload` with the given `sessionId`, and retries with `PAYMENT-SIGNATURE`. Use this if you want to manage the session handle yourself (e.g. persist it across pages).
220
+
221
+ ```ts
222
+ function wrapFetch(sessionId: string): typeof fetch
223
+ ```
224
+
225
+ ### `SessionStellarScheme`
226
+
227
+ Resource-server plugin for `@x402/core`'s `x402ResourceServer.register()`. Advertises the `session` scheme, parses prices, and enriches 402 responses with facilitator metadata.
228
+
229
+ ```ts
230
+ class SessionStellarScheme {
231
+ constructor(config: {
232
+ assetContractId: string; // SAC contract id
233
+ decimals?: number; // default 7
234
+ facilitatorUrl?: string; // exposed to clients in 402.extra
235
+ });
236
+ }
237
+ ```
238
+
239
+ Structurally compatible with `@x402/core`'s `SchemeNetworkServer` interface. (It doesn't `implements` literally, to avoid an `@x402/core/types` module-resolution requirement — your TypeScript will happily pass it to `register(network, new SessionStellarScheme(...))` via structural typing.)
240
+
241
+ ### `ClientSigner`
242
+
243
+ Minimal signer interface. A `Keypair` from `@stellar/stellar-sdk` satisfies it natively (via its `sign(Buffer)` method). For browser wallets, provide the `signTransaction` shape.
244
+
245
+ ```ts
246
+ interface ClientSigner {
247
+ publicKey(): string;
248
+ sign?(data: Buffer): Buffer;
249
+ signTransaction?(
250
+ xdr: string,
251
+ opts?: { networkPassphrase: string },
252
+ ): Promise<string>;
253
+ }
254
+ ```
255
+
256
+ ### Helpers
257
+
258
+ ```ts
259
+ import {
260
+ decimalToBaseUnits, // "1.50" + 7 decimals -> 15000000n
261
+ defaultRpcUrlFor, // network -> public Soroban RPC URL
262
+ networkPassphraseFor, // network -> stellar-sdk Networks constant
263
+ } from "x402-sessions";
264
+ ```
265
+
266
+ ## Wire format (session scheme)
267
+
268
+ - **Scheme:** `"session"`
269
+ - **Network:** `"stellar:testnet"` | `"stellar:pubnet"`
270
+ - **`PaymentPayload.payload`** (the scheme-specific slot): `{ sessionId: string }`
271
+ - **Facilitator HTTP surface** (added to the standard x402 triplet):
272
+ - `GET /supported` — standard x402
273
+ - `POST /verify` — standard x402
274
+ - `POST /settle` — standard x402
275
+ - `POST /sessions` — **new**: register a session from a signed approval tx hash
276
+ - `GET /sessions/:id` — **new**: inspect remaining cap / spent / expiry
277
+
278
+ ## Trust model
279
+
280
+ | Limit | Enforced by | Hardness |
281
+ |---|---|---|
282
+ | **Total cap** (e.g. $1) | SAC `approve` + `transfer_from` reverts past cap | **On-chain** |
283
+ | **Expiry** (ledger) | `expiration_ledger` parameter in SAC `approve` | **On-chain** |
284
+ | **Per-call price** (e.g. $0.10) | Facilitator `/settle` refuses amounts > policy | Off-chain (trust your facilitator) |
285
+ | **Recipient binding** | Facilitator only pays the pre-registered `recipient` | Off-chain |
286
+ | **Session reuse control** | Facilitator sqlite bookkeeping | Off-chain |
287
+ | **Unused balance handling** | Funds never escrowed — they stay in the user's wallet | **Native** |
288
+
289
+ In short: on-chain handles the money-safety invariants (total and expiry). Off-chain handles the application policy (per-call price, recipient, bookkeeping). This is the same trust model as thirdweb's session x402 on EVM, except the cap enforcement is *strictly better* because ours is on-chain.
290
+
291
+ If you need on-chain per-call policy enforcement (e.g. "no more than $0.10 per call, no matter what"), upgrade to a Soroban custom account / smart wallet with a policy-signer session key — out of scope for this package.
292
+
293
+ ## No refund dance
294
+
295
+ Because `approve` is a *permission*, not an escrow, tokens never leave the user's wallet until `transfer_from` is called:
296
+
297
+ | Scenario | What happens |
298
+ |---|---|
299
+ | Session expires unused | Still in user's wallet. Allowance becomes unusable. No refund call. |
300
+ | Session partially used | The untouched portion never moved. No refund call. |
301
+ | User wants to end early | Optional: sign `approve(..., amount=0)` to revoke. Not required — expiry handles it. |
302
+
303
+ Compare this to escrow-based session models where you must explicitly claim refunds.
304
+
305
+ ## Security notes
306
+
307
+ - Treat the `sessionId` as a **bearer token** — anyone who steals it can spend the full cap to the pre-registered recipient. Keep it in memory or secure storage; don't log it.
308
+ - The recipient is bound at session creation. A stolen sessionId cannot redirect funds.
309
+ - Worst-case: an attacker drains the whole cap to *your* game server. User loses up to `cap`; funds still end up where they were meant to go.
310
+ - For higher-trust scenarios, use a smaller cap per session and rotate often.
311
+
312
+ ## FAQ
313
+
314
+ **Q: Is this the same as Coinbase x402's `upto` scheme?**
315
+ A: No — `upto` is a canonical Coinbase-authored scheme that's currently EVM-only and enforces single-use authorizations. This is a **new scheme** called `session`, Stellar-native, built on SAC `approve`.
316
+
317
+ **Q: Is this the same as thirdweb's session x402?**
318
+ A: Same *model* (sign once, settle many), different *mechanism*. Thirdweb uses Uniswap Permit2 on EVM with facilitator-side bookkeeping. This package uses Stellar SAC `approve`/`transfer_from` with on-chain bookkeeping. No EIP-7702 involved on either side.
319
+
320
+ **Q: Can I use it with real Circle USDC on Stellar?**
321
+ A: Yes — the SAC contract ID for Circle testnet USDC is `CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA`, and pubnet is `CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75`. Any SEP-41 token works — native XLM SAC, Circle USDC, or your own issued asset.
322
+
323
+ **Q: What if on-chain `transfer_from` fails after the facilitator debits sqlite?**
324
+ A: The reference facilitator rolls back the sqlite debit automatically and returns `success: false, errorReason: "onchain_transfer_failed"` in the `payment-response` header.
325
+
326
+ **Q: Can I reuse a session across multiple resource servers?**
327
+ A: Only if the `recipient` matches. The facilitator binds a session to exactly one recipient at creation time.
328
+
329
+ **Q: What happens if the session cap runs out mid-request?**
330
+ A: Facilitator `/verify` returns `invalidReason: "cap_exceeded"`. The client sees a 402 with the reason in the `payment-response` header; the resource is not served and no on-chain tx is submitted.
331
+
332
+ **Q: Can I use the browser `fetch` directly, without `wrapFetch`?**
333
+ A: Yes — just handle the 402 yourself. Read the `PAYMENT-REQUIRED` response header (base64 JSON), find the `scheme: "session"` accept, build a `PaymentPayload` with `{ payload: { sessionId } }`, base64-encode it, and retry with a `PAYMENT-SIGNATURE` header.
334
+
335
+ ## License
336
+
337
+ MIT © madhav
@@ -0,0 +1,15 @@
1
+ import type { CreateSessionOptions, SessionHandle } from "./types";
2
+ export declare function createSession(opts: CreateSessionOptions): Promise<SessionHandle>;
3
+ /**
4
+ * Returns a fetch-compatible function that, on a 402 response, decodes the
5
+ * `PAYMENT-REQUIRED` response header (x402 v2), finds a `scheme: "session"`
6
+ * accept entry, and retries with `PAYMENT-SIGNATURE` (plus `X-PAYMENT` as a
7
+ * fallback for older servers).
8
+ *
9
+ * The x402 v2 wire format (per @x402/core):
10
+ * - Server 402 puts PaymentRequired JSON in `PAYMENT-REQUIRED` header (base64).
11
+ * The body is empty `{}`.
12
+ * - Client retries with PaymentPayload JSON in `PAYMENT-SIGNATURE` header (base64).
13
+ */
14
+ export declare function wrapFetch(sessionId: string): typeof fetch;
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,oBAAoB,EAMpB,aAAa,EAEd,MAAM,SAAS,CAAC;AAKjB,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8DtF;AAgCD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,KAAK,CAuDzD"}
package/dist/client.js ADDED
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ // createSession() — the main client-facing API.
3
+ //
4
+ // Flow:
5
+ // 1. GET {facilitatorUrl}/supported to learn the facilitator's spender address.
6
+ // 2. Sign & submit SAC approve(user, spender, cap, expiration_ledger) on-chain.
7
+ // 3. POST /sessions to register the approval. Returns a sessionId.
8
+ // 4. Return a SessionHandle with a wrapFetch() that auto-handles 402 responses.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createSession = createSession;
11
+ exports.wrapFetch = wrapFetch;
12
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
13
+ const stellar_1 = require("./stellar");
14
+ const DEFAULT_DECIMALS = 7;
15
+ const DEFAULT_NETWORK = "stellar:testnet";
16
+ async function createSession(opts) {
17
+ const network = opts.network ?? DEFAULT_NETWORK;
18
+ const decimals = opts.decimals ?? DEFAULT_DECIMALS;
19
+ const rpcUrl = opts.sorobanRpcUrl ?? (0, stellar_1.defaultRpcUrlFor)(network);
20
+ // 1. Fetch facilitator's /supported to discover spender address and scheme config.
21
+ const supported = await fetchSupported(opts.facilitatorUrl);
22
+ const kind = supported.kinds.find((k) => k.scheme === "session" && k.network === network);
23
+ if (!kind) {
24
+ throw new Error(`facilitator ${opts.facilitatorUrl} does not advertise scheme=session network=${network}`);
25
+ }
26
+ const spender = kind.extra?.spender ?? supported.signers["stellar:*"]?.[0];
27
+ if (!spender) {
28
+ throw new Error(`facilitator /supported did not include a spender address; got: ${JSON.stringify(supported)}`);
29
+ }
30
+ // 2. Compute expiration ledger from expiresIn seconds.
31
+ const server = new stellar_sdk_1.rpc.Server(rpcUrl, { allowHttp: rpcUrl.startsWith("http://") });
32
+ const expirationLedger = await (0, stellar_1.computeExpirationLedger)(server, opts.expiresIn);
33
+ // 3. Sign & submit SAC approve on-chain.
34
+ const capBaseUnits = (0, stellar_1.decimalToBaseUnits)(opts.spendingCap, decimals);
35
+ const { txHash } = await (0, stellar_1.approveSAC)({
36
+ signer: opts.signer,
37
+ assetContractId: opts.asset,
38
+ spender,
39
+ amount: capBaseUnits,
40
+ expirationLedger,
41
+ network,
42
+ sorobanRpcUrl: rpcUrl,
43
+ });
44
+ // 4. Register the session with the facilitator.
45
+ const createReq = {
46
+ approvalTxHash: txHash,
47
+ user: opts.signer.publicKey(),
48
+ asset: opts.asset,
49
+ recipient: opts.recipient,
50
+ cap: capBaseUnits.toString(),
51
+ expirationLedger,
52
+ network,
53
+ };
54
+ const created = await postSession(opts.facilitatorUrl, createReq);
55
+ // 5. Build the session handle with wrapped fetch.
56
+ return {
57
+ sessionId: created.sessionId,
58
+ user: created.user,
59
+ spender: created.spender,
60
+ asset: created.asset,
61
+ recipient: created.recipient,
62
+ cap: created.cap,
63
+ spent: created.spent,
64
+ expirationLedger: created.expirationLedger,
65
+ network: created.network,
66
+ facilitatorUrl: opts.facilitatorUrl,
67
+ fetch: wrapFetch(created.sessionId),
68
+ };
69
+ }
70
+ async function fetchSupported(facilitatorUrl) {
71
+ const res = await fetch(joinUrl(facilitatorUrl, "/supported"));
72
+ if (!res.ok)
73
+ throw new Error(`facilitator /supported returned ${res.status}`);
74
+ return (await res.json());
75
+ }
76
+ async function postSession(facilitatorUrl, body) {
77
+ const res = await fetch(joinUrl(facilitatorUrl, "/sessions"), {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify(body),
81
+ });
82
+ if (!res.ok) {
83
+ const text = await res.text();
84
+ throw new Error(`facilitator /sessions ${res.status}: ${text}`);
85
+ }
86
+ return (await res.json());
87
+ }
88
+ function joinUrl(base, path) {
89
+ return base.replace(/\/+$/, "") + (path.startsWith("/") ? path : `/${path}`);
90
+ }
91
+ // --------------------------------------------------------------------------
92
+ // wrapFetch: automatically handles 402 responses by attaching the session payload
93
+ // --------------------------------------------------------------------------
94
+ /**
95
+ * Returns a fetch-compatible function that, on a 402 response, decodes the
96
+ * `PAYMENT-REQUIRED` response header (x402 v2), finds a `scheme: "session"`
97
+ * accept entry, and retries with `PAYMENT-SIGNATURE` (plus `X-PAYMENT` as a
98
+ * fallback for older servers).
99
+ *
100
+ * The x402 v2 wire format (per @x402/core):
101
+ * - Server 402 puts PaymentRequired JSON in `PAYMENT-REQUIRED` header (base64).
102
+ * The body is empty `{}`.
103
+ * - Client retries with PaymentPayload JSON in `PAYMENT-SIGNATURE` header (base64).
104
+ */
105
+ function wrapFetch(sessionId) {
106
+ return async (input, init = {}) => {
107
+ const first = await fetch(input, init);
108
+ if (first.status !== 402)
109
+ return first;
110
+ // Try v2 header-based discovery first, then v1 body-based.
111
+ let required = null;
112
+ const headerValue = first.headers.get("payment-required") ??
113
+ first.headers.get("PAYMENT-REQUIRED") ??
114
+ first.headers.get("x-payment-required");
115
+ if (headerValue) {
116
+ try {
117
+ required = base64DecodeJson(headerValue);
118
+ }
119
+ catch {
120
+ /* fall through */
121
+ }
122
+ }
123
+ if (!required) {
124
+ try {
125
+ const body = (await first.clone().json());
126
+ if (body && Array.isArray(body.accepts))
127
+ required = body;
128
+ }
129
+ catch {
130
+ /* fall through */
131
+ }
132
+ }
133
+ if (!required)
134
+ return first;
135
+ const sessionAccept = (required.accepts ?? []).find((a) => a.scheme === "session");
136
+ if (!sessionAccept)
137
+ return first;
138
+ const paymentPayload = {
139
+ x402Version: required.x402Version ?? 2,
140
+ accepted: sessionAccept,
141
+ payload: { sessionId },
142
+ };
143
+ const encoded = base64EncodeJson(paymentPayload);
144
+ const retryInit = {
145
+ ...init,
146
+ headers: {
147
+ ...init.headers,
148
+ // v2 canonical name
149
+ "PAYMENT-SIGNATURE": encoded,
150
+ // v1 fallback — harmless if server ignores it
151
+ "X-PAYMENT": encoded,
152
+ },
153
+ };
154
+ return fetch(input, retryInit);
155
+ };
156
+ }
157
+ function base64EncodeJson(obj) {
158
+ const json = JSON.stringify(obj);
159
+ if (typeof Buffer !== "undefined")
160
+ return Buffer.from(json, "utf-8").toString("base64");
161
+ // Browser fallback
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ const g = globalThis;
164
+ return g.btoa(unescape(encodeURIComponent(json)));
165
+ }
166
+ function base64DecodeJson(b64) {
167
+ let json;
168
+ if (typeof Buffer !== "undefined") {
169
+ json = Buffer.from(b64, "base64").toString("utf-8");
170
+ }
171
+ else {
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ const g = globalThis;
174
+ json = decodeURIComponent(escape(g.atob(b64)));
175
+ }
176
+ return JSON.parse(json);
177
+ }
178
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { createSession, wrapFetch } from "./client";
2
+ export { SessionStellarScheme } from "./scheme-server";
3
+ export type { ClientSigner, CreateSessionOptions, CreateSessionRequest, CreateSessionResponse, Network, PaymentPayload, PaymentRequired, PaymentRequirements, SessionHandle, SessionPaymentPayloadBody, SupportedResponse, } from "./types";
4
+ export { decimalToBaseUnits, defaultRpcUrlFor, networkPassphraseFor } from "./stellar";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,OAAO,EACP,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ // x402-sessions
3
+ // Sign once, settle many times — session-based x402 payments on Stellar.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.networkPassphraseFor = exports.defaultRpcUrlFor = exports.decimalToBaseUnits = exports.SessionStellarScheme = exports.wrapFetch = exports.createSession = void 0;
6
+ var client_1 = require("./client");
7
+ Object.defineProperty(exports, "createSession", { enumerable: true, get: function () { return client_1.createSession; } });
8
+ Object.defineProperty(exports, "wrapFetch", { enumerable: true, get: function () { return client_1.wrapFetch; } });
9
+ var scheme_server_1 = require("./scheme-server");
10
+ Object.defineProperty(exports, "SessionStellarScheme", { enumerable: true, get: function () { return scheme_server_1.SessionStellarScheme; } });
11
+ var stellar_1 = require("./stellar");
12
+ Object.defineProperty(exports, "decimalToBaseUnits", { enumerable: true, get: function () { return stellar_1.decimalToBaseUnits; } });
13
+ Object.defineProperty(exports, "defaultRpcUrlFor", { enumerable: true, get: function () { return stellar_1.defaultRpcUrlFor; } });
14
+ Object.defineProperty(exports, "networkPassphraseFor", { enumerable: true, get: function () { return stellar_1.networkPassphraseFor; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,gBAAgB;AAChB,yEAAyE;;;AAEzE,mCAAoD;AAA3C,uGAAA,aAAa,OAAA;AAAE,mGAAA,SAAS,OAAA;AACjC,iDAAuD;AAA9C,qHAAA,oBAAoB,OAAA;AAc7B,qCAAuF;AAA9E,6GAAA,kBAAkB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAAE,+GAAA,oBAAoB,OAAA"}
@@ -0,0 +1,42 @@
1
+ import type { Network, PaymentRequirements } from "./types";
2
+ type AssetAmount = {
3
+ asset: string;
4
+ amount: string;
5
+ extra?: Record<string, unknown>;
6
+ };
7
+ type Price = string | number | AssetAmount;
8
+ export type SessionSchemeConfig = {
9
+ /** SAC contract id of the payment token (e.g. USDC). */
10
+ assetContractId: string;
11
+ /** Token decimals. Default 7 (USDC on Stellar). */
12
+ decimals?: number;
13
+ /** Facilitator base URL — embedded into /402 extras so clients can find the sessions endpoint. */
14
+ facilitatorUrl?: string;
15
+ };
16
+ /**
17
+ * Resource-server plugin for the `session` scheme on Stellar.
18
+ *
19
+ * Usage (in your proxy.ts):
20
+ *
21
+ * import { SessionStellarScheme } from "x402-sessions/scheme-server";
22
+ *
23
+ * const server = new x402ResourceServer(facilitatorClient)
24
+ * .register("stellar:testnet", new SessionStellarScheme({
25
+ * assetContractId: "CBIELTK6...",
26
+ * facilitatorUrl: "http://localhost:4021",
27
+ * }));
28
+ */
29
+ export declare class SessionStellarScheme {
30
+ readonly scheme: "session";
31
+ private readonly config;
32
+ constructor(config: SessionSchemeConfig);
33
+ parsePrice(price: Price, _network: Network): Promise<AssetAmount>;
34
+ enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
35
+ x402Version: number;
36
+ scheme: string;
37
+ network: Network;
38
+ extra?: Record<string, unknown>;
39
+ }, _facilitatorExtensions: string[]): Promise<PaymentRequirements>;
40
+ }
41
+ export {};
42
+ //# sourceMappingURL=scheme-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheme-server.d.ts","sourceRoot":"","sources":["../src/scheme-server.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE5D,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AACtF,KAAK,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG;IAChC,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAYF;;;;;;;;;;;;GAYG;AAMH,qBAAa,oBAAoB;IAC/B,QAAQ,CAAC,MAAM,EAAG,SAAS,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwE;gBAEnF,MAAM,EAAE,mBAAmB;IAKjC,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjE,0BAA0B,CAC9B,mBAAmB,EAAE,mBAAmB,EACxC,aAAa,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,EAED,sBAAsB,EAAE,MAAM,EAAE,GAC/B,OAAO,CAAC,mBAAmB,CAAC;CAUhC"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ // SessionStellarScheme — resource-server plugin.
3
+ //
4
+ // Implements @x402/core's SchemeNetworkServer interface so it can be registered
5
+ // with `x402ResourceServer.register(network, new SessionStellarScheme())`.
6
+ //
7
+ // Responsibilities:
8
+ // - parsePrice(): convert "1.00" (decimal USD-ish) to { asset, amount } base units.
9
+ // - enhancePaymentRequirements(): add scheme-specific extras (facilitator spender,
10
+ // sessions endpoint URL) to the 402 response so the client SDK knows where to
11
+ // create a session.
12
+ //
13
+ // The actual verify/settle happen on the HTTP facilitator (our Express service),
14
+ // which x402's HTTPFacilitatorClient talks to.
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.SessionStellarScheme = void 0;
17
+ const DEFAULT_DECIMALS = 7;
18
+ function decimalToBaseUnits(amount, decimals) {
19
+ const s = typeof amount === "number" ? amount.toString() : amount;
20
+ if (!/^\d+(\.\d+)?$/.test(s))
21
+ throw new Error(`invalid decimal amount: ${s}`);
22
+ const [whole, frac = ""] = s.split(".");
23
+ const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
24
+ return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || "0")).toString();
25
+ }
26
+ /**
27
+ * Resource-server plugin for the `session` scheme on Stellar.
28
+ *
29
+ * Usage (in your proxy.ts):
30
+ *
31
+ * import { SessionStellarScheme } from "x402-sessions/scheme-server";
32
+ *
33
+ * const server = new x402ResourceServer(facilitatorClient)
34
+ * .register("stellar:testnet", new SessionStellarScheme({
35
+ * assetContractId: "CBIELTK6...",
36
+ * facilitatorUrl: "http://localhost:4021",
37
+ * }));
38
+ */
39
+ // NOTE: We intentionally do NOT `implements SchemeNetworkServer` here — @x402/core
40
+ // uses an exports map that requires node16+ module resolution, which would force
41
+ // us to emit ESM or use .js extensions. Instead, this class is structurally
42
+ // compatible with SchemeNetworkServer. TypeScript's duck typing means consumers
43
+ // can pass `new SessionStellarScheme(...)` directly to `x402ResourceServer.register(network, ...)`.
44
+ class SessionStellarScheme {
45
+ constructor(config) {
46
+ this.scheme = "session";
47
+ this.config = { decimals: DEFAULT_DECIMALS, ...config };
48
+ }
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
+ async parsePrice(price, _network) {
51
+ if (typeof price === "object" && "asset" in price && "amount" in price) {
52
+ return price;
53
+ }
54
+ const amount = decimalToBaseUnits(price, this.config.decimals);
55
+ return { asset: this.config.assetContractId, amount };
56
+ }
57
+ async enhancePaymentRequirements(paymentRequirements, supportedKind,
58
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
59
+ _facilitatorExtensions) {
60
+ const mergedExtra = {
61
+ ...paymentRequirements.extra,
62
+ ...(supportedKind.extra ?? {}),
63
+ };
64
+ if (this.config.facilitatorUrl && !mergedExtra.facilitatorUrl) {
65
+ mergedExtra.facilitatorUrl = this.config.facilitatorUrl;
66
+ }
67
+ return { ...paymentRequirements, extra: mergedExtra };
68
+ }
69
+ }
70
+ exports.SessionStellarScheme = SessionStellarScheme;
71
+ //# sourceMappingURL=scheme-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheme-server.js","sourceRoot":"","sources":["../src/scheme-server.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,EAAE;AACF,gFAAgF;AAChF,2EAA2E;AAC3E,EAAE;AACF,oBAAoB;AACpB,sFAAsF;AACtF,qFAAqF;AACrF,kFAAkF;AAClF,wBAAwB;AACxB,EAAE;AACF,iFAAiF;AACjF,+CAA+C;;;AAgB/C,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B,SAAS,kBAAkB,CAAC,MAAuB,EAAE,QAAgB;IACnE,MAAM,CAAC,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAClE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACtF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,mFAAmF;AACnF,iFAAiF;AACjF,4EAA4E;AAC5E,gFAAgF;AAChF,oGAAoG;AACpG,MAAa,oBAAoB;IAI/B,YAAY,MAA2B;QAH9B,WAAM,GAAG,SAAkB,CAAC;QAInC,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1D,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,UAAU,CAAC,KAAY,EAAE,QAAiB;QAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YACvE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAwB,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClF,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,0BAA0B,CAC9B,mBAAwC,EACxC,aAKC;IACD,6DAA6D;IAC7D,sBAAgC;QAEhC,MAAM,WAAW,GAA4B;YAC3C,GAAG,mBAAmB,CAAC,KAAK;YAC5B,GAAG,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC;SAC/B,CAAC;QACF,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;YAC9D,WAAW,CAAC,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;QAC1D,CAAC;QACD,OAAO,EAAE,GAAG,mBAAmB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACxD,CAAC;CACF;AArCD,oDAqCC"}
@@ -0,0 +1,31 @@
1
+ import { rpc } from "@stellar/stellar-sdk";
2
+ import type { ClientSigner, Network } from "./types";
3
+ export declare function networkPassphraseFor(network: Network): string;
4
+ export declare function defaultRpcUrlFor(network: Network): string;
5
+ export declare function computeExpirationLedger(server: rpc.Server, expiresInSeconds: number): Promise<number>;
6
+ /**
7
+ * Convert a decimal-string amount (e.g. "1.00") to base-unit bigint given decimals.
8
+ * Supports simple decimal form only; rejects scientific notation.
9
+ */
10
+ export declare function decimalToBaseUnits(amount: string, decimals: number): bigint;
11
+ /**
12
+ * Build and submit a SAC `approve(from, spender, amount, expiration_ledger)` transaction.
13
+ *
14
+ * Returns the tx hash on success.
15
+ *
16
+ * Signing: if the signer exposes `signTransaction(xdr, { networkPassphrase })` (browser
17
+ * wallet adapter shape), we use that path. Otherwise we expect a `Keypair` (Node) and
18
+ * call `tx.sign(kp)` directly.
19
+ */
20
+ export declare function approveSAC(args: {
21
+ signer: ClientSigner;
22
+ assetContractId: string;
23
+ spender: string;
24
+ amount: bigint;
25
+ expirationLedger: number;
26
+ network: Network;
27
+ sorobanRpcUrl?: string;
28
+ }): Promise<{
29
+ txHash: string;
30
+ }>;
31
+ //# sourceMappingURL=stellar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stellar.d.ts","sourceRoot":"","sources":["../src/stellar.ts"],"names":[],"mappings":"AAMA,OAAO,EASL,GAAG,EAEJ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAYrD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAE7D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIzD;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,GAAG,CAAC,MAAM,EAClB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO3E;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA4D9B"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ // Client-side Stellar/Soroban helpers for the SDK:
3
+ // - build & submit SAC `approve(from, spender, amount, expiration_ledger)`
4
+ // - fetch the current ledger sequence (to compute expiration_ledger from expiresIn seconds)
5
+ //
6
+ // Imports stellar-sdk as a peer dep — users bring their own version.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.networkPassphraseFor = networkPassphraseFor;
9
+ exports.defaultRpcUrlFor = defaultRpcUrlFor;
10
+ exports.computeExpirationLedger = computeExpirationLedger;
11
+ exports.decimalToBaseUnits = decimalToBaseUnits;
12
+ exports.approveSAC = approveSAC;
13
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
14
+ const LEDGER_SECONDS = 5;
15
+ function toI128(amount) {
16
+ return (0, stellar_sdk_1.nativeToScVal)(typeof amount === "string" ? BigInt(amount) : amount, { type: "i128" });
17
+ }
18
+ function addrToScVal(addr) {
19
+ return stellar_sdk_1.Address.fromString(addr).toScVal();
20
+ }
21
+ function networkPassphraseFor(network) {
22
+ return network === "stellar:pubnet" ? stellar_sdk_1.Networks.PUBLIC : stellar_sdk_1.Networks.TESTNET;
23
+ }
24
+ function defaultRpcUrlFor(network) {
25
+ return network === "stellar:pubnet"
26
+ ? "https://soroban-rpc.mainnet.stellar.gateway.fm"
27
+ : "https://soroban-testnet.stellar.org";
28
+ }
29
+ async function computeExpirationLedger(server, expiresInSeconds) {
30
+ const latest = await server.getLatestLedger();
31
+ return latest.sequence + Math.ceil(expiresInSeconds / LEDGER_SECONDS);
32
+ }
33
+ /**
34
+ * Convert a decimal-string amount (e.g. "1.00") to base-unit bigint given decimals.
35
+ * Supports simple decimal form only; rejects scientific notation.
36
+ */
37
+ function decimalToBaseUnits(amount, decimals) {
38
+ if (!/^\d+(\.\d+)?$/.test(amount)) {
39
+ throw new Error(`invalid decimal amount: ${amount}`);
40
+ }
41
+ const [whole, frac = ""] = amount.split(".");
42
+ const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
43
+ return BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || "0");
44
+ }
45
+ /**
46
+ * Build and submit a SAC `approve(from, spender, amount, expiration_ledger)` transaction.
47
+ *
48
+ * Returns the tx hash on success.
49
+ *
50
+ * Signing: if the signer exposes `signTransaction(xdr, { networkPassphrase })` (browser
51
+ * wallet adapter shape), we use that path. Otherwise we expect a `Keypair` (Node) and
52
+ * call `tx.sign(kp)` directly.
53
+ */
54
+ async function approveSAC(args) {
55
+ const rpcUrl = args.sorobanRpcUrl ?? defaultRpcUrlFor(args.network);
56
+ const server = new stellar_sdk_1.rpc.Server(rpcUrl, { allowHttp: rpcUrl.startsWith("http://") });
57
+ const passphrase = networkPassphraseFor(args.network);
58
+ const from = args.signer.publicKey();
59
+ const sourceAccount = await server.getAccount(from);
60
+ const contract = new stellar_sdk_1.Contract(args.assetContractId);
61
+ const tx = new stellar_sdk_1.TransactionBuilder(sourceAccount, {
62
+ fee: stellar_sdk_1.BASE_FEE,
63
+ networkPassphrase: passphrase,
64
+ })
65
+ .addOperation(contract.call("approve", addrToScVal(from), addrToScVal(args.spender), toI128(args.amount), (0, stellar_sdk_1.nativeToScVal)(args.expirationLedger, { type: "u32" })))
66
+ .setTimeout(60)
67
+ .build();
68
+ const prepared = await server.prepareTransaction(tx);
69
+ // --- signing paths ---
70
+ let signedXdr;
71
+ if (args.signer.signTransaction) {
72
+ const xdrStr = prepared.toXDR();
73
+ signedXdr = await args.signer.signTransaction(xdrStr, { networkPassphrase: passphrase });
74
+ }
75
+ else if (args.signer.sign) {
76
+ prepared.sign(args.signer);
77
+ signedXdr = prepared.toXDR();
78
+ }
79
+ else {
80
+ throw new Error("signer must expose either sign() or signTransaction()");
81
+ }
82
+ // Reconstruct signed tx for submission.
83
+ const signedTx = stellar_sdk_1.TransactionBuilder.fromXDR(signedXdr, passphrase);
84
+ const sent = await server.sendTransaction(signedTx);
85
+ if (sent.status === "ERROR") {
86
+ throw new Error(`approve send failed: ${JSON.stringify(sent.errorResult)}`);
87
+ }
88
+ // Poll for confirmation.
89
+ const deadline = Date.now() + 30000;
90
+ while (Date.now() < deadline) {
91
+ const r = await server.getTransaction(sent.hash);
92
+ if (r.status === stellar_sdk_1.rpc.Api.GetTransactionStatus.SUCCESS) {
93
+ return { txHash: sent.hash };
94
+ }
95
+ if (r.status === stellar_sdk_1.rpc.Api.GetTransactionStatus.FAILED) {
96
+ throw new Error(`approve tx failed: ${JSON.stringify(r)}`);
97
+ }
98
+ await new Promise((r) => setTimeout(r, 1500));
99
+ }
100
+ throw new Error(`approve tx ${sent.hash} timed out`);
101
+ }
102
+ //# sourceMappingURL=stellar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stellar.js","sourceRoot":"","sources":["../src/stellar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ export type Network = `${string}:${string}`;
2
+ export type PaymentRequirements = {
3
+ scheme: string;
4
+ network: Network;
5
+ asset: string;
6
+ amount: string;
7
+ payTo: string;
8
+ maxTimeoutSeconds: number;
9
+ extra: Record<string, unknown>;
10
+ };
11
+ export type SessionPaymentPayloadBody = {
12
+ sessionId: string;
13
+ };
14
+ export type PaymentPayload = {
15
+ x402Version: number;
16
+ accepted: PaymentRequirements;
17
+ payload: SessionPaymentPayloadBody;
18
+ resource?: {
19
+ url: string;
20
+ description?: string;
21
+ mimeType?: string;
22
+ };
23
+ extensions?: Record<string, unknown>;
24
+ };
25
+ export type PaymentRequired = {
26
+ x402Version: number;
27
+ error?: string;
28
+ resource: {
29
+ url: string;
30
+ description?: string;
31
+ mimeType?: string;
32
+ };
33
+ accepts: PaymentRequirements[];
34
+ extensions?: Record<string, unknown>;
35
+ };
36
+ export type CreateSessionOptions = {
37
+ /** Facilitator HTTP base URL, e.g. http://localhost:4021 */
38
+ facilitatorUrl: string;
39
+ /** Stellar network identifier. Default: "stellar:testnet" */
40
+ network?: Network;
41
+ /** SAC contract id of the payment token (e.g. USDC). */
42
+ asset: string;
43
+ /**
44
+ * Spending cap in human units (e.g. "1.00" for 1 USDC).
45
+ * Will be converted to base units using `decimals`.
46
+ */
47
+ spendingCap: string;
48
+ /** Token decimals. USDC on Stellar = 7. Default: 7 */
49
+ decimals?: number;
50
+ /** How long the session is valid, in seconds. Converted to ledgers (~5s/ledger). */
51
+ expiresIn: number;
52
+ /** Address where settled funds go (resource server wallet). */
53
+ recipient: string;
54
+ /**
55
+ * Signer used to sign & submit the SAC approve transaction.
56
+ * For Node: a `Keypair` from @stellar/stellar-sdk.
57
+ * For browser: implement the `ClientSigner` interface below.
58
+ */
59
+ signer: ClientSigner;
60
+ /** Optional custom Soroban RPC URL. Defaults to testnet public RPC. */
61
+ sorobanRpcUrl?: string;
62
+ };
63
+ /**
64
+ * Minimal signer interface the SDK needs from a wallet.
65
+ * A @stellar/stellar-sdk `Keypair` satisfies this directly.
66
+ * For browser wallets (Freighter, etc.), provide an adapter.
67
+ */
68
+ export interface ClientSigner {
69
+ /** The G... public key of this signer. */
70
+ publicKey(): string;
71
+ /** Sign a tx hash (raw 32-byte buffer). Used by the stellar-sdk internal tx.sign flow. */
72
+ sign?(data: Buffer): Buffer;
73
+ /**
74
+ * Alternative: sign a built Transaction's XDR string and return the signed XDR.
75
+ * Use this form for browser wallet adapters like Freighter that expose
76
+ * `signTransaction(xdr): Promise<xdr>`.
77
+ */
78
+ signTransaction?(xdr: string, opts?: {
79
+ networkPassphrase: string;
80
+ }): Promise<string>;
81
+ }
82
+ export type SessionHandle = {
83
+ sessionId: string;
84
+ user: string;
85
+ spender: string;
86
+ asset: string;
87
+ recipient: string;
88
+ cap: string;
89
+ spent: string;
90
+ expirationLedger: number;
91
+ network: Network;
92
+ facilitatorUrl: string;
93
+ /** Fetch wrapper that auto-adds X-PAYMENT session header on 402 responses. */
94
+ fetch: typeof fetch;
95
+ };
96
+ export type CreateSessionRequest = {
97
+ approvalTxHash: string;
98
+ user: string;
99
+ asset: string;
100
+ recipient: string;
101
+ cap: string;
102
+ expirationLedger: number;
103
+ network: Network;
104
+ };
105
+ export type CreateSessionResponse = {
106
+ sessionId: string;
107
+ user: string;
108
+ spender: string;
109
+ asset: string;
110
+ recipient: string;
111
+ cap: string;
112
+ spent: string;
113
+ expirationLedger: number;
114
+ network: Network;
115
+ };
116
+ export type SupportedResponse = {
117
+ kinds: Array<{
118
+ x402Version: number;
119
+ scheme: string;
120
+ network: Network;
121
+ extra?: Record<string, unknown>;
122
+ }>;
123
+ extensions: string[];
124
+ signers: Record<string, string[]>;
125
+ };
126
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // Types shared between client SDK, resource-server scheme plugin, and facilitator.
3
+ // These mirror the wire format defined in the facilitator package.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,mEAAmE"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "x402-sessions",
3
+ "version": "0.1.0",
4
+ "description": "Sign once, settle many times — session-based x402 payments on Stellar. One on-chain SAC approve unlocks unlimited micropayments via transfer_from until the cap or expiry is reached. No per-request wallet popup.",
5
+ "license": "MIT",
6
+ "author": "madhav",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./client": {
21
+ "types": "./dist/client.d.ts",
22
+ "default": "./dist/client.js"
23
+ },
24
+ "./scheme": {
25
+ "types": "./dist/scheme-server.d.ts",
26
+ "default": "./dist/scheme-server.js"
27
+ },
28
+ "./stellar": {
29
+ "types": "./dist/stellar.d.ts",
30
+ "default": "./dist/stellar.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "dev": "tsc -p tsconfig.json --watch",
37
+ "clean": "rm -rf dist",
38
+ "prepublishOnly": "npm run clean && npm run build"
39
+ },
40
+ "keywords": [
41
+ "x402",
42
+ "stellar",
43
+ "soroban",
44
+ "payments",
45
+ "micropayments",
46
+ "session",
47
+ "pay-per-call",
48
+ "sac",
49
+ "usdc",
50
+ "sign-once",
51
+ "settle-many",
52
+ "agentic-payments"
53
+ ],
54
+ "peerDependencies": {
55
+ "@stellar/stellar-sdk": "^14.0.0 || ^15.0.0",
56
+ "@x402/core": "^2.8.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@x402/core": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@stellar/stellar-sdk": "^15.0.1",
65
+ "@types/node": "^22",
66
+ "@x402/core": "^2.8.0",
67
+ "typescript": "^5.5.0"
68
+ },
69
+ "engines": {
70
+ "node": ">=18"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
75
+ }