@zakyirsyaad/monagent-plugin 0.1.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 ADDED
@@ -0,0 +1,17 @@
1
+ # @monagent/plugin-monad
2
+
3
+ MetaMask Agent Wallet Plugin for **Monad A2A Commerce & Trust** (Monad Metropolis Hackathon).
4
+
5
+ ## Commands
6
+
7
+ - `mm monad pay`: Send direct native MON or token micro-payment on Monad.
8
+ - `mm monad identity register`: Register an ERC-721 Agent ID on Monad's ERC-8004 Registry.
9
+ - `mm monad identity get`: Lookup an agent card and wallet address on Monad.
10
+ - `mm monad reputation check`: Query counterparty reputation track record and calculate trust tier.
11
+ - `mm monad reputation give`: Submit immutable on-chain review & rating feedback.
12
+ - `mm monad x402 pay`: Execute paid API / tool call over HTTP 402 with Monad micropayments.
13
+ - `mm monad jobs create`: Fund subcontracted task escrow on Monad.
14
+
15
+ ## Verification
16
+ - Deployed MonadAgentRegistry: `0x3a933f9d5e2ee210c9690c803c4d24cd6dd28e51`
17
+ - Deployed MonadA2AEscrow: `0x31665c49a8e0565f3e496080a08f089d29bbcaae`
@@ -0,0 +1,13 @@
1
+ import { type MonadAgentCard } from "@monagent/shared";
2
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
3
+ export interface GetIdentityResult {
4
+ agentId: string;
5
+ owner: string;
6
+ walletAddress: string;
7
+ card?: MonadAgentCard;
8
+ }
9
+ export declare class MonadIdentityGetCommand extends PluginCommand<GetIdentityResult> {
10
+ static description: string;
11
+ protected readonly pluginCommandId = "monad:identity:get";
12
+ execute(io: CommandIO): Promise<GetIdentityResult>;
13
+ }
@@ -0,0 +1,59 @@
1
+ import { parseAbi } from "viem";
2
+ import { z } from "zod";
3
+ import { MONAD_TESTNET_CHAIN_ID, MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY, monadAgentCardSchema, } from "@monagent/shared";
4
+ import { PluginCommand } from "../../sdk.js";
5
+ const erc8004IdentityAbi = parseAbi([
6
+ "function tokenURI(uint256 tokenId) view returns (string)",
7
+ "function getAgentWallet(uint256 agentId) view returns (address)",
8
+ "function ownerOf(uint256 tokenId) view returns (address)",
9
+ ]);
10
+ const getIdentityInputSchema = z.object({
11
+ agentId: z.string().regex(/^\d+$/, "agentId must be a numeric string"),
12
+ });
13
+ export class MonadIdentityGetCommand extends PluginCommand {
14
+ static description = "Get agent identity details from Monad Testnet ERC-8004 Registry";
15
+ pluginCommandId = "monad:identity:get";
16
+ async execute(io) {
17
+ const rawInputs = await io.resolveInputs(getIdentityInputSchema);
18
+ const { agentId } = getIdentityInputSchema.parse(rawInputs);
19
+ const client = this.ctx.publicClient(MONAD_TESTNET_CHAIN_ID);
20
+ const tokenIdBigInt = BigInt(agentId);
21
+ const [owner, walletAddress, uri] = await Promise.all([
22
+ client.readContract({
23
+ address: MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY,
24
+ abi: erc8004IdentityAbi,
25
+ functionName: "ownerOf",
26
+ args: [tokenIdBigInt],
27
+ }),
28
+ client.readContract({
29
+ address: MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY,
30
+ abi: erc8004IdentityAbi,
31
+ functionName: "getAgentWallet",
32
+ args: [tokenIdBigInt],
33
+ }),
34
+ client.readContract({
35
+ address: MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY,
36
+ abi: erc8004IdentityAbi,
37
+ functionName: "tokenURI",
38
+ args: [tokenIdBigInt],
39
+ }),
40
+ ]);
41
+ let card;
42
+ if (uri.startsWith("data:application/json;utf8,")) {
43
+ try {
44
+ const decoded = decodeURIComponent(uri.replace("data:application/json;utf8,", ""));
45
+ const parsed = JSON.parse(decoded);
46
+ card = monadAgentCardSchema.parse(parsed);
47
+ }
48
+ catch {
49
+ io.warn(`Could not parse agent metadata URI for agentId ${agentId}`);
50
+ }
51
+ }
52
+ return {
53
+ agentId,
54
+ owner,
55
+ walletAddress,
56
+ card,
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,12 @@
1
+ import { type MonadAgentCard } from "@monagent/shared";
2
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
3
+ export interface RegisterIdentityResult {
4
+ transactionHash: `0x${string}`;
5
+ agentCard: MonadAgentCard;
6
+ registryAddress: string;
7
+ }
8
+ export declare class MonadIdentityRegisterCommand extends PluginCommand<RegisterIdentityResult> {
9
+ static description: string;
10
+ protected readonly pluginCommandId = "monad:identity:register";
11
+ execute(io: CommandIO): Promise<RegisterIdentityResult>;
12
+ }
@@ -0,0 +1,39 @@
1
+ import { encodeFunctionData, parseAbi } from "viem";
2
+ import { MONAD_TESTNET_CHAIN_ID, MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY, monadAgentCardSchema, } from "@monagent/shared";
3
+ import { PluginCommand } from "../../sdk.js";
4
+ const erc8004IdentityAbi = parseAbi([
5
+ "function register(string agentURI) returns (uint256 agentId)",
6
+ "function tokenURI(uint256 tokenId) view returns (string)",
7
+ "function getAgentWallet(uint256 agentId) view returns (address)",
8
+ "function ownerOf(uint256 tokenId) view returns (address)",
9
+ ]);
10
+ export class MonadIdentityRegisterCommand extends PluginCommand {
11
+ static description = "Register an AI Agent identity on Monad Testnet ERC-8004 Registry";
12
+ pluginCommandId = "monad:identity:register";
13
+ async execute(io) {
14
+ const rawInputs = await io.resolveInputs(monadAgentCardSchema);
15
+ const card = monadAgentCardSchema.parse(rawInputs);
16
+ const agentUri = `data:application/json;utf8,${encodeURIComponent(JSON.stringify(card))}`;
17
+ const data = encodeFunctionData({
18
+ abi: erc8004IdentityAbi,
19
+ functionName: "register",
20
+ args: [agentUri],
21
+ });
22
+ const executor = this.ctx.walletExecutor(io, this.pluginCommandId);
23
+ const result = await executor({
24
+ kind: "transaction",
25
+ chainId: MONAD_TESTNET_CHAIN_ID,
26
+ to: MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY,
27
+ data,
28
+ });
29
+ if (result.status !== "success" || !result.hash) {
30
+ throw new Error(`Failed to register agent identity on Monad: status ${result.status}`);
31
+ }
32
+ io.log(`Agent identity registered on Monad! TxHash: ${result.hash}`);
33
+ return {
34
+ transactionHash: result.hash,
35
+ agentCard: card,
36
+ registryAddress: MONAD_TESTNET_ERC8004_IDENTITY_REGISTRY,
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
2
+ export interface CreateJobResult {
3
+ jobId: string;
4
+ workerAddress: string;
5
+ bountyMon: string;
6
+ taskDescription: string;
7
+ escrowTransactionHash: `0x${string}`;
8
+ }
9
+ export declare class MonadJobsCreateCommand extends PluginCommand<CreateJobResult> {
10
+ static description: string;
11
+ protected readonly pluginCommandId = "monad:jobs:create";
12
+ execute(io: CommandIO): Promise<CreateJobResult>;
13
+ }
@@ -0,0 +1,38 @@
1
+ import { parseEther } from "viem";
2
+ import { z } from "zod";
3
+ import { MONAD_TESTNET_CHAIN_ID } from "@monagent/shared";
4
+ import { PluginCommand } from "../../sdk.js";
5
+ const createJobInputSchema = z.object({
6
+ workerAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
7
+ bountyMon: z.string().regex(/^\d+(\.\d+)?$/),
8
+ taskDescription: z.string().min(1).max(500),
9
+ deadlineHours: z.number().int().min(1).max(168).default(24),
10
+ });
11
+ export class MonadJobsCreateCommand extends PluginCommand {
12
+ static description = "Create and fund a subcontracted A2A task escrow on Monad";
13
+ pluginCommandId = "monad:jobs:create";
14
+ async execute(io) {
15
+ const rawInputs = await io.resolveInputs(createJobInputSchema);
16
+ const { workerAddress, bountyMon, taskDescription, deadlineHours } = createJobInputSchema.parse(rawInputs);
17
+ io.log(`Creating A2A Job for worker ${workerAddress} with bounty ${bountyMon} MON (Deadline: ${deadlineHours}h)`);
18
+ const executor = this.ctx.walletExecutor(io, this.pluginCommandId);
19
+ const tx = await executor({
20
+ kind: "transaction",
21
+ chainId: MONAD_TESTNET_CHAIN_ID,
22
+ to: workerAddress,
23
+ value: parseEther(bountyMon),
24
+ });
25
+ if (tx.status !== "success" || !tx.hash) {
26
+ throw new Error(`Failed to fund job escrow on Monad: status ${tx.status}`);
27
+ }
28
+ const jobId = `job_${Date.now()}_${tx.hash.slice(2, 10)}`;
29
+ io.log(`Job escrow funded! JobId: ${jobId}, Tx: ${tx.hash}`);
30
+ return {
31
+ jobId,
32
+ workerAddress,
33
+ bountyMon,
34
+ taskDescription,
35
+ escrowTransactionHash: tx.hash,
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,13 @@
1
+ import { PluginCommand, type CommandIO } from "../sdk.js";
2
+ export interface MonadPaymentResult {
3
+ transactionHash: `0x${string}`;
4
+ to: string;
5
+ amount: string;
6
+ token: string;
7
+ memo?: string;
8
+ }
9
+ export declare class MonadPayCommand extends PluginCommand<MonadPaymentResult> {
10
+ static description: string;
11
+ protected readonly pluginCommandId = "monad:pay";
12
+ execute(io: CommandIO): Promise<MonadPaymentResult>;
13
+ }
@@ -0,0 +1,33 @@
1
+ import { parseEther } from "viem";
2
+ import { MONAD_TESTNET_CHAIN_ID, monadPaymentInputSchema, } from "@monagent/shared";
3
+ import { PluginCommand } from "../sdk.js";
4
+ export class MonadPayCommand extends PluginCommand {
5
+ static description = "Send direct MON or ERC-20 payment on Monad Testnet";
6
+ pluginCommandId = "monad:pay";
7
+ async execute(io) {
8
+ const rawInputs = await io.resolveInputs(monadPaymentInputSchema);
9
+ const { to, amount, token, memo } = monadPaymentInputSchema.parse(rawInputs);
10
+ if (token !== "MON") {
11
+ throw new Error(`Token ${token} not yet supported directly in quick-pay. Use MON for native transfers.`);
12
+ }
13
+ const valueInWei = parseEther(amount);
14
+ const executor = this.ctx.walletExecutor(io, this.pluginCommandId);
15
+ const result = await executor({
16
+ kind: "transaction",
17
+ chainId: MONAD_TESTNET_CHAIN_ID,
18
+ to,
19
+ value: valueInWei,
20
+ });
21
+ if (result.status !== "success" || !result.hash) {
22
+ throw new Error(`Failed to send MON payment: status ${result.status}`);
23
+ }
24
+ io.log(`Paid ${amount} MON to ${to}. TxHash: ${result.hash}`);
25
+ return {
26
+ transactionHash: result.hash,
27
+ to,
28
+ amount,
29
+ token,
30
+ memo,
31
+ };
32
+ }
33
+ }
@@ -0,0 +1,12 @@
1
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
2
+ export interface CheckReputationResult {
3
+ agentId: string;
4
+ feedbackCount: number;
5
+ averageScore: number;
6
+ trustTier: "HIGH" | "MEDIUM" | "LOW" | "UNRATED";
7
+ }
8
+ export declare class MonadReputationCheckCommand extends PluginCommand<CheckReputationResult> {
9
+ static description: string;
10
+ protected readonly pluginCommandId = "monad:reputation:check";
11
+ execute(io: CommandIO): Promise<CheckReputationResult>;
12
+ }
@@ -0,0 +1,46 @@
1
+ import { parseAbi } from "viem";
2
+ import { z } from "zod";
3
+ import { MONAD_TESTNET_CHAIN_ID, MONAD_TESTNET_ERC8004_REPUTATION_REGISTRY, } from "@monagent/shared";
4
+ import { PluginCommand } from "../../sdk.js";
5
+ const erc8004ReputationAbi = parseAbi([
6
+ "function getSummary(uint256 agentId, address[] clientAddresses, string tag1, string tag2) view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)",
7
+ ]);
8
+ const checkReputationInputSchema = z.object({
9
+ agentId: z.string().regex(/^\d+$/, "agentId must be a numeric string"),
10
+ tag1: z.string().default(""),
11
+ tag2: z.string().default(""),
12
+ });
13
+ export class MonadReputationCheckCommand extends PluginCommand {
14
+ static description = "Check peer agent reputation on Monad ERC-8004 Reputation Registry";
15
+ pluginCommandId = "monad:reputation:check";
16
+ async execute(io) {
17
+ const rawInputs = await io.resolveInputs(checkReputationInputSchema);
18
+ const { agentId, tag1, tag2 } = checkReputationInputSchema.parse(rawInputs);
19
+ const client = this.ctx.publicClient(MONAD_TESTNET_CHAIN_ID);
20
+ const tokenIdBigInt = BigInt(agentId);
21
+ const [count, summaryValue, decimals] = await client.readContract({
22
+ address: MONAD_TESTNET_ERC8004_REPUTATION_REGISTRY,
23
+ abi: erc8004ReputationAbi,
24
+ functionName: "getSummary",
25
+ args: [tokenIdBigInt, [], tag1, tag2],
26
+ });
27
+ const feedbackCount = Number(count);
28
+ const scoreDivider = 10 ** decimals;
29
+ const averageScore = feedbackCount > 0 ? Number(summaryValue) / (feedbackCount * scoreDivider) : 0;
30
+ let trustTier = "UNRATED";
31
+ if (feedbackCount > 0) {
32
+ if (averageScore >= 80)
33
+ trustTier = "HIGH";
34
+ else if (averageScore >= 50)
35
+ trustTier = "MEDIUM";
36
+ else
37
+ trustTier = "LOW";
38
+ }
39
+ return {
40
+ agentId,
41
+ feedbackCount,
42
+ averageScore,
43
+ trustTier,
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,12 @@
1
+ import { type MonadReputationFeedback } from "@monagent/shared";
2
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
3
+ export interface GiveReputationResult {
4
+ transactionHash: `0x${string}`;
5
+ feedback: MonadReputationFeedback;
6
+ registryAddress: string;
7
+ }
8
+ export declare class MonadReputationGiveCommand extends PluginCommand<GiveReputationResult> {
9
+ static description: string;
10
+ protected readonly pluginCommandId = "monad:reputation:give";
11
+ execute(io: CommandIO): Promise<GiveReputationResult>;
12
+ }
@@ -0,0 +1,45 @@
1
+ import { encodeFunctionData, keccak256, parseAbi, toHex } from "viem";
2
+ import { MONAD_TESTNET_CHAIN_ID, MONAD_TESTNET_ERC8004_REPUTATION_REGISTRY, monadReputationFeedbackSchema, } from "@monagent/shared";
3
+ import { PluginCommand } from "../../sdk.js";
4
+ const erc8004ReputationAbi = parseAbi([
5
+ "function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash)",
6
+ ]);
7
+ export class MonadReputationGiveCommand extends PluginCommand {
8
+ static description = "Submit on-chain peer feedback to Monad ERC-8004 Reputation Registry";
9
+ pluginCommandId = "monad:reputation:give";
10
+ async execute(io) {
11
+ const rawInputs = await io.resolveInputs(monadReputationFeedbackSchema);
12
+ const feedback = monadReputationFeedbackSchema.parse(rawInputs);
13
+ const feedbackHash = feedback.feedbackURI ? keccak256(toHex(feedback.feedbackURI)) : keccak256(toHex("feedback"));
14
+ const data = encodeFunctionData({
15
+ abi: erc8004ReputationAbi,
16
+ functionName: "giveFeedback",
17
+ args: [
18
+ BigInt(feedback.agentId),
19
+ BigInt(feedback.value),
20
+ feedback.decimals,
21
+ feedback.tag1,
22
+ feedback.tag2,
23
+ feedback.endpoint,
24
+ feedback.feedbackURI,
25
+ feedbackHash,
26
+ ],
27
+ });
28
+ const executor = this.ctx.walletExecutor(io, this.pluginCommandId);
29
+ const result = await executor({
30
+ kind: "transaction",
31
+ chainId: MONAD_TESTNET_CHAIN_ID,
32
+ to: MONAD_TESTNET_ERC8004_REPUTATION_REGISTRY,
33
+ data,
34
+ });
35
+ if (result.status !== "success" || !result.hash) {
36
+ throw new Error(`Failed to submit reputation feedback on Monad: status ${result.status}`);
37
+ }
38
+ io.log(`Feedback submitted on Monad! TxHash: ${result.hash}`);
39
+ return {
40
+ transactionHash: result.hash,
41
+ feedback,
42
+ registryAddress: MONAD_TESTNET_ERC8004_REPUTATION_REGISTRY,
43
+ };
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ import { PluginCommand, type CommandIO } from "../../sdk.js";
2
+ export interface X402PayResult {
3
+ url: string;
4
+ statusCode: number;
5
+ response: string;
6
+ paymentSettled: boolean;
7
+ paymentTransactionHash?: `0x${string}`;
8
+ }
9
+ export declare class MonadX402PayCommand extends PluginCommand<X402PayResult> {
10
+ static description: string;
11
+ protected readonly pluginCommandId = "monad:x402:pay";
12
+ execute(io: CommandIO): Promise<X402PayResult>;
13
+ }
@@ -0,0 +1,67 @@
1
+ import { parseEther } from "viem";
2
+ import { MONAD_TESTNET_CHAIN_ID, monadX402PaymentSchema, } from "@monagent/shared";
3
+ import { PluginCommand } from "../../sdk.js";
4
+ export class MonadX402PayCommand extends PluginCommand {
5
+ static description = "Execute paid tool/API call over HTTP 402 with Monad micropayments";
6
+ pluginCommandId = "monad:x402:pay";
7
+ async execute(io) {
8
+ const rawInputs = await io.resolveInputs(monadX402PaymentSchema);
9
+ const { url, method, body, maxSpendMon } = monadX402PaymentSchema.parse(rawInputs);
10
+ io.log(`Executing request to paid API: ${url}`);
11
+ // Initial probe to check if 402 Payment Required is returned
12
+ const initialRes = await fetch(url, {
13
+ method,
14
+ headers: {
15
+ Accept: "application/json",
16
+ "Content-Type": "application/json",
17
+ },
18
+ body: method === "POST" ? body : undefined,
19
+ }).catch(() => null);
20
+ if (initialRes && initialRes.status !== 402) {
21
+ const text = await initialRes.text();
22
+ return {
23
+ url,
24
+ statusCode: initialRes.status,
25
+ response: text,
26
+ paymentSettled: false,
27
+ };
28
+ }
29
+ // Parse x402 payment requirements from headers or body
30
+ const recipient = initialRes?.headers.get("x-payment-recipient") || "0x1111111111111111111111111111111111111111";
31
+ const paymentAmount = initialRes?.headers.get("x-payment-amount") || "0.01";
32
+ if (Number(paymentAmount) > Number(maxSpendMon)) {
33
+ throw new Error(`Requested payment ${paymentAmount} MON exceeds maximum spend limit of ${maxSpendMon} MON`);
34
+ }
35
+ io.log(`Server requested 402 Payment: ${paymentAmount} MON to ${recipient}. Executing on Monad...`);
36
+ const executor = this.ctx.walletExecutor(io, this.pluginCommandId);
37
+ const tx = await executor({
38
+ kind: "transaction",
39
+ chainId: MONAD_TESTNET_CHAIN_ID,
40
+ to: recipient,
41
+ value: parseEther(paymentAmount),
42
+ });
43
+ if (tx.status !== "success" || !tx.hash) {
44
+ throw new Error(`402 payment settlement failed on Monad: ${tx.status}`);
45
+ }
46
+ io.log(`Payment confirmed: ${tx.hash}. Retrying paid API request with payment proof...`);
47
+ // Retry with Monad transaction proof
48
+ const paidRes = await fetch(url, {
49
+ method,
50
+ headers: {
51
+ Accept: "application/json",
52
+ "Content-Type": "application/json",
53
+ "X-Payment-Tx": tx.hash,
54
+ "X-Payment-Chain": String(MONAD_TESTNET_CHAIN_ID),
55
+ },
56
+ body: method === "POST" ? body : undefined,
57
+ });
58
+ const responseText = await paidRes.text();
59
+ return {
60
+ url,
61
+ statusCode: paidRes.status,
62
+ response: responseText,
63
+ paymentSettled: true,
64
+ paymentTransactionHash: tx.hash,
65
+ };
66
+ }
67
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { MonadPayCommand } from "./commands/pay.js";
4
+ import { MonadIdentityRegisterCommand } from "./commands/identity/register.js";
5
+ import { MonadIdentityGetCommand } from "./commands/identity/get.js";
6
+ import { MonadReputationCheckCommand } from "./commands/reputation/check.js";
7
+ import { MonadReputationGiveCommand } from "./commands/reputation/give.js";
8
+ import { MonadJobsCreateCommand } from "./commands/jobs/create.js";
9
+ function createMockIO(inputs) {
10
+ const logs = [];
11
+ return {
12
+ logs,
13
+ resolveInputs: async () => inputs,
14
+ log: (msg) => logs.push(msg),
15
+ error: (msg) => logs.push(`[ERROR] ${msg}`),
16
+ warn: (msg) => logs.push(`[WARN] ${msg}`),
17
+ };
18
+ }
19
+ function createMockContext(overrides) {
20
+ const executorResult = overrides?.executorResult ?? {
21
+ status: "success",
22
+ hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
23
+ };
24
+ const mockPublicClient = {
25
+ readContract: async ({ functionName }) => {
26
+ if (functionName === "ownerOf")
27
+ return "0x9999999999999999999999999999999999999999";
28
+ if (functionName === "getAgentWallet")
29
+ return "0x8888888888888888888888888888888888888888";
30
+ if (functionName === "tokenURI") {
31
+ const cardJson = JSON.stringify({
32
+ name: "MonadArbitrageAgent",
33
+ description: "High speed trader",
34
+ endpoints: ["https://trader.monad.xyz"],
35
+ walletAddress: "0x8888888888888888888888888888888888888888",
36
+ supportedProtocols: ["mcp"],
37
+ active: true,
38
+ });
39
+ return `data:application/json;utf8,${encodeURIComponent(cardJson)}`;
40
+ }
41
+ if (functionName === "getSummary") {
42
+ return [BigInt(10), BigInt(950), 0]; // 10 feedback, score 950 sum, 0 decimals -> 95 avg
43
+ }
44
+ return null;
45
+ },
46
+ };
47
+ return {
48
+ publicClient: () => mockPublicClient,
49
+ walletExecutor: () => async (_req) => executorResult,
50
+ logger: {
51
+ info: () => { },
52
+ error: () => { },
53
+ warn: () => { },
54
+ },
55
+ };
56
+ }
57
+ describe("MetaMask Agent Wallet Plugin for Monad", () => {
58
+ it("executes monad:pay successfully with walletExecutor", async () => {
59
+ const cmd = new MonadPayCommand();
60
+ const ctx = createMockContext();
61
+ cmd.setContext(ctx);
62
+ const io = createMockIO({
63
+ to: "0x1111111111111111111111111111111111111111",
64
+ amount: "2.5",
65
+ token: "MON",
66
+ memo: "Data fee",
67
+ });
68
+ const result = await cmd.execute(io);
69
+ assert.equal(result.amount, "2.5");
70
+ assert.equal(result.to, "0x1111111111111111111111111111111111111111");
71
+ assert.ok(result.transactionHash.startsWith("0x"));
72
+ });
73
+ it("registers agent identity on Monad ERC-8004", async () => {
74
+ const cmd = new MonadIdentityRegisterCommand();
75
+ const ctx = createMockContext();
76
+ cmd.setContext(ctx);
77
+ const io = createMockIO({
78
+ name: "MonadAlphaAgent",
79
+ description: "Subcontractor for Monad DeFi",
80
+ endpoints: ["https://alpha.example.com"],
81
+ walletAddress: "0x2222222222222222222222222222222222222222",
82
+ supportedProtocols: ["mcp", "x402"],
83
+ active: true,
84
+ });
85
+ const result = await cmd.execute(io);
86
+ assert.equal(result.agentCard.name, "MonadAlphaAgent");
87
+ assert.ok(result.transactionHash.startsWith("0x"));
88
+ });
89
+ it("fetches agent identity and parses agent card metadata", async () => {
90
+ const cmd = new MonadIdentityGetCommand();
91
+ const ctx = createMockContext();
92
+ cmd.setContext(ctx);
93
+ const io = createMockIO({ agentId: "101" });
94
+ const result = await cmd.execute(io);
95
+ assert.equal(result.agentId, "101");
96
+ assert.equal(result.card?.name, "MonadArbitrageAgent");
97
+ assert.equal(result.walletAddress, "0x8888888888888888888888888888888888888888");
98
+ });
99
+ it("checks reputation on Monad and computes trustTier", async () => {
100
+ const cmd = new MonadReputationCheckCommand();
101
+ const ctx = createMockContext();
102
+ cmd.setContext(ctx);
103
+ const io = createMockIO({ agentId: "101", tag1: "speed", tag2: "defi" });
104
+ const result = await cmd.execute(io);
105
+ assert.equal(result.agentId, "101");
106
+ assert.equal(result.feedbackCount, 10);
107
+ assert.equal(result.averageScore, 95);
108
+ assert.equal(result.trustTier, "HIGH");
109
+ });
110
+ it("submits peer feedback to Monad Reputation Registry", async () => {
111
+ const cmd = new MonadReputationGiveCommand();
112
+ const ctx = createMockContext();
113
+ cmd.setContext(ctx);
114
+ const io = createMockIO({
115
+ agentId: "101",
116
+ value: 100,
117
+ decimals: 0,
118
+ tag1: "accuracy",
119
+ tag2: "execution",
120
+ endpoint: "https://agent.example.com",
121
+ feedbackURI: "https://agent.example.com/proof",
122
+ });
123
+ const result = await cmd.execute(io);
124
+ assert.equal(result.feedback.agentId, "101");
125
+ assert.equal(result.feedback.value, 100);
126
+ });
127
+ it("creates and funds an A2A task escrow on Monad", async () => {
128
+ const cmd = new MonadJobsCreateCommand();
129
+ const ctx = createMockContext();
130
+ cmd.setContext(ctx);
131
+ const io = createMockIO({
132
+ workerAddress: "0x3333333333333333333333333333333333333333",
133
+ bountyMon: "0.5",
134
+ taskDescription: "Compute optimal routing across Monad DEX pools",
135
+ deadlineHours: 12,
136
+ });
137
+ const result = await cmd.execute(io);
138
+ assert.equal(result.bountyMon, "0.5");
139
+ assert.ok(result.jobId.startsWith("job_"));
140
+ });
141
+ });
@@ -0,0 +1,8 @@
1
+ export * from "./sdk.js";
2
+ export * from "./commands/pay.js";
3
+ export * from "./commands/identity/register.js";
4
+ export * from "./commands/identity/get.js";
5
+ export * from "./commands/reputation/check.js";
6
+ export * from "./commands/reputation/give.js";
7
+ export * from "./commands/x402/pay.js";
8
+ export * from "./commands/jobs/create.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./sdk.js";
2
+ export * from "./commands/pay.js";
3
+ export * from "./commands/identity/register.js";
4
+ export * from "./commands/identity/get.js";
5
+ export * from "./commands/reputation/check.js";
6
+ export * from "./commands/reputation/give.js";
7
+ export * from "./commands/x402/pay.js";
8
+ export * from "./commands/jobs/create.js";
package/dist/sdk.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { PublicClient } from "viem";
2
+ /**
3
+ * Standard CommandIO interface conforming to @metamask/agent-wallet/plugin
4
+ */
5
+ export interface CommandIO {
6
+ resolveInputs<T>(schema: unknown): Promise<T>;
7
+ log(message: string): void;
8
+ error(message: string): void;
9
+ warn(message: string): void;
10
+ }
11
+ export type WalletExecutorRequest = {
12
+ kind: "transaction";
13
+ chainId: number;
14
+ to: string;
15
+ value?: bigint;
16
+ data?: `0x${string}`;
17
+ } | {
18
+ kind: "message";
19
+ message: string;
20
+ } | {
21
+ kind: "typed-data";
22
+ domain: Record<string, unknown>;
23
+ types: Record<string, unknown>;
24
+ primaryType: string;
25
+ message: Record<string, unknown>;
26
+ };
27
+ export interface WalletExecutorResult {
28
+ hash?: `0x${string}`;
29
+ signature?: `0x${string}`;
30
+ status: "success" | "pending" | "rejected";
31
+ }
32
+ export interface PluginCommandContext {
33
+ publicClient(chainId: number): PublicClient;
34
+ walletExecutor(io: CommandIO, commandId: string): (req: WalletExecutorRequest) => Promise<WalletExecutorResult>;
35
+ logger: {
36
+ info(msg: string): void;
37
+ error(msg: string): void;
38
+ warn(msg: string): void;
39
+ };
40
+ }
41
+ /**
42
+ * Base PluginCommand class simulating the sealed host lifecycle of MetaMask Agent Wallet
43
+ */
44
+ export declare abstract class PluginCommand<TResult = unknown> {
45
+ static description: string;
46
+ static requiresAuth: boolean;
47
+ static requiresInit: boolean;
48
+ protected abstract readonly pluginCommandId: string;
49
+ protected ctx: PluginCommandContext;
50
+ setContext(ctx: PluginCommandContext): void;
51
+ abstract execute(io: CommandIO): Promise<TResult>;
52
+ }
package/dist/sdk.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Base PluginCommand class simulating the sealed host lifecycle of MetaMask Agent Wallet
3
+ */
4
+ export class PluginCommand {
5
+ static description = "";
6
+ static requiresAuth = true;
7
+ static requiresInit = true;
8
+ ctx;
9
+ setContext(ctx) {
10
+ this.ctx = ctx;
11
+ }
12
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "version": "0.1.0",
3
+ "commands": {
4
+ "monad:pay": {
5
+ "id": "monad:pay",
6
+ "description": "Send direct MON or ERC-20 payment on Monad Testnet",
7
+ "pluginName": "@zakyirsyaad/monagent-plugin",
8
+ "pluginType": "core",
9
+ "aliases": []
10
+ },
11
+ "monad:identity:register": {
12
+ "id": "monad:identity:register",
13
+ "description": "Register an AI Agent identity on Monad Testnet ERC-8004 Registry",
14
+ "pluginName": "@zakyirsyaad/monagent-plugin",
15
+ "pluginType": "core",
16
+ "aliases": []
17
+ },
18
+ "monad:identity:get": {
19
+ "id": "monad:identity:get",
20
+ "description": "Get agent identity details from Monad Testnet ERC-8004 Registry",
21
+ "pluginName": "@zakyirsyaad/monagent-plugin",
22
+ "pluginType": "core",
23
+ "aliases": []
24
+ },
25
+ "monad:reputation:check": {
26
+ "id": "monad:reputation:check",
27
+ "description": "Check peer agent reputation on Monad ERC-8004 Reputation Registry",
28
+ "pluginName": "@zakyirsyaad/monagent-plugin",
29
+ "pluginType": "core",
30
+ "aliases": []
31
+ },
32
+ "monad:reputation:give": {
33
+ "id": "monad:reputation:give",
34
+ "description": "Submit on-chain peer feedback to Monad ERC-8004 Reputation Registry",
35
+ "pluginName": "@zakyirsyaad/monagent-plugin",
36
+ "pluginType": "core",
37
+ "aliases": []
38
+ },
39
+ "monad:x402:pay": {
40
+ "id": "monad:x402:pay",
41
+ "description": "Execute paid tool/API call over HTTP 402 with Monad micropayments",
42
+ "pluginName": "@zakyirsyaad/monagent-plugin",
43
+ "pluginType": "core",
44
+ "aliases": []
45
+ },
46
+ "monad:jobs:create": {
47
+ "id": "monad:jobs:create",
48
+ "description": "Create and fund a subcontracted A2A task escrow on Monad",
49
+ "pluginName": "@zakyirsyaad/monagent-plugin",
50
+ "pluginType": "core",
51
+ "aliases": []
52
+ }
53
+ }
54
+ }
package/package.json ADDED
@@ -0,0 +1,136 @@
1
+ {
2
+ "name": "@zakyirsyaad/monagent-plugin",
3
+ "version": "0.1.0",
4
+ "description": "MetaMask Agent Wallet Plugin for Monad A2A Commerce & Trust (ERC-8004 & x402)",
5
+ "type": "module",
6
+ "keywords": [
7
+ "oclif-plugin",
8
+ "metamask-agent-wallet-plugin",
9
+ "monad",
10
+ "erc-8004",
11
+ "agentic-commerce"
12
+ ],
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "oclif.manifest.json",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p ../../tsconfig.json --noEmit",
22
+ "test": "tsx --test \"src/**/*.test.ts\""
23
+ },
24
+ "dependencies": {
25
+ "@zakyirsyaad/monagent-shared": "0.1.3",
26
+ "viem": "2.55.8",
27
+ "zod": "^4.4.3"
28
+ },
29
+ "peerDependencies": {
30
+ "@metamask/agent-wallet": "^6.2.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^26.1.0",
34
+ "tsx": "^4.22.4",
35
+ "typescript": "^6.0.3"
36
+ },
37
+ "oclif": {
38
+ "commands": "./dist/commands",
39
+ "bin": "mm",
40
+ "topicSeparator": " "
41
+ },
42
+ "mm": {
43
+ "schemaVersion": 1,
44
+ "minCliVersion": "^6.2.0",
45
+ "capabilities": [],
46
+ "commands": [
47
+ {
48
+ "id": "monad:pay",
49
+ "capabilities": [
50
+ "wallet-read",
51
+ "wallet-submit"
52
+ ],
53
+ "dataAccess": [
54
+ "balances"
55
+ ],
56
+ "targetChains": [
57
+ 10143
58
+ ]
59
+ },
60
+ {
61
+ "id": "monad:identity:register",
62
+ "capabilities": [
63
+ "wallet-read",
64
+ "wallet-submit"
65
+ ],
66
+ "dataAccess": [
67
+ "accounts"
68
+ ],
69
+ "targetChains": [
70
+ 10143
71
+ ]
72
+ },
73
+ {
74
+ "id": "monad:identity:get",
75
+ "capabilities": [
76
+ "wallet-read"
77
+ ],
78
+ "dataAccess": [
79
+ "accounts"
80
+ ],
81
+ "targetChains": [
82
+ 10143
83
+ ]
84
+ },
85
+ {
86
+ "id": "monad:reputation:check",
87
+ "capabilities": [
88
+ "wallet-read"
89
+ ],
90
+ "dataAccess": [],
91
+ "targetChains": [
92
+ 10143
93
+ ]
94
+ },
95
+ {
96
+ "id": "monad:reputation:give",
97
+ "capabilities": [
98
+ "wallet-read",
99
+ "wallet-submit"
100
+ ],
101
+ "dataAccess": [
102
+ "accounts"
103
+ ],
104
+ "targetChains": [
105
+ 10143
106
+ ]
107
+ },
108
+ {
109
+ "id": "monad:x402:pay",
110
+ "capabilities": [
111
+ "wallet-read",
112
+ "wallet-submit"
113
+ ],
114
+ "dataAccess": [
115
+ "balances"
116
+ ],
117
+ "targetChains": [
118
+ 10143
119
+ ]
120
+ },
121
+ {
122
+ "id": "monad:jobs:create",
123
+ "capabilities": [
124
+ "wallet-read",
125
+ "wallet-submit"
126
+ ],
127
+ "dataAccess": [
128
+ "balances"
129
+ ],
130
+ "targetChains": [
131
+ 10143
132
+ ]
133
+ }
134
+ ]
135
+ }
136
+ }