@relai-fi/x402 0.5.30 → 0.5.32

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 CHANGED
@@ -174,6 +174,46 @@ const result = await response.json();
174
174
 
175
175
  ---
176
176
 
177
+ ### Crossmint Smart Wallet (server-side / agent)
178
+
179
+ Use `createCrossmintX402Fetch` from `@relai-fi/x402/crossmint` — no `signTransaction` needed, Crossmint handles signing and broadcasting.
180
+
181
+ ```typescript
182
+ import { createCrossmintX402Fetch } from '@relai-fi/x402/crossmint';
183
+ import { Connection } from '@solana/web3.js';
184
+
185
+ const fetch402 = createCrossmintX402Fetch({
186
+ apiKey: process.env.CROSSMINT_API_KEY!, // sk_production_... or sk_staging_...
187
+ wallet: process.env.CROSSMINT_WALLET!, // Crossmint smart wallet address
188
+ connection: new Connection(process.env.SOLANA_RPC_URL!),
189
+ onPayment: (txHash) => console.log('On-chain tx:', txHash),
190
+ });
191
+
192
+ // RelAI sponsors SOL gas — wallet only needs USDC
193
+ const response = await fetch402('https://api.example.com/protected');
194
+ const data = await response.json();
195
+ ```
196
+
197
+ For agents that require explicit transaction approval before Crossmint broadcasts, use the **delegated mode** — an external Ed25519 signer must be registered on the Crossmint smart wallet:
198
+
199
+ ```typescript
200
+ import { createCrossmintDelegatedX402Fetch } from '@relai-fi/x402/crossmint';
201
+ import { Connection } from '@solana/web3.js';
202
+
203
+ const fetch402 = createCrossmintDelegatedX402Fetch({
204
+ apiKey: process.env.CROSSMINT_API_KEY!,
205
+ wallet: process.env.CROSSMINT_WALLET!,
206
+ signerSecretKey: Buffer.from(process.env.SIGNER_SECRET_KEY!, 'base64'), // 64-byte Ed25519
207
+ connection: new Connection(process.env.SOLANA_RPC_URL!),
208
+ });
209
+
210
+ const response = await fetch402('https://api.example.com/protected');
211
+ ```
212
+
213
+ > Both modes use Crossmint's API to sign and broadcast — no private key handling in your code.
214
+
215
+ ---
216
+
177
217
  ### WebSocket relay transport (optional)
178
218
 
179
219
  If your protected API is behind a relay URL like `https://api.relai.fi/relay/:apiId/...`
@@ -300,6 +340,9 @@ import {
300
340
  fromAtomicUnits,
301
341
  } from '@relai-fi/x402/utils';
302
342
 
343
+ // Plugins — extend protect() with free tier, custom logic
344
+ import { freeTier } from '@relai-fi/x402/plugins';
345
+
303
346
  // Management API — create/manage APIs, pricing, analytics, agent bootstrap
