这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@jorellis
Copy link
Collaborator

Adding python example to demo how Mandates from AP2 (ap2-protocol.org) can be used while facilitating an x402 transaction.

The demo steps are explain in the readme in python/examples/ap2-demo

Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

Fixes #<issue_number_goes_here> 🦕

@jorellis jorellis requested a review from a team as a code owner October 16, 2025 22:06
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jorellis, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new, fully functional example that demonstrates the end-to-end process of agent-to-agent commerce using the Agent Payments Protocol (AP2) and the x402 payments extension. It provides a practical implementation of a client agent, a merchant agent, and a mock wallet, illustrating how mandates for intent, cart, and payment are created, signed, and processed to facilitate a secure and verifiable transaction flow. The example also includes necessary infrastructure for running these agents and services, along with detailed instructions for setup and execution.

Highlights

  • New AP2 & x402 Demo: Introduced a comprehensive Python example demonstrating the Agent Payments Protocol (AP2) integrated with the x402 payments extension, showcasing an 'Embedded Flow' for agent-to-agent transactions.
  • Client Agent Implementation: Added a Client Agent built with the Google Agent Development Kit (ADK) that orchestrates the purchase flow, handles user interaction, and manages the sequence of digitally signed mandates.
  • Mock Wallet Service: Implemented a Flask-based Mock Wallet service responsible for simulating a user's wallet, capable of signing both standard AP2 mandates and EIP-712 typed data for x402 transactions.
  • Merchant Agent & Payment Processing: Developed a Merchant Agent that can create product carts, generate CartMandates with x402 payment requirements, and process PaymentMandates by interacting with a Mock Facilitator for settlement.
  • EIP-712 Signing Refinement: Refactored the x402_a2a/core/wallet.py to directly construct and sign EIP-712 typed data for EIP-3009 transferWithAuthorization transactions, improving clarity and directness of the signing process.
  • Server Executor State Management: Adjusted the x402ServerExecutor in x402_a2a/executors/server.py to manage _payment_requirements_store as an instance-level attribute rather than a class-level attribute, enhancing isolation and preventing unintended state sharing across requests.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@jorellis jorellis changed the title Adding example demo showing how the Intent, Cart and Payment mandates… feat: Adding example demo showing how the Intent, Cart and Payment mandates… Oct 16, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a comprehensive example demonstrating an agent-to-agent payment flow using AP2 mandates and the x402 protocol. The changes are extensive, introducing a client agent, a merchant agent, and a mock wallet. My review focuses on improving security, correctness, and maintainability. Key findings include a critical security issue with a hardcoded API key, incorrect function calls that will lead to runtime errors, improper nonce generation, and several areas where the code can be made more robust and maintainable by removing hardcoded values and using Pydantic models for validation.

