@nibgate/sdk 0.2.0 → 0.2.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/SKILL.md +82 -11
- package/package.json +1 -1
- package/src/server/access.js +14 -9
package/SKILL.md
CHANGED
|
@@ -212,6 +212,21 @@ app.use(admin.router(express))
|
|
|
212
212
|
// app.post('/admin/nibgate/resources/:id', admin.handleUpdate)
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
### Postgres store
|
|
216
|
+
|
|
217
|
+
For production (Render, Railway, Fly), use the Postgres store instead of the file store (file changes get wiped on redeploy):
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import pkg from 'pg'
|
|
221
|
+
const { Pool } = pkg
|
|
222
|
+
|
|
223
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
224
|
+
const store = createPostgresStore(pool, { table: 'nibgate_settings' })
|
|
225
|
+
const admin = createAdminApi({ store })
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The Postgres store auto-creates the table. It uses JSONB for settings and `ON CONFLICT` upserts. Works with any `pg` Pool-like interface.
|
|
229
|
+
|
|
215
230
|
### Protect the admin route
|
|
216
231
|
|
|
217
232
|
Pass an `authorize` function to `createAdminApi`:
|
|
@@ -308,7 +323,18 @@ NIBGATE_BUYER_PRIVATE_KEY=0x... # Only for local testing
|
|
|
308
323
|
|
|
309
324
|
### For plain HTML/JS sites without a bundler
|
|
310
325
|
|
|
311
|
-
|
|
326
|
+
For plain HTML/JS sites without a bundler, use the pre-built CDN bundle:
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<script src="https://unpkg.com/@nibgate/sdk@0.2.0/dist/nibgate.min.js"></script>
|
|
330
|
+
<script>
|
|
331
|
+
const { nibgate, gate, mountRatingUI, createEvmGatewayUnlock, createOnchainRating } = Nibgate
|
|
332
|
+
</script>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The bundle includes all dependencies (viem, x402) compiled into a single IIFE script. All browser exports are on the `Nibgate` global. No esbuild step needed.
|
|
336
|
+
|
|
337
|
+
If you prefer to build your own bundle:
|
|
312
338
|
|
|
313
339
|
```bash
|
|
314
340
|
esbuild --bundle --format=iife --global-name=Nibgate \
|
|
@@ -316,15 +342,6 @@ esbuild --bundle --format=iife --global-name=Nibgate \
|
|
|
316
342
|
node_modules/@nibgate/sdk/src/browser/index.js
|
|
317
343
|
```
|
|
318
344
|
|
|
319
|
-
Then load it:
|
|
320
|
-
|
|
321
|
-
```html
|
|
322
|
-
<script src="/nibgate-bundle.js"></script>
|
|
323
|
-
<script>
|
|
324
|
-
const { nibgate, gate } = Nibgate
|
|
325
|
-
</script>
|
|
326
|
-
```
|
|
327
|
-
|
|
328
345
|
### Smart Contract Wallets
|
|
329
346
|
|
|
330
347
|
Some smart contract wallets (Gnosis Safe, Argent, etc.) may not support `eth_signTypedData_v4` required by the Gateway payment signing. Test with an EOA (MetaMask, Rabby, Coinbase Wallet) first. If your users primarily use smart contract wallets, use the server-side pay route (`payAndUnlockResponse`) with `NIBGATE_BUYER_PRIVATE_KEY` instead.
|
|
@@ -412,7 +429,48 @@ Only expose values with `NEXT_PUBLIC_` or client-side env names when they are in
|
|
|
412
429
|
|
|
413
430
|
---
|
|
414
431
|
|
|
415
|
-
## 13.
|
|
432
|
+
## 13. Webhooks (Server-to-Server Notifications)
|
|
433
|
+
|
|
434
|
+
Receive notifications when payments, unlocks, or ratings happen — without polling.
|
|
435
|
+
|
|
436
|
+
### Setup
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
import { createWebhookManager, createWebhookApi } from '@nibgate/sdk/server'
|
|
440
|
+
import express from 'express'
|
|
441
|
+
|
|
442
|
+
const manager = createWebhookManager({
|
|
443
|
+
webhookUrl: 'https://my-server.com/nibgate-webhook',
|
|
444
|
+
webhookSecret: process.env.NIBGATE_WEBHOOK_SECRET
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
// Mount admin API for subscribing webhooks
|
|
448
|
+
const webhookApi = createWebhookApi(manager, { adminKey: process.env.ADMIN_KEY })
|
|
449
|
+
app.use(webhookApi.router(express))
|
|
450
|
+
|
|
451
|
+
// Emit events from your code
|
|
452
|
+
await manager.emit('payment.completed', { contentId: 'post-1', amount: '0.01', payer: '0x...' })
|
|
453
|
+
await manager.emit('content.unlocked', { contentId: 'post-1', actor: 'human' })
|
|
454
|
+
await manager.emit('content.rated', { contentId: 'post-1', rating: 4, rater: '0x...' })
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Webhook payload format
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"event": "payment.completed",
|
|
462
|
+
"timestamp": "2026-07-09T12:00:00.000Z",
|
|
463
|
+
"data": { "contentId": "post-1", "amount": "0.01" }
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
The `x-nibgate-webhook-signature` header contains an HMAC-SHA256 signature of the body using your webhook secret. Verify it on the receiving end to confirm the webhook is authentic.
|
|
468
|
+
|
|
469
|
+
Events emitted automatically by `createNibgateServer`: `payment_completed`, `unlock_completed`.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 14. Never Do These
|
|
416
474
|
|
|
417
475
|
- Do not hide paid HTML with CSS or client state after rendering it publicly
|
|
418
476
|
- Do not fake payment IDs, receipts, unlocks, or successful access
|
|
@@ -427,6 +485,19 @@ Only expose values with `NEXT_PUBLIC_` or client-side env names when they are in
|
|
|
427
485
|
|
|
428
486
|
- **Next.js**: Use route handlers for `/nibgate.json` and `/api/nibgate/access`. Protect content before rendering paid payloads. Use `server-only` for private modules.
|
|
429
487
|
- **Express/NestJS**: Use middleware, controllers, or guards around protected routes. Mount the admin router at `/admin/nibgate`.
|
|
488
|
+
|
|
489
|
+
For a one-line payment verification middleware:
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
import { verifyPayment } from '@nibgate/sdk/server'
|
|
493
|
+
|
|
494
|
+
app.get('/api/content/:id', verifyPayment({ resource: { id: 'my-post', price: '0.01' } }), (req, res) => {
|
|
495
|
+
// req.nibgate.verified is true
|
|
496
|
+
res.json({ content: 'Protected content here' })
|
|
497
|
+
})
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The middleware checks `x-nibgate-payment-proof` (existing unlock) and `payment-signature` (new Gateway payment). Returns 402 with a challenge if neither is present.
|
|
430
501
|
- **Astro/SvelteKit/Remix**: Use SSR/server routes or endpoints. Plain static builds need a protected API or signed URL for private payloads.
|
|
431
502
|
- **CMS apps**: Save Nibgate resource settings beside each content record. Use `settingsToAccessPolicy()` and `settingsToUnlockPolicy()` to convert stored settings to resource shapes. Mount the admin panel for easy management.
|
|
432
503
|
- **Static/vanilla HTML/JS**: Compile the SDK into a bundle with esbuild (see section 8). Use `gate()` and `nibgate` from the global `Nibgate` namespace.
|
package/package.json
CHANGED
package/src/server/access.js
CHANGED
|
@@ -218,10 +218,12 @@ export function protect(resource, handler, options = {}) {
|
|
|
218
218
|
|
|
219
219
|
export function verifyPayment(options = {}) {
|
|
220
220
|
return async function verifyPaymentMiddleware(req, res, next) {
|
|
221
|
-
const
|
|
221
|
+
const contentId = req.params?.id || req.body?.resourceId || '';
|
|
222
|
+
|
|
223
|
+
const proofHeader = req.headers?.['x-nibgate-payment-proof'] || '';
|
|
222
224
|
if (proofHeader) {
|
|
223
225
|
const nibgate = createNibgateServer(options);
|
|
224
|
-
const payload = nibgate.verifyUnlockToken(proofHeader, { id:
|
|
226
|
+
const payload = nibgate.verifyUnlockToken(proofHeader, { id: contentId });
|
|
225
227
|
if (payload) {
|
|
226
228
|
req.nibgate = { unlock: payload, verified: true };
|
|
227
229
|
return next();
|
|
@@ -229,11 +231,16 @@ export function verifyPayment(options = {}) {
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
if ((options.paymentMode || process.env.NIBGATE_PAYMENT_MODE) === 'circle-gateway') {
|
|
232
|
-
const sigHeader = req.headers?.['payment-signature'] ||
|
|
234
|
+
const sigHeader = req.headers?.['payment-signature'] || '';
|
|
233
235
|
if (sigHeader) {
|
|
234
|
-
const resource = options.resource || { id:
|
|
236
|
+
const resource = options.resource || { id: contentId, price: req.body?.price || '0' };
|
|
237
|
+
const gatewayHeaders = Object.assign(Object.create(null), req.headers || {});
|
|
238
|
+
gatewayHeaders.forEach = function (cb) {
|
|
239
|
+
var self = this;
|
|
240
|
+
Object.keys(self).forEach(function (k) { if (k !== 'forEach') cb(self[k], k); });
|
|
241
|
+
};
|
|
235
242
|
const gateway = await runCircleGatewayRequirement(
|
|
236
|
-
{ headers:
|
|
243
|
+
{ headers: gatewayHeaders, method: req.method || 'GET', url: req.originalUrl || req.url || '/' },
|
|
237
244
|
resource,
|
|
238
245
|
options
|
|
239
246
|
);
|
|
@@ -245,9 +252,7 @@ export function verifyPayment(options = {}) {
|
|
|
245
252
|
}
|
|
246
253
|
}
|
|
247
254
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
challenge: createPaymentChallenge(options.resource || { id: req.params?.id || '', price: req.body?.price || '0' }, options)
|
|
251
|
-
});
|
|
255
|
+
const errBody = createPaymentChallenge(options.resource || { id: contentId, price: req.body?.price || '0' }, options);
|
|
256
|
+
return res.status(402).json(errBody);
|
|
252
257
|
};
|
|
253
258
|
}
|