Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CDP_API_KEY_ID=
CDP_API_KEY_SECRET=
OPENAI_API_KEY=
MAINSTREET_MIN_SCORE=30
79 changes: 79 additions & 0 deletions typescript/examples/langchain-mainstreet-trust-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# MainStreet Trust-Gate Chatbot

This example demonstrates a buyer agent that **refuses to pay any wallet scoring below a configurable MainStreet reputation threshold**.

## What it does

```
user → "send 1 USDC to 0xAbc..."
tool: mainstreet_trust_check
├── fetch /api/agent/attestation/0xAbc
├── verify EIP-712 signature against MainStreet operator (0xAC3c...)
├── check freshness (<24h)
└── check score >= MIN_SCORE (default 30)
↓ (only if ok)
tool: CDP erc20 transfer
```

If the wallet's MainStreet score is below threshold (or no attestation exists, or signature invalid), the agent refuses and explains why.

## Why this matters

CDP Bazaar lists 800+ AI agents on Base. Some are legitimate businesses settling thousands of USDC; some are throwaway wallets that drained LPs and disappeared. **Today AgentKit users can pay any of them.** A trust gate prevents users from paying low-rep wallets they wouldn't pay if they could see the score.

MainStreet is an EIP-712 reputation oracle on Base. Free attestation fetch, free off-chain verify, onchain verifier contract live at [`0x7397adb9713934c36d22aa54b4dbbcd70263592b`](https://basescan.org/address/0x7397adb9713934c36d22aa54b4dbbcd70263592b).

## Configuration

```env
CDP_API_KEY_ID=...
CDP_API_KEY_SECRET=...
OPENAI_API_KEY=...
MAINSTREET_MIN_SCORE=30 # default 30/100; raise for stricter gate
```

## Run

```bash
npm install
npm start
```

Try:

```
you: what's the MainStreet score for 0xAC3ca7c5d3cDD7702fd08F9C4C28dAA22296aDa9?
you: send 0.01 USDC to 0xAC3ca7c5d3cDD7702fd08F9C4C28dAA22296aDa9
you: send 0.01 USDC to 0x0000000000000000000000000000000000000001
```

The first known address will pass the gate; the second (no attestation) will be refused with `reason: "no attestation found"`.

## How verification works

MainStreet attestations follow EIP-712 with this schema:

```
Attestation(
string version, // "mainstreet-v1"
string subjectType, // "agent-onchain"
bytes32 subject, // sha256(toLowerCase(address))
uint8 score, // 0-100
uint64 timestamp, // ≤24h old
address operator, // 0xAC3ca7c5d3cDD7702fd08F9C4C28dAA22296aDa9
uint64 nonce
)
```

Domain: `{ name: "MainStreet", version: "1", chainId: 8453 }`.

For on-chain verification (smart contract gate), call the deployed `MainStreetVerifier.requireMinScore()` instead — see [avisradar.app/integrations.html](https://avisradar.app/integrations.html).

## Resources

- MainStreet oracle landing: <https://avisradar.app/oracle.html>
- Catalog of paid endpoints: <https://avisradar.app/api/agent/catalog>
- Integration recipes for 10+ Base ecosystem platforms: <https://avisradar.app/api/agent/integrate>
- npm package: [`@raskhaaa/mainstreet-oracle`](https://www.npmjs.com/package/@raskhaaa/mainstreet-oracle) (includes `/verifier` helpers)
- Open-source repo: <https://github.com/philpof102-svg/mainstreet>
92 changes: 92 additions & 0 deletions typescript/examples/langchain-mainstreet-trust-gate/chatbot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* MainStreet Trust-Gate Chatbot — AgentKit example.
*
* Demonstrates a buyer agent that REFUSES to pay any wallet scoring below
* a configurable MainStreet threshold. The reputation lookup is free; the
* payment only happens when score >= MIN_SCORE.
*
* Pattern:
* user → "send 1 USDC to 0xAbc..."
* ↓
* tool: mainstreet_score_check → fetch + verify EIP-712 attestation
* ↓ (score >= MIN_SCORE)
* tool: native CDP transfer
*
* MainStreet docs: https://avisradar.app/oracle.html
* Deployed verifier on Base: 0x7397adb9713934c36d22aa54b4dbbcd70263592b
*/
import * as dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { AgentKit, walletActionProvider, erc20ActionProvider, CdpEvmWalletProvider } from "@coinbase/agentkit";
import { getLangChainTools } from "@coinbase/agentkit-langchain";
import { recoverTypedDataAddress } from "viem";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

dotenv.config();

const MIN_SCORE = parseInt(process.env.MAINSTREET_MIN_SCORE || "30", 10);
const MS_SIGNER = "0xAC3ca7c5d3cDD7702fd08F9C4C28dAA22296aDa9";

const mainstreetTrustCheck = tool(
async ({ address }: { address: string }) => {
const att = await fetch(`https://avisradar.app/api/agent/attestation/${address}`).then((r) => r.json());
if (!att.payload) return JSON.stringify({ ok: false, reason: "no attestation found" });
const recovered = await recoverTypedDataAddress({
domain: att.eip712.domain,
types: att.eip712.types,
primaryType: "Attestation",
message: att.payload,
signature: att.signature,
});
if (recovered.toLowerCase() !== MS_SIGNER.toLowerCase()) return JSON.stringify({ ok: false, reason: "signature invalid" });
const ageSec = Math.floor(Date.now() / 1000) - Number(att.payload.timestamp);
if (ageSec > 86400) return JSON.stringify({ ok: false, reason: "attestation stale (>24h)" });
if (att.payload.score < MIN_SCORE)
return JSON.stringify({ ok: false, reason: `score ${att.payload.score} < ${MIN_SCORE}` });
return JSON.stringify({ ok: true, score: att.payload.score, tier: att.payload.score >= 60 ? "high" : "medium" });
},
{
name: "mainstreet_trust_check",
description: `Check MainStreet reputation BEFORE paying any wallet. Verifies an EIP-712 signed attestation. Returns {ok, score, tier} or {ok: false, reason}. Use this BEFORE every transfer/swap. Free.`,
schema: z.object({ address: z.string().describe("0x-prefixed wallet to check") }),
}
);

async function main() {
const walletProvider = await CdpEvmWalletProvider.configureWithWallet({
apiKeyId: process.env.CDP_API_KEY_ID!,
apiKeySecret: process.env.CDP_API_KEY_SECRET!,
networkId: "base-mainnet",
});
const agentkit = await AgentKit.from({
walletProvider,
actionProviders: [walletActionProvider(), erc20ActionProvider()],
});
const tools = [...(await getLangChainTools(agentkit)), mainstreetTrustCheck];
const agent = createReactAgent({
llm: new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }),
tools,
checkpointer: new MemorySaver(),
messageModifier: `You are a careful onchain payment agent. NEVER send funds to an address without first calling mainstreet_trust_check. If the check returns ok:false, refuse the transfer and explain why.`,
});
const config = { configurable: { thread_id: "mainstreet-trust-gate" } };
console.log(`MainStreet Trust-Gate active. MIN_SCORE=${MIN_SCORE}. Type a request or "exit".`);
const readline = await import("node:readline/promises");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
for (;;) {
const userInput = await rl.question("\nyou: ");
if (!userInput || userInput.trim().toLowerCase() === "exit") break;
const stream = await agent.stream({ messages: [new HumanMessage(userInput)] }, config);
for await (const chunk of stream) {
if ("agent" in chunk) console.log("\nagent:", chunk.agent.messages[chunk.agent.messages.length - 1].content);
else if ("tools" in chunk) console.log("\ntool:", chunk.tools.messages[0].content);
}
}
rl.close();
}

main().catch((e) => { console.error(e); process.exit(1); });
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@coinbase/agentkit-langchain-mainstreet-trust-gate-example",
"description": "AgentKit example — gate x402 payments by MainStreet reputation score before sending USDC",
"version": "1.0.0",
"private": true,
"author": "Coinbase Inc.",
"license": "Apache-2.0",
"scripts": {
"start": "NODE_OPTIONS='--no-warnings' tsx ./chatbot.ts",
"dev": "nodemon ./chatbot.ts"
},
"dependencies": {
"@coinbase/agentkit": "workspace:*",
"@coinbase/agentkit-langchain": "workspace:*",
"@langchain/core": "^1.1.0",
"@langchain/langgraph": "^1.2.0",
"@langchain/openai": "^1.2.0",
"dotenv": "^16.4.5",
"langchain": "^1.1.0",
"viem": "^2.0.0",
"zod": "^4.0.0"
},
"devDependencies": {
"nodemon": "^3.1.0",
"tsx": "^4.7.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"preserveSymlinks": true,
"outDir": "./dist",
"rootDir": "."
},
"include": ["*.ts"]
}
Loading