From 39c8669c8dbe0a1f741c3b696a12447691010cea Mon Sep 17 00:00:00 2001 From: PaulieB14 <94752445+PaulieB14@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:38:56 -0400 Subject: [PATCH] feat(action-providers): add Graph Advocate provider (trader intel + agent reputation over x402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `graphadvocate` action provider with three paid actions (get_hyperliquid_trader_score, get_polymarket_trader_score, get_agent_reputation) that auto-pay a small USDC fee on Base through the agent's own wallet, reusing AgentKit's existing x402 signing stack (x402Client + wrapFetchWithPayment + registerExactEvmScheme) — no new dependencies. Base-only, with a per-call maxPaymentUsdc cap enforced before signing. Includes README, unit tests, and a changeset. --- .../graph-advocate-action-provider.md | 5 + .../action-providers/graphadvocate/README.md | 60 ++++ .../graphadvocate/constants.ts | 29 ++ .../graphAdvocateActionProvider.test.ts | 48 +++ .../graphAdvocateActionProvider.ts | 307 ++++++++++++++++++ .../action-providers/graphadvocate/index.ts | 3 + .../action-providers/graphadvocate/schemas.ts | 41 +++ .../agentkit/src/action-providers/index.ts | 1 + 8 files changed, 494 insertions(+) create mode 100644 typescript/.changeset/graph-advocate-action-provider.md create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/README.md create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/constants.ts create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.test.ts create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.ts create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/index.ts create mode 100644 typescript/agentkit/src/action-providers/graphadvocate/schemas.ts diff --git a/typescript/.changeset/graph-advocate-action-provider.md b/typescript/.changeset/graph-advocate-action-provider.md new file mode 100644 index 000000000..47c206727 --- /dev/null +++ b/typescript/.changeset/graph-advocate-action-provider.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": minor +--- + +Add Graph Advocate action provider: paid trader-intelligence and agent-reputation actions (Hyperliquid score, Polymarket score, ERC-8004 reputation) that auto-pay over x402 from the agent's wallet on Base. Reuses the existing x402 signing stack, so no new dependencies. diff --git a/typescript/agentkit/src/action-providers/graphadvocate/README.md b/typescript/agentkit/src/action-providers/graphadvocate/README.md new file mode 100644 index 000000000..37ea86895 --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/README.md @@ -0,0 +1,60 @@ +# Graph Advocate Action Provider + +This directory provides an action provider for [Graph Advocate](https://graphadvocate.com), giving an AgentKit agent paid access to agent-priced onchain-intelligence endpoints. Each action calls a Graph Advocate endpoint gated by x402; the agent's own wallet auto-pays a small USDC fee on Base, and the action returns the JSON result. + +Use it when an agent needs to **vet a counterparty wallet or agent** before trusting, paying, following, mirroring a copy-trade, or transacting. + +## Actions + +| Action | What it returns | Price (USDC on Base) | +|---|---|---| +| `get_hyperliquid_trader_score` | Hyperliquid perps trading skill (0-100): win rate, Sharpe-like return, liquidation rate, funding burn, classification | ~$0.02 | +| `get_polymarket_trader_score` | Polymarket trading skill (0-100): Sharpe-weighted score, win rate, sample size, PnL | ~$0.01 | +| `get_agent_reputation` | Onchain reputation (0-100) from ERC-8004 identity + USDC settlement velocity + recency | ~$0.02 | + +All take `{ "wallet": "0x..." }`. + +## Wallet Providers + +Requires an `EvmWalletProvider` (payments settle in USDC on Base). The provider signs x402 payments exactly the way AgentKit's built-in `x402` provider does (`x402Client` + `wrapFetchWithPayment` + `registerExactEvmScheme`). + +## Network Support + +Base mainnet only (that is where the x402 payments settle). + +## Configuration + +```typescript +import { graphAdvocateActionProvider } from "@coinbase/agentkit"; + +const provider = graphAdvocateActionProvider({ + // Per-call spend ceiling in whole USDC. An action priced above this is + // refused before any payment is signed. Defaults to + // GRAPH_ADVOCATE_MAX_PAYMENT_USDC env var, then 1.0. + maxPaymentUsdc: 0.10, + // Optional: override the base URL (e.g. a self-hosted instance). + // baseUrl: "https://graphadvocate.com", +}); +``` + +## Example + +```typescript +const agentKit = await AgentKit.from({ + walletProvider, + actionProviders: [graphAdvocateActionProvider({ maxPaymentUsdc: 0.10 })], +}); +``` + +Then the agent can, on its own: + +``` +User: Before I mirror 0x38e5…8a4a on Hyperliquid, is this a sharp trader? +Agent: (calls get_hyperliquid_trader_score) skill_score 71.4, classification "sharp", + win_rate 0.61, sharpe_like 0.84 — paid $0.02 USDC on Base. +``` + +## Notes + +- Payment only settles on a `200`. Non-200 responses return `success: false` and do **not** charge. +- The per-call price is checked against `maxPaymentUsdc` before any signature, so a misconfiguration cannot overspend. diff --git a/typescript/agentkit/src/action-providers/graphadvocate/constants.ts b/typescript/agentkit/src/action-providers/graphadvocate/constants.ts new file mode 100644 index 000000000..0d8edf19e --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/constants.ts @@ -0,0 +1,29 @@ +/** + * Base URL for the Graph Advocate agent (routing + agent-priced trader intelligence). + */ +export const GRAPH_ADVOCATE_BASE_URL = "https://graphadvocate.com"; + +/** + * Graph Advocate's x402-paid endpoints. Payments settle in USDC on Base. + * `priceUsdc` is the published per-call price, used to enforce the + * `maxPaymentUsdc` guard before a payment is ever signed. + */ +export const GRAPH_ADVOCATE_ENDPOINTS = { + hyperliquid_trader_score: { path: "/hyperliquid/score", priceUsdc: 0.02 }, + polymarket_trader_score: { path: "/polymarket/pnl-quick", priceUsdc: 0.01 }, + agent_reputation: { path: "/agent/score", priceUsdc: 0.02 }, +} as const; + +export type GraphAdvocateEndpointKey = keyof typeof GRAPH_ADVOCATE_ENDPOINTS; + +/** + * Graph Advocate settles x402 payments in USDC on Base, so the provider only + * supports Base mainnet. + */ +export const GRAPH_ADVOCATE_SUPPORTED_NETWORKS = ["base-mainnet"]; + +/** + * Default per-call spend ceiling in whole USDC. Overridable via config or the + * GRAPH_ADVOCATE_MAX_PAYMENT_USDC env var. + */ +export const DEFAULT_MAX_PAYMENT_USDC = 1.0; diff --git a/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.test.ts b/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.test.ts new file mode 100644 index 000000000..03d2bfe5b --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.test.ts @@ -0,0 +1,48 @@ +import { graphAdvocateActionProvider, GraphAdvocateActionProvider } from "./graphAdvocateActionProvider"; +import { Network } from "../../network"; + +describe("GraphAdvocateActionProvider", () => { + let provider: GraphAdvocateActionProvider; + + beforeEach(() => { + provider = graphAdvocateActionProvider({ maxPaymentUsdc: 0.1 }); + }); + + describe("supportsNetwork", () => { + it("supports base-mainnet", () => { + expect(provider.supportsNetwork({ networkId: "base-mainnet" } as Network)).toBe(true); + }); + + it("does not support other networks", () => { + expect(provider.supportsNetwork({ networkId: "ethereum-mainnet" } as Network)).toBe(false); + expect(provider.supportsNetwork({ networkId: "base-sepolia" } as Network)).toBe(false); + }); + }); + + describe("payment cap guard", () => { + it("refuses an action priced above maxPaymentUsdc before signing", async () => { + // maxPaymentUsdc of 0.001 is below every endpoint price, so the action + // must return a 'Payment exceeds limit' error and never touch the wallet. + const capped = graphAdvocateActionProvider({ maxPaymentUsdc: 0.001 }); + const result = await capped.getHyperliquidTraderScore( + {} as never, + { wallet: "0x38e598961dd0456a7fb2e758bd433d3e59fb8a4a" }, + ); + const parsed = JSON.parse(result); + expect(parsed.error).toBe(true); + expect(parsed.message).toBe("Payment exceeds limit"); + }); + }); + + describe("wallet provider guard", () => { + it("rejects a non-EVM wallet provider", async () => { + const result = await provider.getAgentReputation( + {} as never, // not an EvmWalletProvider + { wallet: "0x38e598961dd0456a7fb2e758bd433d3e59fb8a4a" }, + ); + const parsed = JSON.parse(result); + expect(parsed.error).toBe(true); + expect(parsed.message).toBe("Unsupported wallet provider"); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.ts b/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.ts new file mode 100644 index 000000000..0f2c90611 --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/graphAdvocateActionProvider.ts @@ -0,0 +1,307 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { EvmWalletProvider, WalletProvider } from "../../wallet-providers"; +import { x402Client, wrapFetchWithPayment } from "@x402/fetch"; +import { registerExactEvmScheme } from "@x402/evm/exact/client"; +import { + HyperliquidTraderScoreSchema, + PolymarketTraderScoreSchema, + AgentReputationSchema, +} from "./schemas"; +import { + GRAPH_ADVOCATE_BASE_URL, + GRAPH_ADVOCATE_ENDPOINTS, + GraphAdvocateEndpointKey, + GRAPH_ADVOCATE_SUPPORTED_NETWORKS, + DEFAULT_MAX_PAYMENT_USDC, +} from "./constants"; + +/** + * Configuration for the GraphAdvocateActionProvider. + */ +export interface GraphAdvocateConfig { + /** + * Per-call spend ceiling in whole USDC. An action whose published price + * exceeds this is refused before any payment is signed. Defaults to + * GRAPH_ADVOCATE_MAX_PAYMENT_USDC env var, then 1.0 USDC. + */ + maxPaymentUsdc?: number; + + /** + * Override the Graph Advocate base URL (e.g. for a self-hosted instance). + */ + baseUrl?: string; +} + +/** + * GraphAdvocateActionProvider gives an agent paid access to Graph Advocate's + * agent-priced onchain-intelligence endpoints. Each action calls a Graph + * Advocate endpoint that gates on x402; the agent's own wallet auto-pays the + * small USDC fee on Base, and the action returns the resulting JSON. + * + * Use it when an agent needs to vet a counterparty wallet or agent before + * trusting, paying, following, mirroring, or transacting with it. + * + * @augments ActionProvider + */ +export class GraphAdvocateActionProvider extends ActionProvider { + private readonly maxPaymentUsdc: number; + private readonly baseUrl: string; + + /** + * Constructor for the GraphAdvocateActionProvider. + * + * @param config - Configuration options for the provider + */ + constructor(config: GraphAdvocateConfig = {}) { + super("graphadvocate", []); + this.maxPaymentUsdc = + config.maxPaymentUsdc ?? + parseFloat(process.env.GRAPH_ADVOCATE_MAX_PAYMENT_USDC ?? String(DEFAULT_MAX_PAYMENT_USDC)); + this.baseUrl = config.baseUrl ?? GRAPH_ADVOCATE_BASE_URL; + } + + /** + * Scores a wallet's Hyperliquid perps trading skill. + * + * @param walletProvider - The wallet provider used to pay the x402 fee + * @param args - The wallet address to score + * @returns A JSON string with the skill score and breakdown, or an error + */ + @CreateAction({ + name: "get_hyperliquid_trader_score", + description: ` +Scores a wallet's Hyperliquid perps trading skill (0-100), derived from real +onchain trading history: win rate, Sharpe-like risk-adjusted return, liquidation +rate, and funding burn. Returns a classification (sharp / neutral / retail) and +supporting metrics. + +Use before mirroring a copy-trade, ranking traders, or sizing exposure to a +counterparty. Costs ~$0.02 USDC on Base, auto-paid from the agent's wallet. + +Input: { "wallet": "0x..." }`, + schema: HyperliquidTraderScoreSchema, + }) + async getHyperliquidTraderScore( + walletProvider: WalletProvider, + args: z.infer, + ): Promise { + return this.paidPost(walletProvider, "hyperliquid_trader_score", { wallet: args.wallet }); + } + + /** + * Scores a wallet's Polymarket trading skill. + * + * @param walletProvider - The wallet provider used to pay the x402 fee + * @param args - The wallet address to score + * @returns A JSON string with the skill score and breakdown, or an error + */ + @CreateAction({ + name: "get_polymarket_trader_score", + description: ` +Scores a wallet's Polymarket prediction-market trading skill (0-100): a +Sharpe-weighted skill score with win rate, sample size, and realized/unrealized +PnL. Returns a classification (sharp / neutral / retail). + +Use for batch-screening top holders of a market before entering, or vetting a +copy-trade target. Costs ~$0.01 USDC on Base, auto-paid from the agent's wallet. + +Input: { "wallet": "0x..." }`, + schema: PolymarketTraderScoreSchema, + }) + async getPolymarketTraderScore( + walletProvider: WalletProvider, + args: z.infer, + ): Promise { + return this.paidPost(walletProvider, "polymarket_trader_score", { wallet: args.wallet }); + } + + /** + * Scores an agent/wallet's onchain reputation. + * + * @param walletProvider - The wallet provider used to pay the x402 fee + * @param args - The agent/wallet address to score + * @returns A JSON string with the reputation score and signals, or an error + */ + @CreateAction({ + name: "get_agent_reputation", + description: ` +Scores an agent or wallet's onchain reputation (0-100) from ground-truth signals +that resist gaming: ERC-8004 registration + age, IPFS metadata health, USDC +settlement velocity, and recency of paid activity. Returns a tier and the signal +breakdown. + +Use before trusting, paying, or integrating with another agent. Costs ~$0.02 +USDC on Base, auto-paid from the agent's wallet. + +Input: { "wallet": "0x..." }`, + schema: AgentReputationSchema, + }) + async getAgentReputation( + walletProvider: WalletProvider, + args: z.infer, + ): Promise { + return this.paidPost(walletProvider, "agent_reputation", { wallet: args.wallet }); + } + + /** + * POSTs to a Graph Advocate x402 endpoint, auto-paying the fee from the + * agent's wallet, and returns the JSON response as a string. + * + * @param walletProvider - The wallet provider used to sign the x402 payment + * @param endpointKey - Which Graph Advocate endpoint to call + * @param body - The JSON request body + * @returns A JSON string with the result or a structured error + */ + private async paidPost( + walletProvider: WalletProvider, + endpointKey: GraphAdvocateEndpointKey, + body: Record, + ): Promise { + const endpoint = GRAPH_ADVOCATE_ENDPOINTS[endpointKey]; + + // Enforce the per-call spend ceiling before signing anything. + if (endpoint.priceUsdc > this.maxPaymentUsdc) { + return JSON.stringify( + { + error: true, + message: "Payment exceeds limit", + details: `This action costs ${endpoint.priceUsdc} USDC, above the configured maxPaymentUsdc of ${this.maxPaymentUsdc}.`, + }, + null, + 2, + ); + } + + // Payments settle in USDC on Base, so an EVM wallet is required. + if (!(walletProvider instanceof EvmWalletProvider)) { + return JSON.stringify( + { + error: true, + message: "Unsupported wallet provider", + details: "Graph Advocate settles x402 payments in USDC on Base and requires an EvmWalletProvider.", + }, + null, + 2, + ); + } + + try { + const client = await this.createX402Client(walletProvider); + const fetchWithPayment = wrapFetchWithPayment(fetch, client); + const url = `${this.baseUrl}${endpoint.path}`; + + const response = await fetchWithPayment(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + const data = await this.parseResponseData(response); + + // Payment only settles on a 200. + if (response.status !== 200) { + return JSON.stringify( + { + success: false, + message: `Request failed with status ${response.status}. Payment was not settled.`, + endpoint: endpoint.path, + status: response.status, + data, + }, + null, + 2, + ); + } + + const paymentResponseHeader = + response.headers.get("payment-response") ?? response.headers.get("x-payment-response"); + + return JSON.stringify( + { + success: true, + endpoint: endpoint.path, + priceUsdc: endpoint.priceUsdc, + paymentSettled: !!paymentResponseHeader, + data, + }, + null, + 2, + ); + } catch (error) { + return JSON.stringify( + { + error: true, + message: "Graph Advocate request failed", + details: error instanceof Error ? error.message : String(error), + }, + null, + 2, + ); + } + } + + /** + * Checks if the action provider supports the given network. + * Graph Advocate settles payments in USDC on Base. + * + * @param network - The network to check + * @returns True if the network is Base mainnet + */ + supportsNetwork = (network: Network) => + (GRAPH_ADVOCATE_SUPPORTED_NETWORKS as readonly string[]).includes(network.networkId!); + + /** + * Creates an x402 client configured to sign payments with the agent's wallet. + * Mirrors AgentKit's x402 provider so signing behaves identically. + * + * @param walletProvider - The EVM wallet provider to sign with + * @returns A configured x402Client + */ + private async createX402Client(walletProvider: EvmWalletProvider): Promise { + const client = new x402Client(); + const account = walletProvider.toSigner(); + const signer = { + ...account, + readContract: (args: { + address: `0x${string}`; + abi: readonly unknown[]; + functionName: string; + args?: readonly unknown[]; + }) => + walletProvider.readContract({ + address: args.address, + abi: args.abi as never, + functionName: args.functionName as never, + args: args.args as never, + }), + }; + registerExactEvmScheme(client, { signer }); + return client; + } + + /** + * Parses a fetch response by content type. + * + * @param response - The fetch Response object + * @returns Parsed JSON or raw text + */ + private async parseResponseData(response: Response): Promise { + const contentType = response.headers.get("content-type") ?? ""; + if (contentType.includes("application/json")) { + return response.json(); + } + return response.text(); + } +} + +/** + * Factory function to create a new GraphAdvocateActionProvider instance. + * + * @param config - Configuration options for the provider + * @returns A new GraphAdvocateActionProvider + */ +export const graphAdvocateActionProvider = (config: GraphAdvocateConfig = {}) => + new GraphAdvocateActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/graphadvocate/index.ts b/typescript/agentkit/src/action-providers/graphadvocate/index.ts new file mode 100644 index 000000000..8e53582d8 --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/index.ts @@ -0,0 +1,3 @@ +export * from "./graphAdvocateActionProvider"; +export * from "./schemas"; +export * from "./constants"; diff --git a/typescript/agentkit/src/action-providers/graphadvocate/schemas.ts b/typescript/agentkit/src/action-providers/graphadvocate/schemas.ts new file mode 100644 index 000000000..1635eac2e --- /dev/null +++ b/typescript/agentkit/src/action-providers/graphadvocate/schemas.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; + +/** + * An EVM address (0x-prefixed, 40 hex chars). + */ +const EvmAddressSchema = z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, "Must be a 0x-prefixed EVM address"); + +/** + * Input schema for scoring a wallet's Hyperliquid perps trading skill. + */ +export const HyperliquidTraderScoreSchema = z + .object({ + wallet: EvmAddressSchema.describe( + "The wallet address to score for Hyperliquid perps trading skill", + ), + }) + .describe("Score a wallet's Hyperliquid perps trading skill via Graph Advocate"); + +/** + * Input schema for scoring a wallet's Polymarket trading skill. + */ +export const PolymarketTraderScoreSchema = z + .object({ + wallet: EvmAddressSchema.describe( + "The wallet address to score for Polymarket prediction-market trading skill", + ), + }) + .describe("Score a wallet's Polymarket trading skill via Graph Advocate"); + +/** + * Input schema for scoring an agent/wallet's onchain reputation. + */ +export const AgentReputationSchema = z + .object({ + wallet: EvmAddressSchema.describe( + "The agent or wallet address to score for onchain reputation (ERC-8004 identity + USDC settlement history)", + ), + }) + .describe("Score an agent/wallet's onchain reputation via Graph Advocate"); diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 9f7164086..1c5cdbf6d 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -17,6 +17,7 @@ export * from "./erc20"; export * from "./erc721"; export * from "./erc8004"; export * from "./farcaster"; +export * from "./graphadvocate"; export * from "./jupiter"; export * from "./messari"; export * from "./pyth";