Skip to content

Commit f6848e8

Browse files
committed
feat: subscription
1 parent 2928b3d commit f6848e8

32 files changed

Lines changed: 2697 additions & 351 deletions

examples/acp-base/cc-swap/buyer.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import AcpClient, {
2+
AcpContractClientV2,
3+
AcpJobPhases,
4+
AcpJob,
5+
AcpMemo,
6+
AcpAgentSort,
7+
AcpGraduationStatus,
8+
AcpOnlineStatus,
9+
baseSepoliaAcpX402ConfigV2,
10+
AcpMemoState,
11+
} from "../../../src/index";
12+
import {
13+
BUYER_AGENT_WALLET_ADDRESS,
14+
BUYER_ENTITY_ID,
15+
WHITELISTED_WALLET_PRIVATE_KEY,
16+
} from "./env";
17+
import { arbitrumSepolia, baseSepolia, polygonAmoy } from "@account-kit/infra";
18+
import * as readline from "readline";
19+
import { SupportedChain } from "./jobTypes";
20+
21+
const SUPPORTED_CHAINS: Record<
22+
SupportedChain,
23+
{ chain: typeof baseSepolia | typeof arbitrumSepolia | typeof polygonAmoy }
24+
> = {
25+
"base-sepolia": { chain: baseSepolia },
26+
"arbitrum-sepolia": { chain: arbitrumSepolia },
27+
"polygon-amoy": { chain: polygonAmoy },
28+
};
29+
30+
const config = {
31+
...baseSepoliaAcpX402ConfigV2,
32+
chains: Object.entries(SUPPORTED_CHAINS)
33+
.filter(([key]) => key !== "base-sepolia")
34+
.map(([, { chain }]) => chain),
35+
};
36+
37+
async function promptUser(question: string): Promise<string> {
38+
const rl = readline.createInterface({
39+
input: process.stdin,
40+
output: process.stdout,
41+
});
42+
43+
return new Promise((resolve) => {
44+
rl.question(question, (answer) => {
45+
rl.close();
46+
resolve(answer.trim());
47+
});
48+
});
49+
}
50+
51+
async function buyer() {
52+
console.log("=== Munchy the Swapmaster - Buyer ===\n");
53+
console.log("Supported chains:");
54+
console.log(" 1. base-sepolia");
55+
console.log(" 2. arbitrum-sepolia");
56+
console.log(" 3. polygon-amoy\n");
57+
58+
const sourceChainInput = await promptUser(
59+
"Enter source chain (1-3 or name): ",
60+
);
61+
const targetChainInput = await promptUser(
62+
"Enter target chain (1-3 or name): ",
63+
);
64+
const amountInput = await promptUser("Enter amount to transfer: ");
65+
66+
const chainMap: Record<string, SupportedChain> = {
67+
"1": "base-sepolia",
68+
"2": "arbitrum-sepolia",
69+
"3": "polygon-amoy",
70+
"base-sepolia": "base-sepolia",
71+
"arbitrum-sepolia": "arbitrum-sepolia",
72+
"polygon-amoy": "polygon-amoy",
73+
};
74+
75+
const sourceChain = chainMap[sourceChainInput.toLowerCase()];
76+
const targetChain = chainMap[targetChainInput.toLowerCase()];
77+
const amount = amountInput;
78+
79+
if (!sourceChain || !SUPPORTED_CHAINS[sourceChain]) {
80+
console.error("Invalid source chain:", sourceChainInput);
81+
process.exit(1);
82+
}
83+
84+
if (!targetChain || !SUPPORTED_CHAINS[targetChain]) {
85+
console.error("Invalid target chain:", targetChainInput);
86+
process.exit(1);
87+
}
88+
89+
// if (isNaN(amount) || amount <= 0) {
90+
// console.error("Invalid amount:", amountInput);
91+
// process.exit(1);
92+
// }
93+
94+
console.log("\n=== Transfer Details ===");
95+
console.log("Source Chain:", sourceChain);
96+
console.log("Target Chain:", targetChain);
97+
console.log("Amount:", amount);
98+
console.log("Symbol: TEST\n");
99+
100+
const acpClient = new AcpClient({
101+
acpContractClient: await AcpContractClientV2.build(
102+
WHITELISTED_WALLET_PRIVATE_KEY,
103+
BUYER_ENTITY_ID,
104+
BUYER_AGENT_WALLET_ADDRESS,
105+
config,
106+
),
107+
onNewTask: async (job: AcpJob, memoToSign?: AcpMemo) => {
108+
if (
109+
job.phase === AcpJobPhases.NEGOTIATION &&
110+
(memoToSign?.nextPhase === AcpJobPhases.TRANSACTION ||
111+
memoToSign?.nextPhase === AcpJobPhases.COMPLETED)
112+
) {
113+
console.log(`Paying for job ${job.id}`);
114+
await job.payAndAcceptRequirement();
115+
console.log(`Job ${job.id} paid`);
116+
} else if (
117+
job.phase === AcpJobPhases.TRANSACTION &&
118+
memoToSign?.nextPhase === AcpJobPhases.REJECTED
119+
) {
120+
console.log(
121+
`Signing job ${job.id} rejection memo, rejection reason: ${memoToSign?.content}`,
122+
);
123+
await memoToSign?.sign(true, "Accepts job rejection");
124+
console.log(`Job ${job.id} rejection memo signed`);
125+
} else if (job.phase === AcpJobPhases.COMPLETED) {
126+
console.log(
127+
`Job ${job.id} completed, received deliverable:`,
128+
job.deliverable,
129+
);
130+
} else if (job.phase === AcpJobPhases.REJECTED) {
131+
console.log(`Job ${job.id} rejected by seller`);
132+
} else if (job.phase === AcpJobPhases.TRANSACTION) {
133+
await memoToSign?.sign(true, "Accepts transaction memo");
134+
}
135+
},
136+
});
137+
138+
console.log("Browsing for Munchy the Swapmaster agents...");
139+
const relevantAgents = await acpClient.browseAgents("Munchy the Swapmaster", {
140+
sortBy: [AcpAgentSort.SUCCESSFUL_JOB_COUNT],
141+
topK: 5,
142+
graduationStatus: AcpGraduationStatus.ALL,
143+
onlineStatus: AcpOnlineStatus.ALL,
144+
});
145+
146+
if (!relevantAgents || relevantAgents.length === 0) {
147+
console.error("No agents found for 'cross chain transfer'");
148+
process.exit(1);
149+
}
150+
151+
console.log(`Found ${relevantAgents.length} agents`);
152+
153+
const chosenAgent = relevantAgents[0];
154+
console.log(
155+
`Chosen agent: ${chosenAgent.name || "Unknown"} (ID: ${chosenAgent.id})`,
156+
);
157+
158+
if (!chosenAgent.jobOfferings || chosenAgent.jobOfferings.length === 0) {
159+
console.error("Agent has no job offerings");
160+
process.exit(1);
161+
}
162+
163+
const chosenJobOffering = chosenAgent.jobOfferings.find(
164+
(offering) => offering.name === "cross_chain_transfer",
165+
);
166+
167+
if (!chosenJobOffering) {
168+
console.error("Agent does not have 'cross_chain_transfer' offering");
169+
process.exit(1);
170+
}
171+
172+
console.log(`Initiating job with offering: ${chosenJobOffering.name}`);
173+
174+
const jobId = await chosenJobOffering.initiateJob(
175+
{
176+
symbol: "TEST",
177+
sourceChain,
178+
targetChain,
179+
amount,
180+
},
181+
undefined, // evaluator address
182+
new Date(Date.now() + 1000 * 60 * 15), // 15 minute expiry
183+
);
184+
185+
console.log(`Job ${jobId} initiated successfully`);
186+
}
187+
188+
buyer().catch((error) => {
189+
console.error("Buyer error:", error);
190+
process.exit(1);
191+
});

