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
84 changes: 84 additions & 0 deletions examples/acp-base/cross-chain-transfer-service/buyer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import AcpClient, {
AcpContractClientV2,
AcpJobPhases,
AcpJob,
AcpMemo,
AcpAgentSort,
AcpGraduationStatus,
AcpOnlineStatus,
baseSepoliaAcpX402ConfigV2,
} from "../../../src/index";
import {
BUYER_AGENT_WALLET_ADDRESS,
WHITELISTED_WALLET_PRIVATE_KEY,
BUYER_ENTITY_ID,
} from "./env";

async function buyer() {
const acpClient = new AcpClient({
acpContractClient: await AcpContractClientV2.build(
WHITELISTED_WALLET_PRIVATE_KEY,
BUYER_ENTITY_ID,
BUYER_AGENT_WALLET_ADDRESS,
baseSepoliaAcpX402ConfigV2
),
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) {
// console.log(`Memo to sign ${memoToSign?.id} for job ${job.id}`);
await memoToSign?.sign(true, "Accepts transaction memo");
}
},
});

// Browse available agents based on a keyword
const relevantAgents = await acpClient.browseAgents("cross chain transfer", {
sort_by: [AcpAgentSort.SUCCESSFUL_JOB_COUNT],
top_k: 5,
graduationStatus: AcpGraduationStatus.ALL,
onlineStatus: AcpOnlineStatus.ALL,
// showHiddenOfferings: true,
});

console.log("Relevant agents:", relevantAgents);

// Pick one of the agents based on your criteria (in this example we just pick the first one)
const chosenAgent = relevantAgents[0];
// Pick one of the service offerings based on your criteria (in this example we just pick the first one)
const chosenJobOffering = chosenAgent.jobOfferings[1];

const jobId = await chosenJobOffering.initiateJob(
// <your-schema-field> can be found in your ACP Visualiser's "Edit Service" pop-up.
// Reference: (./images/specify_requirement_toggle_switch.png)
{},
undefined, // evaluator address, undefined fallback to empty address - skip-evaluation
new Date(Date.now() + 1000 * 60 * 15) // job expiry duration, minimum 5 minutes
);

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

buyer();
37 changes: 37 additions & 0 deletions examples/acp-base/cross-chain-transfer-service/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`);
}
94 changes: 94 additions & 0 deletions examples/acp-base/cross-chain-transfer-service/seller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Address } from "viem";
import AcpClient, {
AcpContractClientV2,
AcpJob,
AcpJobPhases,
AcpMemo,
Fare,
FareAmount,
MemoType,
baseSepoliaAcpX402ConfigV2,
} from "../../../src/index";
import {
SELLER_AGENT_WALLET_ADDRESS,
SELLER_ENTITY_ID,
WHITELISTED_WALLET_PRIVATE_KEY,
} from "./env";
import { bscTestnet } from "@account-kit/infra";

const REJECT_JOB = false;
const SOURCE_TOKEN_ADDRESS = "" as Address;
const TARGET_TOKEN_ADDRESS = "" as Address;
const TARGET_CHAIN = bscTestnet;

async function seller() {
new AcpClient({
acpContractClient: await AcpContractClientV2.build(
WHITELISTED_WALLET_PRIVATE_KEY,
SELLER_ENTITY_ID,
SELLER_AGENT_WALLET_ADDRESS,
baseSepoliaAcpX402ConfigV2
),
onNewTask: async (job: AcpJob, memoToSign?: AcpMemo) => {
if (
job.phase === AcpJobPhases.REQUEST &&
memoToSign?.nextPhase === AcpJobPhases.NEGOTIATION
) {
const response = true;
console.log(
`Responding to job ${job.id} with requirement`,
job.requirement
);
if (response) {
await job.accept("Job requirement matches agent capability");
const swappedToken = new FareAmount(
1,
await Fare.fromContractAddress(
SOURCE_TOKEN_ADDRESS,
baseSepoliaAcpX402ConfigV2,
TARGET_CHAIN.id
)
);

await job.createPayableRequirement(
"Requesting token from client on destination chain",
MemoType.PAYABLE_REQUEST,
swappedToken,
job.providerAddress
);
} else {
await job.reject("Job requirement does not meet agent capability");
}
console.log(`Job ${job.id} responded with ${response}`);
} else if (job.phase === AcpJobPhases.TRANSACTION) {
console.log("Delivering swapped token");

// to cater cases where agent decide to reject job after payment has been madep
if (REJECT_JOB) {
// conditional check for job rejection logic
const reason = "Job requirement does not meet agent capability";
console.log(`Rejecting job ${job.id} with reason: ${reason}`);
await job.reject(reason);
console.log(`Job ${job.id} rejected`);
return;
}

const swappedToken = new FareAmount(
1,
await Fare.fromContractAddress(
TARGET_TOKEN_ADDRESS,
baseSepoliaAcpX402ConfigV2,
TARGET_CHAIN.id
)
);

await job.deliverPayable(
"Delivered swapped token on destination chain",
swappedToken
);
}
},
});
}

seller();
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions src/abis/acpAbiV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,34 @@ const ACP_V2_ABI = [
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{ internalType: "uint256", name: "jobId", type: "uint256" },
{ internalType: "string", name: "content", type: "string" },
{ internalType: "address", name: "token", type: "address" },
{ internalType: "uint256", name: "amount", type: "uint256" },
{ internalType: "address", name: "recipient", type: "address" },
{ internalType: "uint256", name: "feeAmount", type: "uint256" },
{ internalType: "enum ACPTypes.FeeType", name: "feeType", type: "uint8" },
{
internalType: "enum ACPTypes.MemoType",
name: "memoType",
type: "uint8",
},
{ internalType: "uint256", name: "expiredAt", type: "uint256" },
{ internalType: "bool", name: "isSecured", type: "bool" },
{
internalType: "enum ACPTypes.JobPhase",
name: "nextPhase",
type: "uint8",
},
{ internalType: "uint32", name: "lzDstEid", type: "uint32" },
],
name: "createCrossChainPayableMemo",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{ internalType: "address", name: "provider", type: "address" },
Expand Down Expand Up @@ -551,6 +579,11 @@ const ACP_V2_ABI = [
type: "uint8",
},
{ internalType: "uint256", name: "expiredAt", type: "uint256" },
{
internalType: "enum ACPTypes.MemoState",
name: "state",
type: "uint8",
},
],
internalType: "struct ACPTypes.Memo[]",
name: "memos",
Expand Down Expand Up @@ -598,6 +631,11 @@ const ACP_V2_ABI = [
type: "uint8",
},
{ internalType: "uint256", name: "expiredAt", type: "uint256" },
{
internalType: "enum ACPTypes.MemoState",
name: "state",
type: "uint8",
},
],
internalType: "struct ACPTypes.Memo[]",
name: "memos",
Expand Down Expand Up @@ -641,6 +679,11 @@ const ACP_V2_ABI = [
type: "uint8",
},
{ internalType: "uint256", name: "expiredAt", type: "uint256" },
{
internalType: "enum ACPTypes.MemoState",
name: "state",
type: "uint8",
},
],
internalType: "struct ACPTypes.Memo[]",
name: "memos",
Expand Down
Loading
Loading