这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
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…
Oct 16, 2025
89f6e05
ruff formatting
Oct 16, 2025
b192b96
fix rpc endpoing defaults
Oct 16, 2025
1f4df1f
rename mock wallet and facilitator to local since they are able to ma…
Oct 16, 2025
44f42fa
Update python/examples/ap2-demo/client_agent/client_agent.py
jorellis Oct 16, 2025
97063e4
clean up logging
Oct 16, 2025
153fd7d
Update python/examples/ap2-demo/client_agent/_task_store.py
jorellis Oct 17, 2025
7473fea
Update python/examples/ap2-demo/client_agent/_remote_agent_connection.py
jorellis Oct 17, 2025
a47a63d
Update local_wallet.py to include copyright
jorellis Oct 17, 2025
780b22b
chore: allow .env.example files to be tracked in git
lingzhong Oct 28, 2025
7abf21f
feat: add missing .env.example template for ap2-demo setup
lingzhong Oct 28, 2025
4c67458
fix: server build break due to missing ap2 source dependency
lingzhong Oct 28, 2025
4fb122f
docs: add GOOGLE_API_KEY requirement to ap2-demo setup instructions
lingzhong Oct 28, 2025
7c4552f
style: format wallet.py with ruff
lingzhong Oct 28, 2025
ca94630
fix: replace invalid 'number' type with 'int' in type hint
lingzhong Oct 28, 2025
bed8f7e
fix: add missing logger import in _task_store.py
lingzhong Oct 28, 2025
89dbf74
security: use double underscore prefix for private key variables
lingzhong Oct 28, 2025
71056ad
fix: replace blocking HTTP calls with async in client agent methods
lingzhong Oct 28, 2025
58418a9
responding to ap2-demo PR comments
Oct 30, 2025
bc8d2da
remove ap2 local dependency and point to github repo
Oct 31, 2025
33b2f89
chore: bump Python version requirement to >=3.13 for ap2-demo
lingzhong Nov 3, 2025
8183020
docs: update Python version requirement to 3.13+ in README
lingzhong Nov 3, 2025
2a7d0bc
update readme docs
Nov 4, 2025
2b516f7
Move hard coded wallet address to env variable
Nov 4, 2025
c908e3e
clean up comments
Nov 4, 2025
cb73d94
Merge branch 'main' into feature/ap2-demo
jorellis Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Environment variables
.env
.env.*
!.env.example

# Python virtual environments
.venv/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def _get_product_price(self, product_name: str) -> str:
price = (
int(hashlib.sha256(product_name.lower().encode()).hexdigest(), 16)
% 99900001
+ 100000
+ 5000
)
return str(price)

Expand Down
30 changes: 30 additions & 0 deletions python/examples/ap2-demo/.env.example
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
97 changes: 97 additions & 0 deletions python/examples/ap2-demo/README.md
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:
Copy link
Collaborator

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.

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.
16 changes: 16 additions & 0 deletions python/examples/ap2-demo/client_agent/__init__.py
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"]
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
Loading
Loading