examples/acp-base/cc-swap/env.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import dotenv from "dotenv";
2+
import { Address } from "viem";
3+
4+
dotenv.config({ path: __dirname + "/.env" });
5+
6+
function getEnvVar<T extends string = string>(key: string, required = true): T {
7+
const value = process.env[key];
8+
if (required && (value === undefined || value === "")) {
9+
throw new Error(`${key} is not defined or is empty in the .env file`);
10+
}
11+
return value as T;
12+
}
13+
14+
export const WHITELISTED_WALLET_PRIVATE_KEY = getEnvVar<Address>(
15+
"WHITELISTED_WALLET_PRIVATE_KEY"
16+
);
17+
18+
export const BUYER_AGENT_WALLET_ADDRESS = getEnvVar<Address>(
19+
"BUYER_AGENT_WALLET_ADDRESS"
20+
);
21+
22+
export const BUYER_ENTITY_ID = parseInt(getEnvVar("BUYER_ENTITY_ID"));
23+
24+
export const SELLER_AGENT_WALLET_ADDRESS = getEnvVar<Address>(
25+
"SELLER_AGENT_WALLET_ADDRESS"
26+
);
27+
28+
export const SELLER_ENTITY_ID = parseInt(getEnvVar("SELLER_ENTITY_ID"));
29+
30+
const entities = {
31+
BUYER_ENTITY_ID,
32+
SELLER_ENTITY_ID,
33+
};
34+
35+
for (const [key, value] of Object.entries(entities)) {
36+
if (isNaN(value)) throw new Error(`${key} must be a valid number`);
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export type SupportedChain =
2+
  | "base-sepolia"
3+
  | "arbitrum-sepolia"
4+
  | "polygon-amoy";
5+
6+
export type CrossChainTransferPayload = {
7+
  symbol: string;
8+
  sourceChain: SupportedChain;
9+
  targetChain: SupportedChain;
10+
  amount: number;
11+
};

0 commit comments

Comments
 (0)