@nickthelegend69/fund402 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 nickthelegend
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,192 @@
1
+ # @nickthelegend69/fund402
2
+
3
+ **Create x402-gated HTTP endpoints that are settled by a lending pool — on Casper.**
4
+
5
+ A normal [x402](https://x402.org) paywall makes the *caller* pay from their own
6
+ balance. Fund402 adds one twist that changes everything for AI agents:
7
+
8
+ > the caller borrows **just-in-time** from the Fund402 vault, the **lending pool
9
+ > fronts the CEP-18 payment to you (the merchant)**, and the agent repays later.
10
+
11
+ To you it looks like any x402 endpoint — except your callers can pay **even with an
12
+ empty wallet**, because the pool settles on their behalf. One SDK, both sides:
13
+
14
+ | | |
15
+ |---|---|
16
+ | 🟢 **Server (merchant)** | `paywall()` + drop-in middleware for **Express / Hono / Next.js** — issue the 402 challenge, verify the pool settled on-chain, serve the resource. |
17
+ | 🔵 **Client (agent)** | `fund402Fetch()` — a drop-in `fetch` that pays any Fund402 endpoint with JIT pool credit and replays the request. |
18
+
19
+ Casper-native: CEP-18 token + EIP-712 `exact` scheme over the `casper:*` network
20
+ family, verified against the live **CSPR.cloud x402 facilitator** and the deployed
21
+ **Fund402 vault** on testnet.
22
+
23
+ ```
24
+ ┌────────────┐ 1. GET (no payment) ┌─────────────────────┐
25
+ │ │ ───────────────────────────▶ │ your API │
26
+ │ agent │ 2. 402 + x402 challenge │ paywall(...) │
27
+ │ fund402- │ ◀─────────────────────────── │ (Express/Hono/Next)│
28
+ │ Fetch │ └─────────┬───────────┘
29
+ │ │ 3. borrow_and_pay ┌─────────────────┐ │ 5. verify settlement
30
+ │ │ ──────────────────▶ │ Fund402 vault │ │ on-chain (CSPR.cloud)
31
+ │ │ (pool fronts $) │ (lending pool) │◀┘
32
+ │ │ 4. retry + payment-signature ▲
33
+ │ │ ───────────────────────────▶ pays merchant from pool
34
+ └────────────┘ 6. 200 + resource
35
+ ```
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm i @nickthelegend69/fund402
41
+ # the agent/client side also needs a Casper key; axios is optional (fetch is built in)
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Server — create an endpoint settled by the pool
47
+
48
+ ### Express
49
+
50
+ ```ts
51
+ import express from "express";
52
+ import { expressPaywall } from "@nickthelegend69/fund402/express";
53
+
54
+ const app = express();
55
+
56
+ app.use("/v", expressPaywall({
57
+ payTo: "00" + MERCHANT_ACCOUNT_HASH, // who gets paid (tagged account hash)
58
+ asset: CEP18_PACKAGE_HASH, // the CEP-18 settlement token
59
+ price: "1000000", // base units per call (0.001 @ 9 decimals)
60
+ vaultContract: VAULT_PACKAGE_HASH, // the lending pool that settles
61
+ csprCloudApiKey: process.env.CSPR_CLOUD_API_KEY,
62
+ }));
63
+
64
+ app.get("/v/price/:pair", (req, res) => {
65
+ // only runs once the pool settled on-chain; proof is on req.fund402
66
+ res.json({ pair: req.params.pair, price: 64250.12, settledBy: req.fund402.deployHash });
67
+ });
68
+
69
+ app.listen(3000);
70
+ ```
71
+
72
+ ### Next.js (App Router)
73
+
74
+ ```ts
75
+ // app/api/v/[...path]/route.ts
76
+ import { withPaywall } from "@nickthelegend69/fund402/next";
77
+
78
+ export const GET = withPaywall(
79
+ { payTo, asset, price: "1000000", vaultContract, csprCloudApiKey: process.env.CSPR_CLOUD_API_KEY },
80
+ async () => Response.json({ data: "the protected resource" })
81
+ );
82
+ ```
83
+
84
+ ### Hono (Node, Bun, Workers, Deno)
85
+
86
+ ```ts
87
+ import { Hono } from "hono";
88
+ import { honoPaywall } from "@nickthelegend69/fund402/hono";
89
+
90
+ const app = new Hono();
91
+ app.use("/v/*", honoPaywall({ payTo, asset, price: "1000000", vaultContract, csprCloudApiKey }));
92
+ app.get("/v/price", (c) => c.json({ price: 64250.12 }));
93
+ ```
94
+
95
+ ### Any framework
96
+
97
+ ```ts
98
+ import { paywall } from "@nickthelegend69/fund402";
99
+
100
+ const pay = paywall({ payTo, asset, price: "1000000", vaultContract, csprCloudApiKey });
101
+
102
+ const g = await pay.guard({ method: req.method, url: fullUrl, headers: req.headers });
103
+ if (!g.paid) return send(g.response); // 402 challenge / 400 / error
104
+ res.setHeader("payment-response", g.paymentResponseHeader);
105
+ return serveTheResource(); // g.settlement has the on-chain proof
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Client — pay an endpoint with JIT pool credit
111
+
112
+ ```ts
113
+ import { fund402Fetch } from "@nickthelegend69/fund402";
114
+
115
+ const f = fund402Fetch({
116
+ agentSecretKey, // PEM or hex
117
+ agentPublicKey, // 01.. / 02.. account-key hex
118
+ vaultContract: VAULT_PACKAGE_HASH,
119
+ network: "casper:casper-test",
120
+ onEvent: (e) => console.log(e.type, e.data), // intercepted_402 → borrowing → … → payment_confirmed
121
+ });
122
+
123
+ // transparent: on 402, it borrows from the pool, settles on-chain, retries.
124
+ const res = await f("https://merchant.example/v/price/BTC-USD");
125
+ const data = await res.json(); // paid + served — the agent's own balance can be zero
126
+ ```
127
+
128
+ Prefer Axios? `withPaymentInterceptor(config)` returns an `AxiosInstance` with the
129
+ same behaviour (Axios is an optional peer dependency).
130
+
131
+ ---
132
+
133
+ ## How settlement works (and why you can trust it)
134
+
135
+ 1. The agent calls the vault's `borrow_and_pay(merchant, amount, collateral, vault_id)`.
136
+ The **pool transfers `amount` CEP-18 to the merchant** and books a loan.
137
+ 2. The agent attaches the resulting **deploy hash** to a signed x402 `exact` payload
138
+ and replays the request.
139
+ 3. The server calls `verifyPoolSettlement()` — it reads the deploy from **CSPR.cloud**
140
+ and confirms it `processed`, paid the right **merchant + amount**, and targeted
141
+ the configured **vault package**. The gateway trusts the chain, not the caller.
142
+ 4. Optionally also POSTs the signed authorization to an x402 **facilitator** `/verify`
143
+ (set `facilitatorUrl`) for defense-in-depth.
144
+
145
+ `verifyPoolSettlement` is resilient to indexer lag — it polls a bounded window while
146
+ the deploy is still propagating, and fails fast on an executed on-chain failure.
147
+
148
+ ## Live deployment (casper-test)
149
+
150
+ | | |
151
+ |---|---|
152
+ | Vault (lending pool) package | `664d99de146b9b573161a387d89fefc649677351d8a6d2acbe22109bf88f6b12` |
153
+ | CEP-18 asset (Fund402 USDC / F402) | `389cedc529cc553e2639884c9dcc5e6dcbeb3920f7f5ca5a39bf7f7b866bccd0` |
154
+ | Network | `casper:casper-test` |
155
+ | Facilitator | `https://x402-facilitator.cspr.cloud` |
156
+
157
+ These are the defaults the SDK ships with — point `vaultContract` / `asset` at your
158
+ own deployment for production.
159
+
160
+ ## Verified live ✅
161
+
162
+ `npm run test:e2e` stands up a real HTTP server with `paywall()`, points a
163
+ `fund402Fetch()` agent at it, and runs the **whole loop on casper-test**: the agent
164
+ hits the endpoint → gets a 402 → borrows from the pool (the vault fronts the F402 to
165
+ the merchant) → retries → the server verifies the settlement on-chain via CSPR.cloud
166
+ → serves the resource.
167
+
168
+ Last live run — agent `01baa8d8…` (Tier-3, **zero collateral**) borrowed `1000000`
169
+ F402; the pool fronted it to the merchant and the server served the resource after
170
+ verifying it on-chain:
171
+ **[settlement deploy `96f30ddf…` ↗](https://testnet.cspr.live/deploy/96f30ddfac9b3b8bc04a9fe274b1c006aff398ac624e7360669a2c1f3dc28264)** (`status: processed`).
172
+
173
+ ## API
174
+
175
+ **Server:** `paywall(cfg)` → `{ challenge, verify, guard }` · `buildPaymentRequirements`
176
+ · `challengeBody` · `decodePaymentSignature` · `verifyPoolSettlement` ·
177
+ `verifyWithFacilitator` · `explorerTx` · adapters `expressPaywall` / `honoPaywall` /
178
+ `withPaywall` (Next).
179
+
180
+ **Client:** `fund402Fetch(cfg)` · `withPaymentInterceptor(cfg)` (Axios) · `payViaPool`
181
+ · `decodeChallenge` · `selectCasperOption` · `testnetClient` / `mainnetClient`.
182
+
183
+ **On-chain primitives:** `borrowAndPayOnChain` · `repayLoanOnChain` ·
184
+ `ensureCollateralAllowance` · `buildExactPayload` · `waitForDeploy` ·
185
+ `agentTaggedAddress` · `transferAuthorizationDigest`.
186
+
187
+ ## ⚠️ Security
188
+
189
+ The client side signs real deploys with a local key — **testnet only**, never reuse
190
+ keys on mainnet. Keep keys out of source control. MIT licensed.
191
+
192
+ Part of [Fund402](https://github.com/nickthelegend/fund402-casper) — JIT credit for AI agents on Casper.
@@ -0,0 +1,2 @@
1
+ import { type PaywallConfig, type Fund402Paywall } from "../server";
2
+ export declare function expressPaywall(config: PaywallConfig | Fund402Paywall): (req: any, res: any, next: any) => Promise<any>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // Express adapter. `app.use("/v", expressPaywall({...}))` gates everything under
3
+ // it: unpaid requests get the 402 x402 challenge; settled ones fall through to
4
+ // your handler with `req.fund402` set to the on-chain settlement proof.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.expressPaywall = expressPaywall;
7
+ const server_1 = require("../server");
8
+ function fullUrl(req) {
9
+ const proto = req.headers["x-forwarded-proto"] || req.protocol || "http";
10
+ const host = req.headers["x-forwarded-host"] || req.headers["host"] || "localhost";
11
+ return `${proto}://${host}${req.originalUrl ?? req.url ?? ""}`;
12
+ }
13
+ function expressPaywall(config) {
14
+ const pay = "guard" in config ? config : (0, server_1.paywall)(config);
15
+ return async function fund402Middleware(req, res, next) {
16
+ try {
17
+ const g = await pay.guard({ method: req.method, url: fullUrl(req), headers: req.headers });
18
+ if (g.paid) {
19
+ res.setHeader("payment-response", g.paymentResponseHeader);
20
+ req.fund402 = g.settlement;
21
+ return next();
22
+ }
23
+ const r = g.response;
24
+ for (const [k, v] of Object.entries(r.headers))
25
+ res.setHeader(k, v);
26
+ return res.status(r.status).json(r.body);
27
+ }
28
+ catch (e) {
29
+ return res.status(500).json({ error: `fund402 paywall error: ${e?.message ?? e}` });
30
+ }
31
+ };
32
+ }
@@ -0,0 +1,2 @@
1
+ import { type PaywallConfig, type Fund402Paywall } from "../server";
2
+ export declare function honoPaywall(config: PaywallConfig | Fund402Paywall): (c: any, next: any) => Promise<any>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // Hono adapter. `app.use("/v/*", honoPaywall({...}))`. Works on any Hono runtime
3
+ // (Node, Bun, Workers, Deno). Settled requests expose the proof via `c.get("fund402")`.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.honoPaywall = honoPaywall;
6
+ const server_1 = require("../server");
7
+ function honoPaywall(config) {
8
+ const pay = "guard" in config ? config : (0, server_1.paywall)(config);
9
+ return async function fund402Middleware(c, next) {
10
+ const headers = {};
11
+ c.req.raw.headers.forEach((v, k) => (headers[k] = v));
12
+ const g = await pay.guard({ method: c.req.method, url: c.req.url, headers });
13
+ if (g.paid) {
14
+ c.header("payment-response", g.paymentResponseHeader);
15
+ c.set("fund402", g.settlement);
16
+ return next();
17
+ }
18
+ const r = g.response;
19
+ return c.json(r.body, r.status, r.headers);
20
+ };
21
+ }
@@ -0,0 +1,4 @@
1
+ import { type PaywallConfig, type Fund402Paywall } from "../server";
2
+ type NextHandler = (req: any, ctx?: any) => Response | Promise<Response>;
3
+ export declare function withPaywall(config: PaywallConfig | Fund402Paywall, handler: NextHandler): NextHandler;
4
+ export {};
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ // Next.js App Router adapter. Wrap a route handler so it only runs once payment
3
+ // has settled on-chain; otherwise the wrapper returns the 402 x402 challenge.
4
+ //
5
+ // // app/api/v/[...path]/route.ts
6
+ // import { withPaywall } from "@nickthelegend69/fund402/next";
7
+ // export const GET = withPaywall(
8
+ // { payTo, asset, price: "1000000", vaultContract, csprCloudApiKey: process.env.CSPR_CLOUD_API_KEY },
9
+ // async (req) => Response.json({ data: "the protected resource" })
10
+ // );
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.withPaywall = withPaywall;
13
+ const server_1 = require("../server");
14
+ function withPaywall(config, handler) {
15
+ const pay = "guard" in config ? config : (0, server_1.paywall)(config);
16
+ return async function fund402Route(req, ctx) {
17
+ const url = req?.nextUrl?.href ?? req?.url ?? "";
18
+ const headers = {};
19
+ if (req?.headers?.forEach)
20
+ req.headers.forEach((v, k) => (headers[k] = v));
21
+ const g = await pay.guard({ method: req?.method, url, headers });
22
+ if (!g.paid) {
23
+ return new Response(JSON.stringify(g.response.body), {
24
+ status: g.response.status,
25
+ headers: g.response.headers,
26
+ });
27
+ }
28
+ const out = await handler(req, ctx);
29
+ try {
30
+ out.headers.set("payment-response", g.paymentResponseHeader);
31
+ }
32
+ catch {
33
+ /* immutable response headers — best effort */
34
+ }
35
+ return out;
36
+ };
37
+ }
@@ -0,0 +1,116 @@
1
+ import { RpcClient, PrivateKey, KeyAlgorithm } from "casper-js-sdk";
2
+ import type { PaymentRequirements } from "./types";
3
+ export interface CasperWiringConfig {
4
+ nodeUrl: string;
5
+ network: string;
6
+ chainName: string;
7
+ vaultContractHash: string;
8
+ agentSecretKey: string;
9
+ agentPublicKey: string;
10
+ keyAlgorithm?: KeyAlgorithm;
11
+ /** payment (gas) in motes for a borrow_and_pay call. Default 5 CSPR. */
12
+ borrowGasMotes?: string;
13
+ }
14
+ export declare function rpc(nodeUrl: string): RpcClient;
15
+ export declare function loadPrivateKey(secret: string, algo?: KeyAlgorithm): Promise<PrivateKey>;
16
+ /**
17
+ * Call the vault's `borrow_and_pay(merchant, amount, collateral, vault_id)`.
18
+ * The vault pulls the agent's CEP-18 collateral into escrow (transfer_from) and
19
+ * fronts the CEP-18 `amount` to the merchant from the liquidity pool. Returns the
20
+ * settlement deploy hash. (Tier-3 agents borrow with zero collateral; lower tiers
21
+ * must `approve` the vault for >= collateral first — see ensureCollateralAllowance.)
22
+ */
23
+ export declare function borrowAndPayOnChain(cfg: CasperWiringConfig, args: {
24
+ merchant: string;
25
+ amount: bigint;
26
+ collateral: bigint;
27
+ vaultId: string;
28
+ }): Promise<{
29
+ deployHash: string;
30
+ }>;
31
+ /** Repay a loan: vault pulls principal back from the agent (CEP-18 allowance required). */
32
+ export declare function repayLoanOnChain(cfg: CasperWiringConfig, loanId: number): Promise<{
33
+ deployHash: string;
34
+ }>;
35
+ /**
36
+ * Approve the vault to pull `amount` of the CEP-18 asset from the agent (needed
37
+ * before a collateralized borrow_and_pay can escrow, and before repay_loan can
38
+ * pull the principal). One-time per allowance top-up.
39
+ */
40
+ export declare function ensureCollateralAllowance(cfg: CasperWiringConfig & {
41
+ assetPackageHash: string;
42
+ }, spender: {
43
+ vaultContractHash: string;
44
+ }, amount: bigint): Promise<{
45
+ deployHash: string;
46
+ }>;
47
+ /**
48
+ * Poll the node until a deploy executes; resolves true on success. Mirrors the
49
+ * pattern proven live in production: stringify the whole getDeploy response (the
50
+ * v5 SDK shape varies across Casper 1.x / 2.0 Condor), treat a non-null
51
+ * errorMessage / "Failure" tag as failure, and "cost"/"Success" as success.
52
+ */
53
+ export declare function waitForDeploy(cfg: Pick<CasperWiringConfig, "nodeUrl">, deployHash: string, { tries, intervalMs }?: {
54
+ tries?: number;
55
+ intervalMs?: number;
56
+ }): Promise<boolean>;
57
+ /** Casper account-hash ("00" + 32-byte hash) for the agent's public key. */
58
+ export declare function agentTaggedAddress(agentPublicKey: string): string;
59
+ /**
60
+ * Build the real x402 v2 `exact` PaymentPayload for the casper:* family. The
61
+ * `authorization` + 65-byte EIP-712 `signature` verify against the CSPR.cloud
62
+ * facilitator's POST /verify. We attach the Fund402 `settlement.deployHash`
63
+ * extension — the on-chain vault borrow_and_pay deploy the gateway checks.
64
+ */
65
+ export declare function buildExactPayload(cfg: CasperWiringConfig, req: Partial<PaymentRequirements> & {
66
+ payTo: string;
67
+ }, proof: {
68
+ deployHash: string;
69
+ }): Promise<{
70
+ x402Version: 2;
71
+ resource: {
72
+ url: string;
73
+ } | undefined;
74
+ accepted: {
75
+ scheme: "exact";
76
+ network: string;
77
+ asset: string;
78
+ payTo: string;
79
+ amount: string;
80
+ maxTimeoutSeconds: number;
81
+ extra: {
82
+ name: string;
83
+ version: string;
84
+ };
85
+ };
86
+ scheme: "exact";
87
+ network: string;
88
+ payload: {
89
+ signature: string;
90
+ publicKey: string;
91
+ authorization: {
92
+ from: string;
93
+ to: string;
94
+ value: string;
95
+ validAfter: string;
96
+ validBefore: string;
97
+ nonce: string;
98
+ };
99
+ settlement: {
100
+ deployHash: string;
101
+ asset: string;
102
+ };
103
+ };
104
+ paymentRequirements: {
105
+ scheme: "exact";
106
+ network: string;
107
+ asset: string;
108
+ payTo: string;
109
+ amount: string;
110
+ maxTimeoutSeconds: number;
111
+ extra: {
112
+ name: string;
113
+ version: string;
114
+ };
115
+ };
116
+ }>;
package/dist/casper.js ADDED
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ // Real Casper wiring (casper-js-sdk@5.x / Casper 2.0 Condor). Turns the abstract
3
+ // "borrow + settle" steps into actual on-chain calls against the Fund402 Vault,
4
+ // and builds the x402 `exact` PaymentPayload the facilitator's POST /verify checks.
5
+ //
6
+ // The payload is signed off the proven `eip712.ts` digest + `signAndAddAlgorithmBytes`
7
+ // (65-byte [algorithm|sig]) — byte-identical to the official @make-software/casper-x402
8
+ // client, but WITHOUT depending on its broken CJS build at runtime.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.rpc = rpc;
11
+ exports.loadPrivateKey = loadPrivateKey;
12
+ exports.borrowAndPayOnChain = borrowAndPayOnChain;
13
+ exports.repayLoanOnChain = repayLoanOnChain;
14
+ exports.ensureCollateralAllowance = ensureCollateralAllowance;
15
+ exports.waitForDeploy = waitForDeploy;
16
+ exports.agentTaggedAddress = agentTaggedAddress;
17
+ exports.buildExactPayload = buildExactPayload;
18
+ const casper_js_sdk_1 = require("casper-js-sdk");
19
+ const eip712_1 = require("./eip712");
20
+ function rpc(nodeUrl) {
21
+ return new casper_js_sdk_1.RpcClient(new casper_js_sdk_1.HttpHandler(nodeUrl));
22
+ }
23
+ async function loadPrivateKey(secret, algo = casper_js_sdk_1.KeyAlgorithm.ED25519) {
24
+ const trimmed = secret.trim();
25
+ if (trimmed.includes("BEGIN") && trimmed.includes("PRIVATE KEY")) {
26
+ return casper_js_sdk_1.PrivateKey.fromPem(trimmed, algo);
27
+ }
28
+ return casper_js_sdk_1.PrivateKey.fromHex(trimmed, algo);
29
+ }
30
+ const stripPkg = (s) => s.replace(/^(hash-|contract-package-|package-)/, "");
31
+ /**
32
+ * Build a CLKey arg from an account address. Accepts a raw 64-hex account hash,
33
+ * an "account-hash-…" formatted string, OR an x402 *tagged* address ("00" + 64-hex,
34
+ * the Account tag) as carried in a payment challenge's `payTo` — the 1-byte tag is
35
+ * stripped so the on-chain Key uses the bare 32-byte account hash.
36
+ */
37
+ function addressKey(addr) {
38
+ let h = addr.replace(/^account-hash-/, "");
39
+ if (/^00[0-9a-fA-F]{64}$/.test(h))
40
+ h = h.slice(2); // drop the "00" account tag
41
+ return casper_js_sdk_1.CLValue.newCLKey(casper_js_sdk_1.Key.newKey(`account-hash-${h}`));
42
+ }
43
+ /** Build a CLKey arg for a contract/package ("hash-<package>"). */
44
+ function pkgKey(pkgHash) {
45
+ return casper_js_sdk_1.CLValue.newCLKey(casper_js_sdk_1.Key.newKey("hash-" + stripPkg(pkgHash)));
46
+ }
47
+ /**
48
+ * Session item calling a stored contract BY PACKAGE HASH (StoredVersionedContractByHash,
49
+ * latest version). This is the path proven live on testnet — the deployed Fund402
50
+ * vault + CEP-18 are addressed by their package hash (what cspr.live shows).
51
+ */
52
+ function callPkg(pkgHash, entryPoint, args) {
53
+ const s = new casper_js_sdk_1.ExecutableDeployItem();
54
+ s.storedVersionedContractByHash = new casper_js_sdk_1.StoredVersionedContractByHash(casper_js_sdk_1.ContractHash.newContract(stripPkg(pkgHash)), entryPoint, args, undefined);
55
+ return s;
56
+ }
57
+ /**
58
+ * Call the vault's `borrow_and_pay(merchant, amount, collateral, vault_id)`.
59
+ * The vault pulls the agent's CEP-18 collateral into escrow (transfer_from) and
60
+ * fronts the CEP-18 `amount` to the merchant from the liquidity pool. Returns the
61
+ * settlement deploy hash. (Tier-3 agents borrow with zero collateral; lower tiers
62
+ * must `approve` the vault for >= collateral first — see ensureCollateralAllowance.)
63
+ */
64
+ async function borrowAndPayOnChain(cfg, args) {
65
+ const client = rpc(cfg.nodeUrl);
66
+ const algo = cfg.keyAlgorithm ?? casper_js_sdk_1.KeyAlgorithm.ED25519;
67
+ const signer = await loadPrivateKey(cfg.agentSecretKey, algo);
68
+ const sender = casper_js_sdk_1.PublicKey.fromHex(cfg.agentPublicKey);
69
+ const runtimeArgs = casper_js_sdk_1.Args.fromMap({
70
+ merchant: addressKey(args.merchant),
71
+ amount: casper_js_sdk_1.CLValue.newCLUInt256(args.amount.toString()),
72
+ collateral: casper_js_sdk_1.CLValue.newCLUInt256(args.collateral.toString()),
73
+ vault_id: casper_js_sdk_1.CLValue.newCLString(args.vaultId),
74
+ });
75
+ const header = casper_js_sdk_1.DeployHeader.default();
76
+ header.account = sender;
77
+ header.chainName = cfg.chainName;
78
+ header.ttl = new casper_js_sdk_1.Duration(casper_js_sdk_1.DEFAULT_DEPLOY_TTL);
79
+ const session = callPkg(cfg.vaultContractHash, "borrow_and_pay", runtimeArgs);
80
+ const payment = casper_js_sdk_1.ExecutableDeployItem.standardPayment(cfg.borrowGasMotes ?? "5000000000");
81
+ const deploy = casper_js_sdk_1.Deploy.makeDeploy(header, payment, session);
82
+ deploy.sign(signer);
83
+ const result = await client.putDeploy(deploy);
84
+ return { deployHash: result.deployHash.toHex() };
85
+ }
86
+ /** Repay a loan: vault pulls principal back from the agent (CEP-18 allowance required). */
87
+ async function repayLoanOnChain(cfg, loanId) {
88
+ const client = rpc(cfg.nodeUrl);
89
+ const algo = cfg.keyAlgorithm ?? casper_js_sdk_1.KeyAlgorithm.ED25519;
90
+ const signer = await loadPrivateKey(cfg.agentSecretKey, algo);
91
+ const sender = casper_js_sdk_1.PublicKey.fromHex(cfg.agentPublicKey);
92
+ const header = casper_js_sdk_1.DeployHeader.default();
93
+ header.account = sender;
94
+ header.chainName = cfg.chainName;
95
+ header.ttl = new casper_js_sdk_1.Duration(casper_js_sdk_1.DEFAULT_DEPLOY_TTL);
96
+ const session = callPkg(cfg.vaultContractHash, "repay_loan", casper_js_sdk_1.Args.fromMap({ loan_id: casper_js_sdk_1.CLValue.newCLUint64(BigInt(loanId)) }));
97
+ const payment = casper_js_sdk_1.ExecutableDeployItem.standardPayment("3000000000");
98
+ const deploy = casper_js_sdk_1.Deploy.makeDeploy(header, payment, session);
99
+ deploy.sign(signer);
100
+ const result = await client.putDeploy(deploy);
101
+ return { deployHash: result.deployHash.toHex() };
102
+ }
103
+ /**
104
+ * Approve the vault to pull `amount` of the CEP-18 asset from the agent (needed
105
+ * before a collateralized borrow_and_pay can escrow, and before repay_loan can
106
+ * pull the principal). One-time per allowance top-up.
107
+ */
108
+ async function ensureCollateralAllowance(cfg, spender, amount) {
109
+ const client = rpc(cfg.nodeUrl);
110
+ const algo = cfg.keyAlgorithm ?? casper_js_sdk_1.KeyAlgorithm.ED25519;
111
+ const signer = await loadPrivateKey(cfg.agentSecretKey, algo);
112
+ const sender = casper_js_sdk_1.PublicKey.fromHex(cfg.agentPublicKey);
113
+ const header = casper_js_sdk_1.DeployHeader.default();
114
+ header.account = sender;
115
+ header.chainName = cfg.chainName;
116
+ header.ttl = new casper_js_sdk_1.Duration(casper_js_sdk_1.DEFAULT_DEPLOY_TTL);
117
+ const session = callPkg(cfg.assetPackageHash, "approve", casper_js_sdk_1.Args.fromMap({
118
+ spender: pkgKey(spender.vaultContractHash),
119
+ amount: casper_js_sdk_1.CLValue.newCLUInt256(amount.toString()),
120
+ }));
121
+ const payment = casper_js_sdk_1.ExecutableDeployItem.standardPayment("2000000000");
122
+ const deploy = casper_js_sdk_1.Deploy.makeDeploy(header, payment, session);
123
+ deploy.sign(signer);
124
+ const result = await client.putDeploy(deploy);
125
+ return { deployHash: result.deployHash.toHex() };
126
+ }
127
+ /**
128
+ * Poll the node until a deploy executes; resolves true on success. Mirrors the
129
+ * pattern proven live in production: stringify the whole getDeploy response (the
130
+ * v5 SDK shape varies across Casper 1.x / 2.0 Condor), treat a non-null
131
+ * errorMessage / "Failure" tag as failure, and "cost"/"Success" as success.
132
+ */
133
+ async function waitForDeploy(cfg, deployHash, { tries = 60, intervalMs = 3000 } = {}) {
134
+ const client = rpc(cfg.nodeUrl);
135
+ for (let i = 0; i < tries; i++) {
136
+ try {
137
+ const res = await client.getDeploy(deployHash);
138
+ const txt = JSON.stringify(res?.executionResults ?? res?.executionInfo ?? res ?? {});
139
+ if (txt.includes('"Failure"') || /"error_?[Mm]essage":\s*"[^"]/.test(txt))
140
+ return false;
141
+ if (txt.includes('"Success"') || txt.includes('"cost"'))
142
+ return true;
143
+ }
144
+ catch {
145
+ /* not propagated yet */
146
+ }
147
+ await new Promise((r) => setTimeout(r, intervalMs));
148
+ }
149
+ return false;
150
+ }
151
+ /** Casper account-hash ("00" + 32-byte hash) for the agent's public key. */
152
+ function agentTaggedAddress(agentPublicKey) {
153
+ const pk = casper_js_sdk_1.PublicKey.fromHex(agentPublicKey);
154
+ const ah = pk.accountHash();
155
+ let hex = (ah?.toHex?.() ?? ah?.toString?.() ?? String(ah)).replace(/^account-hash-/, "");
156
+ hex = hex.replace(/^0x/, "");
157
+ return "00" + hex; // 00 = AccountHash tag
158
+ }
159
+ /**
160
+ * Build the real x402 v2 `exact` PaymentPayload for the casper:* family. The
161
+ * `authorization` + 65-byte EIP-712 `signature` verify against the CSPR.cloud
162
+ * facilitator's POST /verify. We attach the Fund402 `settlement.deployHash`
163
+ * extension — the on-chain vault borrow_and_pay deploy the gateway checks.
164
+ */
165
+ async function buildExactPayload(cfg, req, proof) {
166
+ const algo = cfg.keyAlgorithm ?? casper_js_sdk_1.KeyAlgorithm.ED25519;
167
+ const priv = await loadPrivateKey(cfg.agentSecretKey, algo);
168
+ const assetPkg = (req.asset ?? req.extra?.name ?? "").replace(/^0x/, "");
169
+ const amount = String(req.amount ?? "0");
170
+ const maxTimeoutSeconds = req.maxTimeoutSeconds ?? 300;
171
+ const name = req.extra?.name ?? "Cep18x402";
172
+ const version = req.extra?.version ?? "1";
173
+ const now = Math.floor(Date.now() / 1000);
174
+ const validAfter = now - 600;
175
+ const validBefore = now + maxTimeoutSeconds;
176
+ const nonce = (0, eip712_1.randomNonce)();
177
+ const from = "00" + priv.publicKey.accountHash().toHex(); // tagged account hash
178
+ const to = req.payTo;
179
+ const digest = (0, eip712_1.transferAuthorizationDigest)({ name, version, chainName: cfg.network, contractPackageHash: assetPkg }, { from, to, value: amount, validAfter: String(validAfter), validBefore: String(validBefore), nonce });
180
+ const signature = (0, eip712_1.bytesToHex)(priv.signAndAddAlgorithmBytes(digest));
181
+ const publicKey = priv.publicKey.toHex();
182
+ const requirements = {
183
+ scheme: "exact",
184
+ network: cfg.network,
185
+ asset: assetPkg,
186
+ payTo: to,
187
+ amount,
188
+ maxTimeoutSeconds,
189
+ extra: { name, version },
190
+ };
191
+ return {
192
+ x402Version: 2,
193
+ resource: req.resource ? { url: req.resource } : undefined,
194
+ accepted: requirements,
195
+ scheme: "exact",
196
+ network: cfg.network,
197
+ payload: {
198
+ signature,
199
+ publicKey,
200
+ authorization: {
201
+ from,
202
+ to,
203
+ value: amount,
204
+ validAfter: String(validAfter),
205
+ validBefore: String(validBefore),
206
+ nonce,
207
+ },
208
+ settlement: { deployHash: proof.deployHash, asset: assetPkg },
209
+ },
210
+ paymentRequirements: requirements,
211
+ };
212
+ }