diff --git a/typescript/.changeset/mainstreet-action-provider.md b/typescript/.changeset/mainstreet-action-provider.md new file mode 100644 index 000000000..b6833895c --- /dev/null +++ b/typescript/.changeset/mainstreet-action-provider.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": patch +--- + +Add MainStreet action provider: `check_reputation` returns an onchain-verifiable SAFE/CAUTION/BLOCK reputation verdict + 0–100 score for a Base counterparty before payment, so an agent can refuse to pay BLOCK-rated or unscored addresses. diff --git a/typescript/agentkit/README.md b/typescript/agentkit/README.md index 37b14207f..4df8d70e7 100644 --- a/typescript/agentkit/README.md +++ b/typescript/agentkit/README.md @@ -485,6 +485,15 @@ const agent = createAgent({
+MainStreet + + + + + +
check_reputationReturns an onchain-verifiable SAFE/CAUTION/BLOCK reputation verdict + 0-100 score for a Base wallet, agent, or token before payment, so an agent can refuse to pay BLOCK-rated or unscored counterparties.
+
+
Messari diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 9f7164086..486e286d5 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -18,6 +18,7 @@ export * from "./erc721"; export * from "./erc8004"; export * from "./farcaster"; export * from "./jupiter"; +export * from "./mainstreet"; export * from "./messari"; export * from "./pyth"; export * from "./moonwell"; diff --git a/typescript/agentkit/src/action-providers/mainstreet/README.md b/typescript/agentkit/src/action-providers/mainstreet/README.md new file mode 100644 index 000000000..e03eac835 --- /dev/null +++ b/typescript/agentkit/src/action-providers/mainstreet/README.md @@ -0,0 +1,26 @@ +# MainStreet Action Provider + +This directory contains the **MainStreetActionProvider** — an onchain reputation check for a Base +counterparty before an agent transacts or pays it (e.g. via x402). + +[MainStreet](https://avisradar-production.up.railway.app/mainstreet.html) is an onchain reputation +oracle for agent-to-agent payments on Base. For any wallet, agent, or token it returns a +SAFE / CAUTION / BLOCK verdict + a 0–100 score as an **EIP-712-signed attestation, verifiable onchain** +against the MainStreetVerifier contract (`0x7397adb9713934c36d22aa54b4dbbcd70263592b`) — the signal is +checkable, not "trust us". Free, no signup (100 checks/day/IP). + +## Actions + +- `check_reputation`: Returns the reputation verdict + score for a Base address, with a pay / don't-pay + recommendation and a link to verify the signed attestation. Use it as a pre-payment / pre-routing + trust gate — refuse to settle to a BLOCK-rated or unscored counterparty. + +## Usage + +```typescript +import { mainstreetActionProvider } from "@coinbase/agentkit"; + +const provider = mainstreetActionProvider(); +``` + +No configuration required (public read API). Pairs naturally with the `x402` and `erc8004` providers. diff --git a/typescript/agentkit/src/action-providers/mainstreet/index.ts b/typescript/agentkit/src/action-providers/mainstreet/index.ts new file mode 100644 index 000000000..53cc04459 --- /dev/null +++ b/typescript/agentkit/src/action-providers/mainstreet/index.ts @@ -0,0 +1 @@ +export * from "./mainstreetActionProvider"; diff --git a/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.test.ts b/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.test.ts new file mode 100644 index 000000000..73f720d4f --- /dev/null +++ b/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.test.ts @@ -0,0 +1,69 @@ +import { mainstreetActionProvider } from "./mainstreetActionProvider"; + +describe("MainStreetActionProvider", () => { + const fetchMock = jest.fn(); + global.fetch = fetchMock; + + const provider = mainstreetActionProvider(); + const ADDR = "0x325bdf6f7efab24a2210c48c1b64cab2eae1d430"; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe("checkReputation", () => { + it("recommends proceeding for a SAFE counterparty with a real score", async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ score: 76, verdict: "SAFE" }), + }); + + const result = JSON.parse(await provider.checkReputation({ address: ADDR })); + expect(result.score).toBe(76); + expect(result.verdict).toBe("SAFE"); + expect(result.recommendation).toContain("OK to proceed"); + }); + + it("refuses to pay a BLOCK-rated counterparty", async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ score: 10, verdict: "BLOCK" }), + }); + + const result = JSON.parse(await provider.checkReputation({ address: ADDR })); + expect(result.recommendation).toContain("DO NOT PAY"); + }); + + it("treats an unknown / unscored counterparty as CAUTION (never auto-OK)", async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ score: null, verdict: null }), + }); + + const result = JSON.parse(await provider.checkReputation({ address: ADDR })); + expect(result.recommendation).toContain("CAUTION"); + }); + + it("handles HTTP errors gracefully", async () => { + fetchMock.mockResolvedValue({ ok: false, status: 404 }); + + const result = await provider.checkReputation({ address: ADDR }); + expect(result).toContain("MainStreet error"); + expect(result).toContain("404"); + }); + + it("handles network errors gracefully", async () => { + fetchMock.mockRejectedValue(new Error("Network error")); + + const result = await provider.checkReputation({ address: ADDR }); + expect(result).toContain("MainStreet error"); + expect(result).toContain("Network error"); + }); + }); + + describe("supportsNetwork", () => { + it("supports any network (network-agnostic API call)", () => { + expect(provider.supportsNetwork()).toBe(true); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.ts b/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.ts new file mode 100644 index 000000000..d5e0d43b8 --- /dev/null +++ b/typescript/agentkit/src/action-providers/mainstreet/mainstreetActionProvider.ts @@ -0,0 +1,101 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { CreateAction } from "../actionDecorator"; +import { CheckReputationSchema } from "./schemas"; + +const MAINSTREET_BASE_URL = "https://avisradar-production.up.railway.app"; + +/** + * MainStreetActionProvider lets an agent check the onchain reputation of a Base + * counterparty BEFORE it transacts or pays via x402. + * + * MainStreet is an onchain reputation oracle for agent-to-agent payments on Base: + * for any wallet, agent, or token it returns a SAFE / CAUTION / BLOCK verdict + a + * 0-100 score as an EIP-712-signed attestation, verifiable onchain against the + * MainStreetVerifier contract on Base (0x7397adb9713934c36d22aa54b4dbbcd70263592b) — + * so the trust signal is checkable, not "trust us". Network-agnostic API call. + */ +export class MainStreetActionProvider extends ActionProvider { + /** + * Constructor for the MainStreetActionProvider class. + */ + constructor() { + super("mainstreet", []); + } + + /** + * Checks the onchain reputation of a Base wallet / agent / token deployer. + * + * @param args - The address to check. + * @returns A JSON string with the score, verdict, a pay/don't-pay recommendation, and a verify link. + */ + @CreateAction({ + name: "check_reputation", + description: `Check the onchain reputation of a Base wallet, agent, or token deployer BEFORE paying or transacting with it. +Returns a SAFE / CAUTION / BLOCK verdict + a 0-100 score from MainStreet, an onchain reputation oracle for agent-to-agent payments on Base. +Use this as a pre-payment / pre-routing trust gate: refuse to settle to a BLOCK-rated or low-scoring counterparty. +Takes the following input: +- address: the 0x Base address to check. +Notes: +- Free, no signup (100 checks/day/IP). +- The verdict is backed by settlement history, endpoint health, ERC-8004 feedback and identity proofs, and is EIP-712-signed + verifiable onchain against the MainStreetVerifier contract.`, + schema: CheckReputationSchema, + }) + async checkReputation(args: z.infer): Promise { + try { + const url = `${MAINSTREET_BASE_URL}/api/agent/score/${args.address}`; + const response = await fetch(url, { headers: { accept: "application/json" } }); + + if (!response.ok) { + return `MainStreet error: HTTP ${response.status} checking ${args.address}`; + } + + const data = (await response.json()) as { + score?: number | null; + verdict?: string | null; + trustLevel?: string | null; + }; + const score = data.score ?? null; + const verdict = data.verdict ?? data.trustLevel ?? "UNKNOWN"; + + // Safe default: only green-light a known SAFE counterparty with a real score. + // An unknown / unscored address (no reputation history — e.g. a fresh wallet) + // must NOT default to "OK", or the gate is useless against new scam wallets. + const recommendation = + verdict === "BLOCK" || (typeof score === "number" && score < 30) + ? "DO NOT PAY — BLOCK-rated or below a safe threshold." + : verdict === "SAFE" && typeof score === "number" && score >= 30 + ? "OK to proceed — counterparty looks trustworthy." + : "CAUTION — no / insufficient reputation history; verify before paying."; + + return JSON.stringify({ + address: args.address, + score, + verdict, + recommendation, + verify: `${MAINSTREET_BASE_URL}/api/agent/attestation/${args.address}`, + source: + "MainStreet — onchain reputation oracle on Base (EIP-712 signed, verifiable against MainStreetVerifier 0x7397adb9713934c36d22aa54b4dbbcd70263592b).", + }); + } catch (error) { + return `MainStreet error: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Checks if the MainStreet action provider supports the given network. + * MainStreet scores Base addresses but the lookup is a network-agnostic API call. + * + * @returns True — MainStreet actions are callable from any network context. + */ + supportsNetwork(): boolean { + return true; + } +} + +/** + * Creates a new instance of the MainStreet action provider. + * + * @returns A new MainStreetActionProvider instance. + */ +export const mainstreetActionProvider = () => new MainStreetActionProvider(); diff --git a/typescript/agentkit/src/action-providers/mainstreet/schemas.ts b/typescript/agentkit/src/action-providers/mainstreet/schemas.ts new file mode 100644 index 000000000..be4930025 --- /dev/null +++ b/typescript/agentkit/src/action-providers/mainstreet/schemas.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +/** + * Input schema for checking the MainStreet onchain reputation of a Base address. + */ +export const CheckReputationSchema = z + .object({ + address: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, "must be a 0x-prefixed 40-hex Base address") + .describe("The Base wallet, agent, or token-deployer address to check (0x...)."), + }) + .strict();