@nibgate/sdk 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/README.md +605 -0
- package/package.json +58 -0
- package/src/browser/env.js +3 -0
- package/src/browser/events.js +57 -0
- package/src/browser/gateway.js +89 -0
- package/src/browser/index.js +710 -0
- package/src/browser/json.js +8 -0
- package/src/browser/reputation.js +161 -0
- package/src/browser/storage.js +68 -0
- package/src/browser/tracking.js +42 -0
- package/src/browser/transfer.js +53 -0
- package/src/core/payment.js +8 -0
- package/src/core/rating.js +28 -0
- package/src/core/resource.js +143 -0
- package/src/core/settings.js +47 -0
- package/src/index.d.ts +423 -0
- package/src/index.js +1 -0
- package/src/server/access.js +217 -0
- package/src/server/actor.js +23 -0
- package/src/server/challenge.js +51 -0
- package/src/server/env.js +3 -0
- package/src/server/gateway.js +186 -0
- package/src/server/hub.js +36 -0
- package/src/server/index.js +15 -0
- package/src/server/manifest.js +19 -0
- package/src/server/presets.js +14 -0
- package/src/server/proof.js +73 -0
- package/src/server/response.js +9 -0
- package/src/server/runtime.js +39 -0
- package/src/server.d.ts +212 -0
- package/src/server.js +1 -0
- package/src/testing.d.ts +23 -0
- package/src/testing.js +56 -0
- package/src/tracking.d.ts +18 -0
- package/src/tracking.js +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
# nibgate
|
|
2
|
+
|
|
3
|
+
Framework-agnostic browser and server package for creator-owned paid content.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @nibgate/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use one package with two entrypoints:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { gate } from '@nibgate/sdk'; // browser/client events and UI helpers
|
|
15
|
+
import { createCircleGatewayServer, createNibgateServer } from '@nibgate/sdk/server'; // server-side access enforcement
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This works with Next.js, React apps with an API backend, Express, NestJS, Remix, SvelteKit, Astro SSR, custom servers, and CMS/plugin environments. Plain HTML can use the widget and browser events, but real gating still requires a server, edge function, API route, or signed file endpoint.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
First paste the widget script from your Nibgate dashboard into your site:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script async src="https://nibgate.xyz/widget.js" data-nibgate-site="SITE_ID" data-nibgate-token="PUBLIC_SITE_TOKEN" data-nibgate-api="https://api.nibgate.xyz"></script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then define a resource and let the package handle the repeated browser wiring. Nibgate registers the content and reports unlock activity through the widget:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { checkResourceAccess, trackResourcePage } from '@nibgate/sdk';
|
|
32
|
+
|
|
33
|
+
const premiumGuide = {
|
|
34
|
+
id: 'premium-guide',
|
|
35
|
+
title: 'Premium Guide',
|
|
36
|
+
type: 'article',
|
|
37
|
+
price: '0.01',
|
|
38
|
+
recipient: '0x558e7BFaF2Cf1A494F44E50D92431Afc060c9D12',
|
|
39
|
+
path: '/premium-guide',
|
|
40
|
+
access: {
|
|
41
|
+
humans: 'paid',
|
|
42
|
+
agents: 'paid'
|
|
43
|
+
},
|
|
44
|
+
unlock: {
|
|
45
|
+
mode: 'one_time'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
trackResourcePage(premiumGuide, { source: 'creator-site' });
|
|
50
|
+
|
|
51
|
+
await checkResourceAccess(premiumGuide, {
|
|
52
|
+
accessPath: '/api/nibgate/access',
|
|
53
|
+
source: 'creator-site',
|
|
54
|
+
async createPaymentSignature({ paymentRequiredHeader, resource }) {
|
|
55
|
+
// Production path:
|
|
56
|
+
// Ask the connected user/agent wallet or Gateway adapter to sign/pay
|
|
57
|
+
// the PAYMENT-REQUIRED challenge returned by the creator server.
|
|
58
|
+
return walletGatewayAdapter.pay({
|
|
59
|
+
paymentRequiredHeader,
|
|
60
|
+
resource
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
onStatus(message) {
|
|
64
|
+
console.log(message);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For plain browser pages, bind a button without writing custom event glue:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { setupResourcePage } from '@nibgate/sdk';
|
|
73
|
+
|
|
74
|
+
setupResourcePage(premiumGuide, {
|
|
75
|
+
source: 'creator-site',
|
|
76
|
+
accessPath: '/api/nibgate/access',
|
|
77
|
+
createPaymentSignature: walletGatewayAdapter.pay,
|
|
78
|
+
button: '[data-nibgate-unlock]',
|
|
79
|
+
status: '[data-nibgate-status]'
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a ready-made unlock button/controller, use `createWalletCheckout`. The package owns the UI state, retries, unlock events, and proof-backed access retry; your wallet/Gateway adapter only has to return the payment signature for the `PAYMENT-REQUIRED` challenge.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
import { createCircleGatewayBrowserAdapter, createWalletCheckout } from '@nibgate/sdk';
|
|
87
|
+
|
|
88
|
+
const [address] = await walletClient.getAddresses();
|
|
89
|
+
const circle = await createCircleGatewayBrowserAdapter({
|
|
90
|
+
chainId: 5042002,
|
|
91
|
+
signer: {
|
|
92
|
+
address,
|
|
93
|
+
signTypedData: (params) => walletClient.signTypedData({
|
|
94
|
+
account: address,
|
|
95
|
+
...params
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
createWalletCheckout(premiumGuide, {
|
|
101
|
+
button: '[data-nibgate-unlock]',
|
|
102
|
+
status: '[data-nibgate-status]',
|
|
103
|
+
accessPath: '/api/nibgate/access',
|
|
104
|
+
pay: circle.pay
|
|
105
|
+
}).mount();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Do not replace the Gateway payment with a normal wallet message signature. Gateway/x402 payment signatures are payment proofs; wallet message signatures are only used for rating/reputation proof.
|
|
109
|
+
|
|
110
|
+
The browser Circle Gateway adapter expects the creator server to return Circle's real `PAYMENT-REQUIRED` batching challenge. Use the preset on your server route:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import { createCircleGatewayServer } from '@nibgate/sdk/server';
|
|
114
|
+
|
|
115
|
+
const nibgateServer = createCircleGatewayServer({
|
|
116
|
+
origin: 'https://creator.example',
|
|
117
|
+
secret: process.env.NIBGATE_SECRET,
|
|
118
|
+
network: 'eip155:5042002'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export function GET(request) {
|
|
122
|
+
return nibgateServer.accessResponse(request, premiumGuide);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Equivalent manual config:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
createNibgateServer({
|
|
130
|
+
paymentMode: 'circle-gateway',
|
|
131
|
+
network: 'eip155:5042002'
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If the server is left in fallback challenge mode, browser checkout will fail closed because there is no Circle `GatewayWalletBatched` verifying contract to sign.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
Lower-level event helpers are still available when you need them:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import { nibgate } from '@nibgate/sdk';
|
|
142
|
+
|
|
143
|
+
nibgate.unlockCompleted('premium-guide', {
|
|
144
|
+
revenue: 0.01,
|
|
145
|
+
currency: 'USDC'
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
After a verified unlock, a creator UI should submit an onchain rating for the same resource. The hub only counts it into reputation when it can connect the rating wallet to an unlock receipt/proof. Use the built-in controller when you want simple selector-based UI wiring:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import { createEvmGatewayUnlock, createOnchainRating } from '@nibgate/sdk';
|
|
153
|
+
|
|
154
|
+
const premiumGuide = {
|
|
155
|
+
id: 'premium-guide',
|
|
156
|
+
title: 'Premium Guide',
|
|
157
|
+
type: 'article',
|
|
158
|
+
price: '0.01',
|
|
159
|
+
recipient: post.recipientWallet,
|
|
160
|
+
url: `https://creator.example/blog/${post.slug}`,
|
|
161
|
+
path: `/blog/${post.slug}`
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
let lastPayment = null;
|
|
165
|
+
|
|
166
|
+
const rating = createOnchainRating(premiumGuide, {
|
|
167
|
+
contractAddress: process.env.NEXT_PUBLIC_NIBGATE_REPUTATION_CONTRACT,
|
|
168
|
+
siteId: process.env.NEXT_PUBLIC_NIBGATE_SITE_ID,
|
|
169
|
+
token: process.env.NEXT_PUBLIC_NIBGATE_SITE_TOKEN,
|
|
170
|
+
indexUrl: 'https://api.nibgate.xyz/api/hub/reputation/ratings/index',
|
|
171
|
+
ratingTarget: '[data-nibgate-rating]',
|
|
172
|
+
ratingButtons: '[data-rating]',
|
|
173
|
+
status: '[data-nibgate-status]',
|
|
174
|
+
visible: false,
|
|
175
|
+
getPaymentId: () => lastPayment?.paymentId,
|
|
176
|
+
getUnlockRef: () => lastPayment?.paymentId || lastPayment?.txHash || ''
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
createEvmGatewayUnlock(premiumGuide, {
|
|
180
|
+
accessPath: `/api/content/${post.slug}`,
|
|
181
|
+
connectButton: '[data-nibgate-connect]',
|
|
182
|
+
unlockButton: '[data-nibgate-unlock]',
|
|
183
|
+
walletLabel: '[data-nibgate-wallet]',
|
|
184
|
+
status: '[data-nibgate-status]',
|
|
185
|
+
onUnlock(result) {
|
|
186
|
+
lastPayment = result.payment;
|
|
187
|
+
rating.setPayment(lastPayment);
|
|
188
|
+
rating.setVisible(true);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The lower-level `rateContentOnchain(resource, options)` function is also exported for custom UIs.
|
|
194
|
+
|
|
195
|
+
Ratings are proof-gated. Page views, time spent, scroll depth, and referrers are analytics signals; they should not become trust by themselves. Reputation-critical inputs use indexed onchain rating proofs. Signed ratings remain available only for local tests and migration tooling.
|
|
196
|
+
|
|
197
|
+
Nibgate reputation uses a versioned content identity:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
keccak256("nibgate:content:v1|domain|externalContentId|canonicalUrl")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The namespace lets future versions add metadata hashes, content version hashes, IPFS/Arweave pointers, or creator signatures without changing what old ratings mean.
|
|
204
|
+
|
|
205
|
+
The package talks to the widget through `window.nibgateHub`. If your app runs before the async widget finishes loading, events are queued and flushed once the widget is ready.
|
|
206
|
+
|
|
207
|
+
Content types are `music`, `video`, `article`, and `image`.
|
|
208
|
+
|
|
209
|
+
## Discovery metadata quality
|
|
210
|
+
|
|
211
|
+
Nibgate can only make good Explore cards and agent-readable records from metadata the creator site provides. Pass the same shape whether content comes from MDX frontmatter, a CMS row, a media table, or a custom admin dashboard:
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
import { trackResourcePage, validateResourceMetadata } from '@nibgate/sdk';
|
|
215
|
+
|
|
216
|
+
const resource = {
|
|
217
|
+
id: post.id,
|
|
218
|
+
title: post.title,
|
|
219
|
+
description: post.excerpt,
|
|
220
|
+
type: 'article',
|
|
221
|
+
imageUrl: post.coverImageUrl,
|
|
222
|
+
tags: post.tags,
|
|
223
|
+
price: post.price,
|
|
224
|
+
currency: 'USDC',
|
|
225
|
+
recipient: post.recipientWallet,
|
|
226
|
+
path: `/blog/${post.slug}`,
|
|
227
|
+
url: `https://creator.example/blog/${post.slug}`,
|
|
228
|
+
access: { humans: 'paid', agents: 'paid' },
|
|
229
|
+
unlock: { mode: 'one_time' }
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const quality = validateResourceMetadata(resource);
|
|
233
|
+
if (!quality.ok) console.warn(quality.errors);
|
|
234
|
+
|
|
235
|
+
trackResourcePage(resource);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Required for clean discovery: `id`, `title`, `url`, `type`.
|
|
239
|
+
|
|
240
|
+
Recommended for rich cards: `description`, `imageUrl`, `tags`.
|
|
241
|
+
|
|
242
|
+
Required for paid content: `price` and `recipient`.
|
|
243
|
+
|
|
244
|
+
The package warns in the browser when important metadata is missing and sends a metadata quality summary to the hub with content events. The backend stores that summary so dashboards can surface setup issues instead of silently creating weak content cards.
|
|
245
|
+
|
|
246
|
+
## Package UI included
|
|
247
|
+
|
|
248
|
+
The package includes small controller UIs, not a heavy design system:
|
|
249
|
+
|
|
250
|
+
- `createEvmGatewayUnlock(...)` wires connect wallet, disconnect, unlock, wallet label, status text, and unlocked content visibility.
|
|
251
|
+
- `createTransferCheckout(...)` supports direct Arc testnet transfer-style unlocks where Gateway is not used.
|
|
252
|
+
- `createOnchainRating(...)` wires rating buttons, status text, rating panel visibility, payment proof references, onchain rating submission, and hub indexing.
|
|
253
|
+
- `createNibgateContentSettings(...)` gives admin pages stable fields for content type, human/agent access, unlock mode, payment rail, price, recipient wallet, and license.
|
|
254
|
+
|
|
255
|
+
Creators keep their own UI/theme. Nibgate provides the hard parts: resource normalization, metadata validation, x402/Gateway unlock, transfer fallback, event streaming, proof storage, rating tx submission, and hub sync.
|
|
256
|
+
|
|
257
|
+
## FAQ / integration gotchas
|
|
258
|
+
|
|
259
|
+
- `Does a creator need a server?` Yes for real paid gating. Static-only sites can register discovery events, but protected content needs a server, edge function, CMS webhook, or API route that can return `402` and verify payment proof.
|
|
260
|
+
- `Can every post pay a different wallet?` Yes. Set `recipient` or `payTo` per resource. The package does not force one site-wide receiver.
|
|
261
|
+
- `Can this work with DB blogs, MDX, CMS, plain HTML, or custom apps?` Yes. Convert each content item into a Nibgate resource with `id`, `title`, `type`, `url`, `path`, `price`, and `recipient`.
|
|
262
|
+
- `Why does content not show in Explore?` Usually the widget is missing, the site is not verified, the manifest has not synced yet, `url` is not absolute, or required metadata is missing.
|
|
263
|
+
- `Why does reputation not update?` The rating wallet must have an unlock/payment proof for that content, the rating tx must be onchain, and the backend indexer must sync the tx.
|
|
264
|
+
- `Are page views reputation?` No. Views, referrers, scroll depth, and time spent are analytics signals. Reputation-critical scores come from indexed onchain ratings tied to unlock proof.
|
|
265
|
+
- `Do agents use a different flow?` No. Agents discover the same resource metadata, pay through the same protected route, and can emit/index reputation using the same proof model.
|
|
266
|
+
- `Should hidden content live in browser HTML?` No. The browser should only receive teasers before payment. The full body/media URL should come from a protected route after proof verification.
|
|
267
|
+
- `What breaks most often locally?` Missing wallet provider, wrong chain, missing Gateway client module, CORS on the backend, wrong `NIBGATE_API_BASE`, wrong site token, or a demo route using fallback challenge mode instead of real Gateway mode.
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
## Payment rails
|
|
271
|
+
|
|
272
|
+
Gateway is the default rail because it fits x402 and agent-paid HTTP best:
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
const resource = {
|
|
276
|
+
id: post.id,
|
|
277
|
+
title: post.title,
|
|
278
|
+
paymentRail: 'gateway',
|
|
279
|
+
price: '0.005',
|
|
280
|
+
recipient: post.recipientWallet
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Direct wallet transfer is also a first-class rail for creator apps that want a normal token/native transfer flow:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
const resource = {
|
|
288
|
+
id: post.id,
|
|
289
|
+
title: post.title,
|
|
290
|
+
paymentRail: 'transfer',
|
|
291
|
+
price: '0.005',
|
|
292
|
+
recipient: post.recipientWallet
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Transfer unlocks are fail-closed. The browser helper can send a tx hash, but the creator server must verify it before issuing an unlock proof:
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
createNibgateServer({
|
|
300
|
+
paymentRail: 'transfer',
|
|
301
|
+
async verifyTransfer({ resource, txHash, payment }) {
|
|
302
|
+
// Verify onchain with viem/RPC/indexer:
|
|
303
|
+
// recipient matches resource.recipient
|
|
304
|
+
// amount/token/chain match resource price/currency/network
|
|
305
|
+
// tx hash has not already been used
|
|
306
|
+
return verified;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Access policies
|
|
312
|
+
|
|
313
|
+
For CMS/database-driven sites, keep the gating fields in your own content table, then map each record into a Nibgate resource. Nibgate does not replace your CMS or DB.
|
|
314
|
+
|
|
315
|
+
If the creator has an admin dashboard, put Nibgate settings in that UI and save them beside the post/content record. The package exports canonical field metadata so each framework can render the same settings natively:
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
import { NIBGATE_CONTENT_SETTING_FIELDS, createNibgateContentSettings } from '@nibgate/sdk';
|
|
319
|
+
|
|
320
|
+
const defaults = createNibgateContentSettings({
|
|
321
|
+
recipient: creatorDefaultWallet
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then save those values beside the content row:
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
```txt
|
|
329
|
+
Nibgate settings
|
|
330
|
+
- Publish to Nibgate discovery
|
|
331
|
+
- Content type: article / music / image / video
|
|
332
|
+
- Human access: free / paid / blocked
|
|
333
|
+
- Agent access: free / paid / blocked
|
|
334
|
+
- Unlock mode: one_time for the MVP
|
|
335
|
+
- Price
|
|
336
|
+
- Currency
|
|
337
|
+
- Payment receiver for this exact content
|
|
338
|
+
- License or citation terms
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Example creator DB row:
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
const post = {
|
|
345
|
+
id: 'post_123',
|
|
346
|
+
slug: 'agent-economy',
|
|
347
|
+
title: 'The agent economy needs native payments',
|
|
348
|
+
price: '0.005',
|
|
349
|
+
recipientWallet: '0xPostSpecificReceiver',
|
|
350
|
+
humanAccess: 'paid',
|
|
351
|
+
agentAccess: 'paid',
|
|
352
|
+
body: 'Private content stays in your DB.'
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Map it before calling the package:
|
|
357
|
+
|
|
358
|
+
```js
|
|
359
|
+
function postToNibgateResource(post) {
|
|
360
|
+
return {
|
|
361
|
+
id: post.id,
|
|
362
|
+
title: post.title,
|
|
363
|
+
type: 'article',
|
|
364
|
+
price: post.price,
|
|
365
|
+
recipient: post.recipientWallet,
|
|
366
|
+
path: `/blog/${post.slug}`,
|
|
367
|
+
access: {
|
|
368
|
+
humans: post.humanAccess,
|
|
369
|
+
agents: post.agentAccess
|
|
370
|
+
},
|
|
371
|
+
unlock: {
|
|
372
|
+
mode: 'one_time'
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
`recipient` is resource-level on purpose. A creator can run a blog, marketplace, media library, or API product where each post, video, image pack, or API route pays a different wallet from the database. `recipient`, `payTo`, `receiver`, `receiverAddress`, and `creatorWallet` are accepted aliases. The server-level `recipient` or `NIBGATE_SELLER_ADDRESS` should be treated as a fallback, not the only way to route money.
|
|
379
|
+
|
|
380
|
+
This means Nibgate can fit different creator architectures:
|
|
381
|
+
|
|
382
|
+
- hardcoded MDX posts with frontmatter
|
|
383
|
+
- CMS posts from Sanity, WordPress, or a custom admin
|
|
384
|
+
- DB-backed blogs with per-resource payout wallets
|
|
385
|
+
- paid media/download routes
|
|
386
|
+
- agent-readable API routes
|
|
387
|
+
|
|
388
|
+
The creator app maps its own record into a Nibgate resource; Nibgate does not force a hosted marketplace schema.
|
|
389
|
+
|
|
390
|
+
Use `access` to decide who can read the origin payload before payment:
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
access: {
|
|
394
|
+
humans: 'free' | 'paid' | 'blocked',
|
|
395
|
+
agents: 'free' | 'paid' | 'blocked'
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Examples:
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
// Humans and agents both need payment proof.
|
|
403
|
+
access: { humans: 'paid', agents: 'paid' }
|
|
404
|
+
|
|
405
|
+
// Humans can read publicly, agents need x402/payment proof to crawl or cite.
|
|
406
|
+
access: { humans: 'free', agents: 'paid' }
|
|
407
|
+
|
|
408
|
+
// Humans can pay, agents cannot access this route.
|
|
409
|
+
access: { humans: 'paid', agents: 'blocked' }
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Real locking must happen on the server. If you render the full protected payload into HTML and hide it with CSS, crawlers can still scrape it. `@nibgate/sdk/server` is what prevents the protected response from being returned until the request is free, paid with proof, or explicitly allowed by policy.
|
|
413
|
+
|
|
414
|
+
## Unlock modes
|
|
415
|
+
|
|
416
|
+
The MVP unlock is intentionally simple:
|
|
417
|
+
|
|
418
|
+
```txt
|
|
419
|
+
pay once -> verify receipt/proof -> issue unlock token -> serve content -> report receipt
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Use this today:
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
unlock: {
|
|
426
|
+
mode: 'one_time'
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The resource shape already has room for future unlock modes, but they should not be presented as production-ready until the payment/session adapters exist:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
unlock: { mode: 'metered_stream', unit: 'second', pricePerUnit: '0.0001' }
|
|
434
|
+
unlock: { mode: 'metered_read', unit: 'paragraph', pricePerUnit: '0.00005' }
|
|
435
|
+
unlock: { mode: 'time_pass', duration: '24h' }
|
|
436
|
+
unlock: { mode: 'agent_quota', maxReads: 20 }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Those later modes let Nibgate grow into background video/audio streaming payments, partial article reads, time passes, and agent usage quotas without changing the one-package architecture.
|
|
440
|
+
|
|
441
|
+
## Server protection
|
|
442
|
+
|
|
443
|
+
Use `@nibgate/sdk/server` for real route protection. The server layer creates x402-style payment challenges, verifies your payment receipt, and issues a short-lived Nibgate unlock token for the route.
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
import { createNibgateServer } from '@nibgate/sdk/server';
|
|
447
|
+
|
|
448
|
+
const nibgateServer = createNibgateServer({
|
|
449
|
+
secret: process.env.NIBGATE_SECRET,
|
|
450
|
+
origin: 'https://creator.example',
|
|
451
|
+
recipient: process.env.NIBGATE_SELLER_ADDRESS, // fallback only
|
|
452
|
+
async verifyPayment({ resource, payment }) {
|
|
453
|
+
// Plug Circle/x402 verification here.
|
|
454
|
+
return Boolean(payment.paymentId);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
export const GET = nibgateServer.protect({
|
|
459
|
+
id: 'premium-guide',
|
|
460
|
+
title: 'Premium Guide',
|
|
461
|
+
type: 'article',
|
|
462
|
+
price: '0.01',
|
|
463
|
+
recipient: '0x558e7BFaF2Cf1A494F44E50D92431Afc060c9D12',
|
|
464
|
+
path: '/premium-guide',
|
|
465
|
+
access: {
|
|
466
|
+
humans: 'paid',
|
|
467
|
+
agents: 'paid'
|
|
468
|
+
},
|
|
469
|
+
unlock: {
|
|
470
|
+
mode: 'one_time'
|
|
471
|
+
}
|
|
472
|
+
}, async () => {
|
|
473
|
+
return new Response('Premium content');
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
For JSON API routes, use the smaller helpers:
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
import { createNibgateServer, manifestResponse } from '@nibgate/sdk/server';
|
|
481
|
+
|
|
482
|
+
const nibgateServer = createNibgateServer({
|
|
483
|
+
secret: process.env.NIBGATE_SECRET,
|
|
484
|
+
origin: 'https://creator.example',
|
|
485
|
+
recipient: process.env.NIBGATE_SELLER_ADDRESS // fallback only
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
export function GET() {
|
|
489
|
+
return manifestResponse({
|
|
490
|
+
name: 'Creator site',
|
|
491
|
+
origin: 'https://creator.example',
|
|
492
|
+
content: [premiumGuide]
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function access(request) {
|
|
497
|
+
return nibgateServer.accessResponse(request, premiumGuide);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function pay(request) {
|
|
501
|
+
return nibgateServer.payAndUnlockResponse(request, premiumGuide, {
|
|
502
|
+
accessPath: '/api/nibgate/access'
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
`accessResponse` is the production route. It returns a real `PAYMENT-REQUIRED` header, verifies the returned `Payment-Signature`, issues a signed `unlockProof`, and accepts future access through the `x-nibgate-payment-proof` header.
|
|
508
|
+
|
|
509
|
+
`payAndUnlockResponse` is only for local test harnesses and server-side agent tests where you intentionally configure a funded tester key. In `circle-gateway` mode it requires:
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
NIBGATE_PAYMENT_MODE=circle-gateway
|
|
513
|
+
NIBGATE_SELLER_ADDRESS=0xCreatorReceiver
|
|
514
|
+
NIBGATE_BUYER_PRIVATE_KEY=0xFundedTesterPrivateKey
|
|
515
|
+
NIBGATE_BUYER_CHAIN=arcTestnet
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The handler calls Gateway, verifies the returned payment, and returns a signed `unlockProof`. It is for controlled server/agent harnesses, not human browser UX.
|
|
519
|
+
|
|
520
|
+
Do not ship `NIBGATE_BUYER_PRIVATE_KEY` in a public creator website. In production, the buyer is the visitor or agent. They connect their own wallet/Gateway, sign/pay the `PAYMENT-REQUIRED` challenge, and send the resulting payment signature back to the creator route.
|
|
521
|
+
|
|
522
|
+
Gateway balance, deposit, and withdraw helpers are also available from the same package:
|
|
523
|
+
|
|
524
|
+
```js
|
|
525
|
+
import { depositToGateway, getGatewayBalances, withdrawFromGateway } from '@nibgate/sdk/server';
|
|
526
|
+
|
|
527
|
+
const balances = await getGatewayBalances({
|
|
528
|
+
buyerPrivateKey: process.env.NIBGATE_BUYER_PRIVATE_KEY
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
await depositToGateway('1', {
|
|
532
|
+
buyerPrivateKey: process.env.NIBGATE_BUYER_PRIVATE_KEY
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
await withdrawFromGateway('0.5', {
|
|
536
|
+
buyerPrivateKey: process.env.NIBGATE_BUYER_PRIVATE_KEY,
|
|
537
|
+
recipient: '0xCreatorOrBuyer'
|
|
538
|
+
});
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
For the MVP, browser demos should use the package wallet checkout helper. Server-side funded tester keys are only for command/API harnesses and agent/server tests.
|
|
542
|
+
|
|
543
|
+
For command/API-only demos, package helpers can emit the same standard event sequence to the hub:
|
|
544
|
+
|
|
545
|
+
```js
|
|
546
|
+
import { emitTestEvents } from '@nibgate/sdk/testing';
|
|
547
|
+
|
|
548
|
+
await emitTestEvents(premiumGuide, {
|
|
549
|
+
origin: 'https://creator.example',
|
|
550
|
+
source: 'creator-site'
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The browser `gate(...)` API gives creators the simple unlock UX. The server API is what should enforce real paid access.
|
|
555
|
+
|
|
556
|
+
Payments are non-custodial in the Nibgate model. The receiving address belongs to the creator site/package config, and different sites can use different receiving addresses. Nibgate Hub records paid unlock events and payment metadata; it does not hold funds or provide withdrawals.
|
|
557
|
+
|
|
558
|
+
## Payment receipts
|
|
559
|
+
|
|
560
|
+
Nibgate supports two receipt paths today:
|
|
561
|
+
|
|
562
|
+
- `circle-gateway`: store the Circle payment id and a `receiptUrl` only when Circle or your gateway layer returns a real public/internal receipt URL.
|
|
563
|
+
- `arc-testnet`: store the Arc transaction hash and optional `chainExplorerUrl`, usually an Arcscan transaction URL.
|
|
564
|
+
|
|
565
|
+
Do not fabricate gateway receipt URLs. If no provider receipt URL exists, send the payment id/hash and Nibgate will show the recorded payment with the best available explorer link.
|
|
566
|
+
|
|
567
|
+
On Arc testnet, Gateway payments carry a signed authorization payload rather than a simple transfer hash for every unlock. Nibgate stores that signed payment payload as the payment id/receipt metadata. If your provider exposes a memo, transaction hash, or explorer URL, pass it as `memo`, `txHash`, or `chainExplorerUrl`; the hub will keep it with the payment event.
|
|
568
|
+
|
|
569
|
+
## End-to-end product flow
|
|
570
|
+
|
|
571
|
+
1. Creator installs `@nibgate/sdk`.
|
|
572
|
+
2. Creator maps posts, media, downloads, API routes, or CMS records into Nibgate resources.
|
|
573
|
+
3. Creator adds the widget snippet from the Nibgate hub.
|
|
574
|
+
4. Creator exposes `nibgate.json` with package helpers.
|
|
575
|
+
5. Nibgate backend verifies the widget and discovers resources.
|
|
576
|
+
6. Human visitors or AI agents hit a protected route.
|
|
577
|
+
7. Creator server returns `402 PAYMENT-REQUIRED`.
|
|
578
|
+
8. Human wallet or agent wallet/Gateway pays and returns `Payment-Signature`.
|
|
579
|
+
9. Creator server verifies the payment, issues a signed unlock proof, and serves content.
|
|
580
|
+
10. Browser requests include that proof through `x-nibgate-payment-proof` for future access checks.
|
|
581
|
+
11. Package/widget emits view, content, unlock, payment, and engagement events to the hub.
|
|
582
|
+
12. After unlock, the visitor or agent can submit an onchain content rating tied to the same proof.
|
|
583
|
+
13. Hub stores metrics for content, site, and creator analytics, then indexes onchain ratings into content, site, and creator reputation.
|
|
584
|
+
|
|
585
|
+
## Local demo
|
|
586
|
+
|
|
587
|
+
The repo includes a plain Express creator site that uses the package without any framework adapter:
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
npm run dev:demo
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
The demo imports `@nibgate/sdk/server`, serves the browser client locally, registers article/music/image/video content, and protects `/articles/premium-agent-economy`.
|
|
594
|
+
|
|
595
|
+
It also includes local routes for database/custom CMS, MDX/frontmatter, headless CMS, static teaser/protected API, media/file, and agent/API publishing styles under `/examples`.
|
|
596
|
+
|
|
597
|
+
Real unlocks are fail-closed. Set these env vars before using the local Gateway payment button:
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
NIBGATE_PAYMENT_MODE=circle-gateway
|
|
601
|
+
NIBGATE_SELLER_ADDRESS=0xYourSellerAddress
|
|
602
|
+
NIBGATE_BUYER_PRIVATE_KEY=0xYourFundedBuyerPrivateKey
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Without a real Gateway payment, the server will not issue a Nibgate unlock token.
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nibgate/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic browser and server package for creator-owned gated content, unlock events, and receipts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"module": "./src/index.js",
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.d.ts",
|
|
13
|
+
"import": "./src/index.js",
|
|
14
|
+
"default": "./src/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./src/server.d.ts",
|
|
18
|
+
"import": "./src/server.js",
|
|
19
|
+
"default": "./src/server.js"
|
|
20
|
+
},
|
|
21
|
+
"./tracking": {
|
|
22
|
+
"types": "./src/tracking.d.ts",
|
|
23
|
+
"import": "./src/tracking.js",
|
|
24
|
+
"default": "./src/tracking.js"
|
|
25
|
+
},
|
|
26
|
+
"./testing": {
|
|
27
|
+
"types": "./src/testing.d.ts",
|
|
28
|
+
"import": "./src/testing.js",
|
|
29
|
+
"default": "./src/testing.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"pack:check": "npm pack --dry-run",
|
|
35
|
+
"prepublishOnly": "npm run pack:check"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"src",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"keywords": [
|
|
45
|
+
"nibgate",
|
|
46
|
+
"creator",
|
|
47
|
+
"paywall",
|
|
48
|
+
"analytics",
|
|
49
|
+
"x402"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@circle-fin/x402-batching": "^3.0.4",
|
|
54
|
+
"@x402/core": "^2.17.0",
|
|
55
|
+
"@x402/evm": "^2.17.0",
|
|
56
|
+
"viem": "^2.54.1"
|
|
57
|
+
}
|
|
58
|
+
}
|