-
Notifications
You must be signed in to change notification settings - Fork 80
feat: Adding example demo showing how the Intent, Cart and Payment mandates… #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
529ac32
Adding example demo showing how the Intent, Cart and Payment mandates…
89f6e05
ruff formatting
b192b96
fix rpc endpoing defaults
1f4df1f
rename mock wallet and facilitator to local since they are able to ma…
44f42fa
Update python/examples/ap2-demo/client_agent/client_agent.py
jorellis 97063e4
clean up logging
153fd7d
Update python/examples/ap2-demo/client_agent/_task_store.py
jorellis 7473fea
Update python/examples/ap2-demo/client_agent/_remote_agent_connection.py
jorellis a47a63d
Update local_wallet.py to include copyright
jorellis 780b22b
chore: allow .env.example files to be tracked in git
lingzhong 7abf21f
feat: add missing .env.example template for ap2-demo setup
lingzhong 4c67458
fix: server build break due to missing ap2 source dependency
lingzhong 4fb122f
docs: add GOOGLE_API_KEY requirement to ap2-demo setup instructions
lingzhong 7c4552f
style: format wallet.py with ruff
lingzhong ca94630
fix: replace invalid 'number' type with 'int' in type hint
lingzhong bed8f7e
fix: add missing logger import in _task_store.py
lingzhong 89dbf74
security: use double underscore prefix for private key variables
lingzhong 71056ad
fix: replace blocking HTTP calls with async in client agent methods
lingzhong 58418a9
responding to ap2-demo PR comments
bc8d2da
remove ap2 local dependency and point to github repo
33b2f89
chore: bump Python version requirement to >=3.13 for ap2-demo
lingzhong 8183020
docs: update Python version requirement to 3.13+ in README
lingzhong 2a7d0bc
update readme docs
2b516f7
Move hard coded wallet address to env variable
c908e3e
clean up comments
cb73d94
Merge branch 'main' into feature/ap2-demo
jorellis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| # Environment variables | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
|
|
||
| # Python virtual environments | ||
| .venv/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Client Wallet Configuration | ||
| # Your Ethereum private key for signing transactions | ||
| # This account must have Base Sepolia USDC test tokens | ||
| CLIENT_PRIVATE_KEY=your_private_key_here | ||
|
|
||
| # URL for the Local Wallet service | ||
| LOCAL_WALLET_URL=http://localhost:5001 | ||
|
|
||
| # Merchant Wallet Configuration | ||
| # Public Ethereum address that will receive payments | ||
| MERCHANT_WALLET_ADDRESS=0x0000000000000000000000000000000000000000 | ||
|
|
||
| # Private key for facilitator that is responsible for settling transaction | ||
| FACILITATOR_PRIVATE_KEY=0x0000000000000000000000000000000000000000 | ||
|
|
||
| # Wallet address of facilitator that will be allowed to settle transaction sent from client. | ||
| FACILITATOR_WALLET_ADDRESS=0x0000000000000000000000000000000000000000 | ||
|
|
||
| # RPC URL that will be the endpoint for the local facilitator | ||
| RPC_URL=https://base-sepolia.g.alchemy.com/v2/{api_key} | ||
|
|
||
| # Google AI Configuration | ||
| # Required: Your Google API key for ADK agent | ||
| GOOGLE_API_KEY=your_google_api_key_here | ||
|
|
||
| # Default USDC Base Sepolia Asset Contract | ||
| ASSET_CONTRACT_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e | ||
|
|
||
| # Optional: Use Vertex AI instead of API key (set to TRUE to enable) | ||
| # GOOGLE_GENAI_USE_VERTEXAI=FALSE |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # AP2 & x402 Agent-to-Agent Demo | ||
|
|
||
| This directory contains a demonstration of the Agent Payments Protocol (AP2) integrated with the x402 payments extension. It showcases the "Embedded Flow," where a client agent orchestrates a purchase from a merchant agent using a sequence of digitally signed mandates. | ||
|
|
||
| The demo consists of three main components: | ||
| 1. **Client Agent**: An orchestrator built with the Google Agent Development Kit (ADK) that manages the user interaction and the overall purchase flow. | ||
| 2. **Merchant Agent**: A seller agent that can create product carts and process payments using the x402 protocol. | ||
| 3. **Mock Wallet**: A simple Flask service that simulates a user's wallet, responsible for signing all mandates and transactions. | ||
|
|
||
| ## How to Run | ||
|
|
||
| ### 1. Prerequisites | ||
|
|
||
| - Python 3.13+ | ||
| - [uv](https://github.com/astral-sh/uv) (for environment and package management) | ||
|
|
||
| ### 2. Installation | ||
|
|
||
| From this directory (`a2a-x402/python/examples/ap2-demo`), install the required dependencies: | ||
| ```bash | ||
| uv pip install -e . | ||
| ``` | ||
|
|
||
| ### 3. Setup Environment | ||
|
|
||
| Copy the example environment file and populate it with your details. This file will be used by the Mock Wallet and Merchant Agent. | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| Now, edit the `.env` file: | ||
| - `CLIENT_PRIVATE_KEY`: Your Ethereum private key. This account will be used for signing and must have Base Sepolia USDC test tokens. | ||
| - `MERCHANT_WALLET_ADDRESS`: The public Ethereum address of the merchant who will receive the payment. | ||
| - `GOOGLE_API_KEY`: Your Google API key for the ADK agent. Get one from [Google AI Studio](https://aistudio.google.com/apikey). | ||
|
|
||
| ### 4. Run the Demo | ||
|
|
||
| You need to run the three services in separate terminals. | ||
|
|
||
| **Terminal 1: Mock Wallet** | ||
|
|
||
| From the `python/examples/ap2-demo` directory, run: | ||
| ```bash | ||
| uv run python local_wallet.py | ||
| ``` | ||
|
|
||
| **Terminal 2: Client Agent (ADK Web UI)** | ||
|
|
||
| From the `python/examples/ap2-demo` directory, run: | ||
| ```bash | ||
| uv run adk web | ||
| ``` | ||
|
|
||
| **Terminal 3: Server Agent** | ||
|
|
||
| From the `python/examples/ap2-demo` directory, run: | ||
| ```bash | ||
| uv run server | ||
| ``` | ||
|
|
||
| Once the client agent is running, you can connect to it from the ADK Web UI to begin the purchase flow. | ||
|
|
||
| ## Understanding the Flow | ||
|
|
||
| This demo illustrates a complete, secure, and verifiable agent-to-agent transaction using AP2 mandates. | ||
|
|
||
| **Step 1: Intent to Purchase** | ||
| 1. The user tells the **Client Agent** their purchase intent (e.g., "I want to buy two bananas"). | ||
| 2. The Client Agent creates an `IntentMandate` and asks the user for approval. | ||
| 3. The user approves, and the Client Agent sends the `IntentMandate` to the **Mock Wallet** to be signed. | ||
| 4. The Client Agent forwards the signed `IntentMandate` to the **Merchant Agent**. | ||
|
|
||
| **Step 2: Cart Creation & Payment Requirements** | ||
| 1. The Merchant Agent receives the `IntentMandate` and uses its `get_product_details_and_create_cart` tool. | ||
| 2. This tool constructs a `CartMandate`, which includes the price and, crucially, the x402 payment requirements (specifying the token, network, and amount). | ||
| 3. The Merchant Agent signs the `CartMandate` and returns it to the Client Agent as a structured A2A Artifact. | ||
|
|
||
| **Step 3: Transaction Signing (EIP-712)** | ||
| 1. The Client Agent receives the `CartMandate` and informs the user that the order is ready for payment. | ||
| 2. When the user agrees to pay, the Client Agent's `pay_for_cart` tool is triggered. | ||
| 3. It inspects the `CartMandate` to get the payment details and fetches the correct `nonce` from the USDC smart contract. | ||
| 4. It constructs a secure **EIP-712 typed data** structure for the `transferWithAuthorization` function call. | ||
| 5. This typed data is sent to the **Mock Wallet**, which uses the correct `sign_typed_data` method to produce a valid EIP-712 signature. | ||
|
|
||
| **Step 4: Payment Authorization** | ||
| 1. The Client Agent receives the signature and uses it to create a `PaymentMandate`. | ||
| 2. The user is asked to approve this final mandate. | ||
| 3. The Client Agent sends the `PaymentMandate` to the **Mock Wallet** for a final signature (authorizing the payment). | ||
| 4. The Client Agent sends the fully signed `PaymentMandate` back to the **Merchant Agent**. | ||
|
|
||
| **Step 5: Settlement** | ||
| 1. The Merchant Agent receives the `PaymentMandate` and triggers its `process_payment` tool. | ||
| 2. It reconstructs the `PaymentPayload` and `PaymentRequirements`. | ||
| 3. It calls the `verify()` and then `settle()` methods on the **Mock Facilitator**. | ||
| 4. The Mock Facilitator performs an off-chain signature check and then submits the transaction to the Base Sepolia testnet, completing the payment. | ||
| 5. The Merchant Agent receives the successful settlement response and confirms the purchase is complete. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Copyright 2025 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| from .agent import root_agent | ||
|
|
||
| __all__ = ["root_agent"] |
80 changes: 80 additions & 0 deletions
80
python/examples/ap2-demo/client_agent/_remote_agent_connection.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Copyright 2025 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| from typing import Callable | ||
|
|
||
| import httpx | ||
| from a2a.client import A2AClient | ||
| from a2a.types import ( | ||
| AgentCard, | ||
| JSONRPCErrorResponse, | ||
| Message, | ||
| MessageSendParams, | ||
| SendMessageRequest, | ||
| SendStreamingMessageRequest, | ||
| Task, | ||
| TaskArtifactUpdateEvent, | ||
| TaskStatusUpdateEvent, | ||
| ) | ||
|
|
||
| type TaskCallbackArg = Task | TaskStatusUpdateEvent | TaskArtifactUpdateEvent | ||
| TaskUpdateCallback = Callable[[TaskCallbackArg], Task] | ||
|
|
||
|
|
||
| class RemoteAgentConnections: | ||
| """A class to hold the connections to the remote agents.""" | ||
|
|
||
| def __init__(self, client: httpx.AsyncClient, agent_card: AgentCard): | ||
| self.agent_client = A2AClient(client, agent_card) | ||
| self.card = agent_card | ||
| self.pending_tasks = set() | ||
|
|
||
| def get_agent(self) -> AgentCard: | ||
| return self.card | ||
|
|
||
| async def send_message( | ||
| self, | ||
| id: int | str, | ||
| request: MessageSendParams, | ||
| task_callback: TaskUpdateCallback | None, | ||
| ) -> Task | Message | None: | ||
| if self.card.capabilities.streaming: | ||
| task = None | ||
| async for response in self.agent_client.send_message_streaming( | ||
| SendStreamingMessageRequest(id=id, params=request) | ||
| ): | ||
| if not response.root.result: | ||
| return response.root.error | ||
| # In the case a message is returned, that is the end of the interaction. | ||
| event = response.root.result | ||
| if isinstance(event, Message): | ||
| return event | ||
|
|
||
| # Otherwise we are in the Task + TaskUpdate cycle. | ||
| if task_callback and event: | ||
| task = task_callback(event) | ||
| if hasattr(event, "final") and event.final: | ||
| break | ||
| return task | ||
| else: # Non-streaming | ||
| response = await self.agent_client.send_message( | ||
| SendMessageRequest(id=id, params=request) | ||
| ) | ||
| if isinstance(response.root, JSONRPCErrorResponse): | ||
| return response.root.error | ||
| if isinstance(response.root.result, Message): | ||
| return response.root.result | ||
|
|
||
| if task_callback: | ||
| task_callback(response.root.result) | ||
| return response.root.result |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This missed the mention of the facilitor component.