304
347
  import {
305
348
  createManagementClient,
@@ -468,6 +511,105 @@ app.get('/api/solana-data', relai.protect({
468
511
 
469
512
  ---
470
513
 
514
+ ## Plugins
515
+
516
+ Extend `Relai.protect()` with plugins that run before payment checks. Plugins can skip payment (e.g. free tier), add headers, or attach metadata to requests.
517
+
518
+ ### Free Tier Plugin
519
+
520
+ Allow buyers to make free API calls before requiring x402 payment. Usage is tracked per buyer (by JWT `sub`, wallet address, or IP) with optional global caps and periodic resets.
521
+
522
+ ```typescript
523
+ import Relai from '@relai-fi/x402/server';
524
+ import { freeTier } from '@relai-fi/x402/plugins';
525
+
526
+ const relai = new Relai({
527
+ network: 'base',
528
+ plugins: [
529
+ freeTier({
530
+ serviceKey: process.env.RELAI_SERVICE_KEY!,
531
+ perBuyerLimit: 10, // 10 free calls per buyer
532
+ resetPeriod: 'daily', // reset daily (or 'monthly', 'never')
533
+ globalCap: 1000, // optional: max 1000 free calls total
534
+ paths: ['*'], // optional: apply to all endpoints (default)
535
+ }),
536
+ ],
537
+ });
538
+
539
+ app.get('/api/data', relai.protect({
540
+ payTo: '0xYourWallet',
541
+ price: 0.01,
542
+ }), (req, res) => {
543
+ if (req.x402Free) {
544
+ // Free tier call — no payment was made
545
+ console.log('Free call from:', req.x402Plugin);
546
+ }
547
+ res.json({ data: 'content' });
548
+ });
549
+ ```
550
+
551
+ **How it works:**
552
+ 1. On server start, the plugin syncs its config to the RelAI backend via your service key.
553
+ 2. On each request, `beforePaymentCheck` asks the RelAI API if the buyer has free calls remaining.
554
+ 3. If free → `next()` is called without payment, `req.x402Free = true`, and usage is recorded.
555
+ 4. If exhausted → normal x402 payment flow continues.
556
+
557
+ **Free Tier config:**
558
+
559
+ | Option | Type | Default | Description |
560
+ |--------|------|---------|-------------|
561
+ | `serviceKey` | `string` | **required** | Your RelAI service key (`sk_live_...`) |
562
+ | `perBuyerLimit` | `number` | **required** | Free calls each buyer gets per period |
563
+ | `resetPeriod` | `'never' \| 'daily' \| 'monthly'` | `'never'` | When counters reset |
564
+ | `globalCap` | `number` | — | Max total free calls across all buyers |
565
+ | `paths` | `string[]` | `['*']` | Which endpoints the free tier applies to |
566
+ | `baseUrl` | `string` | `https://api.relai.fi` | RelAI API URL (override for testing) |
567
+
568
+ **Request properties set on free-tier bypass:**
569
+
570
+ | Property | Type | Description |
571
+ |----------|------|-------------|
572
+ | `req.x402Free` | `boolean` | `true` when request was served for free |
573
+ | `req.x402Paid` | `boolean` | `false` on free tier, `true` on paid |
574
+ | `req.x402Plugin` | `string` | Plugin name that granted the bypass (`'freeTier'`) |
575
+ | `req.pluginMeta` | `object` | `{ freeTier: true, remaining: number }` |
576
+
577
+ **Dashboard:** Manage plugin config and view usage in the RelAI dashboard under **SDK Plugins**.
578
+
579
+ ### Custom Plugins
580
+
581
+ ```typescript
582
+ import type { RelaiPlugin } from '@relai-fi/x402';
583
+
584
+ const myPlugin: RelaiPlugin = {
585
+ name: 'myPlugin',
586
+ beforePaymentCheck: async (ctx) => {
587
+ if (ctx.req.headers['x-vip'] === 'true') {
588
+ return { skip: true, reason: 'VIP bypass' };
589
+ }
590
+ return {};
591
+ },
592
+ };
593
+
594
+ const relai = new Relai({
595
+ network: 'base',
596
+ plugins: [myPlugin],
597
+ });
598
+ ```
599
+
600
+ **Plugin interface:**
601
+
602
+ ```typescript
603
+ interface RelaiPlugin {
604
+ name: string;
605
+ beforePaymentCheck?: (ctx: PluginContext) => Promise<PluginResult>;
606
+ afterSettled?: (ctx: PluginContext) => Promise<void>;
607
+ onInit?: (config: RelaiServerConfig) => Promise<void>;
608
+ }
609
+ ```
610
+
611
+ ---
612
+
471
613
  ## Management API
472
614
 
473
615
  Programmatically create and manage monetised APIs, update pricing, and read analytics. Designed for agents and CI/CD — no browser needed.
package/dist/client.cjs CHANGED
@@ -742,7 +742,7 @@ function createX402Client(config) {
742
742
  }
743
743
  function getBridgeExtension(requirements) {
744
744
  const ext = requirements?.extensions?.bridge;
745
- if (!ext?.info?.endpoint || !Array.isArray(ext.info.supportedSourceChains)) return null;
745
+ if (!ext?.info?.settleEndpoint || !Array.isArray(ext.info.supportedSourceChains)) return null;
746
746
  return ext.info;
747
747
  }
748
748
  function selectBridgeSource(bridge) {
@@ -783,7 +783,7 @@ function createX402Client(config) {
783
783
  payTo: bridge.payTo,
784
784
  amount: targetAccept.amount || targetAccept.maxAmountRequired,
785
785
  extra: {
786
- ...bridge.feePayer ? { feePayer: bridge.feePayer } : {},
786
+ ...bridge.feePayerSvm ? { feePayer: bridge.feePayerSvm } : {},
787
787
  decimals: 6
788
788
  }
789
789
  };
@@ -799,7 +799,7 @@ function createX402Client(config) {
799
799
  };
800
800
  sourcePaymentHeader = usePermit ? await buildEvmPermitPayment(sourceAccept, requirements, url) : await buildEvmPayment(sourceAccept, requirements, url);
801
801
  }
802
- const bridgeRes = await fetch(bridge.endpoint, {
802
+ const bridgeRes = await fetch(bridge.settleEndpoint, {
803
803
  method: "POST",
804
804
  headers: { "Content-Type": "application/json" },
805
805
  body: JSON.stringify({