Skip to content

Commit ce6565c

Browse files
committed
feat: initialize agent-fetch with L402 client and token cache
0 parents  commit ce6565c

21 files changed

+9359
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[\*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
checks:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Node
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: 22
19+
cache: npm
20+
21+
- name: Install dependencies
22+
run: npm ci
23+
24+
- name: Lint
25+
run: npm run lint --if-present
26+
27+
- name: Test
28+
run: npm test --if-present
29+
30+
- name: Build
31+
run: npm run build --if-present

.github/workflows/release.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
issues: write
11+
pull-requests: write
12+
id-token: write
13+
14+
jobs:
15+
release:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Setup Node
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: 22
27+
registry-url: https://registry.npmjs.org
28+
cache: npm
29+
30+
- name: Install dependencies
31+
run: npm ci
32+
33+
- name: Build
34+
run: npm run build --if-present
35+
36+
- name: Release
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
NPM_CONFIG_PROVENANCE: true
40+
run: npx semantic-release

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
coverage/
4+
*.log
5+
.DS_Store

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
engine-strict=true
2+
package-lock=true

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

.releaserc.cjs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const isDryRun = process.env.SEMANTIC_RELEASE_DRY_RUN === "true";
2+
const repositoryUrl = process.env.SEMANTIC_RELEASE_REPOSITORY_URL || (process.env.GITHUB_REPOSITORY
3+
? `${process.env.GITHUB_SERVER_URL || "https://github.com"}/${process.env.GITHUB_REPOSITORY}.git`
4+
: `file://${process.cwd()}`);
5+
6+
const plugins = [
7+
["@semantic-release/commit-analyzer", { preset: "conventionalcommits" }],
8+
["@semantic-release/release-notes-generator", { preset: "conventionalcommits" }],
9+
"@semantic-release/changelog",
10+
];
11+
12+
if (!isDryRun) {
13+
plugins.push(
14+
["@semantic-release/npm", { npmPublish: true, tarballDir: "dist-release" }],
15+
"@semantic-release/github",
16+
[
17+
"@semantic-release/git",
18+
{
19+
assets: ["CHANGELOG.md", "package.json", "package-lock.json"],
20+
message:
21+
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
22+
},
23+
],
24+
);
25+
}
26+
27+
module.exports = {
28+
branches: ["main"],
29+
repositoryUrl,
30+
tagFormat: "${version}",
31+
plugins,
32+
};

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 ZBD
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.

README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# @zbdpay/agent-fetch
2+
3+
L402-aware fetch client for paid HTTP resources.
4+
5+
This package handles the full payment challenge flow:
6+
- parse `402 Payment Required` responses
7+
- support both `L402` and `LSAT` schemes
8+
- pay using caller-provided hooks
9+
- retry with `Authorization` proof
10+
- cache proofs locally to avoid duplicate payments
11+
12+
## Requirements
13+
14+
- Node.js `>=22`
15+
- npm
16+
17+
## Install
18+
19+
```bash
20+
npm install @zbdpay/agent-fetch
21+
```
22+
23+
## Quick Start
24+
25+
```ts
26+
import { agentFetch, FileTokenCache } from "@zbdpay/agent-fetch";
27+
28+
const tokenCache = new FileTokenCache(`${process.env.HOME}/.zbd-wallet/token-cache.json`);
29+
30+
const response = await agentFetch("https://example.com/protected", {
31+
tokenCache,
32+
maxPaymentSats: 100,
33+
pay: async (challenge) => {
34+
// Pay challenge.invoice with your wallet implementation.
35+
// Return preimage, plus optional paymentId/amountPaidSats.
36+
return {
37+
preimage: "<payment-preimage>",
38+
paymentId: "<payment-id>",
39+
amountPaidSats: challenge.amountSats,
40+
};
41+
},
42+
waitForPayment: async (paymentId) => {
43+
// Optional poller for async settlement.
44+
// Return pending/completed/failed.
45+
return {
46+
status: "completed",
47+
paymentId,
48+
preimage: "<payment-preimage>",
49+
amountPaidSats: 21,
50+
};
51+
},
52+
});
53+
54+
const body = await response.json();
55+
console.log(response.status, body);
56+
```
57+
58+
## Behavior
59+
60+
- If cached auth exists and is not expired, request is sent immediately with proof.
61+
- If response is not `402`, original response is returned untouched.
62+
- If response is `402`, challenge is parsed from `WWW-Authenticate` and/or JSON body.
63+
- If payment succeeds, proof is generated as `<SCHEME> <macaroon-or-token>:<preimage>` and request is retried.
64+
- If `maxPaymentSats` is set and challenge exceeds it, call fails before payment.
65+
- If async settlement is used and times out, call fails with a timeout error.
66+
67+
## Public API
68+
69+
Exports from `src/index.ts`:
70+
71+
- `agentFetch`
72+
- `requestChallenge`
73+
- `payChallenge`
74+
- `fetchWithProof`
75+
- `FileTokenCache`
76+
- types: `AgentFetchOptions`, `PaymentChallenge`, `PaidChallenge`, `PaymentSettlement`, `TokenCache`, `TokenRecord`, `ChallengeScheme`
77+
78+
## Options (`AgentFetchOptions`)
79+
80+
- `pay` (required): function to pay a parsed challenge
81+
- `waitForPayment` (optional): poller for async settlement
82+
- `tokenCache` (optional): token cache backend
83+
- `requestInit` (optional): forwarded fetch options
84+
- `fetchImpl` (optional): custom fetch implementation
85+
- `maxPaymentSats` (optional): payment guardrail
86+
- `paymentTimeoutMs` (optional, default `30000`)
87+
- `paymentPollIntervalMs` (optional, default `300`)
88+
- `now`, `sleep` (optional testability hooks)
89+
90+
## Token Cache
91+
92+
`FileTokenCache` stores per-URL tokens as JSON and writes atomically.
93+
94+
- no `expiresAt`: token is reused until overwritten/deleted
95+
- with `expiresAt`: expired token is evicted on read
96+
97+
Default cache location is chosen by the caller. In this suite, `agent-wallet` uses `~/.zbd-wallet/token-cache.json`.
98+
99+
## Scripts
100+
101+
```bash
102+
npm run build
103+
npm run test
104+
npm run lint
105+
npm run typecheck
106+
npm run smoke:imports
107+
npm run release:dry-run
108+
```
109+
110+
## Related Packages
111+
112+
- `@zbdpay/agent-wallet` uses this package for `zbdw fetch`
113+
- `@zbdpay/agent-pay` provides middleware that emits L402 challenges this client can consume

0 commit comments

Comments
 (0)