acp-plugin-gamesdk 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,13 @@ from datetime import datetime, timedelta
2
2
  from typing import List, Optional
3
3
  from web3 import Web3
4
4
  import requests
5
-
6
5
  import sys
7
6
  import os
8
7
  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
9
8
  from .interface import AcpAgent, AcpJobPhases, AcpState
10
9
  from .acp_token import AcpToken, MemoType
10
+ import time
11
+
11
12
 
12
13
  class AcpClient:
13
14
  def __init__(self, api_key: str, acp_token: AcpToken):
@@ -17,23 +18,26 @@ class AcpClient:
17
18
  self.web3 = Web3()
18
19
 
19
20
  @property
20
- def wallet_address(self) -> str:
21
- return self.acp_token.get_wallet_address()
21
+ def agent_wallet_address(self) -> str:
22
+ return self.acp_token.get_agent_wallet_address()
22
23
 
23
24
  def get_state(self) -> AcpState:
24
25
  response = requests.get(
25
- f"{self.base_url}/states/{self.wallet_address}",
26
+ f"{self.base_url}/states/{self.agent_wallet_address}",
26
27
  headers={"x-api-key": self.api_key}
27
28
  )
28
29
  return response.json()
29
30
 
30
- def browse_agents(self, cluster: Optional[str] = None) -> List[AcpAgent]:
31
+ def browse_agents(self, cluster: Optional[str] = None, query: Optional[str] = None) -> List[AcpAgent]:
31
32
  url = "https://acpx.virtuals.gg/api/agents"
32
33
 
33
34
  params = {}
35
+ if query:
36
+ params["search"] = query
37
+
34
38
  if cluster:
35
39
  params["filters[cluster]"] = cluster
36
-
40
+
37
41
  response = requests.get(url, params=params)
38
42
 
39
43
  if response.status_code != 200:
@@ -41,35 +45,73 @@ class AcpClient:
41
45
 
42
46
  response_json = response.json()
43
47
 
44
- return [
45
- {
48
+ result = []
49
+
50
+ for agent in response_json.get("data", []):
51
+ result.append(
52
+ {
46
53
  "id": agent["id"],
47
54
  "name": agent["name"],
48
55
  "description": agent["description"],
49
56
  "walletAddress": agent["walletAddress"]
50
- }
51
- for agent in response_json.get("data", [])
52
- ]
57
+ }
58
+ )
59
+
60
+ return result
53
61
 
54
62
  def create_job(self, provider_address: str, price: float, job_description: str) -> int:
55
63
  expire_at = datetime.now() + timedelta(days=1)
56
64
 
57
65
  tx_result = self.acp_token.create_job(
58
66
  provider_address=provider_address,
67
+ evaluator_address=provider_address,
59
68
  expire_at=expire_at
60
69
  )
61
- job_id = tx_result["jobId"]
62
- memo_response = self.acp_token.create_memo(
63
- job_id=job_id,
64
- content=job_description,
65
- memo_type=MemoType.MESSAGE,
66
- is_secured=False,
67
- next_phase=AcpJobPhases.NEGOTIATION
68
- )
70
+ job_id = None
71
+ retry_count = 3
72
+ retry_delay = 3
73
+
74
+ time.sleep(retry_delay)
75
+ for attempt in range(retry_count):
76
+ try:
77
+ response = self.acp_token.await_transaction(tx_result["txHash"])
78
+ data = response.get("data", {})
79
+ if not data:
80
+ raise Exception("Invalid tx_hash!")
81
+
82
+ if (data.get("status") == "retry"):
83
+ raise Exception("Transaction failed, retrying...")
84
+
85
+ if (data.get("status") == "failed"):
86
+ break
87
+
88
+ if (data.get("status") == "success"):
89
+ job_id = data.get("result").get("jobId")
90
+
91
+ if (job_id is not None and job_id != ""):
92
+ break
93
+
94
+ except Exception as e:
95
+ print(f"Error creating job: {e}")
96
+ if attempt < retry_count - 1:
97
+ time.sleep(retry_delay)
98
+ else:
99
+ raise
100
+
101
+ if (job_id is None or job_id == ""):
102
+ raise Exception("Failed to create job")
103
+
104
+ self.acp_token.create_memo(
105
+ job_id=int(job_id),
106
+ content=job_description,
107
+ memo_type=MemoType.MESSAGE,
108
+ is_secured=False,
109
+ next_phase=AcpJobPhases.NEGOTIATION
110
+ )
69
111
 
