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
5 changes: 5 additions & 0 deletions plugins/x402BazaarPlugin/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
src
example
tsconfig.json
*.test.ts
plugin_metadata.yml
61 changes: 61 additions & 0 deletions plugins/x402BazaarPlugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# x402 Bazaar Plugin for G.A.M.E.

Give a [G.A.M.E.](https://docs.game.virtuals.io/) agent onchain intelligence on
Base — token risk, wallet net worth, OFAC sanctions, an AI token verdict and a
market brief — each **paid per call in USDC over the [x402](https://x402.org)
protocol**, with no API keys.

> G.A.M.E. decides & acts · **x402 Bazaar tells the agent what to act on** —
> risk, intelligence and AI verdicts — before it commits.

Backed by [x402 Bazaar](https://402.com.tr), a marketplace of 118 pay-per-call
services on Base — the live list is always at
<https://402.com.tr/.well-known/x402>. The agent's wallet key is used locally to
sign x402 payments and is **never sent anywhere**.

## Installation

```bash
npm install @virtuals-protocol/game-x402-bazaar-plugin
```

## Usage

```ts
import { GameAgent } from "@virtuals-protocol/game";
import X402BazaarPlugin from "@virtuals-protocol/game-x402-bazaar-plugin";

const plugin = new X402BazaarPlugin({
credentials: { privateKey: process.env.AGENT_PRIVATE_KEY }, // Base wallet w/ USDC
});

const agent = new GameAgent(process.env.GAME_API_KEY, {
name: "Base Safety Agent",
goal: "Vet Base tokens before acting",
description: "...",
workers: [plugin.getWorker()],
});

await agent.init();
await agent.run(60);
```

## Functions

| Function | What it does |
|---|---|
| `get_ai_token_report` | AI buy/avoid verdict for a token (safety score, risks, positives) |
| `get_token_risk` | Fast token safety scan (honeypot, taxes, ownership, holders) |
| `get_wallet_networth` | Wallet token portfolio + USD net worth |
| `check_sanctions` | OFAC sanctions screening for an address |
| `get_market_brief` | AI situational brief of the Base token market |

## Credentials

| Option / Env | Purpose |
|---|---|
| `credentials.privateKey` / `AGENT_PRIVATE_KEY` | Base wallet (holds USDC) used to pay x402 calls. Local only. |
| `origin` / `X402_BAZAAR_ORIGIN` | optional — defaults to `https://402.com.tr` |

Discover all services: <https://402.com.tr/.well-known/x402> ·
docs: <https://402.com.tr/agents> · MCP: `npx x402-bazaar-mcp`
32 changes: 32 additions & 0 deletions plugins/x402BazaarPlugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@virtuals-protocol/game-x402-bazaar-plugin",
"version": "0.1.0",
"description": "A plugin for Virtuals Protocol G.A.M.E. to get onchain data & AI reports for Base agents — token risk, wallet net worth, OFAC sanctions, an AI token verdict and a market brief — each paid per call in USDC over the x402 protocol (no API keys).",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"tsup": "tsup src/index.ts --dts --format cjs,esm --out-dir dist",
"build": "npm run tsup"
},
"author": "x402 Bazaar",
"license": "MIT",
"devDependencies": {
"@types/node": "^22.10.3",
"tsup": "^8.3.5",
"typescript": "^5.7.3"
},
"dependencies": {
"@virtuals-protocol/game": "^0.1.7",
"@x402/fetch": "^2.16.0",
"@x402/evm": "^2.16.0",
"@x402/extensions": "^2.16.0",
"viem": "^2.21.0"
},
"files": [
"dist"
],
"repository": {
"type": "git"
}
}
18 changes: 18 additions & 0 deletions plugins/x402BazaarPlugin/plugin_metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# General Information
plugin_name: "x402 Bazaar"
author: "x402 Bazaar (@sukrutkrdg)"
logo_url: "https://402.com.tr/brand/icon"
release_date: "2026-06"

# Description
short_description: "Onchain data & AI reports for Base agents — token risk, wallet intel, OFAC sanctions, AI verdicts — paid per call over x402, no API keys."
detailed_description: "x402 Bazaar is a marketplace of 118 pay-per-call services on Base; this plugin surfaces the ones a trading or research agent needs as G.A.M.E. functions: AI token due-diligence verdict, fast token risk scan, wallet net worth, OFAC sanctions screening, and an AI market brief. Each call settles a tiny USDC micro-payment over the x402 protocol on Base (gasless for the payer); the wallet key stays local. Use it to give an agent the intelligence to know what to act on before it acts. Also available as an MCP server (npx x402-bazaar-mcp)."

# Media & Assets
plugin_logo_url: "https://402.com.tr/brand/icon"
documentation_url: "https://402.com.tr/agents"

# Contact & Support
x_account_handle: "@sukrutkrdg"
support_contact: "sukrutkrdg@gmail.com"
community_url: "https://t.me/Bazaar402_bot"
20 changes: 20 additions & 0 deletions plugins/x402BazaarPlugin/src/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { GameAgent } from "@virtuals-protocol/game";
import X402BazaarPlugin from "./x402BazaarPlugin";

// A Base wallet holding a little USDC; used locally to sign x402 payments.
const plugin = new X402BazaarPlugin({
credentials: { privateKey: process.env.AGENT_PRIVATE_KEY },
});

const agent = new GameAgent(process.env.GAME_API_KEY as string, {
name: "Base Safety Agent",
goal: "Vet Base tokens and wallets for safety before acting.",
description:
"An agent that uses x402 Bazaar to pull token risk, wallet net worth, OFAC sanctions, AI token verdicts and a market brief — paying per call over x402.",
workers: [plugin.getWorker()],
});

(async () => {
await agent.init();
await agent.run(60, { verbose: true });
})();
3 changes: 3 additions & 0 deletions plugins/x402BazaarPlugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import X402BazaarPlugin from "./x402BazaarPlugin";

export default X402BazaarPlugin;
171 changes: 171 additions & 0 deletions plugins/x402BazaarPlugin/src/x402BazaarPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
GameWorker,
GameFunction,
ExecutableGameFunctionResponse,
ExecutableGameFunctionStatus,
} from "@virtuals-protocol/game";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { BuilderCodeClientExtension } from "@x402/extensions/builder-code";
import { privateKeyToAccount } from "viem/accounts";

