Skip to content
Draft
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
645 changes: 645 additions & 0 deletions docs/testing-subscription.md

Large diffs are not rendered by default.

191 changes: 191 additions & 0 deletions examples/acp-base/cc-swap/buyer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import AcpClient, {
AcpContractClientV2,
AcpJobPhases,
AcpJob,
AcpMemo,
AcpAgentSort,
AcpGraduationStatus,
AcpOnlineStatus,
baseSepoliaAcpX402ConfigV2,
AcpMemoState,
} from "../../../src/index";
import {
BUYER_AGENT_WALLET_ADDRESS,
BUYER_ENTITY_ID,
WHITELISTED_WALLET_PRIVATE_KEY,
} from "./env";
import { arbitrumSepolia, baseSepolia, polygonAmoy } from "@account-kit/infra";
import * as readline from "readline";
import { SupportedChain } from "./jobTypes";

const SUPPORTED_CHAINS: Record<
SupportedChain,
{ chain: typeof baseSepolia | typeof arbitrumSepolia | typeof polygonAmoy }
> = {
"base-sepolia": { chain: baseSepolia },
"arbitrum-sepolia": { chain: arbitrumSepolia },
"polygon-amoy": { chain: polygonAmoy },
};

const config = {
...baseSepoliaAcpX402ConfigV2,
chains: Object.entries(SUPPORTED_CHAINS)
.filter(([key]) => key !== "base-sepolia")
.map(([, { chain }]) => chain),
};

async function promptUser(question: string): Promise<string> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}

async function buyer() {
console.log("=== Munchy the Swapmaster - Buyer ===\n");
console.log("Supported chains:");
console.log(" 1. base-sepolia");
console.log(" 2. arbitrum-sepolia");
console.log(" 3. polygon-amoy\n");

const sourceChainInput = await promptUser(
"Enter source chain (1-3 or name): ",
);
const targetChainInput = await promptUser(
"Enter target chain (1-3 or name): ",
);
const amountInput = await promptUser("Enter amount to transfer: ");

const chainMap: Record<string, SupportedChain> = {
"1": "base-sepolia",
"2": "arbitrum-sepolia",
"3": "polygon-amoy",
"base-sepolia": "base-sepolia",
"arbitrum-sepolia": "arbitrum-sepolia",
"polygon-amoy": "polygon-amoy",
};

const sourceChain = chainMap[sourceChainInput.toLowerCase()];
const targetChain = chainMap[targetChainInput.toLowerCase()];
const amount = amountInput;

if (!sourceChain || !SUPPORTED_CHAINS[sourceChain]) {
console.error("Invalid source chain:", sourceChainInput);
process.exit(1);
}

if (!targetChain || !SUPPORTED_CHAINS[targetChain]) {
console.error("Invalid target chain:", targetChainInput);
process.exit(1);
}

// if (isNaN(amount) || amount <= 0) {
// console.error("Invalid amount:", amountInput);
// process.exit(1);
// }

console.log("\n=== Transfer Details ===");
console.log("Source Chain:", sourceChain);
console.log("Target Chain:", targetChain);
console.log("Amount:", amount);
console.log("Symbol: TEST\n");

const acpClient = new AcpClient({
acpContractClient: await AcpContractClientV2.build(
WHITELISTED_WALLET_PRIVATE_KEY,
BUYER_ENTITY_ID,
BUYER_AGENT_WALLET_ADDRESS,
config,
),
onNewTask: async (job: AcpJob, memoToSign?: AcpMemo) => {
if (
job.phase === AcpJobPhases.NEGOTIATION &&
(memoToSign?.nextPhase === AcpJobPhases.TRANSACTION ||
memoToSign?.nextPhase === AcpJobPhases.COMPLETED)
) {
console.log(`Paying for job ${job.id}`);
await job.payAndAcceptRequirement();
console.log(`Job ${job.id} paid`);
} else if (
job.phase === AcpJobPhases.TRANSACTION &&
memoToSign?.nextPhase === AcpJobPhases.REJECTED
) {
console.log(
`Signing job ${job.id} rejection memo, rejection reason: ${memoToSign?.content}`,
);
await memoToSign?.sign(true, "Accepts job rejection");
console.log(`Job ${job.id} rejection memo signed`);
} else if (job.phase === AcpJobPhases.COMPLETED) {
console.log(
`Job ${job.id} completed, received deliverable:`,
job.deliverable,
);
} else if (job.phase === AcpJobPhases.REJECTED) {
console.log(`Job ${job.id} rejected by seller`);
} else if (job.phase === AcpJobPhases.TRANSACTION) {
await memoToSign?.sign(true, "Accepts transaction memo");
}
},
});

console.log("Browsing for Munchy the Swapmaster agents...");
const relevantAgents = await acpClient.browseAgents("Munchy the Swapmaster", {
sortBy: [AcpAgentSort.SUCCESSFUL_JOB_COUNT],
topK: 5,
graduationStatus: AcpGraduationStatus.ALL,
onlineStatus: AcpOnlineStatus.ALL,
});

if (!relevantAgents || relevantAgents.length === 0) {
console.error("No agents found for 'cross chain transfer'");
process.exit(1);
}

console.log(`Found ${relevantAgents.length} agents`);

const chosenAgent = relevantAgents[0];
console.log(
`Chosen agent: ${chosenAgent.name || "Unknown"} (ID: ${chosenAgent.id})`,
);

if (!chosenAgent.jobOfferings || chosenAgent.jobOfferings.length === 0) {
console.error("Agent has no job offerings");
process.exit(1);
}

const chosenJobOffering = chosenAgent.jobOfferings.find(
(offering) => offering.name === "cross_chain_transfer",
);

if (!chosenJobOffering) {
console.error("Agent does not have 'cross_chain_transfer' offering");
process.exit(1);
}

console.log(`Initiating job with offering: ${chosenJobOffering.name}`);

const jobId = await chosenJobOffering.initiateJob(
{
symbol: "TEST",
sourceChain,
targetChain,
amount,
},
undefined, // evaluator address
new Date(Date.now() + 1000 * 60 * 15), // 15 minute expiry
);

console.log(`Job ${jobId} initiated successfully`);
}

