-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathbuyer.ts
More file actions
193 lines (178 loc) · 6.83 KB
/
buyer.ts
File metadata and controls
193 lines (178 loc) · 6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
* Subscription Example - Buyer (Client) using jobOffering.initiateJob
*
* This version uses `chosenJobOffering.initiateJob(...)` which automatically
* wraps the service requirement with offering metadata (name, priceValue, priceType).
*
* Run a specific scenario via --scenario flag:
* npx ts-node buyer.ts --scenario 1 # Subscription offering
* npx ts-node buyer.ts --scenario 2 # Non-subscription offering (fixed-price)
*
* Default: scenario 1
*
* Assumption:
* - chosenAgent.jobOfferings[0] is a subscription offering
* - chosenAgent.jobOfferings[1] is a non-subscription (fixed-price) offering
*/
import AcpClient, {
AcpContractClientV2,
AcpJobPhases,
AcpJob,
AcpMemo,
MemoType,
AcpAgentSort,
AcpGraduationStatus,
AcpOnlineStatus,
baseSepoliaAcpConfigV2,
} from "../../../src/index";
import {
BUYER_AGENT_WALLET_ADDRESS,
BUYER_ENTITY_ID,
WHITELISTED_WALLET_PRIVATE_KEY,
} from "./env";
// Subscription tier name — adjust to match your offering config
const SUBSCRIPTION_TIER = "sub_premium";
// Parse --scenario N from argv
const scenarioArg = process.argv.indexOf("--scenario");
const SCENARIO =
scenarioArg !== -1 ? parseInt(process.argv[scenarioArg + 1], 10) : 1;
async function buyer() {
console.log(`=== Subscription Example - Buyer (Scenario ${SCENARIO}) ===\n`);
const acpClient = new AcpClient({
acpContractClient: await AcpContractClientV2.build(
WHITELISTED_WALLET_PRIVATE_KEY,
BUYER_ENTITY_ID,
BUYER_AGENT_WALLET_ADDRESS,
baseSepoliaAcpConfigV2,
),
onNewTask: async (job: AcpJob, memoToSign?: AcpMemo) => {
console.log(
`Buyer: onNewTask - Job ${job.id}, phase: ${AcpJobPhases[job.phase]}, ` +
`memoToSign: ${memoToSign?.id ?? "None"}, ` +
`nextPhase: ${memoToSign?.nextPhase !== undefined ? AcpJobPhases[memoToSign.nextPhase] : "None"}`,
);
// Subscription payment requested (Scenario 1)
if (
job.phase === AcpJobPhases.NEGOTIATION &&
memoToSign?.type === MemoType.PAYABLE_REQUEST_SUBSCRIPTION
) {
console.log(
`Buyer: Job ${job.id} — Subscription payment requested: ${memoToSign.content}`,
);
console.log(
`Buyer: Job ${job.id} — Amount: ${memoToSign.payableDetails?.amount}`,
);
const { txnHash: subPayTx } = await job.paySubscription(
`Subscription payment for ${SUBSCRIPTION_TIER}`,
);
console.log(
`Buyer: Job ${job.id} — Subscription paid (tx: ${subPayTx})`,
);
// Requirement to proceed — two paths:
// - Active subscription (budget = 0): just sign the memo, no payment needed.
// - Fixed-price (budget > 0): approve budget and sign.
} else if (
job.phase === AcpJobPhases.NEGOTIATION &&
memoToSign?.type === MemoType.MESSAGE &&
memoToSign?.nextPhase === AcpJobPhases.TRANSACTION
) {
const isSubscriptionJob = job.price === 0;
if (isSubscriptionJob) {
console.log(
`Buyer: Job ${job.id} — Subscription active, advancing without payment`,
);
const { txnHash } = await job.acceptRequirement(
memoToSign,
"Subscription active, proceeding to delivery",
);
console.log(
`Buyer: Job ${job.id} — Advanced to TRANSACTION phase (tx: ${txnHash})`,
);
} else {
console.log(`Buyer: Job ${job.id} — Paying budget and advancing`);
const payResult =
await job.payAndAcceptRequirement("Payment for job");
console.log(
`Buyer: Job ${job.id} — Advanced to TRANSACTION phase (tx: ${payResult?.txnHash})`,
);
}
} else if (job.phase === AcpJobPhases.COMPLETED) {
const lastMemo = job.memos[job.memos.length - 1];
const completedTx = lastMemo?.signedTxHash ?? lastMemo?.txHash;
console.log(
`Buyer: Job ${job.id} — Completed (tx: ${completedTx})! Deliverable:`,
await job.getDeliverable(),
);
} else if (job.phase === AcpJobPhases.REJECTED) {
console.log(
`Buyer: Job ${job.id} — Rejected. Reason:`,
job.rejectionReason,
);
} else {
console.log(
`Buyer: Job ${job.id} — Unhandled event (phase: ${AcpJobPhases[job.phase]}, ` +
`memoType: ${memoToSign?.type !== undefined ? MemoType[memoToSign.type] : "None"}, ` +
`nextPhase: ${memoToSign?.nextPhase !== undefined ? AcpJobPhases[memoToSign.nextPhase] : "None"})`,
);
}
},
});
// Browse available agents
const relevantAgents = await acpClient.browseAgents("your-agent-keyword", {
sortBy: [AcpAgentSort.SUCCESSFUL_JOB_COUNT],
topK: 5,
graduationStatus: AcpGraduationStatus.ALL,
onlineStatus: AcpOnlineStatus.ALL,
showHiddenOfferings: true,
});
console.log("Relevant agents:", relevantAgents);
if (!relevantAgents || relevantAgents.length === 0) {
console.error("No agents found");
return;
}
// 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:
// - index 0: subscription offering
// - index 1: non-subscription (fixed-price) offering
const subscriptionOffering = chosenAgent.jobOfferings[0];
const fixedOffering = chosenAgent.jobOfferings[1];
switch (SCENARIO) {
case 1: {
const chosenJobOffering = subscriptionOffering;
const jobId = await chosenJobOffering.initiateJob(
// Requirement payload schema depends on your ACP service configuration.
// If your service requires fields, replace {} with the expected schema payload.
{},
undefined, // evaluator address, undefined fallback to empty address
new Date(Date.now() + 1000 * 60 * 15), // job expiry duration, minimum 5 minutes
SUBSCRIPTION_TIER,
);
console.log(
`Buyer: [Scenario 1 — Subscription Offering] Job ${jobId} initiated`,
);
break;
}
case 2: {
const chosenJobOffering = fixedOffering;
const jobId = await chosenJobOffering.initiateJob(
// Requirement payload schema depends on your ACP service configuration.
// If your service requires fields, replace {} with the expected schema payload.
{},
undefined, // evaluator address, undefined fallback to empty address
new Date(Date.now() + 1000 * 60 * 15), // job expiry duration, minimum 5 minutes
);
console.log(
`Buyer: [Scenario 2 — Fixed-Price Job] Job ${jobId} initiated`,
);
break;
}
default:
console.error(`Unknown scenario: ${SCENARIO}. Use --scenario 1 or 2.`);
process.exit(1);
}
}
buyer().catch((error) => {
console.error("Buyer error:", error);
process.exit(1);
});