From 7a15d0bc0c1bdf8154a42c50b8fdad5153fb30eb Mon Sep 17 00:00:00 2001 From: Andrew Khor Date: Mon, 26 Jan 2026 17:37:07 +0800 Subject: [PATCH 1/2] feat: cross chain transfer service --- .../cross_chain_transfer_service/seller.py | 125 ++ virtuals_acp/abis/abi_v2.py | 367 +++--- virtuals_acp/abis/memo_manager.py | 1128 +++++++++++++++++ virtuals_acp/alchemy.py | 30 +- virtuals_acp/configs/configs.py | 7 +- .../contract_clients/base_contract_client.py | 45 +- .../contract_clients/contract_client.py | 3 + .../contract_clients/contract_client_v2.py | 20 +- virtuals_acp/fare.py | 19 +- virtuals_acp/job.py | 115 +- virtuals_acp/memo.py | 2 + virtuals_acp/models.py | 11 + virtuals_acp/utils.py | 17 + virtuals_acp/web3.py | 44 + 14 files changed, 1724 insertions(+), 209 deletions(-) create mode 100644 examples/acp_base/cross_chain_transfer_service/seller.py create mode 100644 virtuals_acp/abis/memo_manager.py create mode 100644 virtuals_acp/web3.py diff --git a/examples/acp_base/cross_chain_transfer_service/seller.py b/examples/acp_base/cross_chain_transfer_service/seller.py new file mode 100644 index 0000000..b2cf52c --- /dev/null +++ b/examples/acp_base/cross_chain_transfer_service/seller.py @@ -0,0 +1,125 @@ +import logging +import threading + +import sys +sys.path.append("../../../") + +from typing import Optional +from dotenv import load_dotenv + +from virtuals_acp.client import VirtualsACP +from virtuals_acp.configs.configs import BASE_SEPOLIA_ACP_X402_CONFIG_V2 +from virtuals_acp.contract_clients.contract_client_v2 import ACPContractClientV2 +from virtuals_acp.env import EnvSettings +from virtuals_acp.fare import Fare, FareAmount +from virtuals_acp.job import ACPJob +from virtuals_acp.memo import ACPMemo +from virtuals_acp.models import ACPJobPhase, ChainConfig, MemoType + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", +) +logger = logging.getLogger("SellerAgent") + +load_dotenv(override=True) + +REJECT_JOB_IN_REQUEST_PHASE = False +REJECT_JOB_IN_OTHER_PHASE = False +SOURCE_TOKEN_ADDRESS = "" +TARGET_TOKEN_ADDRESS = "" +TARGET_CHAIN_ID = 97 + +config = BASE_SEPOLIA_ACP_X402_CONFIG_V2 +config.chains = [ + ChainConfig( + chain_id=TARGET_CHAIN_ID, + rpc_url="https://bsc-testnet-dataseed.bnbchain.org" + ) +] + +def seller(): + env = EnvSettings() + + def on_new_task(job: ACPJob, memo_to_sign: Optional[ACPMemo] = None): + logger.info(f"[on_new_task] Received job {job.id} (phase: {job.phase})") + + if ( + job.phase == ACPJobPhase.REQUEST + and memo_to_sign is not None + and memo_to_sign.next_phase == ACPJobPhase.NEGOTIATION + ): + logger.info(f"Responding to job {job.id} with requirement: {job.requirement}") + if REJECT_JOB_IN_REQUEST_PHASE: + job.reject("Job requirement does not meet agent capability") + else: + job.accept("Job requirement matches agent capability") + + swappedToken = FareAmount( + 1, + Fare.from_contract_address( + TARGET_TOKEN_ADDRESS, + config, + TARGET_CHAIN_ID + ) + ) + + job.create_payable_requirement( + "Requesting token from client on destination chain", + MemoType.PAYABLE_REQUEST, + swappedToken, + job.client_address, + ) + + logger.info(f"Job {job.id} responded with {'rejected' if REJECT_JOB_IN_REQUEST_PHASE else 'accepted'}") + + elif ( + job.phase == ACPJobPhase.TRANSACTION + and memo_to_sign is not None + and memo_to_sign.next_phase == ACPJobPhase.EVALUATION + ): + # to cater cases where agent decide to reject job after payment has been made + if REJECT_JOB_IN_OTHER_PHASE: # conditional check for job rejection logic + reason = "Job requirement does not meet agent capability" + logger.info(f"Rejecting job {job.id} with reason: {reason}") + job.reject(reason) + logger.info(f"Job {job.id} rejected") + return + + deliverable = FareAmount( + 1, + Fare.from_contract_address( + TARGET_TOKEN_ADDRESS, + config, + TARGET_CHAIN_ID + ) + ) + logger.info(f"Delivering job {job.id} with deliverable {deliverable}") + # job.deliver(deliverable) + job.deliver_payable("Deliver swapped token on destination chain", deliverable) + logger.info(f"Job {job.id} delivered") + return + + elif job.phase == ACPJobPhase.COMPLETED: + logger.info(f"Job {job.id} completed") + + elif job.phase == ACPJobPhase.REJECTED: + logger.info(f"Job {job.id} rejected") + + VirtualsACP( + acp_contract_clients=ACPContractClientV2( + wallet_private_key=env.WHITELISTED_WALLET_PRIVATE_KEY, + agent_wallet_address=env.SELLER_AGENT_WALLET_ADDRESS, + entity_id=env.SELLER_ENTITY_ID, + config=config + ), + on_new_task=on_new_task + ) + + logger.info("Seller agent is running, waiting for new tasks...") + threading.Event().wait() + + +if __name__ == "__main__": + seller() diff --git a/virtuals_acp/abis/abi_v2.py b/virtuals_acp/abis/abi_v2.py index 0775470..90282e8 100644 --- a/virtuals_acp/abis/abi_v2.py +++ b/virtuals_acp/abis/abi_v2.py @@ -4,27 +4,27 @@ { "inputs": [ { "internalType": "address", "name": "account", "type": "address" }, - { "internalType": "bytes32", "name": "neededRole", "type": "bytes32" }, + { "internalType": "bytes32", "name": "neededRole", "type": "bytes32" } ], "name": "AccessControlUnauthorizedAccount", - "type": "error", + "type": "error" }, { "inputs": [{ "internalType": "address", "name": "target", "type": "address" }], "name": "AddressEmptyCode", - "type": "error", + "type": "error" }, { "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], "name": "AddressInsufficientBalance", - "type": "error", + "type": "error" }, { "inputs": [ - { "internalType": "address", "name": "implementation", "type": "address" }, + { "internalType": "address", "name": "implementation", "type": "address" } ], "name": "ERC1967InvalidImplementation", - "type": "error", + "type": "error" }, { "inputs": [], "name": "ERC1967NonPayable", "type": "error" }, { "inputs": [], "name": "EnforcedPause", "type": "error" }, @@ -36,13 +36,13 @@ { "inputs": [{ "internalType": "address", "name": "token", "type": "address" }], "name": "SafeERC20FailedOperation", - "type": "error", + "type": "error" }, { "inputs": [], "name": "UUPSUnauthorizedCallContext", "type": "error" }, { "inputs": [{ "internalType": "bytes32", "name": "slot", "type": "bytes32" }], "name": "UUPSUnsupportedProxiableUUID", - "type": "error", + "type": "error" }, { "anonymous": False, @@ -51,23 +51,23 @@ "indexed": True, "internalType": "uint256", "name": "accountId", - "type": "uint256", + "type": "uint256" }, { "indexed": True, "internalType": "address", "name": "client", - "type": "address", + "type": "address" }, { "indexed": True, "internalType": "address", "name": "provider", - "type": "address", - }, + "type": "address" + } ], "name": "AccountCreated", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -76,12 +76,12 @@ "indexed": True, "internalType": "uint256", "name": "accountId", - "type": "uint256", + "type": "uint256" }, - { "indexed": False, "internalType": "bool", "name": "isActive", "type": "bool" }, + { "indexed": False, "internalType": "bool", "name": "isActive", "type": "bool" } ], "name": "AccountStatusUpdated", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -90,23 +90,23 @@ "indexed": False, "internalType": "uint256", "name": "jobId", - "type": "uint256", + "type": "uint256" }, { "indexed": True, "internalType": "address", "name": "evaluator", - "type": "address", + "type": "address" }, { "indexed": False, "internalType": "uint256", "name": "evaluatorFee", - "type": "uint256", - }, + "type": "uint256" + } ], "name": "ClaimedEvaluatorFee", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -115,23 +115,23 @@ "indexed": False, "internalType": "uint256", "name": "jobId", - "type": "uint256", + "type": "uint256" }, { - "indexed": True, + "indexed": True , "internalType": "address", "name": "provider", - "type": "address", + "type": "address" }, { "indexed": False, "internalType": "uint256", "name": "providerFee", - "type": "uint256", - }, + "type": "uint256" + } ], "name": "ClaimedProviderFee", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -140,11 +140,11 @@ "indexed": False, "internalType": "uint64", "name": "version", - "type": "uint64", - }, + "type": "uint64" + } ], "name": "Initialized", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -153,23 +153,23 @@ "indexed": True, "internalType": "string", "name": "moduleType", - "type": "string", + "type": "string" }, { "indexed": True, "internalType": "address", "name": "oldModule", - "type": "address", + "type": "address" }, { "indexed": True, "internalType": "address", "name": "newModule", - "type": "address", - }, + "type": "address" + } ], "name": "ModuleUpdated", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -178,11 +178,11 @@ "indexed": False, "internalType": "address", "name": "account", - "type": "address", - }, + "type": "address" + } ], "name": "Paused", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -191,23 +191,23 @@ "indexed": False, "internalType": "uint256", "name": "jobId", - "type": "uint256", + "type": "uint256" }, { "indexed": True, "internalType": "address", "name": "client", - "type": "address", + "type": "address" }, { "indexed": False, "internalType": "uint256", "name": "amount", - "type": "uint256", - }, + "type": "uint256" + } ], "name": "RefundedBudget", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -217,17 +217,17 @@ "indexed": True, "internalType": "bytes32", "name": "previousAdminRole", - "type": "bytes32", + "type": "bytes32" }, { "indexed": True, "internalType": "bytes32", "name": "newAdminRole", - "type": "bytes32", - }, + "type": "bytes32" + } ], "name": "RoleAdminChanged", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -237,17 +237,17 @@ "indexed": True, "internalType": "address", "name": "account", - "type": "address", + "type": "address" }, { "indexed": True, "internalType": "address", "name": "sender", - "type": "address", - }, + "type": "address" + } ], "name": "RoleGranted", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -257,17 +257,17 @@ "indexed": True, "internalType": "address", "name": "account", - "type": "address", + "type": "address" }, { "indexed": True, "internalType": "address", "name": "sender", - "type": "address", - }, + "type": "address" + } ], "name": "RoleRevoked", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -276,11 +276,11 @@ "indexed": False, "internalType": "address", "name": "account", - "type": "address", - }, + "type": "address" + } ], "name": "Unpaused", - "type": "event", + "type": "event" }, { "anonymous": False, @@ -289,82 +289,110 @@ "indexed": True, "internalType": "address", "name": "implementation", - "type": "address", - }, + "type": "address" + } ], "name": "Upgraded", - "type": "event", + "type": "event" }, { "inputs": [], "name": "ADMIN_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "MODULE_MANAGER_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "OPERATOR_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "UPGRADE_INTERFACE_VERSION", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "accountManager", "outputs": [ - { "internalType": "contract IAccountManager", "name": "", "type": "address" }, + { "internalType": "contract IAccountManager", "name": "", "type": "address" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" }, - { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" } ], "name": "canSign", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "jobId", "type": "uint256" }], "name": "claimBudget", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "address", "name": "provider", "type": "address" }, - { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "createAccount", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "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": [ @@ -373,12 +401,12 @@ { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, { "internalType": "address", "name": "paymentToken", "type": "address" }, { "internalType": "uint256", "name": "budget", "type": "uint256" }, - { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "createJob", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ @@ -386,12 +414,12 @@ { "internalType": "address", "name": "evaluator", "type": "address" }, { "internalType": "uint256", "name": "budget", "type": "uint256" }, { "internalType": "address", "name": "paymentToken", "type": "address" }, - { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" } ], "name": "createJobWithAccount", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ @@ -400,19 +428,19 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "bool", "name": "isSecured", "type": "bool" }, { "internalType": "enum ACPTypes.JobPhase", "name": "nextPhase", - "type": "uint8", - }, + "type": "uint8" + } ], "name": "createMemo", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ @@ -426,20 +454,20 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, { "internalType": "bool", "name": "isSecured", "type": "bool" }, { "internalType": "enum ACPTypes.JobPhase", "name": "nextPhase", - "type": "uint8", - }, + "type": "uint8" + } ], "name": "createPayableMemo", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ @@ -448,12 +476,12 @@ { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, { "internalType": "address", "name": "paymentToken", "type": "address" }, { "internalType": "uint256", "name": "budget", "type": "uint256" }, - { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "createX402Job", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ @@ -461,36 +489,36 @@ { "internalType": "address", "name": "evaluator", "type": "address" }, { "internalType": "uint256", "name": "budget", "type": "uint256" }, { "internalType": "address", "name": "paymentToken", "type": "address" }, - { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" } ], "name": "createX402JobWithAccount", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [], "name": "defaultPaymentToken", "outputs": [{ "internalType": "contract IERC20", "name": "", "type": "address" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "emergencyWithdraw", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [], "name": "evaluatorFeeBP", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "accountId", "type": "uint256" }], @@ -507,23 +535,23 @@ { "internalType": "uint256", "name": "completedJobCount", - "type": "uint256", + "type": "uint256" }, - { "internalType": "bool", "name": "isActive", "type": "bool" }, + { "internalType": "bool", "name": "isActive", "type": "bool" } ], "internalType": "struct ACPTypes.Account", "name": "", - "type": "tuple", - }, + "type": "tuple" + } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "jobId", "type": "uint256" }, { "internalType": "uint256", "name": "offset", "type": "uint256" }, - { "internalType": "uint256", "name": "limit", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } ], "name": "getAllMemos", "outputs": [ @@ -536,7 +564,7 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, { "internalType": "bool", "name": "isApproved", "type": "bool" }, @@ -548,18 +576,23 @@ { "internalType": "enum ACPTypes.JobPhase", "name": "nextPhase", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } ], "internalType": "struct ACPTypes.Memo[]", "name": "memos", - "type": "tuple[]", + "type": "tuple[]" }, - { "internalType": "uint256", "name": "total", "type": "uint256" }, + { "internalType": "uint256", "name": "total", "type": "uint256" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ @@ -567,10 +600,10 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "offset", "type": "uint256" }, - { "internalType": "uint256", "name": "limit", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } ], "name": "getMemosForMemoType", "outputs": [ @@ -583,7 +616,7 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, { "internalType": "bool", "name": "isApproved", "type": "bool" }, @@ -595,25 +628,30 @@ { "internalType": "enum ACPTypes.JobPhase", "name": "nextPhase", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } ], "internalType": "struct ACPTypes.Memo[]", "name": "memos", - "type": "tuple[]", + "type": "tuple[]" }, - { "internalType": "uint256", "name": "total", "type": "uint256" }, + { "internalType": "uint256", "name": "total", "type": "uint256" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "jobId", "type": "uint256" }, { "internalType": "enum ACPTypes.JobPhase", "name": "phase", "type": "uint8" }, { "internalType": "uint256", "name": "offset", "type": "uint256" }, - { "internalType": "uint256", "name": "limit", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } ], "name": "getMemosForPhaseType", "outputs": [ @@ -626,7 +664,7 @@ { "internalType": "enum ACPTypes.MemoType", "name": "memoType", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, { "internalType": "bool", "name": "isApproved", "type": "bool" }, @@ -638,257 +676,262 @@ { "internalType": "enum ACPTypes.JobPhase", "name": "nextPhase", - "type": "uint8", + "type": "uint8" }, { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } ], "internalType": "struct ACPTypes.Memo[]", "name": "memos", - "type": "tuple[]", + "type": "tuple[]" }, - { "internalType": "uint256", "name": "total", "type": "uint256" }, + { "internalType": "uint256", "name": "total", "type": "uint256" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "getPhases", "outputs": [{ "internalType": "string[7]", "name": "", "type": "string[7]" }], "stateMutability": "pure", - "type": "function", + "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], "name": "getRoleAdmin", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "account", "type": "address" } ], "name": "grantRole", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "account", "type": "address" } ], "name": "hasRole", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "address", "name": "defaultPaymentToken_", - "type": "address", + "type": "address" }, { "internalType": "uint256", "name": "platformFeeBP_", "type": "uint256" }, { "internalType": "address", "name": "platformTreasury_", "type": "address" }, - { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" }, + { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" } ], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "jobId", "type": "uint256" }, - { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "account", "type": "address" } ], "name": "isJobEvaluator", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "jobManager", "outputs": [ - { "internalType": "contract IJobManager", "name": "", "type": "address" }, + { "internalType": "contract IJobManager", "name": "", "type": "address" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "memoManager", "outputs": [ - { "internalType": "contract IMemoManager", "name": "", "type": "address" }, + { "internalType": "contract IMemoManager", "name": "", "type": "address" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "pause", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [], "name": "paused", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "paymentManager", "outputs": [ - { "internalType": "contract IPaymentManager", "name": "", "type": "address" }, + { "internalType": "contract IPaymentManager", "name": "", "type": "address" } ], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "platformFeeBP", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "platformTreasury", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "proxiableUUID", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "callerConfirmation", "type": "address" }, + { "internalType": "address", "name": "callerConfirmation", "type": "address" } ], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "account", "type": "address" } ], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "jobId", "type": "uint256" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "setBudget", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "jobId", "type": "uint256" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "address", "name": "paymentToken", "type": "address" }, + { "internalType": "address", "name": "paymentToken", "type": "address" } ], "name": "setBudgetWithPaymentToken", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "memoId", "type": "uint256" }, { "internalType": "bool", "name": "isApproved", "type": "bool" }, - { "internalType": "string", "name": "reason", "type": "string" }, + { "internalType": "string", "name": "reason", "type": "string" } ], "name": "signMemo", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], "name": "supportsInterface", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", - "type": "function", + "type": "function" }, { "inputs": [], "name": "unpause", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "accountId", "type": "uint256" }, - { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "updateAccountMetadata", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ - { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" }, + { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" } ], "name": "updateEvaluatorFee", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "string", "name": "moduleType", "type": "string" }, - { "internalType": "address", "name": "moduleAddress", "type": "address" }, + { "internalType": "address", "name": "moduleAddress", "type": "address" } ], "name": "updateModule", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "platformFeeBP_", "type": "uint256" }, { "internalType": "address", "name": "platformTreasury_", "type": "address" }, - { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" }, + { "internalType": "uint256", "name": "evaluatorFeeBP_", "type": "uint256" } ], "name": "updatePlatformConfig", "outputs": [], "stateMutability": "nonpayable", - "type": "function", + "type": "function" }, { "inputs": [ { "internalType": "address", "name": "newImplementation", "type": "address" }, - { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } ], "name": "upgradeToAndCall", "outputs": [], "stateMutability": "payable", - "type": "function", - }, + "type": "function" + } ] custom_abi = [ diff --git a/virtuals_acp/abis/memo_manager.py b/virtuals_acp/abis/memo_manager.py new file mode 100644 index 0000000..25b4c01 --- /dev/null +++ b/virtuals_acp/abis/memo_manager.py @@ -0,0 +1,1128 @@ +MEMO_MANAGER_ABI = [ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { "inputs": [], "name": "AccessControlBadConfirmation", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "bytes32", "name": "neededRole", "type": "bytes32" } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "target", "type": "address" }], + "name": "AddressEmptyCode", + "type": "error" + }, + { "inputs": [], "name": "AlreadyVoted", "type": "error" }, + { "inputs": [], "name": "CannotApproveMemo", "type": "error" }, + { "inputs": [], "name": "CannotUpdateApprovedMemo", "type": "error" }, + { "inputs": [], "name": "CannotUpdateMemo", "type": "error" }, + { "inputs": [], "name": "CannotWithdrawYet", "type": "error" }, + { "inputs": [], "name": "DestinationChainNotConfigured", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "implementation", "type": "address" } + ], + "name": "ERC1967InvalidImplementation", + "type": "error" + }, + { "inputs": [], "name": "ERC1967NonPayable", "type": "error" }, + { "inputs": [], "name": "EmptyContent", "type": "error" }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { "inputs": [], "name": "InvalidInitialization", "type": "error" }, + { "inputs": [], "name": "InvalidMemoState", "type": "error" }, + { "inputs": [], "name": "InvalidMemoStateTransition", "type": "error" }, + { "inputs": [], "name": "InvalidMemoType", "type": "error" }, + { "inputs": [], "name": "JobAlreadyCompleted", "type": "error" }, + { "inputs": [], "name": "JobDoesNotExist", "type": "error" }, + { "inputs": [], "name": "MemoAlreadyApproved", "type": "error" }, + { "inputs": [], "name": "MemoAlreadyExecuted", "type": "error" }, + { "inputs": [], "name": "MemoAlreadySigned", "type": "error" }, + { "inputs": [], "name": "MemoCannotBeSigned", "type": "error" }, + { "inputs": [], "name": "MemoDoesNotExist", "type": "error" }, + { "inputs": [], "name": "MemoDoesNotRequireApproval", "type": "error" }, + { "inputs": [], "name": "MemoExpired", "type": "error" }, + { "inputs": [], "name": "MemoNotApproved", "type": "error" }, + { "inputs": [], "name": "MemoNotReadyToBeSigned", "type": "error" }, + { "inputs": [], "name": "MemoStateUnchanged", "type": "error" }, + { "inputs": [], "name": "NoAmountToTransfer", "type": "error" }, + { "inputs": [], "name": "NoPaymentAmount", "type": "error" }, + { "inputs": [], "name": "NotEscrowTransferMemoType", "type": "error" }, + { "inputs": [], "name": "NotInitializing", "type": "error" }, + { "inputs": [], "name": "NotPayableMemoType", "type": "error" }, + { "inputs": [], "name": "OnlyACPContract", "type": "error" }, + { "inputs": [], "name": "OnlyAssetManager", "type": "error" }, + { "inputs": [], "name": "OnlyClientOrProvider", "type": "error" }, + { "inputs": [], "name": "OnlyCounterParty", "type": "error" }, + { "inputs": [], "name": "OnlyEvaluator", "type": "error" }, + { "inputs": [], "name": "OnlyMemoSender", "type": "error" }, + { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, + { "inputs": [], "name": "UUPSUnauthorizedCallContext", "type": "error" }, + { + "inputs": [{ "internalType": "bytes32", "name": "slot", "type": "bytes32" }], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { "inputs": [], "name": "ZeroAcpContractAddress", "type": "error" }, + { "inputs": [], "name": "ZeroAddressRecipient", "type": "error" }, + { "inputs": [], "name": "ZeroAddressToken", "type": "error" }, + { "inputs": [], "name": "ZeroAssetManagerAddress", "type": "error" }, + { "inputs": [], "name": "ZeroJobManagerAddress", "type": "error" }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "address", + "name": "approver", + "type": "address" + }, + { "indexed": False, "internalType": "bool", "name": "approved", "type": "bool" }, + { + "indexed": False, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "MemoSigned", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "enum ACPTypes.MemoState", + "name": "oldState", + "type": "uint8" + }, + { + "indexed": False, + "internalType": "enum ACPTypes.MemoState", + "name": "newState", + "type": "uint8" + } + ], + "name": "MemoStateUpdated", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "uint256", + "name": "jobId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": False, + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { + "indexed": False, + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { + "indexed": False, + "internalType": "string", + "name": "content", + "type": "string" + } + ], + "name": "NewMemo", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "jobId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PayableFeeRefunded", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "jobId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PayableFundsRefunded", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "memoId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "uint256", + "name": "jobId", + "type": "uint256" + }, + { + "indexed": True, + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PayableMemoExecuted", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { "indexed": True, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { + "indexed": True, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": True, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { "indexed": True, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { "indexed": True, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "ACP_CONTRACT_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acpContract", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "bool", "name": "approved", "type": "bool" }, + { "internalType": "string", "name": "reason", "type": "string" } + ], + "name": "approveMemo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "assetManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "memoIds", "type": "uint256[]" }, + { "internalType": "bool", "name": "approved", "type": "bool" }, + { "internalType": "string", "name": "reason", "type": "string" } + ], + "name": "bulkApproveMemos", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "canApproveMemo", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "string", "name": "metadata", "type": "string" } + ], + "name": "createMemo", + "outputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { + "components": [ + { "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": "bool", "name": "isExecuted", "type": "bool" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint32", "name": "lzSrcEid", "type": "uint32" }, + { "internalType": "uint32", "name": "lzDstEid", "type": "uint32" } + ], + "internalType": "struct ACPTypes.PayableDetails", + "name": "payableDetails_", + "type": "tuple" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" } + ], + "name": "createPayableMemo", + "outputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "emergencyApproveMemo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "executePayableMemo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAssetManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "uint256", "name": "offset", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } + ], + "name": "getJobMemos", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct ACPTypes.Memo[]", + "name": "memoArray", + "type": "tuple[]" + }, + { "internalType": "uint256", "name": "total", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "enum ACPTypes.JobPhase", "name": "phase", "type": "uint8" }, + { "internalType": "uint256", "name": "offset", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } + ], + "name": "getJobMemosByPhase", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct ACPTypes.Memo[]", + "name": "memoArray", + "type": "tuple[]" + }, + { "internalType": "uint256", "name": "total", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "offset", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } + ], + "name": "getJobMemosByType", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct ACPTypes.Memo[]", + "name": "memoArray", + "type": "tuple[]" + }, + { "internalType": "uint256", "name": "total", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLocalEid", + "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "getMemo", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct ACPTypes.Memo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "getMemoApprovalStatus", + "outputs": [ + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "getMemoWithPayableDetails", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct ACPTypes.Memo", + "name": "memo", + "type": "tuple" + }, + { + "components": [ + { "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": "bool", "name": "isExecuted", "type": "bool" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint32", "name": "lzSrcEid", "type": "uint32" }, + { "internalType": "uint32", "name": "lzDstEid", "type": "uint32" } + ], + "internalType": "struct ACPTypes.PayableDetails", + "name": "details", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "getPayableDetails", + "outputs": [ + { + "components": [ + { "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": "bool", "name": "isExecuted", "type": "bool" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint32", "name": "lzSrcEid", "type": "uint32" }, + { "internalType": "uint32", "name": "lzDstEid", "type": "uint32" } + ], + "internalType": "struct ACPTypes.PayableDetails", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "acpContract_", "type": "address" }, + { "internalType": "address", "name": "jobManager_", "type": "address" }, + { "internalType": "address", "name": "paymentManager_", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "isJobEvaluator", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "isMemoSigner", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "isPayable", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "jobManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "jobMemos", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "enum ACPTypes.JobPhase", "name": "", "type": "uint8" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "jobMemosByPhase", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "enum ACPTypes.MemoType", "name": "", "type": "uint8" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "jobMemosByType", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "memoApprovals", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "memoCounter", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "memos", + "outputs": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "jobId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "string", "name": "content", "type": "string" }, + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "createdAt", "type": "uint256" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "address", "name": "approvedBy", "type": "address" }, + { "internalType": "uint256", "name": "approvedAt", "type": "uint256" }, + { "internalType": "bool", "name": "requiresApproval", "type": "bool" }, + { "internalType": "string", "name": "metadata", "type": "string" }, + { "internalType": "bool", "name": "isSecured", "type": "bool" }, + { + "internalType": "enum ACPTypes.JobPhase", + "name": "nextPhase", + "type": "uint8" + }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "enum ACPTypes.MemoState", "name": "state", "type": "uint8" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "payableDetails", + "outputs": [ + { "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": "bool", "name": "isExecuted", "type": "bool" }, + { "internalType": "uint256", "name": "expiredAt", "type": "uint256" }, + { "internalType": "uint32", "name": "lzSrcEid", "type": "uint32" }, + { "internalType": "uint32", "name": "lzDstEid", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paymentManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "callerConfirmation", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "requiredApprovals", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "requiresApproval", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ACPTypes.MemoType", + "name": "memoType", + "type": "uint8" + }, + { "internalType": "uint256", "name": "requiredApprovals_", "type": "uint256" } + ], + "name": "setApprovalRequirements", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "assetManager_", "type": "address" } + ], + "name": "setAssetManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "bool", "name": "isApproved", "type": "bool" }, + { "internalType": "string", "name": "reason", "type": "string" } + ], + "name": "signMemo", + "outputs": [{ "internalType": "uint256", "name": "jobId", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "acpContract_", "type": "address" }, + { "internalType": "address", "name": "jobManager_", "type": "address" }, + { "internalType": "address", "name": "paymentManager_", "type": "address" }, + { "internalType": "address", "name": "assetManager_", "type": "address" } + ], + "name": "updateContracts", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { "internalType": "string", "name": "newContent", "type": "string" } + ], + "name": "updateMemoContent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "memoId", "type": "uint256" }, + { + "internalType": "enum ACPTypes.MemoState", + "name": "newMemoState", + "type": "uint8" + } + ], + "name": "updateMemoState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newImplementation", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "memoId", "type": "uint256" }], + "name": "withdrawEscrowedFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; \ No newline at end of file diff --git a/virtuals_acp/alchemy.py b/virtuals_acp/alchemy.py index 8ca597e..2a6e644 100644 --- a/virtuals_acp/alchemy.py +++ b/virtuals_acp/alchemy.py @@ -11,7 +11,7 @@ from eth_utils.conversions import to_hex from virtuals_acp.configs.configs import BASE_SEPOLIA_CONFIG, ACPContractConfig -from virtuals_acp.models import OperationPayload +from virtuals_acp.models import ChainConfig, OperationPayload MAX_RETRIES = 10 @@ -101,6 +101,7 @@ def __init__( entity_id: int, owner_account: Account, chain_id: Optional[int] = None, + chains: Optional[List[ChainConfig]] = None, ): """ Initialize the Alchemy Account Kit @@ -114,6 +115,11 @@ def __init__( self.rpc_client = AlchemyRPCClient(config.alchemy_base_url) self.account_address = agent_wallet_address self.owner_account = owner_account + self.rpc_clients = {} + + if chains: + for chain in chains: + self.rpc_clients[chain.chain_id] = AlchemyRPCClient(f"{config.alchemy_base_url}?chainId={chain.chain_id}") self.permissions_context = self.create_session(entity_id) @@ -174,6 +180,7 @@ def prepare_calls( self, calls: List[OperationPayload], capabilities: Optional[Dict[str, Any]] = None, + chain_id: Optional[int] = None, ) -> Dict[str, Any]: if not self.account_address: raise ValueError("Must request account first") @@ -191,15 +198,18 @@ def prepare_calls( params = { "from": self.account_address, - "chainId": to_hex(self.chain_id), + "chainId": to_hex(chain_id or self.chain_id), "calls": [call.model_dump(exclude_none=True) for call in calls], "capabilities": final_capabilities, } - return self.rpc_client.wallet_prepare_calls(params) + if chain_id: + return self.rpc_clients[chain_id].wallet_prepare_calls(params) + else: + return self.rpc_client.wallet_prepare_calls(params) def send_prepared_calls( - self, prepare_calls_result: Dict[str, Any] + self, prepare_calls_result: Dict[str, Any], chain_id: Optional[int] = None ) -> Dict[str, Any]: if not self.permissions_context: raise ValueError("Must create session first") @@ -229,7 +239,10 @@ def send_prepared_calls( "permissions": {"context": self.permissions_context} } - return self.rpc_client.wallet_send_prepared_calls(send_prepared_calls_params) + if chain_id: + return self.rpc_clients[chain_id].wallet_send_prepared_calls(send_prepared_calls_params) + else: + return self.rpc_client.wallet_send_prepared_calls(send_prepared_calls_params) def wait_for_call_status(self, prepared_call_id: str) -> Dict[str, Any]: retries = MAX_RETRIES @@ -250,7 +263,7 @@ def wait_for_call_status(self, prepared_call_id: str) -> Dict[str, Any]: time.sleep(0.1 * (MAX_RETRIES - retries)) def handle_user_operation( - self, calls: List[OperationPayload], capabilities=None + self, calls: List[OperationPayload], capabilities=None, chain_id: Optional[int] = None ) -> Dict[str, Any]: if capabilities is None: capabilities = {} @@ -261,11 +274,10 @@ def handle_user_operation( additional_capabilities = {"maxFeePerGas": {"multiplier": 1.1}} capabilities.update(additional_capabilities) - prepare_result = self.prepare_calls(calls, capabilities) - while True: try: - result = self.send_prepared_calls(prepare_result) + prepare_result = self.prepare_calls(calls, capabilities, chain_id) + result = self.send_prepared_calls(prepare_result, chain_id) return self.wait_for_call_status(result["preparedCallIds"][0]) except Exception as e: retries -= 1 diff --git a/virtuals_acp/configs/configs.py b/virtuals_acp/configs/configs.py index 79699c1..5422368 100644 --- a/virtuals_acp/configs/configs.py +++ b/virtuals_acp/configs/configs.py @@ -1,15 +1,14 @@ # virtuals_acp/configs.py from web3 import Web3 -from typing import Literal, Optional, List, Dict, Any +from typing import Literal, Optional, List, Dict, Any, TypedDict from virtuals_acp.fare import Fare from virtuals_acp.abis.abi import ACP_ABI from virtuals_acp.abis.abi_v2 import ACP_V2_ABI -from virtuals_acp.models import X402Config +from virtuals_acp.models import ChainConfig, X402Config ChainEnv = Literal["base-sepolia", "base"] - class ACPContractConfig: def __init__( self, @@ -24,6 +23,7 @@ def __init__( abi: List[Dict[str, Any]], rpc_endpoint: Optional[str] = None, x402_config: Optional[X402Config] = None, + chains: Optional[List[ChainConfig]] = None, ): self.chain = chain self.rpc_url = rpc_url @@ -36,6 +36,7 @@ def __init__( self.abi = abi self.rpc_endpoint = rpc_endpoint self.x402_config = x402_config + self.chains = chains BASE_SEPOLIA_CONFIG = ACPContractConfig( diff --git a/virtuals_acp/contract_clients/base_contract_client.py b/virtuals_acp/contract_clients/base_contract_client.py index 40bfe51..6895fe1 100644 --- a/virtuals_acp/contract_clients/base_contract_client.py +++ b/virtuals_acp/contract_clients/base_contract_client.py @@ -37,6 +37,7 @@ def __init__(self, agent_wallet_address: str, config: ACPContractConfig): self.agent_wallet_address = Web3.to_checksum_address(agent_wallet_address) self.config = config self.w3 = Web3(Web3.HTTPProvider(config.rpc_url)) + self.public_clients: Dict[int, Web3] = {} self.chain = config.chain self.abi = config.abi @@ -145,7 +146,7 @@ def _build_user_operation( return {"to": target_address, "data": encoded_data} @abstractmethod - def handle_operation(self, trx_data: List[OperationPayload]) -> Dict[str, Any]: + def handle_operation(self, trx_data: List[OperationPayload], chain_id: Optional[int] = None) -> Dict[str, Any]: pass @abstractmethod @@ -231,10 +232,11 @@ def approve_allowance( self, amount_base_unit: int, payment_token_address: Optional[str] = None, + spender_address: Optional[str] = None, ) -> OperationPayload: operation = self._build_user_operation( "approve", - [self.config.contract_address, amount_base_unit], + [spender_address or self.config.contract_address, amount_base_unit], contract_address=payment_token_address, abi=ERC20_ABI, ) @@ -281,6 +283,45 @@ def create_payable_memo( to=operation["to"], ) + def create_cross_chain_payable_memo( + self, + job_id: int, + content: str, + token: str, + amount_base_unit: int, + recipient: str, + fee_amount_base_unit: int, + fee_type: FeeType, + memo_type: MemoType, + expired_at: datetime, + next_phase: ACPJobPhase, + destination_eid: int, + secured: bool = True, + ) -> OperationPayload: + operation = self._build_user_operation( + "createCrossChainPayableMemo", + [ + job_id, + content, + token, + amount_base_unit, + Web3.to_checksum_address(recipient), + fee_amount_base_unit, + fee_type, + memo_type, + math.floor(expired_at.timestamp()), + secured, + next_phase, + destination_eid, + ], + self.config.contract_address, + ) + + return OperationPayload( + data=operation["data"], + to=operation["to"], + ) + def create_memo( self, job_id: int, diff --git a/virtuals_acp/contract_clients/contract_client.py b/virtuals_acp/contract_clients/contract_client.py index 7d4401e..223730c 100644 --- a/virtuals_acp/contract_clients/contract_client.py +++ b/virtuals_acp/contract_clients/contract_client.py @@ -235,3 +235,6 @@ def perform_x402_request( return self.x402.perform_request(url, version, budget, signature) except Exception as e: raise ACPError("Failed to perform X402 request", e) + + def get_asset_manager_address(self) -> str: + raise ACPError("Not Supported") \ No newline at end of file diff --git a/virtuals_acp/contract_clients/contract_client_v2.py b/virtuals_acp/contract_clients/contract_client_v2.py index b310e87..ab0140d 100644 --- a/virtuals_acp/contract_clients/contract_client_v2.py +++ b/virtuals_acp/contract_clients/contract_client_v2.py @@ -7,6 +7,7 @@ from web3 import Web3 from virtuals_acp.abis.job_manager import JOB_MANAGER_ABI +from virtuals_acp.abis.memo_manager import MEMO_MANAGER_ABI from virtuals_acp.alchemy import AlchemyAccountKit from virtuals_acp.configs.configs import ACPContractConfig, BASE_MAINNET_CONFIG_V2 from virtuals_acp.contract_clients.base_contract_client import BaseAcpContractClient @@ -38,10 +39,14 @@ def __init__( entity_id=entity_id, owner_account=self.account, chain_id=config.chain_id, + chains=config.chains, ) self.w3 = Web3(Web3.HTTPProvider(config.rpc_url)) self.x402 = ACPX402(config, self.account, self.w3, self.agent_wallet_address, self.entity_id) + self.public_clients = {} + for c in config.chains: + self.public_clients[c.chain_id] = Web3(Web3.HTTPProvider(c.rpc_url)) def multicall_read( w3: Web3, contract_address: str, abi: list[Dict[str, Any]], calls: list[str] @@ -64,11 +69,15 @@ def multicall_read( raise ACPError("Failed to fetch sub-manager contract addresses") self.job_manager_address = Web3.to_checksum_address(job_manager) - self.job_manager_contract = self.w3.eth.contract( address=self.job_manager_address, abi=JOB_MANAGER_ABI ) + self.memo_manager_address = Web3.to_checksum_address(memo_manager) + self.memo_manager_contract = self.w3.eth.contract( + address=self.memo_manager_address, abi=MEMO_MANAGER_ABI + ) + self.validate_session_key_on_chain(self.account.address, self.entity_id) logger.info( @@ -92,8 +101,8 @@ def _get_random_nonce(self, bits: int = 152) -> int: random_bytes = secrets.token_bytes(bytes_len) return int.from_bytes(random_bytes, byteorder="big") - def handle_operation(self, trx_data: List[OperationPayload]) -> Dict[str, Any]: - return self.alchemy_kit.handle_user_operation(trx_data) + def handle_operation(self, trx_data: List[OperationPayload], chain_id: Optional[int] = None) -> Dict[str, Any]: + return self.alchemy_kit.handle_user_operation(trx_data, chain_id=chain_id) def get_job_id( self, response: Dict[str, Any], client_address: str, provider_address: str @@ -186,4 +195,7 @@ def get_x402_payment_details(self, job_id: int) -> AcpJobX402PaymentDetails: is_x402=result[0], is_budget_received=result[1] ) except Exception as e: - raise ACPError("Failed to get X402 payment details", e) \ No newline at end of file + raise ACPError("Failed to get X402 payment details", e) + + def get_asset_manager_address(self) -> str: + return self.memo_manager_contract.functions.assetManager().call() \ No newline at end of file diff --git a/virtuals_acp/fare.py b/virtuals_acp/fare.py index 85ded6a..a0f9156 100644 --- a/virtuals_acp/fare.py +++ b/virtuals_acp/fare.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from decimal import Decimal, ROUND_DOWN -from typing import Union +from typing import Optional, Union from web3 import Web3 from web3.contract import Contract @@ -13,9 +13,10 @@ class Fare: - def __init__(self, contract_address: str, decimals: int): + def __init__(self, contract_address: str, decimals: int, chain_id: Optional[int] = None): self.contract_address = Web3.to_checksum_address(contract_address) self.decimals = decimals + self.chain_id = chain_id def format_amount(self, amount: Union[int, float, Decimal]) -> int: """Convert to smallest unit (like parseUnits).""" @@ -24,14 +25,22 @@ def format_amount(self, amount: Union[int, float, Decimal]) -> int: @staticmethod def from_contract_address( - contract_address: str, config: "ACPContractConfig" + contract_address: str, config: "ACPContractConfig", chain_id: Optional[int] = None ) -> "Fare": if Web3.to_checksum_address(contract_address) == Web3.to_checksum_address( config.base_fare.contract_address ): return config.base_fare - w3 = Web3(Web3.HTTPProvider(config.rpc_url)) + rpc_url = config.rpc_url + + if chain_id and chain_id != config.chain_id: + rpc_url = next(c.rpc_url for c in config.chains if c.chain_id == chain_id) + + if not rpc_url: + raise ACPError(f"No RPC URL found for chain ID: {chain_id}") + + w3 = Web3(Web3.HTTPProvider(rpc_url)) erc20_abi = [ { @@ -47,7 +56,7 @@ def from_contract_address( address=Web3.to_checksum_address(contract_address), abi=erc20_abi ) decimals = contract.functions.decimals().call() - return Fare(contract_address, decimals) + return Fare(contract_address, decimals, chain_id) class FareAmountBase(ABC): diff --git a/virtuals_acp/job.py b/virtuals_acp/job.py index 8a6eba6..0c97adc 100644 --- a/virtuals_acp/job.py +++ b/virtuals_acp/job.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, ConfigDict, PrivateAttr from virtuals_acp.account import ACPAccount +from virtuals_acp.exceptions import ACPError from virtuals_acp.memo import ACPMemo from virtuals_acp.models import ( PriceType, @@ -14,6 +15,7 @@ RequestPayload, ) from virtuals_acp.utils import ( + get_destination_endpoint_id, try_parse_json_model, prepare_payload, get_txn_hash_from_response, @@ -26,6 +28,7 @@ FeeType, ) from virtuals_acp.fare import Fare, FareAmountBase, FareAmount +from virtuals_acp.web3 import getERC20Allowance, getERC20Balance, getERC20Symbol if TYPE_CHECKING: from virtuals_acp.client import VirtualsACP @@ -208,20 +211,37 @@ def create_payable_requirement( fee_amount = (FareAmount(0, self.base_fare)).amount fee_type = FeeType.NO_FEE - operations.append( - self.acp_contract_client.create_payable_memo( - job_id=self.id, - content=content, - amount_base_unit=amount.amount, - recipient=recipient, - fee_amount_base_unit=fee_amount, - fee_type=fee_type, - next_phase=ACPJobPhase.TRANSACTION, - memo_type=type, - expired_at=expired_at, - token=amount.fare.contract_address, + if amount.fare.chain_id != self.acp_contract_client.config.chain_id: + operations.append( + self.acp_contract_client.create_cross_chain_payable_memo( + job_id=self.id, + content=content, + amount_base_unit=amount.amount, + recipient=recipient, + fee_amount_base_unit=fee_amount, + fee_type=fee_type, + token=amount.fare.contract_address, + next_phase=ACPJobPhase.TRANSACTION, + memo_type=type, + expired_at=expired_at, + destination_eid=get_destination_endpoint_id(amount.fare.chain_id), + ) + ) + else: + operations.append( + self.acp_contract_client.create_payable_memo( + job_id=self.id, + content=content, + amount_base_unit=amount.amount, + recipient=recipient, + fee_amount_base_unit=fee_amount, + fee_type=fee_type, + next_phase=ACPJobPhase.TRANSACTION, + memo_type=type, + expired_at=expired_at, + token=amount.fare.contract_address, + ) ) - ) response = self.acp_contract_client.handle_operation(operations) return get_txn_hash_from_response(response) @@ -405,12 +425,6 @@ def _get_memo_by_id(self, memo_id) -> Optional[ACPMemo]: return next((m for m in self.memos if m.id == memo_id), None) def deliver(self, deliverable: DeliverablePayload) -> str | None: - if ( - self.latest_memo is None - or self.latest_memo.next_phase != ACPJobPhase.EVALUATION - ): - raise ValueError("No transaction memo found") - operations: List[OperationPayload] = [] operations.append( @@ -436,11 +450,8 @@ def deliver_payable( if expired_at is None: expired_at = datetime.now(timezone.utc) + timedelta(minutes=5) - if ( - self.latest_memo is None - or self.latest_memo.next_phase != ACPJobPhase.EVALUATION - ): - raise ValueError("No transaction memo found") + if amount.fare.chain_id != self.acp_contract_client.config.chain_id: + return self._deliver_cross_chain_payable(self.client_address, amount) operations: List[OperationPayload] = [] @@ -628,3 +639,59 @@ def perform_x402_payment(self, budget: float): # Optionally: handle exception, log, or re-raise print(f"An error occurred during perform_x402_payment: {e}") raise + + def _deliver_cross_chain_payable(self, client_address: str, amount: FareAmountBase) -> str | None: + if not amount.fare.chain_id: + raise ACPError("Chain ID is required for cross chain payable delivery") + + chain_id = amount.fare.chain_id + + token_balance = getERC20Balance( + self.acp_contract_client.public_clients[chain_id], + amount.fare.contract_address, + client_address, + ) + + if token_balance < amount.amount: + raise ACPError("Insufficient token balance for cross chain payable delivery") + + asset_manager_address = self.acp_contract_client.get_asset_manager_address() + + token_allowance = getERC20Allowance( + self.acp_contract_client.public_clients[chain_id], + amount.fare.contract_address, + client_address, + asset_manager_address + ) + + approve_allowance_op = self.acp_contract_client.approve_allowance( + amount.amount + token_allowance, + amount.fare.contract_address, + asset_manager_address + ) + + self.acp_contract_client.handle_operation([approve_allowance_op], chain_id) + + token_symbol = getERC20Symbol( + self.acp_contract_client.public_clients[chain_id], + amount.fare.contract_address, + ) + + create_memo_op = self.acp_contract_client.create_cross_chain_payable_memo( + job_id=self.id, + content=f"Performing cross chain transfer of {amount.amount} {token_symbol} to {client_address} on chain {chain_id}", + token=amount.fare.contract_address, + amount_base_unit=amount.amount, + recipient=client_address, + fee_amount_base_unit=0, + fee_type=FeeType.NO_FEE, + memo_type=MemoType.PAYABLE_TRANSFER, + expired_at=datetime.now(timezone.utc) + timedelta(minutes=5), + next_phase=ACPJobPhase.COMPLETED, + destination_eid=get_destination_endpoint_id(chain_id), + secured=True, + ) + + self.acp_contract_client.handle_operation([create_memo_op]) + + diff --git a/virtuals_acp/memo.py b/virtuals_acp/memo.py index 5087793..425b8fc 100644 --- a/virtuals_acp/memo.py +++ b/virtuals_acp/memo.py @@ -5,6 +5,7 @@ from virtuals_acp.models import ( ACPJobPhase, + ACPMemoState, MemoType, PayloadType, GenericPayload, @@ -33,6 +34,7 @@ class ACPMemo(BaseModel): payable_details: Optional[Dict[str, Any]] = None txn_hash: Optional[str] = None signed_txn_hash: Optional[str] = None + state: Optional[ACPMemoState] = None structured_content: Optional[GenericPayload] = None diff --git a/virtuals_acp/models.py b/virtuals_acp/models.py index 731ef07..78acc33 100644 --- a/virtuals_acp/models.py +++ b/virtuals_acp/models.py @@ -25,6 +25,13 @@ class ACPMemoStatus(str, Enum): REJECTED = "REJECTED" EXPIRED = "EXPIRED" +class ACPMemoState(int, Enum): + NONE = 0 + PENDING = 1 + IN_PROGRESS = 2 + READY = 3 + COMPLETED = 4 + REJECTED = 5 class MemoType(int, Enum): MESSAGE = 0 # Text message @@ -325,3 +332,7 @@ class OffChainJob(PayloadModel): class X402PaymentResponse(PayloadModel): isPaymentRequired: bool data: X402PayableRequirements + +class ChainConfig(BaseModel): + chain_id: int + rpc_url: Optional[str] = None \ No newline at end of file diff --git a/virtuals_acp/utils.py b/virtuals_acp/utils.py index f5571a2..b091420 100644 --- a/virtuals_acp/utils.py +++ b/virtuals_acp/utils.py @@ -59,3 +59,20 @@ def safe_base64_encode(data: Union[str, bytes]) -> str: else: data_bytes = data return base64.b64encode(data_bytes).decode("utf-8") + +def get_destination_endpoint_id(chain_id: int) -> int: + id_to_eid = { + 84532: 40245, # baseSepolia.id + 11155111: 40161, # sepolia.id + 80002: 40267, # polygonAmoy.id + 421614: 40231, # arbitrumSepolia.id + 97: 40102, # bscTestnet.id + 8453: 30184, # base.id + 1: 30101, # mainnet.id + 137: 30109, # polygon.id + 42161: 30110, # arbitrum.id + 56: 30102, # bsc.id + } + if chain_id in id_to_eid: + return id_to_eid[chain_id] + raise ValueError(f"Unsupported chain ID: {chain_id}") \ No newline at end of file diff --git a/virtuals_acp/web3.py b/virtuals_acp/web3.py new file mode 100644 index 0000000..a03ec6f --- /dev/null +++ b/virtuals_acp/web3.py @@ -0,0 +1,44 @@ +from typing import Union + +from web3 import Web3 +from virtuals_acp.abis.erc20_abi import ERC20_ABI + + +def getERC20Balance( + public_client: Web3, + contract_address: str, + wallet_address: str, +) -> int: + erc20_contract_instance = public_client.eth.contract( + address=contract_address, abi=ERC20_ABI + ) + + balance = erc20_contract_instance.functions.balanceOf(wallet_address).call() + + return balance + +def getERC20Allowance( + public_client: Web3, + contract_address: str, + wallet_address: str, + spender_address: str, +) -> int: + erc20_contract_instance = public_client.eth.contract( + address=contract_address, abi=ERC20_ABI + ) + + allowance = erc20_contract_instance.functions.allowance(wallet_address, spender_address).call() + + return allowance + +def getERC20Symbol( + public_client: Web3, + contract_address: str, +) -> str: + erc20_contract_instance = public_client.eth.contract( + address=contract_address, abi=ERC20_ABI + ) + + symbol = erc20_contract_instance.functions.symbol().call() + + return symbol \ No newline at end of file From 166d2f664970dc84109709c53cb92701620a0da0 Mon Sep 17 00:00:00 2001 From: Andrew Khor Date: Tue, 27 Jan 2026 10:58:02 +0800 Subject: [PATCH 2/2] feat: cross chain transfer service buyer example --- .../cross_chain_transfer_service/buyer.py | 114 ++++++++++++++++++ virtuals_acp/client.py | 6 + virtuals_acp/job.py | 55 ++++++++- virtuals_acp/utils.py | 19 ++- virtuals_acp/web3.py | 14 ++- 5 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 examples/acp_base/cross_chain_transfer_service/buyer.py diff --git a/examples/acp_base/cross_chain_transfer_service/buyer.py b/examples/acp_base/cross_chain_transfer_service/buyer.py new file mode 100644 index 0000000..0b718d6 --- /dev/null +++ b/examples/acp_base/cross_chain_transfer_service/buyer.py @@ -0,0 +1,114 @@ +import logging +import threading +import os +import sys + +# Add project root to Python path +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")) +sys.path.insert(0, project_root) + +from datetime import datetime, timedelta +from typing import Optional + +from dotenv import load_dotenv + +from virtuals_acp.client import VirtualsACP +from virtuals_acp.configs.configs import BASE_MAINNET_ACP_X402_CONFIG_V2, BASE_SEPOLIA_ACP_X402_CONFIG_V2 +from virtuals_acp.contract_clients.contract_client_v2 import ACPContractClientV2 +from virtuals_acp.env import EnvSettings +from virtuals_acp.job import ACPJob +from virtuals_acp.memo import ACPMemo +from virtuals_acp.models import ( + ACPAgentSort, + ACPJobPhase, + ACPGraduationStatus, + ACPOnlineStatus, + ChainConfig +) + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", +) +logger = logging.getLogger("BuyerAgent") + +load_dotenv(override=True) + +TARGET_CHAIN_ID = 97 + +config = BASE_SEPOLIA_ACP_X402_CONFIG_V2 +config.chains = [ + ChainConfig( + chain_id=TARGET_CHAIN_ID, + rpc_url="https://bsc-testnet-dataseed.bnbchain.org" + ) +] + + +def buyer(): + env = EnvSettings() + + def on_new_task(job: ACPJob, memo_to_sign: Optional[ACPMemo] = None): + if ( + job.phase == ACPJobPhase.NEGOTIATION + and memo_to_sign is not None + and memo_to_sign.next_phase == ACPJobPhase.TRANSACTION + ): + logger.info(f"Paying for job {job.id}") + job.pay_and_accept_requirement() + logger.info(f"Job {job.id} paid") + + elif ( + job.phase == ACPJobPhase.TRANSACTION + and memo_to_sign is not None + and memo_to_sign.next_phase == ACPJobPhase.REJECTED + ): + logger.info(f"Signing job {job.id} rejection memo, rejection reason: {memo_to_sign.content}") + memo_to_sign.sign(True, "Accepts job rejection") + logger.info(f"Job {job.id} rejection memo signed") + + elif job.phase == ACPJobPhase.COMPLETED: + logger.info(f"Job {job.id} completed, received deliverable: {job.deliverable}") + + elif job.phase == ACPJobPhase.REJECTED: + logger.info(f"Job {job.id} rejected by seller") + + acp_client = VirtualsACP( + acp_contract_clients=ACPContractClientV2( + wallet_private_key=env.WHITELISTED_WALLET_PRIVATE_KEY, + agent_wallet_address=env.BUYER_AGENT_WALLET_ADDRESS, + entity_id=env.BUYER_ENTITY_ID, + config=config, # route to x402 for payment, undefined defaulted back to direct transfer + ), + on_new_task=on_new_task + ) + + # Browse available agents based on a keyword + relevant_agents = acp_client.browse_agents( + keyword="cross chain transfer service", + sort_by=[ACPAgentSort.SUCCESSFUL_JOB_COUNT], + top_k=5, + graduation_status=ACPGraduationStatus.ALL, + online_status=ACPOnlineStatus.ALL, + show_hidden_offerings=True, + ) + logger.info(f"Relevant agents: {relevant_agents}") + + # Pick one of the agents based on your criteria (in this example we just pick the first one) + chosen_agent = relevant_agents[0] + # Pick one of the service offerings based on your criteria (in this example we just pick the first one) + chosen_job_offering = chosen_agent.job_offerings[1] + + job_id = chosen_job_offering.initiate_job( + service_requirement={}, + expired_at=datetime.now() + timedelta(minutes=5), # job expiry duration, minimum 3 minutes + ) + logger.info(f"Job {job_id} initiated") + logger.info("Listening for next steps...") + + threading.Event().wait() + + +if __name__ == "__main__": + buyer() diff --git a/virtuals_acp/client.py b/virtuals_acp/client.py index bf4e966..79daa1f 100644 --- a/virtuals_acp/client.py +++ b/virtuals_acp/client.py @@ -31,6 +31,7 @@ ACPAgentSort, ACPJobPhase, ACPGraduationStatus, + ACPMemoState, ACPOnlineStatus, MemoType, IACPAgent, @@ -160,6 +161,7 @@ def handle_new_task(self, data) -> None: payable_details=memo.get("payableDetails"), txn_hash=memo.get("txHash"), signed_txn_hash=memo.get("signedTxHash"), + state=ACPMemoState(memo.get("state")), ) for memo in data["memos"] ] @@ -214,6 +216,7 @@ def handle_evaluate(self, data) -> None: payable_details=memo.get("payableDetails"), txn_hash=memo.get("txHash"), signed_txn_hash=memo.get("signedTxHash"), + state=ACPMemoState(memo.get("state")), ) for memo in data["memos"] ] @@ -607,6 +610,7 @@ def _hydrate_jobs( payable_details=memo.get("payableDetails"), txn_hash=memo.get("txHash"), signed_txn_hash=memo.get("signedTxHash"), + state=ACPMemoState(memo.get("state")), ) for memo in job.get("memos", []) ] @@ -692,6 +696,7 @@ def get_job_by_onchain_id(self, onchain_job_id: int) -> "ACPJob": payable_details=memo.get("payableDetails"), txn_hash=memo.get("txHash"), signed_txn_hash=memo.get("signedTxHash"), + state=ACPMemoState(memo.get("state")), ) ) @@ -750,6 +755,7 @@ def get_memo_by_id(self, onchain_job_id: int, memo_id: int) -> "ACPMemo": payable_details=memo.get("payableDetails"), txn_hash=memo.get("txHash"), signed_txn_hash=memo.get("signedTxHash"), + state=ACPMemoState(memo.get("state")), ) except Exception as e: diff --git a/virtuals_acp/job.py b/virtuals_acp/job.py index 0c97adc..174212e 100644 --- a/virtuals_acp/job.py +++ b/virtuals_acp/job.py @@ -8,6 +8,7 @@ from virtuals_acp.exceptions import ACPError from virtuals_acp.memo import ACPMemo from virtuals_acp.models import ( + ACPMemoState, PriceType, OperationPayload, X402PayableRequest, @@ -15,6 +16,7 @@ RequestPayload, ) from virtuals_acp.utils import ( + get_destination_chain_id, get_destination_endpoint_id, try_parse_json_model, prepare_payload, @@ -28,7 +30,7 @@ FeeType, ) from virtuals_acp.fare import Fare, FareAmountBase, FareAmount -from virtuals_acp.web3 import getERC20Allowance, getERC20Balance, getERC20Symbol +from virtuals_acp.web3 import getERC20Allowance, getERC20Balance, getERC20Decimals, getERC20Symbol if TYPE_CHECKING: from virtuals_acp.client import VirtualsACP @@ -254,6 +256,57 @@ def pay_and_accept_requirement(self, reason: Optional[str] = "") -> str | None: if not memo: raise Exception("No negotiation memo found") + if memo.type == MemoType.PAYABLE_REQUEST and memo.state != ACPMemoState.PENDING and memo.payable_details is not None and memo.payable_details['lzDstEid'] is not None: + print(f"Memo not ready to be signed, state: {memo.state}, payable_details: {memo.payable_details}") + return + + if memo.payable_details: + if "lzDstEid" in memo.payable_details: + destination_chain_id = get_destination_chain_id(memo.payable_details["lzDstEid"]) + else: + destination_chain_id = self.acp_contract_client.config.chain_id + + if(destination_chain_id != self.acp_contract_client.config.chain_id): + token_balance = getERC20Balance( + self.acp_contract_client.public_clients[destination_chain_id], + memo.payable_details["token"], + self.client_address, + ) + + if token_balance < memo.payable_details["amount"]: + token_decimals = getERC20Decimals( + self.acp_contract_client.public_clients[destination_chain_id], + memo.payable_details["token"], + ) + + token_symbol = getERC20Symbol( + self.acp_contract_client.public_clients[destination_chain_id], + memo.payable_details["token"], + ) + + raise ACPError(f"You do not have enough funds to pay for the job which costs {memo.payable_details['amount'] / 10 ** token_decimals} {token_symbol} on chainId {destination_chain_id}") + else: + asset_manager_address = self.acp_contract_client.get_asset_manager_address() + + allowance = getERC20Allowance( + self.acp_contract_client.public_clients[destination_chain_id], + memo.payable_details["token"], + self.client_address, + asset_manager_address, + ) + + destination_chain_operations: List[OperationPayload] = [] + + destination_chain_operations.append( + self.acp_contract_client.approve_allowance( + memo.payable_details["amount"] + allowance, + memo.payable_details["token"], + asset_manager_address, + ) + ) + + self.acp_contract_client.handle_operation(destination_chain_operations, destination_chain_id) + operations: List[OperationPayload] = [] base_fare_amount = FareAmount(self.price, self.base_fare) diff --git a/virtuals_acp/utils.py b/virtuals_acp/utils.py index b091420..454fe34 100644 --- a/virtuals_acp/utils.py +++ b/virtuals_acp/utils.py @@ -75,4 +75,21 @@ def get_destination_endpoint_id(chain_id: int) -> int: } if chain_id in id_to_eid: return id_to_eid[chain_id] - raise ValueError(f"Unsupported chain ID: {chain_id}") \ No newline at end of file + raise ValueError(f"Unsupported chain ID: {chain_id}") + +def get_destination_chain_id(endpoint_id: int) -> int: + eid_to_id = { + 40245: 84532, # baseSepolia.id + 40161: 11155111, # sepolia.id + 40267: 80002, # polygonAmoy.id + 40231: 421614, # arbitrumSepolia.id + 40102: 97, # bscTestnet.id + 30184: 8453, # base.id + 30101: 1, # mainnet.id + 30109: 137, # polygon.id + 30110: 42161, # arbitrum.id + 30102: 56, # bsc.id + } + if endpoint_id in eid_to_id: + return eid_to_id[endpoint_id] + raise ValueError(f"Unsupported endpoint ID: {endpoint_id}") \ No newline at end of file diff --git a/virtuals_acp/web3.py b/virtuals_acp/web3.py index a03ec6f..f711947 100644 --- a/virtuals_acp/web3.py +++ b/virtuals_acp/web3.py @@ -41,4 +41,16 @@ def getERC20Symbol( symbol = erc20_contract_instance.functions.symbol().call() - return symbol \ No newline at end of file + return symbol + +def getERC20Decimals( + public_client: Web3, + contract_address: str, +) -> int: + erc20_contract_instance = public_client.eth.contract( + address=contract_address, abi=ERC20_ABI + ) + + decimals = erc20_contract_instance.functions.decimals().call() + + return decimals \ No newline at end of file