@nickthelegend69/fund402 0.1.0 → 0.1.2
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 +36 -9
- package/dist/casper.d.ts +9 -0
- package/dist/casper.js +26 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -128,6 +128,12 @@ const data = await res.json(); // paid + served — the agent's own balance ca
|
|
|
128
128
|
Prefer Axios? `withPaymentInterceptor(config)` returns an `AxiosInstance` with the
|
|
129
129
|
same behaviour (Axios is an optional peer dependency).
|
|
130
130
|
|
|
131
|
+
**Tiers & collateral.** Trusted (Tier-3) agents borrow with **zero collateral**.
|
|
132
|
+
New/established (Tier-1/2) agents must post 150% collateral — `fund402Fetch`
|
|
133
|
+
**auto-approves** the vault to escrow it before borrowing (`collateralRatio`, default
|
|
134
|
+
`1.5`; set `autoApprove: false` to manage the allowance yourself, or `collateralRatio: 0`
|
|
135
|
+
for Tier-3).
|
|
136
|
+
|
|
131
137
|
---
|
|
132
138
|
|
|
133
139
|
## How settlement works (and why you can trust it)
|
|
@@ -145,17 +151,30 @@ same behaviour (Axios is an optional peer dependency).
|
|
|
145
151
|
`verifyPoolSettlement` is resilient to indexer lag — it polls a bounded window while
|
|
146
152
|
the deploy is still propagating, and fails fast on an executed on-chain failure.
|
|
147
153
|
|
|
154
|
+
## Yield-bearing pool
|
|
155
|
+
|
|
156
|
+
The vault charges a **5% JIT credit fee** on every borrow. On repayment the agent pays
|
|
157
|
+
back principal **+ fee**, and the fee accrues to the pool's value — so **LPs earn yield**:
|
|
158
|
+
deposits are minted as shares, and a share redeems for more than it cost as fees pile up.
|
|
159
|
+
Agents can settle their newest loan with **`repayLatestOnChain`** (no loan id needed),
|
|
160
|
+
which makes auto-repay-from-earnings trivial.
|
|
161
|
+
|
|
162
|
+
**Proven live (casper-test):** an LP deposited `2_000_000`, an agent borrowed `1_000_000`
|
|
163
|
+
and repaid via `repay_latest`, the pool grew to `2_050_000`, and the LP **withdrew
|
|
164
|
+
`2_050_000` for its `2_000_000` deposit — `+50_000` realized yield.**
|
|
165
|
+
[repay `80e90a43…`](https://testnet.cspr.live/deploy/80e90a43120b40524038a405088f3f83c4ce45674a9ff3e28c577a20552cba9e) ·
|
|
166
|
+
[LP withdraw `44318b5b…`](https://testnet.cspr.live/deploy/44318b5b303dab8dbbb3c3b26ac0f148bf7b44701996fbd446f17f9f7622023c)
|
|
167
|
+
|
|
148
168
|
## Live deployment (casper-test)
|
|
149
169
|
|
|
150
170
|
| | |
|
|
151
171
|
|---|---|
|
|
152
|
-
| Vault (lending pool) package | `
|
|
172
|
+
| Vault (yield-bearing lending pool) package | `ca4086d3a7b1abf000d0a79e23a237bb484a14807e9438f2c56f3461073e1b2f` |
|
|
153
173
|
| CEP-18 asset (Fund402 USDC / F402) | `389cedc529cc553e2639884c9dcc5e6dcbeb3920f7f5ca5a39bf7f7b866bccd0` |
|
|
154
174
|
| Network | `casper:casper-test` |
|
|
155
175
|
| Facilitator | `https://x402-facilitator.cspr.cloud` |
|
|
156
176
|
|
|
157
|
-
|
|
158
|
-
own deployment for production.
|
|
177
|
+
Point `vaultContract` / `asset` at your own deployment for production.
|
|
159
178
|
|
|
160
179
|
## Verified live ✅
|
|
161
180
|
|
|
@@ -165,10 +184,17 @@ hits the endpoint → gets a 402 → borrows from the pool (the vault fronts the
|
|
|
165
184
|
the merchant) → retries → the server verifies the settlement on-chain via CSPR.cloud
|
|
166
185
|
→ serves the resource.
|
|
167
186
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
Both credit paths are proven live through the SDK:
|
|
188
|
+
|
|
189
|
+
- **Tier-3 (zero collateral)** — agent `01baa8d8…` borrowed `1000000` F402; the pool
|
|
190
|
+
fronted it to the merchant.
|
|
191
|
+
[settlement deploy `96f30ddf…` ↗](https://testnet.cspr.live/deploy/96f30ddfac9b3b8bc04a9fe274b1c006aff398ac624e7360669a2c1f3dc28264) (`status: processed`).
|
|
192
|
+
- **Tier-1 (150% collateral)** — agent `017e90a1…` had `fund402Fetch` auto-approve and
|
|
193
|
+
**escrow `1500000` collateral** ([approve `f088362a…`](https://testnet.cspr.live/deploy/f088362a89720107dfe475ba8bc0660dbdb278cf25c3d633cf4e846414d8aa47)),
|
|
194
|
+
then borrow; verified on-chain (`collateral=1500000`).
|
|
195
|
+
[settlement deploy `a9dd1581…` ↗](https://testnet.cspr.live/deploy/a9dd158199ca65fda900863cd43db577983c1dcf3d6661409bf8d4c56bab649f) (`status: processed`).
|
|
196
|
+
|
|
197
|
+
Run them yourself: `npm run test:e2e` (Tier-3) and `npm run test:e2e:collateral` (Tier-1).
|
|
172
198
|
|
|
173
199
|
## API
|
|
174
200
|
|
|
@@ -181,8 +207,9 @@ verifying it on-chain:
|
|
|
181
207
|
· `decodeChallenge` · `selectCasperOption` · `testnetClient` / `mainnetClient`.
|
|
182
208
|
|
|
183
209
|
**On-chain primitives:** `borrowAndPayOnChain` · `repayLoanOnChain` ·
|
|
184
|
-
`
|
|
185
|
-
`
|
|
210
|
+
`repayLatestOnChain` (repay your newest loan — no loan id) · `ensureCollateralAllowance`
|
|
211
|
+
· `buildExactPayload` · `waitForDeploy` · `agentTaggedAddress` ·
|
|
212
|
+
`transferAuthorizationDigest`.
|
|
186
213
|
|
|
187
214
|
## ⚠️ Security
|
|
188
215
|
|
package/dist/casper.d.ts
CHANGED
|
@@ -32,6 +32,15 @@ export declare function borrowAndPayOnChain(cfg: CasperWiringConfig, args: {
|
|
|
32
32
|
export declare function repayLoanOnChain(cfg: CasperWiringConfig, loanId: number): Promise<{
|
|
33
33
|
deployHash: string;
|
|
34
34
|
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Repay the agent's **most recent** open loan without needing its id (vault
|
|
37
|
+
* `repay_latest`). Pulls back principal + the JIT fee — the fee accrues to the pool
|
|
38
|
+
* as LP yield. The agent must first `approve` the vault for principal + fee
|
|
39
|
+
* (see ensureCollateralAllowance) and hold that much of the asset.
|
|
40
|
+
*/
|
|
41
|
+
export declare function repayLatestOnChain(cfg: CasperWiringConfig): Promise<{
|
|
42
|
+
deployHash: string;
|
|
43
|
+
}>;
|
|
35
44
|
/**
|
|
36
45
|
* Approve the vault to pull `amount` of the CEP-18 asset from the agent (needed
|
|
37
46
|
* before a collateralized borrow_and_pay can escrow, and before repay_loan can
|
package/dist/casper.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.rpc = rpc;
|
|
|
11
11
|
exports.loadPrivateKey = loadPrivateKey;
|
|
12
12
|
exports.borrowAndPayOnChain = borrowAndPayOnChain;
|
|
13
13
|
exports.repayLoanOnChain = repayLoanOnChain;
|
|
14
|
+
exports.repayLatestOnChain = repayLatestOnChain;
|
|
14
15
|
exports.ensureCollateralAllowance = ensureCollateralAllowance;
|
|
15
16
|
exports.waitForDeploy = waitForDeploy;
|
|
16
17
|
exports.agentTaggedAddress = agentTaggedAddress;
|
|
@@ -100,6 +101,28 @@ async function repayLoanOnChain(cfg, loanId) {
|
|
|
100
101
|
const result = await client.putDeploy(deploy);
|
|
101
102
|
return { deployHash: result.deployHash.toHex() };
|
|
102
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Repay the agent's **most recent** open loan without needing its id (vault
|
|
106
|
+
* `repay_latest`). Pulls back principal + the JIT fee — the fee accrues to the pool
|
|
107
|
+
* as LP yield. The agent must first `approve` the vault for principal + fee
|
|
108
|
+
* (see ensureCollateralAllowance) and hold that much of the asset.
|
|
109
|
+
*/
|
|
110
|
+
async function repayLatestOnChain(cfg) {
|
|
111
|
+
const client = rpc(cfg.nodeUrl);
|
|
112
|
+
const algo = cfg.keyAlgorithm ?? casper_js_sdk_1.KeyAlgorithm.ED25519;
|
|
113
|
+
const signer = await loadPrivateKey(cfg.agentSecretKey, algo);
|
|
114
|
+
const sender = casper_js_sdk_1.PublicKey.fromHex(cfg.agentPublicKey);
|
|
115
|
+
const header = casper_js_sdk_1.DeployHeader.default();
|
|
116
|
+
header.account = sender;
|
|
117
|
+
header.chainName = cfg.chainName;
|
|
118
|
+
header.ttl = new casper_js_sdk_1.Duration(casper_js_sdk_1.DEFAULT_DEPLOY_TTL);
|
|
119
|
+
const session = callPkg(cfg.vaultContractHash, "repay_latest", casper_js_sdk_1.Args.fromMap({}));
|
|
120
|
+
const payment = casper_js_sdk_1.ExecutableDeployItem.standardPayment("3000000000");
|
|
121
|
+
const deploy = casper_js_sdk_1.Deploy.makeDeploy(header, payment, session);
|
|
122
|
+
deploy.sign(signer);
|
|
123
|
+
const result = await client.putDeploy(deploy);
|
|
124
|
+
return { deployHash: result.deployHash.toHex() };
|
|
125
|
+
}
|
|
103
126
|
/**
|
|
104
127
|
* Approve the vault to pull `amount` of the CEP-18 asset from the agent (needed
|
|
105
128
|
* before a collateralized borrow_and_pay can escrow, and before repay_loan can
|
|
@@ -118,7 +141,9 @@ async function ensureCollateralAllowance(cfg, spender, amount) {
|
|
|
118
141
|
spender: pkgKey(spender.vaultContractHash),
|
|
119
142
|
amount: casper_js_sdk_1.CLValue.newCLUInt256(amount.toString()),
|
|
120
143
|
}));
|
|
121
|
-
|
|
144
|
+
// 5 CSPR — the gas the proven live approves use; lower (e.g. 2 CSPR) is rejected
|
|
145
|
+
// by Condor at submission as Invalid Deploy.
|
|
146
|
+
const payment = casper_js_sdk_1.ExecutableDeployItem.standardPayment("5000000000");
|
|
122
147
|
const deploy = casper_js_sdk_1.Deploy.makeDeploy(header, payment, session);
|
|
123
148
|
deploy.sign(signer);
|
|
124
149
|
const result = await client.putDeploy(deploy);
|
package/dist/client.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface Fund402ClientConfig {
|
|
|
16
16
|
keyAlgorithm?: KeyAlgorithm;
|
|
17
17
|
/** Over-collateralisation ratio the agent posts (vault re-checks on-chain). Default 1.5. */
|
|
18
18
|
collateralRatio?: number;
|
|
19
|
+
/** Auto-approve the vault to escrow collateral before a Tier-1/2 borrow. Default true. */
|
|
20
|
+
autoApprove?: boolean;
|
|
19
21
|
/** Gas (motes) for the borrow_and_pay deploy. Default 5 CSPR. */
|
|
20
22
|
borrowGasMotes?: string;
|
|
21
23
|
/** Underlying fetch implementation. Default global fetch. */
|
|
@@ -23,7 +25,7 @@ export interface Fund402ClientConfig {
|
|
|
23
25
|
onEvent?: (event: Fund402Event) => void;
|
|
24
26
|
}
|
|
25
27
|
export interface Fund402Event {
|
|
26
|
-
type: "intercepted_402" | "borrowing" | "borrow_submitted" | "payment_settled" | "payment_sent" | "request_retried" | "payment_confirmed";
|
|
28
|
+
type: "intercepted_402" | "approving" | "approve_submitted" | "borrowing" | "borrow_submitted" | "payment_settled" | "payment_sent" | "request_retried" | "payment_confirmed";
|
|
27
29
|
data: Record<string, unknown>;
|
|
28
30
|
timestamp: number;
|
|
29
31
|
}
|
package/dist/client.js
CHANGED
|
@@ -79,6 +79,20 @@ async function payViaPool(config, option, resource) {
|
|
|
79
79
|
const amount = BigInt(option.amount);
|
|
80
80
|
const ratio = config.collateralRatio ?? 1.5;
|
|
81
81
|
const collateral = (amount * BigInt(Math.round(ratio * 100))) / 100n;
|
|
82
|
+
// Tier-1/2 collateralized borrow: the vault escrows `collateral` via transfer_from,
|
|
83
|
+
// so the agent must approve it on the CEP-18 asset first. Tier-3 borrows post zero
|
|
84
|
+
// collateral and skip this entirely.
|
|
85
|
+
if (collateral > 0n && config.autoApprove !== false) {
|
|
86
|
+
const asset = (option.asset ?? "").replace(/^0x/, "");
|
|
87
|
+
if (!asset)
|
|
88
|
+
throw new Error("402 challenge has no `asset` — cannot approve collateral");
|
|
89
|
+
emit(config, "approving", { asset, collateral: collateral.toString() });
|
|
90
|
+
const { deployHash: approveHash } = await (0, casper_1.ensureCollateralAllowance)({ ...wiring, assetPackageHash: asset }, { vaultContractHash: wiring.vaultContractHash }, collateral);
|
|
91
|
+
emit(config, "approve_submitted", { deployHash: approveHash });
|
|
92
|
+
const okApprove = await (0, casper_1.waitForDeploy)(wiring, approveHash);
|
|
93
|
+
if (!okApprove)
|
|
94
|
+
throw new Error(`collateral approve deploy failed: ${approveHash}`);
|
|
95
|
+
}
|
|
82
96
|
emit(config, "borrowing", { amount: amount.toString(), collateral: collateral.toString() });
|
|
83
97
|
const { deployHash } = await (0, casper_1.borrowAndPayOnChain)(wiring, {
|
|
84
98
|
merchant: option.payTo,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { paywall, buildPaymentRequirements, challengeBody, decodePaymentSignature, verifyPoolSettlement, verifyWithFacilitator, explorerTx, type PaywallConfig, type Fund402Paywall, type VerifyResult, type GuardResult, type RequestLike, type HttpResponseLike, } from "./server";
|
|
3
3
|
export { fund402Fetch, withPaymentInterceptor, payViaPool, decodeChallenge, selectCasperOption, testnetClient, mainnetClient, type Fund402ClientConfig, type Fund402Event, } from "./client";
|
|
4
|
-
export { borrowAndPayOnChain, repayLoanOnChain, ensureCollateralAllowance, buildExactPayload, waitForDeploy, agentTaggedAddress, loadPrivateKey, rpc, type CasperWiringConfig, } from "./casper";
|
|
4
|
+
export { borrowAndPayOnChain, repayLoanOnChain, repayLatestOnChain, ensureCollateralAllowance, buildExactPayload, waitForDeploy, agentTaggedAddress, loadPrivateKey, rpc, type CasperWiringConfig, } from "./casper";
|
|
5
5
|
export { transferAuthorizationDigest, randomNonce, bytesToHex } from "./eip712";
|
|
6
6
|
export { expressPaywall } from "./adapters/express";
|
|
7
7
|
export { honoPaywall } from "./adapters/hono";
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
25
25
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
26
26
|
};
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
-
exports.withPaywall = exports.honoPaywall = exports.expressPaywall = exports.bytesToHex = exports.randomNonce = exports.transferAuthorizationDigest = exports.rpc = exports.loadPrivateKey = exports.agentTaggedAddress = exports.waitForDeploy = exports.buildExactPayload = exports.ensureCollateralAllowance = exports.repayLoanOnChain = exports.borrowAndPayOnChain = exports.mainnetClient = exports.testnetClient = exports.selectCasperOption = exports.decodeChallenge = exports.payViaPool = exports.withPaymentInterceptor = exports.fund402Fetch = exports.explorerTx = exports.verifyWithFacilitator = exports.verifyPoolSettlement = exports.decodePaymentSignature = exports.challengeBody = exports.buildPaymentRequirements = exports.paywall = void 0;
|
|
28
|
+
exports.withPaywall = exports.honoPaywall = exports.expressPaywall = exports.bytesToHex = exports.randomNonce = exports.transferAuthorizationDigest = exports.rpc = exports.loadPrivateKey = exports.agentTaggedAddress = exports.waitForDeploy = exports.buildExactPayload = exports.ensureCollateralAllowance = exports.repayLatestOnChain = exports.repayLoanOnChain = exports.borrowAndPayOnChain = exports.mainnetClient = exports.testnetClient = exports.selectCasperOption = exports.decodeChallenge = exports.payViaPool = exports.withPaymentInterceptor = exports.fund402Fetch = exports.explorerTx = exports.verifyWithFacilitator = exports.verifyPoolSettlement = exports.decodePaymentSignature = exports.challengeBody = exports.buildPaymentRequirements = exports.paywall = void 0;
|
|
29
29
|
__exportStar(require("./types"), exports);
|
|
30
30
|
// Server
|
|
31
31
|
var server_1 = require("./server");
|
|
@@ -49,6 +49,7 @@ Object.defineProperty(exports, "mainnetClient", { enumerable: true, get: functio
|
|
|
49
49
|
var casper_1 = require("./casper");
|
|
50
50
|
Object.defineProperty(exports, "borrowAndPayOnChain", { enumerable: true, get: function () { return casper_1.borrowAndPayOnChain; } });
|
|
51
51
|
Object.defineProperty(exports, "repayLoanOnChain", { enumerable: true, get: function () { return casper_1.repayLoanOnChain; } });
|
|
52
|
+
Object.defineProperty(exports, "repayLatestOnChain", { enumerable: true, get: function () { return casper_1.repayLatestOnChain; } });
|
|
52
53
|
Object.defineProperty(exports, "ensureCollateralAllowance", { enumerable: true, get: function () { return casper_1.ensureCollateralAllowance; } });
|
|
53
54
|
Object.defineProperty(exports, "buildExactPayload", { enumerable: true, get: function () { return casper_1.buildExactPayload; } });
|
|
54
55
|
Object.defineProperty(exports, "waitForDeploy", { enumerable: true, get: function () { return casper_1.waitForDeploy; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nickthelegend69/fund402",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Create x402-gated HTTP endpoints settled by the Fund402 lending pool on Casper — and the agent client that pays them with just-in-time credit. Drop-in middleware for Express, Hono and Next.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"x402",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"build": "tsc -p tsconfig.json",
|
|
37
37
|
"prepublishOnly": "npm run build",
|
|
38
38
|
"test": "npm run build && node test/server.test.mjs && node test/eip712.test.mjs",
|
|
39
|
-
"test:e2e": "npm run build && node test/e2e-live.mjs"
|
|
39
|
+
"test:e2e": "npm run build && node test/e2e-live.mjs",
|
|
40
|
+
"test:e2e:collateral": "npm run build && node test/e2e-collateral.mjs"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@noble/hashes": "^1.4.0",
|