buyer().catch((error) => {
console.error("Buyer error:", error);
process.exit(1);
});
37 changes: 37 additions & 0 deletions examples/acp-base/cc-swap/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import dotenv from "dotenv";
import { Address } from "viem";

dotenv.config({ path: __dirname + "/.env" });

function getEnvVar<T extends string = string>(key: string, required = true): T {
const value = process.env[key];
if (required && (value === undefined || value === "")) {
throw new Error(`${key} is not defined or is empty in the .env file`);
}
return value as T;
}

export const WHITELISTED_WALLET_PRIVATE_KEY = getEnvVar<Address>(
"WHITELISTED_WALLET_PRIVATE_KEY"
);

export const BUYER_AGENT_WALLET_ADDRESS = getEnvVar<Address>(
"BUYER_AGENT_WALLET_ADDRESS"
);

export const BUYER_ENTITY_ID = parseInt(getEnvVar("BUYER_ENTITY_ID"));

export const SELLER_AGENT_WALLET_ADDRESS = getEnvVar<Address>(
"SELLER_AGENT_WALLET_ADDRESS"
);

export const SELLER_ENTITY_ID = parseInt(getEnvVar("SELLER_ENTITY_ID"));

const entities = {
BUYER_ENTITY_ID,
SELLER_ENTITY_ID,
};

for (const [key, value] of Object.entries(entities)) {
if (isNaN(value)) throw new Error(`${key} must be a valid number`);
}
11 changes: 11 additions & 0 deletions examples/acp-base/cc-swap/jobTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type SupportedChain =
  | "base-sepolia"
  | "arbitrum-sepolia"
  | "polygon-amoy";

export type CrossChainTransferPayload = {
  symbol: string;
  sourceChain: SupportedChain;
  targetChain: SupportedChain;
  amount: number;
};
Loading
Loading