@altaga/x402-sui 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD ADDED
@@ -0,0 +1,439 @@
1
+ # @altaga/x402-sui
2
+
3
+ > Modular [x402](https://www.x402.org/) implementation for **Sui Sponsored Transactions** (gas-station style).
4
+
5
+ This package is a small, pluggable reference SDK for the x402 "Payment Required" HTTP
6
+ pattern, targeting the **Sui** blockchain. The design separates concerns into three
7
+ roles — **Client**, **Resource Server**, and **Facilitator** — and exposes an
8
+ HTTP middleware layer so any Express-based API can gate routes behind a 402
9
+ payment challenge with a few lines of glue.
10
+
11
+ The bundled `sui/exact` scheme uses Sui's *sponsored transaction* model: the
12
+ buyer signs the payment intent, the facilitator co-signs as the **gas owner**, and
13
+ the resource server never touches a private key.
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ - [Architecture](#architecture)
20
+ - [Installation](#installation)
21
+ - [Project Layout](#project-layout)
22
+ - [Protocol Flow](#protocol-flow)
23
+ - [Quick Start](#quick-start)
24
+ - [1. Resource Server (the paid API)](#1-resource-server-the-paid-api)
25
+ - [2. Facilitator (the gas station)](#2-facilitator-the-gas-station)
26
+ - [3. Client (the buyer)](#3-client-the-buyer)
27
+ - [Module Reference](#module-reference)
28
+ - [`core/client`](#coreclient)
29
+ - [`core/http`](#corehttp)
30
+ - [`core/server`](#coreserver)
31
+ - [`core/server-http`](#coreserver-http)
32
+ - [`core/facilitator`](#corefacilitator)
33
+ - [`sui/exact/client`](#suiexactclient)
34
+ - [`sui/exact/server`](#suiexactserver)
35
+ - [`sui/exact/facilitator`](#suiexactfacilitator)
36
+ - [Building](#building)
37
+ - [Peer Dependencies](#peer-dependencies)
38
+ - [License](#license)
39
+
40
+ ---
41
+
42
+ ## Architecture
43
+
44
+ x402 is a request/response convention layered on top of HTTP 402 *Payment Required*:
45
+
46
+ ```
47
+ ┌─────────┐ ┌──────────────────┐ ┌──────────────┐
48
+ │ Client │ ──GET──▶│ Resource Server │ │ Facilitator │
49
+ │ │ │ (Express route) │ │ (gas owner) │
50
+ │ │◀─ 402 ──│ │ │ │
51
+ │ │ + x-accepts │ │ │
52
+ │ │ + x-facilitator-url │ │ │
53
+ │ │ │ │ │
54
+ │ │── POST /sponsor ────────────────────▶ │
55
+ │ │◀── sponsored tx bytes ───────────────── │
56
+ │ │── GET + X-PAYMENT ──▶│ │ │
57
+ │ │ │── /verify ──────▶│ │
58
+ │ │ │── /settle ─────▶│ │
59
+ │ │◀── 200 + x-payment-response ────────────│ │
60
+ └─────────┘ └──────────────────┘ └──────────────┘
61
+ ```
62
+
63
+ The SDK mirrors that split into three independent core modules, each of which
64
+ is scheme-agnostic. Scheme implementations (currently just `sui/exact`) are
65
+ plugged in with a simple `register(schemeName:network, impl)` call — wildcards
66
+ (`sui:*`, `*:*`) are supported.
67
+
68
+ | Role | Module | Responsibility |
69
+ |------|--------|----------------|
70
+ | Client | `core/client`, `core/http` | Parse a 402 challenge, pick a supported scheme, ask the facilitator to sponsor, sign the tx, replay the request with `X-PAYMENT`. |
71
+ | Resource Server | `core/server`, `core/server-http` | Issue 402 challenges for protected routes; verify and settle payments via the facilitator. |
72
+ | Facilitator | `core/facilitator` | Co-sign transactions as gas owner, dry-run to verify, and submit to the chain. |
73
+
74
+ ---
75
+
76
+ ## Installation
77
+
78
+ ```bash
79
+ npm install @altaga/x402-sui
80
+ ```
81
+
82
+ You also need the peer dependencies in your project:
83
+
84
+ ```bash
85
+ npm install @mysten/sui @mysten/bcs express
86
+ ```
87
+
88
+ > Pre-built bundles live in [`dist/`](./dist). The build step is only required
89
+ > if you're modifying the source under `core/` or `sui/`.
90
+
91
+ ---
92
+
93
+ ## Project Layout
94
+
95
+ ```
96
+ sdk/
97
+ ├── core/ # Scheme-agnostic framework
98
+ │ ├── client.mjs # x402Client — scheme registry & lookup
99
+ │ ├── http.mjs # x402HTTPClient — parse 402, encode X-PAYMENT
100
+ │ ├── server.mjs # x402ResourceServer — verify+settle via facilitator
101
+ │ ├── server-http.mjs # x402HTTPResourceServer — Express middleware
102
+ │ └── facilitator.mjs # x402Facilitator — sponsor / verify / settle
103
+ ├── sui/
104
+ │ └── exact/ # The "exact" payment scheme on Sui
105
+ │ ├── client.mjs # Builds the payment PTB, signs, asks for sponsor
106
+ │ ├── server.mjs # Passthrough (work happens in facilitator)
107
+ │ └── facilitator.mjs # Co-signs as gas owner, dry-runs, submits
108
+ ├── dist/ # esbuild output (consumed via package exports)
109
+ ├── package.json
110
+ └── README.MD
111
+ ```
112
+
113
+ `package.json` exposes each piece as a subpath export so consumers can import
114
+ only what they need:
115
+
116
+ ```json
117
+ {
118
+ "@altaga/x402-sui/core/client": "…",
119
+ "@altaga/x402-sui/core/http": "…",
120
+ "@altaga/x402-sui/core/server": "…",
121
+ "@altaga/x402-sui/core/server-http": "…",
122
+ "@altaga/x402-sui/core/facilitator": "…",
123
+ "@altaga/x402-sui/sui/exact/client": "…",
124
+ "@altaga/x402-sui/sui/exact/server": "…",
125
+ "@altaga/x402-sui/sui/exact/facilitator":"…"
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Protocol Flow
132
+
133
+ 1. **Challenge.** The client requests a protected route. The server replies
134
+ `402 Payment Required` with:
135
+ - `X-Accepts`: base64-encoded JSON array of accepted `{ scheme, network, asset, payTo, maxAmountRequired, ... }` payment requirements.
136
+ - `X-Facilitator-Url`: where the client should ask for gas sponsorship.
137
+
138
+ 2. **Sponsor.** The client picks a supported scheme from `X-Accepts`, builds
139
+ the payment transaction (a `splitCoins` + `transferObjects` PTB on Sui),
140
+ and POSTs the raw tx bytes + sender address to `{facilitatorUrl}/sponsor`.
141
+ The facilitator returns `sponsoredTxBytes` (with itself as `gasOwner`) and
142
+ its `sponsorSignature`.
143
+
144
+ 3. **Sign.** The client signs the sponsored bytes with its own keypair.
145
+
146
+ 4. **Pay.** The client replays the original request with `X-PAYMENT:`
147
+ (base64 JSON of `{ scheme, network, transaction, signature, amount, payTo, asset }`).
148
+
149
+ 5. **Verify & Settle.** The server forwards the payload to the facilitator:
150
+ - `/verify` performs a `dryRunTransactionBlock` to mathematically prove the
151
+ tx would succeed.
152
+ - `/settle` calls `executeTransactionBlock` with the combined
153
+ `[buyerSignature, sponsorSignature]` array and returns the digest.
154
+
155
+ 6. **Respond.** On success, the server attaches `X-Payment-Response` (base64
156
+ JSON containing the `transactionDigest`) and hands off to the actual route
157
+ handler.
158
+
159
+ ---
160
+
161
+ ## Quick Start
162
+
163
+ ### 1. Resource Server (the paid API)
164
+
165
+ ```js
166
+ import express from "express";
167
+ import { SuiClient } from "@mysten/sui/client";
168
+
169
+ import { x402ResourceServer } from "@altaga/x402-sui/core/server";
170
+ import { HTTPFacilitatorClient } from "@altaga/x402-sui/core/http";
171
+ import { x402HTTPResourceServer } from "@altaga/x402-sui/core/server-http";
172
+ import { ExactSuiServerScheme } from "@altaga/x402-sui/sui/exact/server";
173
+
174
+ const app = express();
175
+
176
+ // 1. The server is configured with the URL of a trusted facilitator
177
+ const facilitator = new HTTPFacilitatorClient({ url: "https://facilitator.example.com" });
178
+ const resourceServer = new x402ResourceServer(facilitator);
179
+
180
+ // 2. Register scheme(s) you want to accept. Wildcards ("sui:*", "*:*") are OK.
181
+ resourceServer.register("exact:sui:mainnet", new ExactSuiServerScheme());
182
+
183
+ // 3. Declare which routes are paid, and what they cost
184
+ const routes = {
185
+ "GET /premium": {
186
+ accepts: {
187
+ scheme: "exact",
188
+ network: "sui:mainnet",
189
+ asset: "0x2::sui::SUI",
190
+ payTo: "0xYOUR_RECEIVING_ADDRESS",
191
+ maxAmountRequired: "1000000", // 0.001 SUI
192
+ description: "Premium data feed"
193
+ }
194
+ }
195
+ };
196
+
197
+ app.use(new x402HTTPResourceServer(resourceServer, routes).middleware());
198
+
199
+ // 4. The actual handler — req.payment is populated by the middleware
200
+ app.get("/premium", (req, res) => {
201
+ res.json({ ok: true, paid: req.payment });
202
+ });
203
+
204
+ app.listen(3000);
205
+ ```
206
+
207
+ ### 2. Facilitator (the gas station)
208
+
209
+ ```js
210
+ import express from "express";
211
+ import { SuiClient } from "@mysten/sui/client";
212
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
213
+
214
+ import { x402Facilitator } from "@altaga/x402-sui/core/facilitator";
215
+ import { ExactSuiFacilitatorScheme } from "@altaga/x402-sui/sui/exact/facilitator";
216
+
217
+ const client = new SuiClient({ url: "https://fullnode.mainnet.sui.io" });
218
+ const keypair = Ed25519Keypair.fromSecretKey(process.env.FACILITATOR_SECRET);
219
+
220
+ const facilitator = new x402Facilitator();
221
+ facilitator.register("exact:sui:mainnet", new ExactSuiFacilitatorScheme(client, keypair));
222
+
223
+ const app = express();
224
+ app.use(express.json());
225
+
226
+ // Sponsor: client posts { txBytes, sender, scheme, network }
227
+ app.post("/sponsor", async (req, res) => {
228
+ try {
229
+ const result = await facilitator.sponsor(req.body.scheme, req.body.network, {
230
+ txBytes: req.body.txBytes,
231
+ sender: req.body.sender,
232
+ });
233
+ res.json(result); // { sponsoredTxBytes, sponsorSignature }
234
+ } catch (e) {
235
+ res.status(400).json({ error: e.message });
236
+ }
237
+ });
238
+
239
+ // Verify: dry-run the candidate transaction
240
+ app.post("/verify", async (req, res) => {
241
+ try {
242
+ const result = await facilitator.verify(req.body.paymentPayload, req.body.paymentRequirement);
243
+ res.json(result); // { isValid: true }
244
+ } catch (e) {
245
+ res.status(400).json({ isValid: false, error: e.message });
246
+ }
247
+ });
248
+
249
+ // Settle: submit the transaction to the chain
250
+ app.post("/settle", async (req, res) => {
251
+ try {
252
+ const result = await facilitator.settle(req.body.paymentPayload, req.body.paymentRequirement);
253
+ res.json(result); // { transactionDigest }
254
+ } catch (e) {
255
+ res.status(400).json({ error: e.message });
256
+ }
257
+ });
258
+
259
+ app.listen: 4000;
260
+ ```
261
+
262
+ > The facilitator keypair must hold SUI for gas. If `client.getCoins` returns
263
+ > zero gas coins, sponsorship fails with `Facilitator out of gas!`.
264
+
265
+ ### 3. Client (the buyer)
266
+
267
+ ```js
268
+ import { SuiClient } from "@mysten/sui/client";
269
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
270
+
271
+ import { x402Client } from "@altaga/x402-sui/core/client";
272
+ import { x402HTTPClient } from "@altaga/x402-sui/core/http";
273
+ import { ExactSuiClientScheme } from "@altaga/x402-sui/sui/exact/client";
274
+
275
+ const sui = new SuiClient({ url: "https://fullnode.mainnet.sui.io" });
276
+ const keypair = Ed25519Keypair.fromSecretKey(process.env.BUYER_SECRET);
277
+
278
+ const core = new x402Client();
279
+ core.register("exact:sui:mainnet", new ExactSuiClientScheme(sui, keypair));
280
+
281
+ const http = new x402HTTPClient(core);
282
+
283
+ // 1. Hit the protected endpoint
284
+ let res = await fetch("https://api.example.com/premium");
285
+ if (res.status === 402) {
286
+ // 2. Parse the challenge
287
+ const paymentRequired = http.getPaymentRequiredResponse(
288
+ (name) => res.headers.get(name),
289
+ null,
290
+ );
291
+
292
+ // 3. Build + sign + sponsor the payment
293
+ const payload = await http.createPaymentPayload(paymentRequired);
294
+
295
+ // 4. Replay the request with X-PAYMENT
296
+ res = await fetch("https://api.example.com/premium", {
297
+ headers: http.encodePaymentSignatureHeader(payload),
298
+ });
299
+ }
300
+
301
+ const settleInfo = http.getPaymentSettleResponse((n) => res.headers.get(n));
302
+ console.log("data:", await res.json(), "settle:", settleInfo);
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Module Reference
308
+
309
+ ### `core/client`
310
+
311
+ `x402Client` — a tiny scheme registry. `register(pattern, impl)` accepts
312
+ `scheme:network`, `scheme:*`, or `*:*`; `getScheme(scheme, network)` resolves
313
+ with the most specific match. Throws if nothing is registered.
314
+
315
+ ### `core/http`
316
+
317
+ `x402HTTPClient(coreClient)` — HTTP-specific glue on top of a core client:
318
+
319
+ - `getPaymentRequiredResponse(getHeaderFn, bodyJson)` — decodes `X-Accepts`
320
+ (base64 JSON) and reads `X-Facilitator-Url` (with body fallback).
321
+ - `createPaymentPayload(paymentRequired)` — iterates `accepts`, hands the
322
+ first one a registered scheme can fulfil, and returns a normalized
323
+ `{ scheme, network, transaction, signature, amount, payTo, asset }`.
324
+ - `encodePaymentSignatureHeader(payload)` — base64-encodes the payload for
325
+ the `X-PAYMENT` request header.
326
+ - `getPaymentSettleResponse(getHeaderFn)` — decodes `X-Payment-Response` if
327
+ present.
328
+
329
+ ### `core/server`
330
+
331
+ `x402ResourceServer(facilitatorClient)` — the server-side brain.
332
+
333
+ - `register(pattern, impl)` — same lookup rules as the client.
334
+ - `initialize()` — async hook for parity with richer implementations.
335
+ - `handlePayment(paymentPayload, routes)` — calls
336
+ `facilitatorClient.verify(...)` then `facilitatorClient.settle(...)` and
337
+ returns the settlement result.
338
+
339
+ `HTTPFacilitatorClient({ url })` — the default transport, POSTing JSON to
340
+ `{url}/verify` and `{url}/settle`. Roll your own if you need mTLS or auth
341
+ headers.
342
+
343
+ ### `core/server-http`
344
+
345
+ `x402HTTPResourceServer(resourceServer, routesConfig).middleware()` returns
346
+ an Express middleware that:
347
+
348
+ 1. Looks up the route via `${req.method} ${req.path}`.
349
+ 2. If no `X-PAYMENT` header is present, responds `402` with
350
+ `X-Accepts` + `X-Facilitator-Url`.
351
+ 3. If a header is present, base64-decodes it, calls
352
+ `resourceServer.handlePayment(...)`, attaches `X-Payment-Response` to the
353
+ outgoing response, populates `req.payment`, and calls `next()`.
354
+ 4. On error, responds `400` with the error message.
355
+
356
+ ### `core/facilitator`
357
+
358
+ `x402Facilitator` — scheme registry + dispatch for the three facilitator
359
+ operations:
360
+
361
+ - `sponsor(schemeName, network, payload)` — typically turns a raw client PTB
362
+ into a sponsored transaction with the facilitator as `gasOwner`.
363
+ - `verify(paymentPayload, paymentRequirements)` — must return
364
+ `{ isValid: true }` or throw.
365
+ - `settle(paymentPayload, paymentRequirements)` — must return
366
+ `{ transactionDigest }` or throw.
367
+
368
+ ### `sui/exact/client`
369
+
370
+ `ExactSuiClientScheme(client, keypair)` — implements `createPaymentPayload`:
371
+
372
+ 1. Fetchs the buyer's coins of `requirement.asset`; aborts if none exist.
373
+ 2. Builds a PTB: `splitCoins(coin, [amount])` → `transferObjects([split], payTo)`.
374
+ 3. If a `facilitatorUrl` was advertised, POSTs the raw bytes to
375
+ `{facilitatorUrl}/sponsor` and receives `sponsoredTxBytes` +
376
+ `sponsorSignature`.
377
+ 4. Signs the (possibly sponsored) bytes with the buyer's keypair and returns
378
+ `{ transaction, signature: [buyer, sponsor?] }`.
379
+
380
+ ### `sui/exact/server`
381
+
382
+ `ExactSuiServerScheme` — intentionally empty. On Sui, the heavy lifting
383
+ (verification, gas sponsorship, submission) is done by the facilitator, so
384
+ the server-side scheme is a passthrough. Register one for parity:
385
+
386
+ ```js
387
+ resourceServer.register("exact:sui:mainnet", new ExactSuiServerScheme());
388
+ ```
389
+
390
+ ### `sui/exact/facilitator`
391
+
392
+ `ExactSuiFacilitatorScheme(client, keypair)` — the gas station.
393
+
394
+ - **`sponsor({ txBytes, sender })`** — deserializes the buyer's PTB, overrides
395
+ `setSender(sender)`, `setGasOwner(self)`, picks a gas coin, sets a
396
+ `setGasBudget(10_000_000)` and `setGasPrice(1000)`, calls `tx.build`, and
397
+ returns `{ sponsoredTxBytes, sponsorSignature }`.
398
+ - **`verify(payload, reqs)`** — `dryRunTransactionBlock` on the decoded
399
+ bytes; succeeds only if `effects.status.status === "success"`.
400
+ - **`settle(payload, reqs)`** — `executeTransactionBlock` with the combined
401
+ signature array; returns `{ transactionDigest }` on success.
402
+
403
+ ---
404
+
405
+ ## Building
406
+
407
+ Source modules are written as `.mjs`. To rebuild the `dist/` bundles:
408
+
409
+ ```bash
410
+ npm run build
411
+ ```
412
+
413
+ Which is shorthand for:
414
+
415
+ ```bash
416
+ esbuild core/*.mjs sui/exact/*.mjs \
417
+ --outdir=dist \
418
+ --minify \
419
+ --format=esm \
420
+ --target=es2022
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Peer Dependencies
426
+
427
+ | Package | Version | Why |
428
+ |---------|---------|-----|
429
+ | [`@mysten/sui`](https://www.npmjs.com/package/@mysten/sui) | `^1.14.0` | `Transaction`, `SuiClient`, keypairs |
430
+ | [`@mysten/bcs`](https://www.npmjs.com/package/@mysten/bcs) | `^1.2.0` | `toBase64` for serializing built PTBs |
431
+ | [`express`](https://www.npmjs.com/package/express) | `^4.21.2` | Used by `x402HTTPResourceServer.middleware()` |
432
+
433
+ `esbuild` is only needed if you re-run the build.
434
+
435
+ ---
436
+
437
+ ## License
438
+
439
+ [MIT](./LICENSE) — © Altaga
@@ -0,0 +1 @@
1
+ class i{constructor(){this.schemes=new Map}register(e,s){return this.schemes.set(e,s),this}getScheme(e,s){const t=`${e}:${s}`;if(this.schemes.has(t))return this.schemes.get(t);const h=`${e}:*`;if(this.schemes.has(h))return this.schemes.get(h);if(this.schemes.has("*:*"))return this.schemes.get("*:*");throw new Error(`No payment scheme registered for ${t}`)}}export{i as x402Client};
@@ -0,0 +1 @@
1
+ class c{constructor(){this.schemes=new Map}register(e,s){return this.schemes.set(e,s),this}getScheme(e,s){const t=`${e}:${s}`;if(this.schemes.has(t))return this.schemes.get(t);if(this.schemes.has(`${e}:*`))return this.schemes.get(`${e}:*`);if(this.schemes.has("*:*"))return this.schemes.get("*:*");throw new Error(`No facilitator scheme registered for ${t}`)}async sponsor(e,s,t){return await this.getScheme(e,s).sponsor(t)}async verify(e,s){return await this.getScheme(e.scheme,e.network).verify(e,s)}async settle(e,s){return await this.getScheme(e.scheme,e.network).settle(e,s)}}export{c as x402Facilitator};
@@ -0,0 +1 @@
1
+ class s{constructor(t){this.coreClient=t}getPaymentRequiredResponse(t,e){const r=t("x-accepts"),n=t("x-facilitator-url");if(!r)throw new Error("Missing x-accepts header in 402 response");return{accepts:JSON.parse(Buffer.from(r,"base64").toString("utf-8")),facilitatorUrl:n||e?.facilitatorUrl}}async createPaymentPayload(t){for(const e of t.accepts)try{const n=await this.coreClient.getScheme(e.scheme,e.network).createPaymentPayload(e,t.facilitatorUrl);return{scheme:e.scheme,network:e.network,transaction:n.transaction,signature:n.signature,amount:e.maxAmountRequired||e.amount,payTo:e.payTo,asset:e.asset}}catch(r){console.warn(`Could not fulfill payment requirement for ${e.scheme}:${e.network}:`,r.message);continue}throw new Error("Could not fulfill any payment requirements provided by the server.")}encodePaymentSignatureHeader(t){const e=JSON.stringify(t);return{"X-PAYMENT":Buffer.from(e).toString("base64")}}getPaymentSettleResponse(t){const e=t("x-payment-response");return e?JSON.parse(Buffer.from(e,"base64").toString("utf-8")):null}}export{s as x402HTTPClient};
@@ -0,0 +1 @@
1
+ class u{constructor(t,e){this.resourceServer=t,this.routesConfig=e}middleware(){return async(t,e,a)=>{const i=`${t.method} ${t.path}`,r=this.routesConfig[i];if(!r)return a();const o=t.headers["x-payment"];if(!o)return e.status(402),e.setHeader("x-accepts",Buffer.from(JSON.stringify([r.accepts])).toString("base64")),this.resourceServer.facilitatorClient&&e.setHeader("x-facilitator-url",this.resourceServer.facilitatorClient.url),e.setHeader("Content-Type","application/json"),e.json({error:"Payment Required",accepts:[r.accepts]});try{const s=JSON.parse(Buffer.from(o,"base64").toString("utf-8")),n=await this.resourceServer.handlePayment(s,[r.accepts]);e.setHeader("x-payment-response",Buffer.from(JSON.stringify(n)).toString("base64")),t.payment=n,a()}catch(s){console.error("Payment settlement error:",s),e.status(400).json({error:"Payment Processing Failed",message:s.message})}}}}export{u as x402HTTPResourceServer};
@@ -0,0 +1 @@
1
+ class a{constructor(t){this.url=t.url}async verify(t,e){const i=await fetch(`${this.url}/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paymentPayload:t,paymentRequirement:e})});if(!i.ok)throw new Error(`Facilitator verify failed: ${await i.text()}`);return await i.json()}async settle(t,e){const i=await fetch(`${this.url}/settle`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paymentPayload:t,paymentRequirement:e})});if(!i.ok)throw new Error(`Facilitator settle failed: ${await i.text()}`);return await i.json()}}class n{constructor(t){this.facilitatorClient=t,this.schemes=new Map}register(t,e){return this.schemes.set(t,e),this}async initialize(){console.log("x402ResourceServer initialized.")}async handlePayment(t,e){if(!(await this.facilitatorClient.verify(t,e)).isValid)throw new Error("Payment verification failed via Facilitator");return await this.facilitatorClient.settle(t,e)}}export{a as HTTPFacilitatorClient,n as x402ResourceServer};
@@ -0,0 +1 @@
1
+ import{Transaction as l}from"@mysten/sui/transactions";import"@mysten/bcs";class S{constructor(s,t){this.client=s,this.keypair=t,this.address=t.getPublicKey().toSuiAddress()}async createPaymentPayload(s,t){const o=new l,i=await this.client.getCoins({owner:this.address,coinType:s.asset});if(i.data.length===0)throw new Error(`Buyer has no ${s.asset}!`);const d=i.data[0],h=s.maxAmountRequired||s.amount||s.price,[y]=o.splitCoins(d.coinObjectId,[h]);o.transferObjects([y],s.payTo);const r=o.serialize();let n=r,e=null;if(t){const w=t.endsWith("/")?`${t}sponsor`:`${t}/sponsor`,a=await fetch(w,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({txBytes:r,sender:this.address,scheme:s.scheme,network:s.network})});if(!a.ok)throw new Error(`Sponsor failed: ${await a.text()}`);const p=await a.json();n=p.sponsoredTxBytes,e=p.sponsorSignature}const{signature:c}=await this.keypair.signTransaction(Buffer.from(n,"base64"));return{transaction:n,signature:e?[c,e]:[c]}}}export{S as ExactSuiClientScheme};
@@ -0,0 +1 @@
1
+ import{Transaction as o}from"@mysten/sui/transactions";import{toBase64 as c}from"@mysten/bcs";class l{constructor(s,e){this.client=s,this.keypair=e,this.address=e.getPublicKey().toSuiAddress()}async sponsor(s){const{txBytes:e,sender:r}=s,t=o.from(e);t.setSender(r),t.setGasOwner(this.address);const n=await this.client.getCoins({owner:this.address,coinType:"0x2::sui::SUI"});if(n.data.length===0)throw new Error("Facilitator out of gas!");t.setGasPayment([{objectId:n.data[0].coinObjectId,version:n.data[0].version,digest:n.data[0].digest}]),t.setGasBudget(1e7),t.setGasPrice(1e3);const a=await t.build({client:this.client}),{signature:i}=await this.keypair.signTransaction(a);return{sponsoredTxBytes:c(a),sponsorSignature:i}}async verify(s,e){const r=Buffer.from(s.transaction,"base64");try{const t=await this.client.dryRunTransactionBlock({transactionBlock:r});if(t.effects.status.status!=="success")throw new Error("Dry run failed: "+t.effects.status.error);return{isValid:!0}}catch(t){throw new Error(`Payment verification failed: ${t.message}`)}}async settle(s,e){const r=Buffer.from(s.transaction,"base64");try{const t=await this.client.executeTransactionBlock({transactionBlock:r,signature:s.signature,options:{showEffects:!0}});if(t.effects?.status.status!=="success")throw new Error(t.effects?.status.error||"Unknown error");return{transactionDigest:t.digest}}catch(t){throw new Error(`Payment settlement error: ${t.message}`)}}}export{l as ExactSuiFacilitatorScheme};
@@ -0,0 +1 @@
1
+ class c{}export{c as ExactSuiServerScheme};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@altaga/x402-sui",
3
+ "version": "1.0.1",
4
+ "description": "Modular x402 implementation for Sui Sponsored Transactions",
5
+ "main": "dist/core/client.js",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "esbuild core/*.mjs sui/exact/*.mjs --outdir=dist --minify --format=esm --target=es2022",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "exports": {
18
+ "./core/client": "./dist/core/client.js",
19
+ "./core/http": "./dist/core/http.js",
20
+ "./core/server": "./dist/core/server.js",
21
+ "./core/server-http": "./dist/core/server-http.js",
22
+ "./core/facilitator": "./dist/core/facilitator.js",
23
+ "./sui/exact/client": "./dist/sui/exact/client.js",
24
+ "./sui/exact/server": "./dist/sui/exact/server.js",
25
+ "./sui/exact/facilitator": "./dist/sui/exact/facilitator.js"
26
+ },
27
+ "keywords": [
28
+ "sui",
29
+ "x402",
30
+ "sponsored",
31
+ "transactions",
32
+ "crypto",
33
+ "payments"
34
+ ],
35
+ "author": "Altaga",
36
+ "license": "MIT",
37
+ "peerDependencies": {
38
+ "@mysten/bcs": "^1.2.0",
39
+ "@mysten/sui": "^1.14.0",
40
+ "express": "^4.21.2"
41
+ },
42
+ "devDependencies": {
43
+ "esbuild": "^0.28.0"
44
+ }
45
+ }