Skip to content

Commit b4e4f48

Browse files
committed
feat: add polling mode examples
1 parent 6c31122 commit b4e4f48

File tree

5 files changed

+179
-198
lines changed

5 files changed

+179
-198
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
WHITELISTED_WALLET_PRIVATE_KEY=<whitelisted-wallet-private-key>
2+
BUYER_AGENT_WALLET_ADDRESS=<buyer-agent-wallet-address>
3+
BUYER_ENTITY_ID=<buyer-entity-id>
4+
5+
SELLER_AGENT_WALLET_ADDRESS=<seller-agent-wallet-address>
6+
SELLER_ENTITY_ID=<seller-entity-id>
7+
8+
EVALUATOR_AGENT_WALLET_ADDRESS=<evaluator-agent-wallet-address>
9+
EVALUATOR_ENTITY_ID=<evaluator-entity-id>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import time
2+
from datetime import datetime, timedelta
3+
4+
from dotenv import load_dotenv
5+
6+
from virtuals_acp import VirtualsACP, ACPJob, ACPJobPhase
7+
from virtuals_acp.env import EnvSettings
8+
9+
load_dotenv(override=True)
10+
11+
# --- Configuration for the job polling interval ---
12+
POLL_INTERVAL_SECONDS = 20
13+
14+
15+
# --------------------------------------------------
16+
17+
18+
def buyer():
19+
env = EnvSettings()
20+
acp = VirtualsACP(
21+
wallet_private_key=env.WHITELISTED_WALLET_PRIVATE_KEY,
22+
agent_wallet_address=env.BUYER_AGENT_WALLET_ADDRESS,
23+
entity_id=env.BUYER_ENTITY_ID,
24+
)
25+
print(f"Buyer ACP Initialized. Agent: {acp.agent_address}")
26+
27+
# Browse available agents based on a keyword and cluster name
28+
relevant_agents = acp.browse_agents(
29+
keyword="Molly",
30+
cluster="yang-mainnet-test",
31+
graduated=False,
32+
)
33+
print(f"Relevant agents: {relevant_agents}")
34+
35+
# Pick one of the agents based on your criteria (in this example we just pick the first one)
36+
chosen_agent = relevant_agents[0]
37+
38+
# Pick one of the service offerings based on your criteria (in this example we just pick the first one)
39+
chosen_job_offering = chosen_agent.offerings[0]
40+
41+
# 1. Initiate Job
42+
print(
43+
f"\nInitiating job with Seller: {chosen_agent.wallet_address}, Evaluator: {env.EVALUATOR_AGENT_WALLET_ADDRESS}")
44+
45+
job_id = chosen_job_offering.initiate_job(
46+
# <your_schema_field> can be found in your ACP Visualiser's "Edit Service" pop-up.
47+
# Reference: (./images/specify_requirement_toggle_switch.png)
48+
service_requirement={"<your_schema_field>": "Help me to generate a flower meme."},
49+
evaluator_address=env.EVALUATOR_AGENT_WALLET_ADDRESS,
50+
expired_at=datetime.now() + timedelta(days=1),
51+
)
52+
53+
print(f"Job {job_id} initiated")
54+
# 2. Wait for Seller's acceptance memo (which sets next_phase to TRANSACTION)
55+
print(f"\nWaiting for Seller to accept job {job_id}...")
56+
57+
while True:
58+
# wait for some time before checking job again
59+
time.sleep(POLL_INTERVAL_SECONDS)
60+
job: ACPJob = acp.get_job_by_onchain_id(job_id)
61+
print(f"Polling Job {job_id}: Current Phase: {job.phase.name}")
62+
63+
if job.phase == ACPJobPhase.NEGOTIATION:
64+
# Check if there's a memo that indicates next phase is TRANSACTION
65+
for memo in job.memos:
66+
if memo.next_phase == ACPJobPhase.TRANSACTION:
67+
print("Paying job", job_id)
68+
job.pay(job.price)
69+
elif job.phase == ACPJobPhase.REQUEST:
70+
print(f"Job {job_id} still in REQUEST phase. Waiting for seller...")
71+
elif job.phase == ACPJobPhase.EVALUATION:
72+
print(f"Job {job_id} is in EVALUATION. Waiting for evaluator's decision...")
73+
elif job.phase == ACPJobPhase.TRANSACTION:
74+
print(f"Job {job_id} is in TRANSACTION. Waiting for seller to deliver...")
75+
elif job.phase == ACPJobPhase.COMPLETED:
76+
print("Job completed", job)
77+
break
78+
elif job.phase == ACPJobPhase.REJECTED:
79+
print("Job rejected", job)
80+
break
81+
82+
print("\n--- Buyer Script Finished ---")
83+
84+
85+
if __name__ == "__main__":
86+
buyer()
Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
1-
import os
21
import time
32
from typing import List
3+
44
from dotenv import load_dotenv
55