70
112
  payload = {
71
- "jobId": job_id,
72
- "clientAddress": self.acp_token.get_wallet_address(),
113
+ "jobId": int(job_id),
114
+ "clientAddress": self.agent_wallet_address,
73
115
  "providerAddress": provider_address,
74
116
  "description": job_description,
75
117
  "price": price,
@@ -90,10 +132,12 @@ class AcpClient:
90
132
 
91
133
  def response_job(self, job_id: int, accept: bool, memo_id: int, reasoning: str):
92
134
  if accept:
93
- tx_hash = self.acp_token.sign_memo(memo_id, accept, reasoning)
135
+ self.acp_token.sign_memo(memo_id, accept, reasoning)
136
+
137
+ time.sleep(5)
94
138
 
95
139
  return self.acp_token.create_memo(
96
- job_id=job_id,
140
+ job_id=int(job_id),
97
141
  content=f"Job {job_id} accepted. {reasoning}",
98
142
  memo_type=MemoType.MESSAGE,
99
143
  is_secured=False,
@@ -101,7 +145,7 @@ class AcpClient:
101
145
  )
102
146
  else:
103
147
  return self.acp_token.create_memo(
104
- job_id=job_id,
148
+ job_id=int(job_id),
105
149
  content=f"Job {job_id} rejected. {reasoning}",
106
150
  memo_type=MemoType.MESSAGE,
107
151
  is_secured=False,
@@ -112,13 +156,15 @@ class AcpClient:
112
156
  # Convert amount to Wei (smallest ETH unit)
113
157
  amount_wei = self.web3.to_wei(amount, 'ether')
114
158
 
115
- tx_hash = self.acp_token.set_budget(job_id, amount_wei)
116
- approval_tx_hash = self.acp_token.approve_allowance(amount_wei)
159
+ self.acp_token.set_budget(job_id, amount_wei)
160
+ time.sleep(5)
161
+ self.acp_token.approve_allowance(amount_wei)
162
+ time.sleep(5)
117
163
  return self.acp_token.sign_memo(memo_id, True, reason)
118
164
 
119
165
  def deliver_job(self, job_id: int, deliverable: str, memo_id: int, reason: str):
120
166
  return self.acp_token.create_memo(
121
- job_id=job_id,
167
+ job_id=int(job_id),
122
168
  content=deliverable,
123
169
  memo_type=MemoType.MESSAGE,
124
170
  is_secured=False,
@@ -143,7 +189,7 @@ class AcpClient:
143
189
  }
144
190
 
145
191
  response = requests.post(
146
- f"{self.base_url}/{job_id}/tweets/{self.wallet_address}",
192
+ f"{self.base_url}/{job_id}/tweets/{self.agent_wallet_address}",
147
193
  json=payload,
148
194
  headers={
149
195
  "Accept": "application/json",
@@ -14,14 +14,14 @@ from .acp_token import AcpToken
14
14
  from .interface import AcpJobPhasesDesc, IInventory
15
15
 
16
16
  @dataclass
17
- class AdNetworkPluginOptions:
17
+ class AcpPluginOptions:
18
18
  api_key: str
19
19
  acp_token_client: AcpToken
20
20
  twitter_plugin: TwitterPlugin | GameTwitterPlugin = None
21
21
  cluster: Optional[str] = None
22
22
 
23
23
  class AcpPlugin:
24
- def __init__(self, options: AdNetworkPluginOptions):
24
+ def __init__(self, options: AcpPluginOptions):
25
25
  print("Initializing AcpPlugin")
26
26
  self.acp_client = AcpClient(options.api_key, options.acp_token_client)
27
27
 
@@ -50,7 +50,7 @@ class AcpPlugin:
50
50
  self.produced_inventory.append(item)
51
51
 
52
52
  def reset_state(self) -> None:
53
- self.acp_client.reset_state(self.acp_client.wallet_address)
53
+ self.acp_client.reset_state(self.acp_client.agent_wallet_address)
54
54
 
55
55
  def get_acp_state(self) -> Dict:
56
56
  server_state = self.acp_client.get_state()
@@ -100,11 +100,11 @@ class AcpPlugin:
100
100
  * phase: request (seller should response to accept/reject to the job) → pending_payment (as a buyer to make the payment for the service) → in_progress (seller to deliver the service) → evaluation → completed/rejected
101
101
  """
102
102
 
103
- def _search_agents_executable(self,reasoning: str) -> Tuple[FunctionResultStatus, str, dict]:
103
+ def _search_agents_executable(self,reasoning: str, keyword: str) -> Tuple[FunctionResultStatus, str, dict]:
104
104
  if not reasoning:
105
105
  return FunctionResultStatus.FAILED, "Reasoning for the search must be provided. This helps track your decision-making process for future reference.", {}
106
106
 
107
- agents = self.acp_client.browse_agents(self.cluster)
107
+ agents = self.acp_client.browse_agents(self.cluster, keyword)
108
108
 
109
109
  if not agents:
110
110
  return FunctionResultStatus.FAILED, "No other trading agents found in the system. Please try again later when more agents are available.", {}
@@ -126,7 +126,12 @@ class AcpPlugin:
126
126
  "name": "reasoning",
127
127
  "type": "string",
128
128
  "description": "Explain why you need to find trading partners at this time",
129
- }
129
+ },
130
+ {
131
+ "name": "keyword",
132
+ "type": "string",
133
+ "description": "Search for agents by name or description. Use this to find specific trading partners or products.",
134
+ },
130
135
  ],
131
136
  executable=self._search_agents_executable
132
137
  )
@@ -177,7 +182,6 @@ class AcpPlugin:
177
182
  return FunctionResultStatus.FAILED, "You already have an active job as a buyer", {}
178
183
 
179
184
  # ... Rest of validation logic ...
180
-
181
185
  job_id = self.acp_client.create_job(
182
186
  sellerWalletAddress,
183
187
  float(price),
@@ -188,7 +192,7 @@ class AcpPlugin:
188
192
  post_tweet_fn = self.twitter_plugin.get_function('post_tweet')
189
193
  tweet_id = post_tweet_fn(tweetContent, None).get('data', {}).get('id')
190
194
  if (tweet_id is not None):
191
- self.acp_client.add_tweet(job_id,tweet_id, tweetContent)
195
+ self.acp_client.add_tweet(job_id, tweet_id, tweetContent)
192
196
  print("Tweet has been posted")
193
197
 
194
198
  return FunctionResultStatus.DONE, json.dumps({
@@ -1,7 +1,6 @@
1
- import asyncio
2
1
  from enum import IntEnum
3
2
  import time
4
- from typing import Optional, Tuple, TypedDict, List
3
+ from typing import Optional, Tuple, TypedDict
5
4
  from datetime import datetime
6
5
  from web3 import Web3
7
6
  from eth_account import Account
@@ -9,6 +8,9 @@ import sys
9
8
  import os
10
9
  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
11
10
  from .acp_token_abi import ACP_TOKEN_ABI
11
+ import requests
12
+ from eth_account.messages import encode_defunct
13
+ import json
12
14
 
13
15
  class MemoType(IntEnum):
14
16
  MESSAGE = 0
@@ -44,94 +46,119 @@ class AcpToken:
44
46
  def __init__(
45
47
  self,
46
48
  wallet_private_key: str,
49
+ agent_wallet_address: str,
47
50
  network_url: str,
48
- contract_address: str = "0x5e4ee2620482f7c4fee12bf27b095e48d441f5cf",
49
- virtuals_token_address: str = "0xbfAB80ccc15DF6fb7185f9498d6039317331846a"
51
+ contract_address: str = "0x2422c1c43451Eb69Ff49dfD39c4Dc8C5230fA1e6",
52
+ virtuals_token_address: str = "0xbfAB80ccc15DF6fb7185f9498d6039317331846a",
53
+ base_url: str = "https://acpx.virtuals.gg/api"
50
54
  ):
51
55
  self.web3 = Web3(Web3.HTTPProvider(network_url))
52
56
  self.account = Account.from_key(wallet_private_key)
57
+ self.agent_wallet_address = agent_wallet_address
53
58
  self.contract_address = Web3.to_checksum_address(contract_address)
54
59
  self.virtuals_token_address = Web3.to_checksum_address(virtuals_token_address)
55
60
  self.contract = self.web3.eth.contract(
56
61
  address=self.contract_address,
57
62
  abi=ACP_TOKEN_ABI
58
63
  )
64
+ self.virtuals_token_contract = self.web3.eth.contract(
65
+ address=self.virtuals_token_address,
66
+ abi=[{
67
+ "inputs": [
68
+ {
69
+ "internalType": "address",
70
+ "name": "spender",
71
+ "type": "address"
72
+ },
73
+ {
74
+ "internalType": "uint256",
75
+ "name": "amount",
76
+ "type": "uint256"
77
+ }
78
+ ],
79
+ "name": "approve",
80
+ "outputs": [
81
+ {
82
+ "internalType": "bool",
83
+ "name": "",
84
+ "type": "bool"
85
+ }
86
+ ],
87
+ "stateMutability": "nonpayable",
88
+ "type": "function"
89
+ }]
90
+ )
91
+ self.base_url = base_url
92
+
93
+ def get_agent_wallet_address(self) -> str:
94
+ return self.agent_wallet_address
59
95
 
60
96
  def get_contract_address(self) -> str:
61
97
  return self.contract_address
62
98
 
63
- def get_wallet_address(self) -> str:
64
- return self.account.address
99
+ def await_transaction(self, hash_value: str) -> object:
100
+ try:
101
+ response = requests.post(f"{self.base_url}/acp-agent-wallets/trx-result", json={"userOpHash": hash_value})
102
+ return response.json()
103
+ except Exception as error:
104
+ print(f"Error getting job_id: {error}")
105
+ raise Exception("Failed to get job_id")
65
106
 
66
107
  def create_job(
67
108
  self,
68
109
  provider_address: str,
110
+ evaluator_address: str,
69
111
  expire_at: datetime
70
112
  ) -> dict:
71
113
  try:
72
114
  provider_address = Web3.to_checksum_address(provider_address)
73
115
  expire_timestamp = int(expire_at.timestamp())
74
-
75
- transaction = self.contract.functions.createJob(
76
- provider_address,
77
- expire_timestamp
78
- ).build_transaction({
79
- 'from': self.account.address,
80
- 'nonce': self.web3.eth.get_transaction_count(self.account.address),
81
- })
82
-
83
- signed_txn = self.web3.eth.account.sign_transaction(
84
- transaction,
85
- self.account.key
116
+
117
+ # Sign the transaction
118
+ trx_data, signature = self._sign_transaction(
119
+ "createJob",
120
+ [provider_address, evaluator_address, expire_timestamp]
86
121
  )
87
-
88
- tx_hash = self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
89
- receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
90
-
91
- # Get job ID from event logs
92
- job_created_event = self.contract.events.JobCreated().process_receipt(receipt)
93
- job_id = job_created_event[0]['args']['jobId']
94
-
95
- return {
96
- 'txHash': tx_hash.hex(),
97
- 'jobId': job_id
122
+
123
+ # Prepare payload
124
+ payload = {
125
+ "agentWallet": self.get_agent_wallet_address(),
126
+ "trxData": trx_data,
127
+ "signature": signature
98
128
  }
129
+
130
+ # Submit to custom API
131
+ api_url = f"{self.base_url}/acp-agent-wallets/transactions"
132
+ response = requests.post(api_url, json=payload)
133
+
134
+ # Return transaction hash or response ID
135
+ return { "txHash": response.json().get("data", {}).get("userOpHash", "")}
136
+
99
137
  except Exception as error:
100
138
  print(f"Error creating job: {error}")
101
139
  raise Exception("Failed to create job")
102
140
 
103
141
  def approve_allowance(self, price_in_wei: int) -> str:
104
142
  try:
105
- erc20_contract = self.web3.eth.contract(
106
- address=self.virtuals_token_address,
107
- abi=[{
108
- "inputs": [
109
- {"name": "spender", "type": "address"},
110
- {"name": "amount", "type": "uint256"}
111
- ],
112
- "name": "approve",
113
- "outputs": [{"name": "", "type": "bool"}],
114
- "stateMutability": "nonpayable",
115
- "type": "function"
116
- }]
143
+ trx_data, signature = self._sign_transaction(
144
+ "approve",
145
+ [self.contract_address, price_in_wei],
146
+ self.virtuals_token_address
117
147
  )
118
148
 
119
- transaction = erc20_contract.functions.approve(
120
- self.contract_address,
121
- price_in_wei
122
- ).build_transaction({
123
- 'from': self.account.address,
124
- 'nonce': self.web3.eth.get_transaction_count(self.account.address),
125
- })
149
+ payload = {
150
+ "agentWallet": self.get_agent_wallet_address(),
151
+ "trxData": trx_data,
152
+ "signature": signature
153
+ }
126
154
 
127
- signed_txn = self.web3.eth.account.sign_transaction(
128
- transaction,
129
- self.account.key
130
- )
131
- tx_hash = self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
132
- self.web3.eth.wait_for_transaction_receipt(tx_hash)
155
+ api_url = f"{self.base_url}/acp-agent-wallets/transactions"
156
+ response = requests.post(api_url, json=payload)
133
157
 
134
- return tx_hash.hex()
158
+ if (response.status_code != 200):
159
+ raise Exception("Failed to approve allowance")
160
+
161
+ return response.json()
135
162
  except Exception as error:
136
163
  print(f"Error approving allowance: {error}")
137
164
  raise Exception("Failed to approve allowance")
@@ -147,32 +174,24 @@ class AcpToken:
147
174
  retries = 3
148
175
  while retries > 0:
149
176
  try:
150
- transaction = self.contract.functions.createMemo(
151
- jobId = job_id,
152
- content = content,
153
- memoType = memo_type,
154
- isSecured = is_secured,
155
- nextPhase = next_phase
156
- ).build_transaction({
157
- 'from': self.account.address,
158
- 'nonce': self.web3.eth.get_transaction_count(self.account.address),
159
- })
160
-
161
- signed_txn = self.web3.eth.account.sign_transaction(
162
- transaction,
163
- self.account.key
177
+ trx_data, signature = self._sign_transaction(
178
+ "createMemo",
179
+ [job_id, content, memo_type, is_secured, next_phase]
164
180
  )
165
- tx_hash = self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
166
- receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
167
181
 
168
- # Get memo ID from event logs
169
- new_memo_event = self.contract.events.NewMemo().process_receipt(receipt)
170
- memo_id = new_memo_event[0]['args']['memoId']
171
-
172
- return {
173
- 'txHash': tx_hash.hex(),
174
- 'memoId': memo_id
182
+ payload = {
183
+ "agentWallet": self.get_agent_wallet_address(),
184
+ "trxData": trx_data,
185
+ "signature": signature
175
186
  }
187
+
188
+ api_url = f"{self.base_url}/acp-agent-wallets/transactions"
189
+ response = requests.post(api_url, json=payload)
190
+
191
+ if (response.status_code != 200):
192
+ raise Exception("Failed to create memo")
193
+
194
+ return { "txHash": response.json().get("txHash", response.json().get("id", "")), "memoId": response.json().get("memoId", "")}
176
195
  except Exception as error:
177
196
  print(f"Error creating memo: {error}")
178
197
  retries -= 1
@@ -180,6 +199,27 @@ class AcpToken:
180
199
 
181
200
  raise Exception("Failed to create memo")
182
201
 
202
+ def _sign_transaction(self, method_name: str, args: list, contract_address: Optional[str] = None) -> Tuple[dict, str]:
203
+ if contract_address:
204
+ encoded_data = self.virtuals_token_contract.encode_abi(method_name, args=args)
205
+ else:
206
+ encoded_data = self.contract.encode_abi(method_name, args=args)
207
+
208
+ trx_data = {
209
+ "target": contract_address if contract_address else self.get_contract_address(),
210
+ "value": "0",
211
+ "data": encoded_data
212
+ }
213
+
214
+ message_json = json.dumps(trx_data, separators=(",", ":"), sort_keys=False)
215
+ message_bytes = message_json.encode()
216
+
217
+ # Sign the transaction
218
+ message = encode_defunct(message_bytes)
219
+ signature = "0x" + self.account.sign_message(message).signature.hex()
220
+
221
+ return trx_data, signature
222
+
183
223
  def sign_memo(
184
224
  self,
185
225
  memo_id: int,
@@ -189,24 +229,25 @@ class AcpToken:
189
229
  retries = 3
190
230
  while retries > 0:
191
231
  try:
192
- transaction = self.contract.functions.signMemo(
193
- memo_id,
194
- is_approved,
195
- reason or ""
196
- ).build_transaction({
197
- 'from': self.account.address,
198
- 'nonce': self.web3.eth.get_transaction_count(self.account.address),
199
- })
200
-
201
- signed_txn = self.web3.eth.account.sign_transaction(
202
- transaction,
203
- self.account.key
232
+ trx_data, signature = self._sign_transaction(
233
+ "signMemo",
234
+ [memo_id, is_approved, reason]
204
235
  )
205
236
 
206
- tx_hash = self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
207
- self.web3.eth.wait_for_transaction_receipt(tx_hash)
237
+ payload = {
238
+ "agentWallet": self.get_agent_wallet_address(),
239
+ "trxData": trx_data,
240
+ "signature": signature
241
+ }
242
+
243
+ api_url = f"{self.base_url}/acp-agent-wallets/transactions"
244
+ response = requests.post(api_url, json=payload)
245
+
246
+ if (response.status_code != 200):
247
+ raise Exception("Failed to sign memo")
248
+
249
+ return response.json().get("txHash", response.json().get("id", ""))
208
250
 
209
- return tx_hash.hex()
210
251
  except Exception as error:
211
252
  print(f"Error signing memo: {error}")
212
253
  retries -= 1
@@ -216,22 +257,24 @@ class AcpToken:
216
257
 
217
258
  def set_budget(self, job_id: int, budget: int) -> str:
218
259
  try:
219
- transaction = self.contract.functions.setBudget(
220
- job_id,
221
- budget
222
- ).build_transaction({
223
- 'from': self.account.address,
224
- 'nonce': self.web3.eth.get_transaction_count(self.account.address),
225
- })
226
-
227
- signed_txn = self.web3.eth.account.sign_transaction(
228
- transaction,
229
- self.account.key
260
+ trx_data, signature = self._sign_transaction(
261
+ "setBudget",
262
+ [job_id, budget]
230
263
  )
231
- tx_hash = self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
232
- self.web3.eth.wait_for_transaction_receipt(tx_hash)
233
264
 
234
- return tx_hash.hex()
265
+ payload = {
266
+ "agentWallet": self.get_agent_wallet_address(),
267
+ "trxData": trx_data,
268
+ "signature": signature
269
+ }
270
+
271
+ api_url = f"{self.base_url}/acp-agent-wallets/transactions"
272
+ response = requests.post(api_url, json=payload)
273
+
274
+ if (response.status_code != 200):
275
+ raise Exception("Failed to set budget")
276
+
277
+ return response.json()
235
278
  except Exception as error:
236
279
  print(f"Error setting budget: {error}")
237
280
  raise Exception("Failed to set budget")
@@ -28,6 +28,25 @@ ACP_TOKEN_ABI = [
28
28
  "name": "SafeERC20FailedOperation",
29
29
  "type": "error",
30
30
  },
31
+ {
32
+ "anonymous": False,
33
+ "inputs": [
34
+ {
35
+ "indexed": True,
36
+ "internalType": "uint256",
37
+ "name": "jobId",
38
+ "type": "uint256",
39
+ },
40
+ {
41
+ "indexed": False,
42
+ "internalType": "uint256",
43
+ "name": "newBudget",
44
+ "type": "uint256",
45
+ },
46
+ ],
47
+ "name": "BudgetSet",
48
+ "type": "event",
49
+ },
31
50
  {
32
51
  "anonymous": False,
33
52
  "inputs": [
@@ -95,7 +114,7 @@ ACP_TOKEN_ABI = [
95
114
  "anonymous": False,
96
115
  "inputs": [
97
116
  {
98
- "indexed": True,
117
+ "indexed": False,
99
118
  "internalType": "uint256",
100
119
  "name": "jobId",
101
120
  "type": "uint256",
@@ -112,6 +131,12 @@ ACP_TOKEN_ABI = [
112
131
  "name": "provider",
113
132
  "type": "address",
114
133
  },
134
+ {
135
+ "indexed": True,
136
+ "internalType": "address",
137
+ "name": "evaluator",
138
+ "type": "address",
139
+ },
115
140
  ],
116
141
  "name": "JobCreated",
117
142
  "type": "event",
@@ -334,13 +359,6 @@ ACP_TOKEN_ABI = [
334
359
  "stateMutability": "view",
335
360
  "type": "function",
336
361
  },
337
- {
338
- "inputs": [{"internalType": "address", "name": "evaluator", "type": "address"}],
339
- "name": "addEvaluator",
340
- "outputs": [],
341
- "stateMutability": "nonpayable",
342
- "type": "function",
343
- },
344
362
  {
345
363
  "inputs": [
346
364
  {"internalType": "address", "name": "account", "type": "address"},
@@ -361,6 +379,7 @@ ACP_TOKEN_ABI = [
361
379
  {
362
380
  "inputs": [
363
381
  {"internalType": "address", "name": "provider", "type": "address"},
382
+ {"internalType": "address", "name": "evaluator", "type": "address"},
364
383
  {"internalType": "uint256", "name": "expiredAt", "type": "uint256"},
365
384
  ],
366
385
  "name": "createJob",
@@ -385,13 +404,6 @@ ACP_TOKEN_ABI = [
385
404
  "stateMutability": "nonpayable",
386
405
  "type": "function",
387
406
  },
388
- {
389
- "inputs": [],
390
- "name": "evaluatorCounter",
391
- "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
392
- "stateMutability": "view",
393
- "type": "function",
394
- },
395
407
  {
396
408
  "inputs": [],
397
409
  "name": "evaluatorFeeBP",
@@ -400,14 +412,11 @@ ACP_TOKEN_ABI = [
400
412
  "type": "function",
401
413
  },
402
414
  {
403
- "inputs": [{"internalType": "address", "name": "", "type": "address"}],
404
- "name": "evaluators",
405
- "outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
406
- "stateMutability": "view",
407
- "type": "function",
408
- },
409
- {
410
- "inputs": [{"internalType": "uint256", "name": "jobId", "type": "uint256"}],
415
+ "inputs": [
416
+ {"internalType": "uint256", "name": "jobId", "type": "uint256"},
417
+ {"internalType": "uint256", "name": "offset", "type": "uint256"},
418
+ {"internalType": "uint256", "name": "limit", "type": "uint256"},
419
+ ],
411
420
  "name": "getAllMemos",
412
421
  "outputs": [
413
422
  {
@@ -421,13 +430,13 @@ ACP_TOKEN_ABI = [
421
430
  {"internalType": "bool", "name": "isSecured", "type": "bool"},
422
431
  {"internalType": "uint8", "name": "nextPhase", "type": "uint8"},
423
432
  {"internalType": "uint256", "name": "jobId", "type": "uint256"},
424
- {"internalType": "uint8", "name": "numApprovals", "type": "uint8"},
425
433
  {"internalType": "address", "name": "sender", "type": "address"},
426
434
  ],
427
435
  "internalType": "struct InteractionLedger.Memo[]",
428
436
  "name": "",
429
437
  "type": "tuple[]",
430
438
  },
439
+ {"internalType": "uint256", "name": "total", "type": "uint256"},
431
440
  ],
432
441
  "stateMutability": "view",
433
442
  "type": "function",
@@ -436,6 +445,8 @@ ACP_TOKEN_ABI = [
436
445
  "inputs": [
437
446
  {"internalType": "uint256", "name": "jobId", "type": "uint256"},
438
447
  {"internalType": "uint8", "name": "phase", "type": "uint8"},
448
+ {"internalType": "uint256", "name": "offset", "type": "uint256"},
449
+ {"internalType": "uint256", "name": "limit", "type": "uint256"},
439
450
  ],
440
451
  "name": "getMemosForPhase",
441
452
  "outputs": [
@@ -450,13 +461,13 @@ ACP_TOKEN_ABI = [
450
461
  {"internalType": "bool", "name": "isSecured", "type": "bool"},
451
462
  {"internalType": "uint8", "name": "nextPhase", "type": "uint8"},
452
463
  {"internalType": "uint256", "name": "jobId", "type": "uint256"},
453
- {"internalType": "uint8", "name": "numApprovals", "type": "uint8"},
454
464
  {"internalType": "address", "name": "sender", "type": "address"},
455
465
  ],
456
466
  "internalType": "struct InteractionLedger.Memo[]",
457
467
  "name": "",
458
468
  "type": "tuple[]",
459
469
  },
470
+ {"internalType": "uint256", "name": "total", "type": "uint256"},
460
471
  ],
461
472
  "stateMutability": "view",
462
473
  "type": "function",
@@ -497,24 +508,16 @@ ACP_TOKEN_ABI = [
497
508
  },
498
509
  {
499
510
  "inputs": [
500
- {"internalType": "address", "name": "_providerRegistry", "type": "address"},
501
511
  {"internalType": "address", "name": "paymentTokenAddress", "type": "address"},
502
512
  {"internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256"},
503
- {"internalType": "uint8", "name": "numEvaluatorsPerJob_", "type": "uint8"},
504
- {"internalType": "uint8", "name": "minApprovals_", "type": "uint8"},
513
+ {"internalType": "uint256", "name": "platformFeeBP_", "type": "uint256"},
514
+ {"internalType": "address", "name": "platformTreasury_", "type": "address"},
505
515
  ],
506
516
  "name": "initialize",
507
517
  "outputs": [],
508
518
  "stateMutability": "nonpayable",
509
519
  "type": "function",
510
520
  },
511
- {
512
- "inputs": [{"internalType": "address", "name": "evaluator", "type": "address"}],
513
- "name": "isEvaluator",
514
- "outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
515
- "stateMutability": "view",
516
- "type": "function",
517
- },
518
521
  {
519
522
  "inputs": [
520
523
  {"internalType": "uint256", "name": "jobId", "type": "uint256"},
@@ -532,16 +535,6 @@ ACP_TOKEN_ABI = [
532
535
  "stateMutability": "view",
533
536
  "type": "function",
534
537
  },
535
- {
536
- "inputs": [
537
- {"internalType": "uint256", "name": "jobId", "type": "uint256"},
538
- {"internalType": "uint256", "name": "", "type": "uint256"},
539
- ],
540
- "name": "jobEvaluators",
541
- "outputs": [{"internalType": "address", "name": "evaluators", "type": "address"}],
542
- "stateMutability": "view",
543
- "type": "function",
544
- },
545
538
  {
546
539
  "inputs": [
547
540
  {"internalType": "uint256", "name": "jobId", "type": "uint256"},
@@ -565,7 +558,7 @@ ACP_TOKEN_ABI = [
565
558
  {"internalType": "uint8", "name": "phase", "type": "uint8"},
566
559
  {"internalType": "uint256", "name": "memoCount", "type": "uint256"},
567
560
  {"internalType": "uint256", "name": "expiredAt", "type": "uint256"},
568
- {"internalType": "uint8", "name": "evaluatorCount", "type": "uint8"},
561
+ {"internalType": "address", "name": "evaluator", "type": "address"},
569
562
  ],
570
563
  "stateMutability": "view",
571
564
  "type": "function",
@@ -577,13 +570,6 @@ ACP_TOKEN_ABI = [
577
570
  "stateMutability": "view",
578
571
  "type": "function",
579
572
  },
580
- {
581
- "inputs": [],
582
- "name": "minApprovals",
583
- "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
584
- "stateMutability": "view",
585
- "type": "function",
586
- },
587
573
  {
588
574
  "inputs": [],
589
575
  "name": "numEvaluatorsPerJob",
@@ -600,22 +586,16 @@ ACP_TOKEN_ABI = [
600
586
  },
601
587
  {
602
588
  "inputs": [],
603
- "name": "providerRegistry",
604
- "outputs": [
605
- {
606
- "internalType": "contract IServiceProviderRegistry",
607
- "name": "",
608
- "type": "address",
609
- },
610
- ],
589
+ "name": "platformFeeBP",
590
+ "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
611
591
  "stateMutability": "view",
612
592
  "type": "function",
613
593
  },
614
594
  {
615
- "inputs": [{"internalType": "address", "name": "evaluator", "type": "address"}],
616
- "name": "removeEvaluator",
617
- "outputs": [],
618
- "stateMutability": "nonpayable",
595
+ "inputs": [],
596
+ "name": "platformTreasury",
597
+ "outputs": [{"internalType": "address", "name": "", "type": "address"}],
598
+ "stateMutability": "view",
619
599
  "type": "function",
620
600
  },
621
601
  {
@@ -679,10 +659,18 @@ ACP_TOKEN_ABI = [
679
659
  {
680
660
  "inputs": [
681
661
  {"internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256"},
682
- {"internalType": "uint8", "name": "numEvaluatorsPerJob_", "type": "uint8"},
683
- {"internalType": "uint8", "name": "minApprovals_", "type": "uint8"},
684
662
  ],
685
- "name": "updateEvaluatorConfigs",
663
+ "name": "updateEvaluatorFee",
664
+ "outputs": [],
665
+ "stateMutability": "nonpayable",
666
+ "type": "function",
667
+ },
668
+ {
669
+ "inputs": [
670
+ {"internalType": "uint256", "name": "platformFeeBP_", "type": "uint256"},
671
+ {"internalType": "address", "name": "platformTreasury_", "type": "address"},
672
+ ],
673
+ "name": "updatePlatformFee",
686
674
  "outputs": [],
687
675
  "stateMutability": "nonpayable",
688
676
  "type": "function",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acp-plugin-gamesdk
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: ACP Plugin for Python SDK for GAME by Virtuals
5
5
  Author: Steven Lee Soon Fatt
6
6
  Author-email: steven@virtuals.io
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: aiohttp (>=3.11.14,<4.0.0)
15
- Requires-Dist: eth-account (>=0.13.5,<0.14.0)
15
+ Requires-Dist: eth-account (>=0.13.6,<0.14.0)
16
16
  Requires-Dist: eth-typing (>=5.2.0,<6.0.0)
17
17
  Requires-Dist: eth-utils (>=5.2.0,<6.0.0)
18
18
  Requires-Dist: game-sdk (>=0.1.5)
@@ -29,9 +29,12 @@ Description-Content-Type: text/markdown
29
29
  <summary>Table of Contents</summary>
30
30
 
31
31
  - [ACP Plugin](#acp-plugin)
32
+ - [Prerequisite](#prerequisite)
32
33
  - [Installation](#installation)
33
34
  - [Usage](#usage)
34
35
  - [Functions](#functions)
36
+ - [Tools](#tools)
37
+ - [Agent Registry](#agent-registry)
35
38
  - [Useful Resources](#useful-resources)
36
39
 
37
40
  </details>
@@ -49,8 +52,6 @@ Description-Content-Type: text/markdown
49
52
  > 1. **Evaluation phase** - In V1 of the ACP plugin, there is a possibility that deliverables from the job provider may not be fully passed on to the job poster due to incomplete evaluation.
50
53
  >
51
54
  > 2. **Wallet functionality** - Currently, you need to use your own wallet address and private key.
52
- >
53
- > 3. **Twitter Client** - Currently, the interactions between the agents would not be broadcasted on twitter - this is WIP. You can refer to the node ACP plugin to understand how the planned implementation would work.
54
55
  >
55
56
 
56
57
  The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions and jobs between agents. This ACP plugin manages:
@@ -68,6 +69,10 @@ The Agent Commerce Protocol (ACP) plugin is used to handle trading transactions
68
69
  - Post tweets and tag other agents for job requests
69
70
  - Respond to tweets from other agents
70
71
 
72
+ ## Prerequisite
73
+ ⚠️⚠️⚠️ Important: Before testing your agent’s services with a counterpart agent, you must register your agent with the [Service Registry](https://acp-staging.virtuals.io/).
74
+ This step is a critical precursor. Without registration, the counterpart agent will not be able to discover or interact with your agent.
75
+
71
76
  ## Installation
72
77
 
73
78
  From this directory (`acp`), run the installation:
@@ -90,24 +95,36 @@ poetry install
90
95
 
91
96
  3. Create and initialize an ACP instance by running:
92
97
 
93
- ```python
94
- acp_plugin = AcpPlugin(
95
- options=AdNetworkPluginOptions(
96
- api_key = "<your-GAME-dev-api-key-here>",
97
- acp_token_client = AcpToken(
98
- "<your-agent-wallet-private-key>",
99
- "<your-chain-here>"
100
- )
101
- )
102
- )
103
- ```
98
+ ```python
99
+ acp_plugin = AcpPlugin(
100
+ options = AcpPluginOptions(
101
+ api_key = "<your-GAME-dev-api-key-here>",
102
+ acp_token_client = AcpToken(
103
+ "<your-whitelisted-wallet-private-key>",
104
+ "<your-agent-wallet-address>",
105
+ "<your-chain-here>"
106
+ )
107
+ )
108
+ )
109
+ ```
110
+
104
111
  > Note:
105
112
  > - Your ACP token for your buyer and seller should be different.
106
113
  > - Speak to a DevRel (Celeste/John) to get a GAME Dev API key
107
114
 
115
+ > To Whitelist your Wallet:
116
+ > - Go to [Service Registry](https://acp-staging.virtuals.io/) page to whitelist your wallet.
117
+ > - Press the Agent Wallet page
118
+ > ![Agent Wallet Page](../../docs/imgs/agent-wallet-page.png)
119
+ > - Whitelist your wallet here:
120
+ > ![Whitelist Wallet](../../docs/imgs/whitelist-wallet.png)
121
+ > ![Whitelist Wallet](../../docs/imgs/whitelist-wallet-info.png)
122
+ > - This is where you can get your session entity key ID:
123
+ > ![Session Entity ID](../../docs/imgs/session-entity-id-location.png)
124
+
108
125
  4. (optional) If you want to use GAME's twitter client with the ACP plugin, you can initialize it by running:
109
126
  ```python
110
- options = {
127
+ twitter_client_options = {
111
128
  "id": "test_game_twitter_plugin",
112
129
  "name": "Test GAME Twitter Plugin",
113
130
  "description": "An example GAME Twitter Plugin for testing.",
@@ -117,14 +134,15 @@ options = {
117
134
  }
118
135
 
119
136
  acp_plugin = AcpPlugin(
120
- options=AdNetworkPluginOptions(
121
- api_key = "<your-GAME-dev-api-key-here>",
122
- acp_token_client = AcpToken(
123
- "<your-agent-wallet-private-key>",
124
- "<your-chain-here>"
125
- ),
126
- twitter_plugin=GameTwitterPlugin(options) # <--- This is the GAME's twitter client
127
- )
137
+ options = AcpPluginOptions(
138
+ api_key = "<your-GAME-dev-api-key-here>",
139
+ acp_token_client = AcpToken(
140
+ "<your-whitelisted-wallet-private-key>",
141
+ "<your-agent-wallet-address>",
142
+ "<your-chain-here>"
143
+ ),
144
+ twitter_plugin=GameTwitterPlugin(twitter_client_options) # <--- This is the GAME's twitter client
145
+ )
128
146
  )
129
147
  ```
130
148
 
@@ -192,6 +210,34 @@ This is a table of available functions that the ACP worker provides:
192
210
  | respond_job | Respond to a job. Used when you are looking to sell a product or service to another agent. |
193
211
  | pay_job | Pay for a job. Used when you are looking to pay for a job. |
194
212
  | deliver_job | Deliver a job. Used when you are looking to deliver a job. |
213
+ | reset_state | Resets the ACP plugin's internal state, clearing all active jobs. Useful for testing or when you need to start fresh. |
214
+
215
+ ## Tools
216
+
217
+ Some helper scripts are provided in the `tools` folder to help with the development of the SDK.
218
+ | Script | Description |
219
+ | ------------- | ------------- |
220
+ | reset_states.py | Resets the ACP plugin's internal state, clearing all active jobs for buyer and seller, based on their ACP tokens. Useful for testing or when you need to start fresh. |
221
+
222
+ ## Agent Registry
223
+
224
+ To register your agent, please head over to the [agent registry](https://acp-staging.virtuals.io/).
225
+
226
+ 1. Click on "Join ACP" button
227
+
228
+ <img src="../../docs/imgs/Join-acp.png" width="400" alt="ACP Agent Registry">
229
+
230
+ 2. Click on "Connect Wallet" button
231
+
232
+ <img src="../../docs/imgs/connect-wallet.png" width="400" alt="Connect Wallet">
233
+
234
+ 3. Register your agent there + include a service offering and a price (up to 5 max for now)
235
+
236
+ <img src="../../docs/imgs/register-agent.png" width="400" alt="Register Agent">
237
+
238
+ 4. For now, don't worry about what the actual price should be—there will be a way for us to help you change it, or eventually, you'll be able to change it yourself.
239
+
240
+ 5. Use a positive number (e.g., USD 1) when setting the arbitrary service offering rate.
195
241
 
196
242
  ## Useful Resources
197
243
 
@@ -0,0 +1,8 @@
1
+ acp_plugin_gamesdk/acp_client.py,sha256=FK-7M2ZvVwhMkz8Gv5AX6-1dmHjwFwOTjLLiIHsz5x0,7283
2
+ acp_plugin_gamesdk/acp_plugin.py,sha256=6FZ3v3SPzRJohElgKYkH63IrBMK0qLw0LnIr_kTzRm8,19972
3
+ acp_plugin_gamesdk/acp_token.py,sha256=JdxwyZonlEa71ik-dtw5EVYGUvNlIW-tkeeKb7htzug,11550
4
+ acp_plugin_gamesdk/acp_token_abi.py,sha256=nllh9xOuDXxFFdhLklTTdtZxWZd2LcUTBoOP2d9xDTA,22319
5
+ acp_plugin_gamesdk/interface.py,sha256=xNorCmjb9HZTOjVK5LJ8rvisgP4yUC8QoLEPCRstBtM,1255
6
+ acp_plugin_gamesdk-0.1.1.dist-info/METADATA,sha256=QO-zYSCN6fSSGjD73kc3JQCHhTfSg2xiPDLEO2-GxEo,9680
7
+ acp_plugin_gamesdk-0.1.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
8
+ acp_plugin_gamesdk-0.1.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- acp_plugin_gamesdk/acp_client.py,sha256=NcU3KNp4kukIygTAwqJOw85QkUPRf59N-VMKVvRM3GA,5756
2
- acp_plugin_gamesdk/acp_plugin.py,sha256=bsTdWIPL-IH_2vCvX3Eh4w1mqWGNSs-n6hj-MJMP_MI,19716
3
- acp_plugin_gamesdk/acp_token.py,sha256=D_t2DHbJNwYhA4gDbW2cnShfSlH5Wz42FWjm7jPHi2U,10126
4
- acp_plugin_gamesdk/acp_token_abi.py,sha256=m-_Pm1IGO2-DOuCqcxxaDnClVlnQ9xH7YI1lMiysomU,22870
5
- acp_plugin_gamesdk/interface.py,sha256=xNorCmjb9HZTOjVK5LJ8rvisgP4yUC8QoLEPCRstBtM,1255
6
- acp_plugin_gamesdk-0.1.0.dist-info/METADATA,sha256=QSL9d-xHJTYRHXRTKFizWG6TH2S2UimOl8dWWN2XJg0,7555
7
- acp_plugin_gamesdk-0.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
8
- acp_plugin_gamesdk-0.1.0.dist-info/RECORD,,