@relai-fi/x402 0.5.39 → 0.6.0-rc.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 CHANGED
@@ -340,8 +340,8 @@ import {
340
340
  fromAtomicUnits,
341
341
  } from '@relai-fi/x402/utils';
342
342
 
343
- // Plugins — extend protect() with free tier, custom logic
344
- import { freeTier } from '@relai-fi/x402/plugins';
343
+ // Plugins — extend protect() with built-in & custom logic
344
+ import { freeTier, bridge, shield, preflight, circuitBreaker, refund } from '@relai-fi/x402/plugins';
345
345
 
346
346
  // Management API — create/manage APIs, pricing, analytics, agent bootstrap
347
347
  import {
@@ -513,7 +513,20 @@ app.get('/api/solana-data', relai.protect({
513
513
 
514
514
  ## Plugins
515
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.
516
+ Extend `Relai.protect()` with plugins that hook into the payment lifecycle. Six built-in plugins ship with the SDK:
517
+
518
+ ```typescript
519
+ import { freeTier, bridge, shield, preflight, circuitBreaker, refund } from '@relai-fi/x402/plugins';
520
+ ```
521
+
522
+ | Plugin | Purpose | Hook |
523
+ |--------|---------|------|
524
+ | **freeTier** | Free API calls before payment | `beforePaymentCheck` → `skip` |
525
+ | **bridge** | Cross-chain payments (Solana ↔ SKALE ↔ Base) | `enrich402Response` |
526
+ | **shield** | Global service health check before payment | `beforePaymentCheck` → `reject` |
527
+ | **preflight** | Per-endpoint liveness probe before payment | `beforePaymentCheck` → `reject` |
528
+ | **circuitBreaker** | Failure history tracking, auto-open circuit | `beforePaymentCheck` → `reject` + `afterSettled` |
529
+ | **refund** | Auto-credit buyers when paid requests fail | `beforePaymentCheck` → `skip` + `afterSettled` |
517
530
 
518
531
  ### Free Tier Plugin
519
532
 
@@ -554,16 +567,15 @@ app.get('/api/data', relai.protect({
554
567
  3. If free → `next()` is called without payment, `req.x402Free = true`, and usage is recorded.
555
568
  4. If exhausted → normal x402 payment flow continues.
556
569
 
557
- **Free Tier config:**
570
+ **Config:**
558
571
 
559
572
  | Option | Type | Default | Description |
560
573
  |--------|------|---------|-------------|
561
- | `serviceKey` | `string` | **required** | Your RelAI service key (`sk_live_...`) |
574
+ | `serviceKey` | `string` | | Your `sk_live_...` key. Omit for local in-memory mode. |
562
575
  | `perBuyerLimit` | `number` | **required** | Free calls each buyer gets per period |
563
- | `resetPeriod` | `'never' \| 'daily' \| 'monthly'` | `'never'` | When counters reset |
576
+ | `resetPeriod` | `'none' \| 'daily' \| 'monthly'` | `'none'` | When counters reset |
564
577
  | `globalCap` | `number` | — | Max total free calls across all buyers |
565
578
  | `paths` | `string[]` | `['*']` | Which endpoints the free tier applies to |
566
- | `baseUrl` | `string` | `https://api.relai.fi` | RelAI API URL (override for testing) |
567
579
 
568
580
  **Request properties set on free-tier bypass:**
569
581
 
@@ -574,27 +586,357 @@ app.get('/api/data', relai.protect({
574
586
  | `req.x402Plugin` | `string` | Plugin name that granted the bypass (`'freeTier'`) |
575
587
  | `req.pluginMeta` | `object` | `{ freeTier: true, remaining: number }` |
576
588
 
577
- **Dashboard:** Manage plugin config and view usage in the RelAI dashboard under **SDK Plugins**.
589
+ ### Bridge Plugin
590
+
591
+ Accept cross-chain payments. Buyers on Solana can pay your SKALE Base API — the SDK handles bridging automatically.
592
+
593
+ ```typescript
594
+ import Relai from '@relai-fi/x402/server';
595
+ import { bridge } from '@relai-fi/x402/plugins';
596
+
597
+ const relai = new Relai({
598
+ network: 'skale-bite',
599
+ plugins: [
600
+ bridge({ serviceKey: process.env.RELAI_SERVICE_KEY }),
601
+ ],
602
+ });
603
+ ```
604
+
605
+ The plugin auto-discovers bridge capabilities from `/bridge/info`. No manual chain configuration needed.
606
+
607
+ | Option | Type | Default | Description |
608
+ |--------|------|---------|-------------|
609
+ | `serviceKey` | `string` | — | Recommended. Tracks bridge usage in dashboard. |
610
+ | `settleEndpoint` | `string` | `/bridge/settle` | Custom settle endpoint |
611
+ | `feeBps` | `number` | `100` | Bridge fee in basis points (100 = 1%) |
612
+
613
+ ### Shield Plugin
614
+
615
+ Global service health check — protects buyers from paying for unhealthy endpoints. Before the server returns 402, Shield runs a health check. If unhealthy, returns 503 instead of asking for payment.
616
+
617
+ ```typescript
618
+ import Relai from '@relai-fi/x402/server';
619
+ import { shield } from '@relai-fi/x402/plugins';
620
+
621
+ const relai = new Relai({
622
+ network: 'base',
623
+ plugins: [
624
+ shield({
625
+ healthUrl: 'https://my-api.com/health',
626
+ timeoutMs: 3000,
627
+ }),
628
+ // Or use a custom function:
629
+ // shield({
630
+ // healthCheck: async () => {
631
+ // const dbOk = await checkDatabase();
632
+ // return dbOk;
633
+ // },
634
+ // }),
635
+ ],
636
+ });
637
+ ```
638
+
639
+ | Option | Type | Default | Description |
640
+ |--------|------|---------|-------------|
641
+ | `healthUrl` | `string` | — | URL to probe. 2xx = healthy. |
642
+ | `healthCheck` | `() => boolean \| Promise<boolean>` | — | Custom function. Takes priority over `healthUrl`. |
643
+ | `timeoutMs` | `number` | `5000` | Timeout for health probe (ms) |
644
+ | `cacheTtlMs` | `number` | `10000` | Cache health result (ms) |
645
+ | `unhealthyStatus` | `number` | `503` | HTTP status when unhealthy |
646
+ | `unhealthyMessage` | `string` | `Service temporarily unavailable...` | Error message |
647
+
648
+ **Response headers:** `X-Shield-Status: healthy|unhealthy`, `Retry-After` (when unhealthy).
649
+
650
+ ### Preflight Plugin
651
+
652
+ Per-endpoint liveness probe — verifies the **specific endpoint** responds before payment. Sends `HEAD` with `X-Preflight: true` header; the middleware responds 200 instantly without triggering payment.
653
+
654
+ ```typescript
655
+ import Relai from '@relai-fi/x402/server';
656
+ import { preflight } from '@relai-fi/x402/plugins';
657
+
658
+ const relai = new Relai({
659
+ network: 'base',
660
+ plugins: [
661
+ preflight({ baseUrl: 'https://my-api.com' }),
662
+ ],
663
+ });
664
+
665
+ // If /api/data doesn't respond, buyers get 503 — never 402
666
+ app.get('/api/data', relai.protect({
667
+ payTo: '0xYourWallet',
668
+ price: 0.01,
669
+ }), handler);
670
+ ```
671
+
672
+ | Option | Type | Default | Description |
673
+ |--------|------|---------|-------------|
674
+ | `baseUrl` | `string` | **required** | Base URL of the API. Request path is appended automatically. |
675
+ | `timeoutMs` | `number` | `3000` | Timeout for probe (ms) |
676
+ | `cacheTtlMs` | `number` | `5000` | Cache per-path result (ms) |
677
+ | `unhealthyStatus` | `number` | `503` | HTTP status when unreachable |
678
+ | `unhealthyMessage` | `string` | `Endpoint not responding...` | Error message |
679
+
680
+ **Response headers:** `X-Preflight-Status: ok|unreachable`, `Retry-After` (when unreachable).
681
+
682
+ **Shield vs Preflight:**
683
+
684
+ | | Shield | Preflight |
685
+ |---|--------|-----------|
686
+ | Scope | Global service health | Per-endpoint liveness |
687
+ | Probe target | Separate health URL / function | The actual protected endpoint |
688
+ | Cache | Single result (10s) | Per-path (5s) |
689
+ | Use case | DB/Redis/external API down | Specific endpoint not responding |
690
+
691
+ ### Circuit Breaker Plugin
692
+
693
+ Tracks failure history and automatically "opens the circuit" after repeated failures — preventing buyers from paying for broken endpoints. **Zero-latency** — no extra HTTP requests.
694
+
695
+ ```typescript
696
+ import Relai from '@relai-fi/x402/server';
697
+ import { circuitBreaker } from '@relai-fi/x402/plugins';
698
+
699
+ const relai = new Relai({
700
+ network: 'base',
701
+ plugins: [
702
+ circuitBreaker({
703
+ failureThreshold: 5, // open after 5 failures
704
+ resetTimeMs: 30000, // try again after 30s
705
+ }),
706
+ ],
707
+ });
708
+ ```
709
+
710
+ **States:** closed (normal) → open (all rejected 503) → half-open (test requests) → closed.
711
+
712
+ | Option | Type | Default | Description |
713
+ |--------|------|---------|-------------|
714
+ | `failureThreshold` | `number` | `5` | Consecutive failures before circuit opens |
715
+ | `resetTimeMs` | `number` | `30000` | Time (ms) circuit stays open before half-open |
716
+ | `halfOpenSuccesses` | `number` | `2` | Successes needed in half-open to close |
717
+ | `openStatus` | `number` | `503` | HTTP status when circuit is open |
718
+ | `openMessage` | `string` | `Service temporarily unavailable...` | Error message |
719
+ | `failureCodes` | `number[]` | `[500, 502, 503, 504]` | HTTP codes treated as failures |
720
+ | `scope` | `'global' \| 'per-path'` | `'per-path'` | Track globally or per endpoint |
721
+
722
+ **Response headers:** `X-Circuit-State: closed|open|half-open`, `Retry-After` (when open).
723
+
724
+ ### Refund Plugin
725
+
726
+ Automatically compensates buyers when paid requests fail. Records an in-memory credit or calls your custom handler.
727
+
728
+ ```typescript
729
+ import Relai from '@relai-fi/x402/server';
730
+ import { refund } from '@relai-fi/x402/plugins';
731
+
732
+ const relai = new Relai({
733
+ network: 'base',
734
+ plugins: [
735
+ refund({
736
+ triggerCodes: [500, 502, 503],
737
+ mode: 'credit',
738
+ onRefund: (event) => {
739
+ console.log(`Refund: $${event.amount} to ${event.payer}`);
740
+ },
741
+ }),
742
+ ],
743
+ });
744
+ ```
745
+
746
+ **Modes:**
747
+ - `credit` — records credit per buyer. Next request skips payment automatically. Header: `X-Refund-Credit: applied`.
748
+ - `log` — only calls `onRefund`. Handle refunds externally (DB, Stripe, etc.).
749
+
750
+ | Option | Type | Default | Description |
751
+ |--------|------|---------|-------------|
752
+ | `triggerCodes` | `number[]` | `[500, 502, 503, 504]` | HTTP codes that trigger a refund |
753
+ | `mode` | `'credit' \| 'log'` | `'credit'` | Auto-credit or callback-only |
754
+ | `onRefund` | `(event: RefundEvent) => void` | — | Callback on every refund event |
755
+ | `refundOnSettlementFailure` | `boolean` | `true` | Also refund when settlement itself fails |
756
+
757
+ ### ERC-8004 Reputation Plugins
758
+
759
+ Build verifiable on-chain reputation for your API using the [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) standard. Scores are stored on SKALE Base Sepolia (zero-cost transactions) and readable by any agent before payment.
760
+
761
+ #### `score()` — Inject reputation into the 402 response
762
+
763
+ Before an agent pays, it can see your API's live on-chain score in the `extensions.score` field of the 402 response. Fetches directly from the SKALE RPC node — no REST API.
764
+
765
+ ```typescript
766
+ import Relai from '@relai-fi/x402/server';
767
+ import { score } from '@relai-fi/x402/plugins';
768
+
769
+ const relai = new Relai({
770
+ network: 'base',
771
+ plugins: [
772
+ score({ agentId: process.env.ERC8004_AGENT_ID! }),
773
+ ],
774
+ });
775
+ ```
776
+
777
+ The 402 response will include:
778
+ ```json
779
+ {
780
+ "extensions": {
781
+ "score": {
782
+ "agentId": "5",
783
+ "feedbackCount": 142,
784
+ "successRate": 98.6,
785
+ "avgResponseMs": 312
786
+ }
787
+ }
788
+ }
789
+ ```
790
+
791
+ | Option | Type | Default | Description |
792
+ |--------|------|---------|-------------|
793
+ | `agentId` | `string \| number` | — | ERC-8004 NFT token ID from your dashboard |
794
+ | `rpcUrl` | `string` | `process.env.ERC8004_RPC_URL` | SKALE Base Sepolia RPC URL |
795
+ | `identityRegistryAddress` | `string` | `process.env.ERC8004_IDENTITY_REGISTRY` | IdentityRegistry contract address |
796
+ | `reputationRegistryAddress` | `string` | `process.env.ERC8004_REPUTATION_REGISTRY` | ReputationRegistry contract address |
797
+ | `cacheTtlMs` | `number` | `300000` | Score cache TTL (default: 5 min) |
798
+
799
+ #### `feedback()` — Record your own API metrics on-chain
800
+
801
+ Submit `successRate` and `responseTime` after every settled x402 payment. This is what builds the score that `score()` later reads.
802
+
803
+ ```typescript
804
+ import Relai from '@relai-fi/x402/server';
805
+ import { score, feedback } from '@relai-fi/x402/plugins';
806
+
807
+ const relai = new Relai({
808
+ network: 'base',
809
+ plugins: [
810
+ score({ agentId: process.env.ERC8004_AGENT_ID! }),
811
+ feedback({ agentId: process.env.ERC8004_AGENT_ID! }),
812
+ ],
813
+ });
814
+ ```
815
+
816
+ Requires `BACKEND_WALLET_PRIVATE_KEY` — the wallet must hold CREDIT tokens on SKALE Base for gas.
817
+
818
+ | Option | Type | Default | Description |
819
+ |--------|------|---------|-------------|
820
+ | `agentId` | `string \| number` | — | Your ERC-8004 agent token ID |
821
+ | `walletPrivateKey` | `string` | `process.env.BACKEND_WALLET_PRIVATE_KEY` | EVM private key, needs CREDIT on SKALE |
822
+ | `rpcUrl` | `string` | `process.env.ERC8004_RPC_URL` | SKALE RPC URL |
823
+ | `reputationRegistryAddress` | `string` | `process.env.ERC8004_REPUTATION_REGISTRY` | Contract address |
824
+ | `submitSuccessRate` | `boolean` | `true` | Submit 1/0 success signal |
825
+ | `submitResponseTime` | `boolean` | `true` | Submit response time in ms |
826
+
827
+ #### `solanaFeedback()` — Native Solana 8004-solana feedback
828
+
829
+ For Solana APIs registered via `8004-solana` (MPL Core NFT). Requires `npm install 8004-solana`.
830
+
831
+ ```typescript
832
+ import Relai from '@relai-fi/x402/server';
833
+ import { solanaFeedback } from '@relai-fi/x402/plugins';
834
+
835
+ const relai = new Relai({
836
+ network: 'solana',
837
+ plugins: [
838
+ solanaFeedback({
839
+ assetPubkey: process.env.SOLANA_AGENT_ASSET!, // MPL Core NFT address
840
+ }),
841
+ ],
842
+ });
843
+ ```
844
+
845
+ | Option | Type | Default | Description |
846
+ |--------|------|---------|-------------|
847
+ | `assetPubkey` | `string` | — | Solana MPL Core NFT address (`solanaAgentAsset`) |
848
+ | `feedbackWalletPrivateKey` | `string` | `process.env.SOLANA_8004_FEEDBACK_KEY` | base58 or JSON array |
849
+ | `cluster` | `'mainnet-beta' \| 'devnet'` | `process.env.SOLANA_8004_CLUSTER` | Solana cluster |
850
+ | `rpcUrl` | `string` | `process.env.SOLANA_8004_RPC_URL` | Custom RPC (Helius / QuickNode) |
851
+
852
+ #### `submitRelayFeedback()` — Third-party feedback utility
853
+
854
+ If your server acts as a **relay or marketplace** that calls other APIs, use this standalone function to record feedback about those APIs. Uses a separate relay wallet to avoid self-feedback restrictions.
855
+
856
+ ```typescript
857
+ import { submitRelayFeedback } from '@relai-fi/x402/plugins';
858
+
859
+ // After calling an external API:
860
+ const start = Date.now();
861
+ const result = await fetch('https://other-api.com/v1/data');
862
+
863
+ submitRelayFeedback({
864
+ agentId: '5', // target API's ERC-8004 agentId
865
+ success: result.ok,
866
+ responseTimeMs: Date.now() - start,
867
+ endpoint: '/v1/data',
868
+ });
869
+ ```
870
+
871
+ | Option | Type | Default | Description |
872
+ |--------|------|---------|-------------|
873
+ | `agentId` | `string \| number` | — | ERC-8004 agentId of the API you called |
874
+ | `success` | `boolean` | — | Whether the call succeeded |
875
+ | `responseTimeMs` | `number` | `0` | Elapsed time in ms |
876
+ | `endpoint` | `string` | `''` | Endpoint path |
877
+ | `feedbackWalletPrivateKey` | `string` | `process.env.FEEDBACK_WALLET_PRIVATE_KEY` | Must differ from API owner key |
878
+
879
+ #### Required environment variables (ERC-8004)
880
+
881
+ ```bash
882
+ ERC8004_IDENTITY_REGISTRY=0x8724C768547d7fFb1722b13a84F21CCF5010641f
883
+ ERC8004_REPUTATION_REGISTRY=0xe946A7F08d1CC0Ed0eC1fC131D0135d9c0Dd7d9D
884
+ ERC8004_RPC_URL=https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha
885
+ ERC8004_AGENT_ID=5 # your agent NFT token ID
886
+
887
+ BACKEND_WALLET_PRIVATE_KEY=0x... # for feedback() — needs CREDIT on SKALE
888
+ FEEDBACK_WALLET_PRIVATE_KEY=0x... # for submitRelayFeedback() — different wallet
889
+
890
+ # Solana 8004 (only if using solanaFeedback)
891
+ SOLANA_AGENT_ASSET=GH93tGR8... # MPL Core NFT pubkey
892
+ SOLANA_8004_FEEDBACK_KEY=... # base58 or JSON array
893
+ SOLANA_8004_CLUSTER=mainnet-beta
894
+ SOLANA_8004_RPC_URL=https://...
895
+ ```
896
+
897
+ ### Combining Plugins
898
+
899
+ Plugins run in array order. Combine them for layered protection:
900
+
901
+ ```typescript
902
+ const relai = new Relai({
903
+ network: 'base',
904
+ plugins: [
905
+ shield({ healthUrl: 'https://my-api.com/health' }), // 1. Is the service up?
906
+ preflight({ baseUrl: 'https://my-api.com' }), // 2. Is this endpoint alive?
907
+ circuitBreaker({ failureThreshold: 5 }), // 3. Too many recent failures?
908
+ freeTier({ perBuyerLimit: 5, resetPeriod: 'daily' }), // 4. Free calls left?
909
+ refund({ triggerCodes: [500, 502, 503] }), // 5. Compensate on error
910
+ bridge({ serviceKey: process.env.RELAI_SERVICE_KEY }), // 6. Cross-chain support
911
+ score({ agentId: process.env.ERC8004_AGENT_ID }), // 7. Show reputation in 402
912
+ feedback({ agentId: process.env.ERC8004_AGENT_ID }), // 8. Record metrics on-chain
913
+ ],
914
+ });
915
+ ```
578
916
 
579
917
  ### Custom Plugins
580
918
 
581
919
  ```typescript
582
- import type { RelaiPlugin } from '@relai-fi/x402';
920
+ import type { RelaiPlugin, PluginContext, PluginResult } from '@relai-fi/x402/plugins';
583
921
 
584
922
  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' };
923
+ name: 'my-plugin',
924
+
925
+ async beforePaymentCheck(req, ctx) {
926
+ if (req.headers['x-vip'] === 'true') {
927
+ return { skip: true, headers: { 'X-VIP': 'true' } };
589
928
  }
590
929
  return {};
591
930
  },
592
- };
593
931
 
594
- const relai = new Relai({
595
- network: 'base',
596
- plugins: [myPlugin],
597
- });
932
+ async afterSettled(req, result, ctx) {
933
+ console.log(`Paid $${ctx.price} by ${result.payer} on ${ctx.network}`);
934
+ },
935
+
936
+ async onInit() {
937
+ console.log('Plugin initialized');
938
+ },
939
+ };
598
940
  ```
599
941
 
600
942
  **Plugin interface:**
@@ -602,9 +944,26 @@ const relai = new Relai({
602
944
  ```typescript
603
945
  interface RelaiPlugin {
604
946
  name: string;
605
- beforePaymentCheck?: (ctx: PluginContext) => Promise<PluginResult>;
606
- afterSettled?: (ctx: PluginContext) => Promise<void>;
607
- onInit?: (config: RelaiServerConfig) => Promise<void>;
947
+ beforePaymentCheck?(req: any, ctx: PluginContext): Promise<PluginResult>;
948
+ afterSettled?(req: any, result: SettleResult, ctx: PluginContext): Promise<void>;
949
+ onInit?(): Promise<void>;
950
+ enrich402Response?(response: any, ctx: PluginContext): any;
951
+ }
952
+
953
+ interface PluginResult {
954
+ skip?: boolean; // Bypass payment, serve content free
955
+ reject?: boolean; // Block request entirely (e.g. unhealthy)
956
+ rejectStatus?: number;
957
+ rejectMessage?: string;
958
+ headers?: Record<string, string>;
959
+ meta?: Record<string, unknown>;
960
+ }
961
+
962
+ interface PluginContext {
963
+ network: string;
964
+ price: number;
965
+ path: string;
966
+ method: string;
608
967
  }
609
968
  ```
610
969
 
package/dist/index.cjs CHANGED
@@ -54,6 +54,7 @@ __export(index_exports, {
54
54
  normalizePaymentHeader: () => normalizePaymentHeader,
55
55
  resolveToken: () => resolveToken,
56
56
  stripePayTo: () => stripePayTo,
57
+ submitRelayFeedback: () => submitRelayFeedback,
57
58
  toAtomicUnits: () => toAtomicUnits
58
59
  });
59
60
  module.exports = __toCommonJS(index_exports);
@@ -568,6 +569,9 @@ var Relai = class {
568
569
  const self = this;
569
570
  return async (req, res, next) => {
570
571
  try {
572
+ if (req.headers["x-preflight"] === "true" || req.headers["x-preflight"] === "1") {
573
+ return res.status(200).json({ status: "ok", preflight: true });
574
+ }
571
575
  const resolvedPrice = typeof options.price === "function" ? await options.price(req) : options.price;
572
576
  if (typeof resolvedPrice !== "number" || !isFinite(resolvedPrice) || resolvedPrice <= 0) {
573
577
  return res.status(400).json({ error: "Invalid price configuration" });
@@ -630,6 +634,18 @@ var Relai = class {
630
634
  if (!plugin.beforePaymentCheck) continue;
631
635
  try {
632
636
  const pluginResult = await plugin.beforePaymentCheck(req, pluginCtx);
637
+ if (pluginResult?.reject) {
638
+ const rejectStatus = pluginResult.rejectStatus || 503;
639
+ if (pluginResult.headers) {
640
+ for (const [k, v] of Object.entries(pluginResult.headers)) {
641
+ res.setHeader(k, v);
642
+ }
643
+ }
644
+ return res.status(rejectStatus).json({
645
+ error: pluginResult.rejectMessage || "Service unavailable",
646
+ plugin: plugin.name
647
+ });
648
+ }
633
649
  if (pluginResult?.skip) {
634
650
  if (pluginResult.headers) {
635
651
  for (const [k, v] of Object.entries(pluginResult.headers)) {
@@ -1946,6 +1962,64 @@ function createX402Client(config) {
1946
1962
  return { fetch: x402Fetch };
1947
1963
  }
1948
1964
 
1965
+ // src/relay-feedback.ts
1966
+ var import_ethers = require("ethers");
1967
+ var RELAY_FEEDBACK_REPUTATION_ABI = [
1968
+ "function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external"
1969
+ ];
1970
+ function submitRelayFeedback(config) {
1971
+ const agentId = String(config.agentId);
1972
+ const endpoint = config.endpoint ?? "";
1973
+ const responseTimeMs = config.responseTimeMs ?? 0;
1974
+ const privateKey = config.feedbackWalletPrivateKey ?? (typeof process !== "undefined" ? process.env?.FEEDBACK_WALLET_PRIVATE_KEY ?? process.env?.ERC8004_FEEDBACK_WALLET_PRIVATE_KEY : void 0);
1975
+ const reputationAddress = config.reputationRegistryAddress ?? (typeof process !== "undefined" ? process.env?.ERC8004_REPUTATION_REGISTRY : void 0);
1976
+ const rpcUrl = config.rpcUrl ?? (typeof process !== "undefined" ? process.env?.ERC8004_RPC_URL : void 0) ?? "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha";
1977
+ if (!privateKey || !reputationAddress) {
1978
+ console.warn("[relai:submitRelayFeedback] FEEDBACK_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set \u2014 skipping");
1979
+ return;
1980
+ }
1981
+ const provider = new import_ethers.ethers.JsonRpcProvider(rpcUrl);
1982
+ const signer = new import_ethers.ethers.Wallet(privateKey, provider);
1983
+ const reputation = new import_ethers.ethers.Contract(reputationAddress, RELAY_FEEDBACK_REPUTATION_ABI, signer);
1984
+ const id = BigInt(agentId);
1985
+ (async () => {
1986
+ const successValue = config.success ? 10000n : 0n;
1987
+ try {
1988
+ const srTx = await reputation.giveFeedback(
1989
+ id,
1990
+ successValue,
1991
+ 2,
1992
+ "successRate",
1993
+ "",
1994
+ endpoint,
1995
+ "",
1996
+ import_ethers.ethers.ZeroHash
1997
+ );
1998
+ await srTx.wait();
1999
+ console.log(`[relai:submitRelayFeedback] successRate confirmed agentId=${agentId} success=${config.success}`);
2000
+ } catch (err) {
2001
+ console.warn(`[relai:submitRelayFeedback] successRate failed (non-fatal): ${err?.message}`);
2002
+ }
2003
+ if (responseTimeMs > 0) {
2004
+ try {
2005
+ const rtTx = await reputation.giveFeedback(
2006
+ id,
2007
+ BigInt(Math.max(0, Math.round(responseTimeMs))),
2008
+ 0,
2009
+ "responseTime",
2010
+ "",
2011
+ endpoint,
2012
+ "",
2013
+ import_ethers.ethers.ZeroHash
2014
+ );
2015
+ console.log(`[relai:submitRelayFeedback] responseTime sent agentId=${agentId} ms=${responseTimeMs} tx=${rtTx.hash}`);
2016
+ } catch (err) {
2017
+ console.warn(`[relai:submitRelayFeedback] responseTime failed (non-fatal): ${err?.message}`);
2018
+ }
2019
+ }
2020
+ })();
2021
+ }
2022
+
1949
2023
  // src/utils/payload-converter.ts
1950
2024
  var NETWORK_V1_TO_V2 = {
1951
2025
  "solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
@@ -2120,6 +2194,7 @@ function formatUsd(usd, maxDecimals = 4) {
2120
2194
  normalizePaymentHeader,
2121
2195
  resolveToken,
2122
2196
  stripePayTo,
2197
+ submitRelayFeedback,
2123
2198
  toAtomicUnits
2124
2199
  });
2125
2200
  //# sourceMappingURL=index.cjs.map