interface IX402BazaarPluginOptions {
id?: string;
name?: string;
description?: string;
credentials?: { privateKey?: string };
origin?: string;
}

/**
* x402 Bazaar plugin for G.A.M.E.
*
* Gives a GAME agent onchain intelligence on Base — token risk, wallet net
* worth, OFAC sanctions, an AI token verdict and a market brief — each paid per
* call in USDC over the x402 protocol (no API keys). The wallet key is used
* locally to sign x402 payments and is never sent anywhere.
*/
class X402BazaarPlugin {
private id: string;
private name: string;
private description: string;
private privateKey?: string;
private origin: string;
private payingFetch: ReturnType<typeof wrapFetchWithPayment> | null = null;

constructor(options: IX402BazaarPluginOptions = {}) {
this.id = options.id || "x402_bazaar_worker";
this.name = options.name || "x402 Bazaar Worker";
this.description =
options.description ||
"Onchain intelligence for Base agents via x402 Bazaar — token risk, wallet net worth, OFAC sanctions, AI token verdicts and a market brief, each paid per call in USDC over x402 (no API keys).";
this.privateKey = options.credentials?.privateKey || process.env.AGENT_PRIVATE_KEY;
this.origin = (options.origin || process.env.X402_BAZAAR_ORIGIN || "https://402.com.tr").replace(/\/$/, "");
}

// Lazy paying-fetch — the key is needed only when a function actually runs.
private getPayingFetch() {
if (this.payingFetch) return this.payingFetch;
if (!this.privateKey) {
throw new Error(
"x402 Bazaar: a Base wallet private key is required to pay for calls. Pass credentials.privateKey or set AGENT_PRIVATE_KEY.",
);
}
const key = (this.privateKey.startsWith("0x") ? this.privateKey : `0x${this.privateKey}`) as `0x${string}`;
const account = privateKeyToAccount(key);
const client = new x402Client();
client.register("eip155:8453", new ExactEvmScheme(account)); // Base mainnet
client.registerExtension(new BuilderCodeClientExtension("x402_bazaar_cli"));
this.payingFetch = wrapFetchWithPayment(fetch, client);
return this.payingFetch;
}

private async call(service: string, query?: string): Promise<string> {
const url = `${this.origin}/api/x402/${service}${query ? `?${query}` : ""}`;
const res = await this.getPayingFetch()(url);
const text = await res.text();
if (!res.ok) throw new Error(text.slice(0, 200));
return text;
}

private addressFunction(name: string, service: string, description: string) {
return new GameFunction({
name,
description,
args: [
{ name: "address", type: "string", description: "A 0x… Base address (token or wallet)" },
] as const,
executable: async (args, logger) => {
try {
const address = (args.address || "").trim();
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
return new ExecutableGameFunctionResponse(
ExecutableGameFunctionStatus.Failed,
"A valid 0x… Base address is required.",
);
}
logger?.(`x402 Bazaar: ${service} ${address}`);
const data = await this.call(service, `address=${address}`);
return new ExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done, data);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return new ExecutableGameFunctionResponse(
ExecutableGameFunctionStatus.Failed,
`x402 Bazaar (${service}) failed: ${msg}`,
);
}
},
});
}

public get aiTokenReportFunction() {
return this.addressFunction(
"get_ai_token_report",
"ai-token-report",
"AI due-diligence verdict for a Base token (avoid→favorable) with a 0-100 safety score, risks and positives. Use before buying or interacting with a token.",
);
}

public get tokenRiskFunction() {
return this.addressFunction(
"get_token_risk",
"token-risk",
"Fast token safety scan (honeypot, taxes, ownership, holder concentration) for a Base token.",
);
}

public get walletNetworthFunction() {
return this.addressFunction(
"get_wallet_networth",
"wallet-networth",
"Full token portfolio and USD net worth for a Base wallet.",
);
}

public get sanctionsFunction() {
return this.addressFunction(
"check_sanctions",
"sanctions",
"Screen a Base address against the OFAC sanctions list before sending funds or interacting.",
);
}

public get marketBriefFunction() {
return new GameFunction({
name: "get_market_brief",
description:
"AI situational brief of the Base token market — mood, highlights, new & notable launches, cautions. Use for market context before trading.",
args: [] as const,
executable: async (_args, logger) => {
try {
logger?.("x402 Bazaar: ai-market-brief");
const data = await this.call("ai-market-brief");
return new ExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done, data);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return new ExecutableGameFunctionResponse(
ExecutableGameFunctionStatus.Failed,
`x402 Bazaar (market-brief) failed: ${msg}`,
);
}
},
});
}

public getWorker(): GameWorker {
return new GameWorker({
id: this.id,
name: this.name,
description: this.description,
functions: [
this.aiTokenReportFunction,
this.tokenRiskFunction,
this.walletNetworthFunction,
this.sanctionsFunction,
this.marketBriefFunction,
],
});
}
}

export default X402BazaarPlugin;
16 changes: 16 additions & 0 deletions plugins/x402BazaarPlugin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}