-
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
Conversation
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
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.
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, | ||
| ) |
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.
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,
)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.
+1
python/examples/ap2-demo/client_agent/_remote_agent_connection.py
Outdated
Show resolved
Hide resolved
| value=value, | ||
| valid_after=valid_after, | ||
| valid_before=valid_before, | ||
| nonce="0x" + os.urandom(32).hex(), # Pass nonce as a hex string |
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.
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.
| 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 |
| current_temp_artifact = self._artifact_chunks[artifact.artifactId][-1] | ||
| # TODO handle if current_temp_artifact is missing |
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.
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]| self.facilitator_private_key = ( | ||
| os.getenv( | ||
| "FACILITATOR_PRIVATE_KEY", | ||
| "0000000000000000000000000000000000000001") | ||
| ) |
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.
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.")
python/examples/ap2-demo/client_agent/_remote_agent_connection.py
Outdated
Show resolved
Hide resolved
python/x402_a2a/core/wallet.py
Outdated
| # 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" |
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.
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?
python/examples/ap2-demo/client_agent/_remote_agent_connection.py
Outdated
Show resolved
Hide resolved
| ASSET_CONTRACT_ADDRESS = os.getenv( | ||
| "ASSET_CONTRACT_ADDRESS", "0x036CbD53842c5426634e7929541eC2318f3dCF7e" | ||
| ) | ||
| ASSET_ABI = json.loads( |
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.
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, | ||
| ) |
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.
+1
| x402_version=x402_VERSION, | ||
| scheme="exact", | ||
| network=requirements.network, |
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.
Are we intentionally only allowing exact scheme?
This is in the core library, so it'll be a breaking change, if I understand correctly.
python/x402_a2a/core/wallet.py
Outdated
| valid_before=auth_data["valid_before"], | ||
| nonce=auth_data["nonce"], | ||
| chain_id=chain_id, | ||
| contract_address=ASSET_CONTRACT_ADDRESS, |
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.
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.""" |
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.
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: |
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.
| Web3.HTTPProvider(base_sepolia_rpc_url, request_kwargs={"timeout": 120}) | ||
| ) | ||
| # TODO: Replace with your facilitator's private key | ||
| self.facilitator_private_key = os.getenv( |
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.
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.
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.
+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.
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.
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.
|
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. |
|
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 Issues:
Potential fixes:
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 |
… 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.
201b71e to
bc8d2da
Compare
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:
CONTRIBUTINGGuide.Fixes #<issue_number_goes_here> 🦕