66
from virtuals_acp import VirtualsACP, ACPJob, ACPJobPhase
7-
from virtuals_acp.configs import BASE_SEPOLIA_CONFIG
7+
from virtuals_acp.env import EnvSettings
88

99
load_dotenv(override=True)
1010

11-
EVALUATOR_PRIVATE_KEY = os.environ.get("WHITELISTED_WALLET_PRIVATE_KEY")
12-
EVALUATOR_WALLET_ADDRESS = os.environ.get("EVALUATOR_AGENT_WALLET_ADDRESS")
13-
11+
# --- Configuration for the job polling interval ---
1412
POLL_INTERVAL_SECONDS = 20
1513

16-
if not all([EVALUATOR_PRIVATE_KEY, EVALUATOR_WALLET_ADDRESS]):
17-
print("Error: Ensure EVALUATOR_PRIVATE_KEY and EVALUATOR_WALLET_ADDRESS are set.")
18-
exit(1)
1914

20-
def main():
21-
print("--- Evaluator Script (Simplified Direct Client Usage) ---")
22-
evaluator_acp = VirtualsACP(
23-
wallet_private_key=EVALUATOR_PRIVATE_KEY,
24-
agent_wallet_address=EVALUATOR_WALLET_ADDRESS,
25-
config=BASE_SEPOLIA_CONFIG
15+
# --------------------------------------------------
16+
17+
def evaluator():
18+
env = EnvSettings()
19+
20+
acp = VirtualsACP(
21+
wallet_private_key=env.WHITELISTED_WALLET_PRIVATE_KEY,
22+
agent_wallet_address=env.EVALUATOR_AGENT_WALLET_ADDRESS,
23+
entity_id=env.EVALUATOR_ENTITY_ID,
2624
)
27-
print(f"Evaluator ACP Initialized. Agent: {evaluator_acp.agent_address}")
25+
print(f"Evaluator ACP Initialized. Agent: {acp.agent_address}")
2826

29-
evaluated_deliverables = set() # Store (job_id, deliverable_memo_id) to avoid re-evaluation
27+
evaluated_deliverables = set() # Store (job_id, deliverable_memo_id) to avoid re-evaluation
3028

3129
while True:
32-
print(f"\nEvaluator: Polling for jobs assigned to {EVALUATOR_WALLET_ADDRESS} requiring evaluation...")
33-
active_jobs_list: List[ACPJob] = evaluator_acp.get_active_jobs()
30+
print(f"\nEvaluator: Polling for jobs assigned to {acp.agent_address} requiring evaluation...")
31+
active_jobs_list: List[ACPJob] = acp.get_active_jobs()
3432

3533
if not active_jobs_list:
3634
print("Evaluator: No active jobs found in this poll.")
@@ -39,52 +37,64 @@ def main():
3937

4038
for job_summary in active_jobs_list:
4139
onchain_job_id = job_summary.id
42-
40+
4341
try:
44-
job_details = evaluator_acp.get_job_by_onchain_id(onchain_job_id)
45-
current_phase = job_details.phase
46-
42+
job = acp.get_job_by_onchain_id(onchain_job_id)
43+
current_phase = job.phase
44+
45+
# Ensure this job is for the current evaluator
46+
if job.evaluator_address != acp.agent_address:
47+
continue
48+
4749
if current_phase == ACPJobPhase.EVALUATION:
4850
print(f"Evaluator: Found Job {onchain_job_id} in EVALUATION phase.")
49-
51+
5052
# Find the seller's deliverable memo. Its next_phase should be COMPLETED.
5153
seller_deliverable_memo_to_sign = None
52-
for memo in reversed(job_details.memos): # Check latest first
54+
for memo in reversed(job.memos): # Check latest first
5355
if ACPJobPhase(memo.next_phase) == ACPJobPhase.COMPLETED:
5456
seller_deliverable_memo_to_sign = memo
5557
break
56-
58+
5759
if seller_deliverable_memo_to_sign:
5860
deliverable_key = (onchain_job_id, seller_deliverable_memo_to_sign.id)
5961
if deliverable_key in evaluated_deliverables:
60-
# print(f"Deliverable memo {seller_deliverable_memo_to_sign.id} for job {onchain_job_id} already processed.")
62+
print(
63+
f"Deliverable memo {seller_deliverable_memo_to_sign.id} for job {onchain_job_id} already processed.")
6164
continue
6265

63-
print(f" Job {onchain_job_id}: Found deliverable memo {seller_deliverable_memo_to_sign.id} to evaluate.")
66+
print(
67+
f" Job {onchain_job_id}: Found deliverable memo {seller_deliverable_memo_to_sign.id} to evaluate.")
6468
print(f" Deliverable Content: {seller_deliverable_memo_to_sign.content}")
65-
69+
6670
# Simple evaluation logic: always accept
6771
accept_the_delivery = True
6872
evaluation_reason = "Deliverable looks great, approved!"
69-
73+
7074
print(f" Job {onchain_job_id}: Evaluating... Accepting: {accept_the_delivery}")
71-
evaluator_acp.evaluate_job_delivery(
75+
acp.evaluate_job_delivery(
7276
memo_id_of_deliverable=seller_deliverable_memo_to_sign.id,
7377
accept=accept_the_delivery,
7478
reason=evaluation_reason
7579
)
76-
print(f" Job {onchain_job_id}: Evaluation submitted for memo {seller_deliverable_memo_to_sign.id}.")
80+
print(
81+
f" Job {onchain_job_id}: Evaluation submitted for memo {seller_deliverable_memo_to_sign.id}.")
7782
evaluated_deliverables.add(deliverable_key)
7883
else:
79-
print(f" Job {onchain_job_id} in EVALUATION, but no deliverable memo (next_phase=COMPLETED) found yet.")
84+
print(
85+
f" Job {onchain_job_id} in EVALUATION, but no deliverable memo (next_phase=COMPLETED) found yet.")
86+
elif current_phase in [ACPJobPhase.REQUEST, ACPJobPhase.NEGOTIATION]:
87+
print(
88+
f"Evaluator: Job {onchain_job_id} is in {current_phase.name} phase. Waiting for job to be delivered.")
89+
continue
8090
elif current_phase in [ACPJobPhase.COMPLETED, ACPJobPhase.REJECTED]:
8191
print(f"Evaluator: Job {onchain_job_id} is already in {current_phase.name}. No action.")
82-
# Potentially add to a "handled" set if not using evaluated_deliverables
83-
92+
8493
except Exception as e:
8594
print(f"Evaluator: Error processing job {onchain_job_id}: {e}")
86-
95+
8796
time.sleep(POLL_INTERVAL_SECONDS)
8897

98+
8999
if __name__ == "__main__":
90-
main()
100+
evaluator()
Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,55 @@
1-
import os
2-
import time
31
import json
2+
import time
43
from typing import List
4+
55
from dotenv import load_dotenv
66

77
from virtuals_acp import VirtualsACP, ACPJob, ACPJobPhase
8-
from virtuals_acp.configs import BASE_SEPOLIA_CONFIG
8+
from virtuals_acp.env import EnvSettings
99

1010
load_dotenv(override=True)
1111

12-
SELLER_PRIVATE_KEY = os.environ.get("WHITELISTED_WALLET_PRIVATE_KEY")
13-
SELLER_WALLET_ADDRESS = os.environ.get("SELLER_AGENT_WALLET_ADDRESS")
12+
# --- Configuration for the job polling interval ---
13+
POLL_INTERVAL_SECONDS = 20
1414

15-
if not all([SELLER_PRIVATE_KEY, SELLER_WALLET_ADDRESS]):
16-
print("Error: Ensure SELLER_PRIVATE_KEY and SELLER_WALLET_ADDRESS are set.")
17-
exit(1)
1815

19-
DELIVERABLE_CONTENT = {
20-
"type": "url",
21-
"value": "https://virtuals.io/delivered_meme.png",
22-
"notes": "Your meme is ready!"
23-
}
16+
# --------------------------------------------------
2417

25-
POLL_INTERVAL_SECONDS = 20
18+
def seller():
19+
env = EnvSettings()
2620

27-
def main():
28-
print("--- Seller Script ---")
29-
seller_acp = VirtualsACP(
30-
wallet_private_key=SELLER_PRIVATE_KEY,
31-
agent_wallet_address=SELLER_WALLET_ADDRESS,
32-
config=BASE_SEPOLIA_CONFIG
21+
acp = VirtualsACP(
22+
wallet_private_key=env.WHITELISTED_WALLET_PRIVATE_KEY,
23+
agent_wallet_address=env.SELLER_AGENT_WALLET_ADDRESS,
24+
entity_id=env.SELLER_ENTITY_ID,
3325
)
34-
print(f"Seller ACP Initialized. Agent: {seller_acp.agent_address}")
26+
print(f"Seller ACP Initialized. Agent: {acp.agent_address}")
3527

3628
# Keep track of jobs to avoid reprocessing in this simple loop
3729
# job_id: {"responded_to_request": bool, "delivered_work": bool}
3830
processed_job_stages = {}
3931

4032
while True:
41-
print(f"\nSeller: Polling for active jobs for {SELLER_WALLET_ADDRESS}...")
42-
active_jobs_list: List[ACPJob] = seller_acp.get_active_jobs()
33+
print(f"\nSeller: Polling for active jobs for {env.SELLER_AGENT_WALLET_ADDRESS}...")
34+
active_jobs_list: List[ACPJob] = acp.get_active_jobs()
4335

4436
if not active_jobs_list:
4537
print("Seller: No active jobs found in this poll.")
4638
time.sleep(POLL_INTERVAL_SECONDS)
4739
continue
48-
49-
for job_summary in active_jobs_list:
50-
onchain_job_id = job_summary.id
40+
41+
for job in active_jobs_list:
42+
onchain_job_id = job.id
5143

5244
# Ensure this job is for the current seller
53-
if job_summary.provider_address != seller_acp.agent_address:
45+
if job.provider_address != acp.agent_address:
5446
continue
5547

5648
job_stages = processed_job_stages.get(onchain_job_id, {})
5749

5850
try:
5951
# Fetch full details to get current phase and memos
60-
job_details = seller_acp.get_job_by_onchain_id(onchain_job_id)
52+
job_details = acp.get_job_by_onchain_id(onchain_job_id)
6153
current_phase = job_details.phase
6254
print(f"Seller: Checking job {onchain_job_id}. Current Phase: {current_phase.name}")
6355

@@ -69,20 +61,21 @@ def main():
6961
if ACPJobPhase(memo.next_phase) == ACPJobPhase.NEGOTIATION:
7062
buyers_initial_memo_to_sign = memo
7163
break
72-
64+
7365
if buyers_initial_memo_to_sign:
74-
print(f"Seller: Job {onchain_job_id} is in REQUEST. Responding to buyer's memo {buyers_initial_memo_to_sign.id}...")
75-
seller_acp.respond_to_job_memo(
66+
print(
67+
f"Seller: Job {onchain_job_id} is in REQUEST. Responding to buyer's memo {buyers_initial_memo_to_sign.id}...")
68+
acp.respond_to_job_memo(
7669
job_id=onchain_job_id,
77-
memo_id=buyers_initial_memo_to_sign.id, # ID of the memo created by Buyer
70+
memo_id=buyers_initial_memo_to_sign.id, # ID of the memo created by Buyer
7871
accept=True,
7972
reason="Seller accepts the job offer."
8073
)
8174
print(f"Seller: Accepted job {onchain_job_id}. Job phase should move to NEGOTIATION.")
8275
job_stages["responded_to_request"] = True
8376
else:
8477
print(f"Seller: Job {onchain_job_id} in REQUEST, but could not find buyer's initial memo.")
85-
78+
8679
# 2. Submit Deliverable (if job is paid and not yet delivered)
8780
elif current_phase == ACPJobPhase.TRANSACTION and not job_stages.get("delivered_work"):
8881
# Buyer has paid, job is in TRANSACTION. Seller needs to deliver.
@@ -92,30 +85,35 @@ def main():
9285
if ACPJobPhase(memo.next_phase) == ACPJobPhase.EVALUATION:
9386
buyers_payment_confirmation_memo = memo
9487
break
95-
88+
9689
if buyers_payment_confirmation_memo:
9790
print(f"Seller: Job {onchain_job_id} is PAID (TRANSACTION phase). Submitting deliverable...")
98-
seller_acp.submit_job_deliverable(
91+
acp.submit_job_deliverable(
9992
job_id=onchain_job_id,
100-
deliverable_content=json.dumps(DELIVERABLE_CONTENT)
93+
deliverable_content=json.dumps({
94+
"type": "url",
95+
"value": "https://example.com"
96+
})
10197
)
10298
print(f"Seller: Deliverable submitted for job {onchain_job_id}. Job should move to EVALUATION.")
10399
job_stages["delivered_work"] = True
104100
else:
105-
print(f"Seller: Job {onchain_job_id} in TRANSACTION, but couldn't find buyer's payment memo to confirm.")
101+
print(
102+
f"Seller: Job {onchain_job_id} in TRANSACTION, but couldn't find buyer's payment memo to confirm.")
106103

107104
elif current_phase in [ACPJobPhase.EVALUATION, ACPJobPhase.COMPLETED, ACPJobPhase.REJECTED]:
108105
print(f"Seller: Job {onchain_job_id} is in {current_phase.name}. No further action for seller.")
109106
# Mark as fully handled for this script
110107
job_stages["responded_to_request"] = True
111108
job_stages["delivered_work"] = True
112-
109+
113110
processed_job_stages[onchain_job_id] = job_stages
114111

115112
except Exception as e:
116113
print(f"Seller: Error processing job {onchain_job_id}: {e}")
117-
114+
118115
time.sleep(POLL_INTERVAL_SECONDS)
119116

117+
120118
if __name__ == "__main__":
121-
main()
119+
seller()

0 commit comments

Comments
 (0)