return process_payment(
selected_requirement,
account,
rpc_url,
ASSET_CONTRACT_ADDRESS,
ASSET_ABI,
max_value,
valid_after,
valid_before,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The process_payment function is called with incorrect arguments. Its new signature is (requirements, account, max_value, valid_after, valid_before), but it's being called here with rpc_url, ASSET_CONTRACT_ADDRESS, and ASSET_ABI as extra arguments. This will raise a TypeError at runtime.

    return process_payment(
        selected_requirement,
        account,
        max_value,
        valid_after,
        valid_before,
    )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

value=value,
valid_after=valid_after,
valid_before=valid_before,
nonce="0x" + os.urandom(32).hex(), # Pass nonce as a hex string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

A random nonce is generated using os.urandom(). For EIP-3009 transferWithAuthorization, the nonce must be a sequential, on-chain value fetched from the smart contract for the user's address to prevent replay attacks. Using a random nonce is incorrect and insecure. You should fetch the nonce from the USDC contract using the nonces function, which is already defined in your USDC_ABI.

Suggested change
nonce="0x" + os.urandom(32).hex(), # Pass nonce as a hex string
nonce=usdc_contract.functions.nonces(self._wallet_address).call().to_bytes(32, 'big'), # Fetch on-chain nonce

Comment on lines +160 to +164
current_temp_artifact = self._artifact_chunks[artifact.artifactId][-1]
# TODO handle if current_temp_artifact is missing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing self._artifact_chunks[artifact.artifactId][-1] is unsafe and will raise an IndexError if the list of chunks for that artifactId is empty. This could occur if an append event is received before the initial chunk. You should add a check to handle this edge case gracefully to prevent the agent from crashing.

            if not self._artifact_chunks.get(artifact.artifactId):
                logger.warning(
                    "Received an append chunk for an artifact ID (%s) that has no initial chunk. Discarding.",
                    artifact.artifactId,
                )
                return
            current_temp_artifact = self._artifact_chunks[artifact.artifactId][-1]

Comment on lines 75 to 79
self.facilitator_private_key = (
os.getenv(
"FACILITATOR_PRIVATE_KEY",
"0000000000000000000000000000000000000001")
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

A default private key is hardcoded in the source code. While this is for a facilitator in a demo, it's a bad practice to have private keys, even test ones, directly in the code. It's better to raise an error if the FACILITATOR_PRIVATE_KEY environment variable is not set, ensuring it's configured explicitly.

        self.facilitator_private_key = os.getenv("FACILITATOR_PRIVATE_KEY")
        if not self.facilitator_private_key:
            raise ValueError("FACILITATOR_PRIVATE_KEY environment variable not set.")

# Contract address for the asset being used. Can be overridden by an environment variable.
# Defaults to the Base Sepolia USDC Asset Contract
ASSET_CONTRACT_ADDRESS = os.getenv(
"ASSET_CONTRACT_ADDRESS", "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create fallback Base sepolia USDC asset contract as const? Shouldnt this be a mandatory check during the flow that would avoid it being hardcoded fallback value and also keep code agnostic of any specifc contract?

ASSET_CONTRACT_ADDRESS = os.getenv(
"ASSET_CONTRACT_ADDRESS", "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
)
ASSET_ABI = json.loads(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ABI for EIP-3009?

return process_payment(
selected_requirement,
account,
rpc_url,
ASSET_CONTRACT_ADDRESS,
ASSET_ABI,
max_value,
valid_after,
valid_before,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment on lines 254 to 242
x402_version=x402_VERSION,
scheme="exact",
network=requirements.network,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we intentionally only allowing exact scheme?

This is in the core library, so it'll be a breaking change, if I understand correctly.

valid_before=auth_data["valid_before"],
nonce=auth_data["nonce"],
chain_id=chain_id,
contract_address=ASSET_CONTRACT_ADDRESS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hard-coded or should be passed in?

) -> PaymentPayload:
"""Create PaymentPayload using proper x402.exact signing logic.
Same as create_payment_header but returns PaymentPayload object (not base64 encoded).
"""Creates a PaymentPayload containing a valid EIP-3009 signature."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentionally to make the reference library to only support EIP-3009 signatures? Is this a core constraint imposed by x402, regardless of whether the flow is embedded or standalone?


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.

Web3.HTTPProvider(base_sepolia_rpc_url, request_kwargs={"timeout": 120})
)
# TODO: Replace with your facilitator's private key
self.facilitator_private_key = os.getenv(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: storing secrets in class public member is highly unsafe.

It's unclear if it's overkill to worry about security best practices in this demo, but people do unfortunately use the same testnet private key as mainnet. There isn't a quick way to fully address this, but consider using Double leading underscore to make the member private to obfuscate the variable in runtime memory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to using double underscore convention here (and for different reason, renaming w3 field to _w3)

To be pedantic: double underscore does not necessary hide this data from callers; for example, the caller can read the key by passing the instance into the built-in vars function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. The moment it's in memory, it's just one line away from python reflection to grab it. Double leading underscore probably the best bang for the buck for this demo repo.

@lingzhong
Copy link
Collaborator

The current branch state fails to start up 2 (wallet, server) out of the 3 services. I've identified issues with missing ap2 source dependencies, and environment variable template set ups.

I'm also seeing a lot of 3.11 incompatibility errors, but I'll try to get the initial fixes in to unbreak the wallet and server start up.

@lingzhong
Copy link
Collaborator

All 3 services can start up now. Also fixed formatting issues causing lint errors since start of PR.

However upon interaction via the client agent, python incompatibility prevents user from starting the demo flow.

Screenshot 2025-10-27 at 6 57 49 PM

{"error": "Fail to load 'client_agent' module. name 'number' is not defined"}

@lingzhong
Copy link
Collaborator

lingzhong commented Oct 28, 2025

Fixed the number type issue and the demo flow works again now.

However I noticed several other Python 3.11 compatibility issues while digging around. The pyproject.toml says we support 3.11+, but there are a handful of Python 3.12-only features being used:

Issues:

  1. The @override decorator (used in adk_merchant_agent.py, local_facilitator.py, x402_merchant_executor.py) - this was added in Python 3.12
  2. The new type alias syntax in _remote_agent_connection.py:32 - also 3.12+
  3. Invalid number type hint in _remote_agent_connection.py:49 - should be int

Potential fixes:

  1. Add typing_extensions to dependencies and make these small changes:
  2. Import override from typing_extensions instead of typing
  3. Replace type TaskCallbackArg = ... with TaskCallbackArg = Union[...]

There may be additional 3.11 incompatibility issues I haven't spot, so perhaps the quickest practical route to take is to mark this demo as 3.12+ instead of 3.11+, which I'm in favor of.

lingzhong
lingzhong previously approved these changes Nov 1, 2025
Jordan Ellis and others added 18 commits November 3, 2025 09:42
… from AP2 can be used while facilitating an x402 transaction
…ke actual calls to the base sepolia network by default
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Replace synchronous httpx.post() with async self.httpx_client.post() in:
- sign_payment_request()
- sign_payment_mandate()
- sign_intent_mandate()

Prevents event loop blocking and enables concurrent request handling.
lingzhong
lingzhong previously approved these changes Nov 3, 2025
lingzhong
lingzhong previously approved these changes Nov 4, 2025
@jorellis jorellis merged commit 46dd48b into main Nov 4, 2025
4 checks passed
@jorellis jorellis deleted the feature/ap2-demo branch November 4, 2025 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants