Add pulsenetwork-data template: agent buys live data mid-task via x402 - #13
Add pulsenetwork-data template: agent buys live data mid-task via x402#13GTCC777 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
…e signed challenge
Addresses all three cubic findings.
templates.json (P2): promoted from a bare single-file template to a complex
one. A single .py copy left users with no pyproject.toml (so x402 was never
installed) and no .env example for the wallet key, so the first 'uv run'
failed on ImportError. Now ships main.py, pyproject.toml.template pinning
x402[httpx,evm], .env.example.template, README.md and the shared gitignore,
plus next_steps that walk through wallet setup, 'uv sync' and the run.
pulse_catalog (P1): was grepping llms-full.txt line by line, so the task's own
query ('token safety') matched no line at all, and any line that did match
carried a relative route with the base URL somewhere else in the file. It now
queries the free /api/catalog JSON endpoint and returns each match as a
complete URL with its price and its documented parameters, so pulse_buy can be
called directly from the result. Catalog-side ranking was fixed to match on
word boundaries with query-side aliases, so 'token safety' now returns the
token scanners. No silent fallback: if the catalog is unreachable the tool
says so rather than answering from a stale grep.
pulse_buy (P1): the preflight quote was advisory only, since x402HttpxClient
went on to sign whatever challenge the second request returned. The quote is
gone from the buy path. The per-call cap and the remaining session budget are
now an x402 payment policy, so they are checked against the challenge that is
actually signed, and a payload is never created when it does not fit. Spend is
recorded from the signed amount via an after-payment-creation hook, and
accumulated rather than overwritten so a retry can never undercount. A lock
around the buy path stops two concurrent calls from both spending the last of
the budget, and an invalid or missing wallet key now refuses cleanly instead
of raising.
Verified against the live fleet: caps refuse without signing, and a real
$0.015 USDC settle on Base returns the token verdict.
|
All three findings were valid and are fixed in 099e06c. templates.json (P2) — registered as a complex template. You were right that pulse_catalog (P1) — reproduced, and it was worse than the report. The pulse_buy (P1) — real TOCTOU, and the quote is gone from the buy path. The Verified against the live endpoints: a cap breach refuses with nothing signed, |
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pulsenetwork-data/.env.example.template">
<violation number="1" location="pulsenetwork-data/.env.example.template:4">
P3: The documented env-check promise doesn't hold with this placeholder: README says leaving the key placeholder yields "Refused: PULSE_WALLET_KEY is not set", but this non-empty placeholder (`0xyour-throwaway-private-key`) passes the `if not key` guard and instead hits Account.from_key, producing a different message ("not a valid private key"). Either add an explicit placeholder sentinel check in main.py alongside the empty check, or correct the README refusal-table entry, so the template's placeholder and the documented refusal actually match.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| # Wallet the agent pays from. Use a THROWAWAY key holding a few USDC on Base, | ||
| # never a wallet you keep funds in. This is the only credential you need: | ||
| # PulseNetwork has no accounts, no signup and no API keys. | ||
| PULSE_WALLET_KEY=0xyour-throwaway-private-key |
There was a problem hiding this comment.
P3: The documented env-check promise doesn't hold with this placeholder: README says leaving the key placeholder yields "Refused: PULSE_WALLET_KEY is not set", but this non-empty placeholder (0xyour-throwaway-private-key) passes the if not key guard and instead hits Account.from_key, producing a different message ("not a valid private key"). Either add an explicit placeholder sentinel check in main.py alongside the empty check, or correct the README refusal-table entry, so the template's placeholder and the documented refusal actually match.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pulsenetwork-data/.env.example.template, line 4:
<comment>The documented env-check promise doesn't hold with this placeholder: README says leaving the key placeholder yields "Refused: PULSE_WALLET_KEY is not set", but this non-empty placeholder (`0xyour-throwaway-private-key`) passes the `if not key` guard and instead hits Account.from_key, producing a different message ("not a valid private key"). Either add an explicit placeholder sentinel check in main.py alongside the empty check, or correct the README refusal-table entry, so the template's placeholder and the documented refusal actually match.</comment>
<file context>
@@ -0,0 +1,12 @@
+# Wallet the agent pays from. Use a THROWAWAY key holding a few USDC on Base,
+# never a wallet you keep funds in. This is the only credential you need:
+# PulseNetwork has no accounts, no signup and no API keys.
+PULSE_WALLET_KEY=0xyour-throwaway-private-key
+
+# Model for the agent (any browser-use supported provider works)
</file context>
…ols, honour the placeholder Second review round, all three valid. Asset pinning (P1): the payment policy checked the network but not the asset, so a Base challenge denominated in some other token would have passed a cap counted in USDC's six decimals. The same atomic amount in an 8 or 18 decimal token is a completely different amount of money. The policy now requires Base USDC by address and refuses anything else. Allowlist on the free tools (P2): pulse_price fetched whatever URL the model handed it. The agent reads live web pages, so a page could have steered it into probing a link-local or internal address, and a free tool with no allowlist would have made that request. Both the host check and an https requirement now live in one helper that every outbound path runs, not just the paying one. pulse_catalog was never affected: it only ever sends the query as a parameter to a fixed catalog URL. Placeholder key (P3): the shipped .env placeholder is non-empty, so an unedited copy reached Account.from_key and reported a malformed key rather than the documented 'not set'. It is treated as unconfigured now, and the README troubleshooting table covers both cases plus the allowlist refusal. Verified: the metadata address, loopback, plain http and an off-domain host are all refused by both free and paying tools; a synthetic non-USDC Base challenge is dropped while real Base USDC is kept; and a live $0.015 settle still returns the token verdict.
|
All three valid again. Fixed in 1bee395. Asset pinning (P1) — the sharp one. You are right, and it was worse than Allowlist on the free tools (P2) — real, fixed. One correction to the report: Placeholder key (P3) — fair catch. The shipped placeholder is non-empty, so Re-verified end to end after the changes, including a live $0.015 USDC settle on |
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
Valid, and it is a hole I opened myself last round. Pinning the asset in the payment policy left the quote path matching on network alone, so a Base challenge denominated in some other token would have had its amount divided by USDC's decimals and printed as a confident dollar figure, for a challenge the buy path then refuses. Wrong number, and an inconsistency between two tools that are supposed to describe the same thing. _quote_usd now requires the same Base USDC asset the policy requires, so an unsupported challenge returns no quote rather than a meaningless one. It also keeps scanning the accepts list instead of taking the first Base entry, so a challenge offering a non-USDC option ahead of a USDC one is priced from the option that can really be paid. Verified against crafted 402 responses covering Base USDC, a non-USDC Base asset, USDC on another network and a mixed list, plus a live quote against a real endpoint that still returns $0.015.
|
Valid, and it is a hole I opened myself last round. Fixed in 010b412. Pinning the asset in the payment policy left
Verified against crafted 402 responses covering Base USDC, a non-USDC Base Three rounds, three real findings, and each one was a consequence of the |
What
A template showing a browser-use agent buying live external data mid-task with x402 micropayments (USDC on Base): the agent searches a catalog of 950+ pay-per-call endpoints, checks the exact price with a free bare-402 quote, then pays a few cents for the call it needs (the demo buys a $0.015 crypto token-safety verdict before evaluating a hyped token).
Follows the tools_template.py pattern: three custom
@tools.actionfunctions (pulse_catalogfree search,pulse_pricefree quote,pulse_buypaid call), plus a runnablemain().Why
Your cloud accepts x402 since last week, so agents in this ecosystem increasingly carry funded wallets. This template shows the complementary direction: the same wallet buying data the agent needs mid-run, with no API keys or signups.
Safety properties (in code, not prompts)
PULSE_WALLET_KEY); the LLM never sees itx402Python SDK (x402[httpx,evm])Tested
Disclosure: I operate the PulseNetwork endpoints the template calls. The catalog/quote tools are free; only
pulse_buyspends, capped as above.Summary by cubic
Adds the
pulsenetwork-datatemplate so an agent can buy live external data mid-task via x402 micropayments (USDC on Base). Ships as a complex template with README,.env.example, andpyproject.toml, and tightens spending by binding caps to the signed 402 challenge with Base USDC pinning and stricter allowlists.New Features
pulse_catalog(free search),pulse_price(free 402 quote),pulse_buy(paid call, returns JSON).x402policy bound to the real 402 challenge; spend recorded from the signed amount; asset pinned to Base USDC; lock prevents concurrent overspend; https-only PulseNetwork host allowlist applied to all outbound tools; placeholder or invalidPULSE_WALLET_KEYrefuses cleanly.pulse_priceonly quotes Base USDC options the buy path can actually pay; unsupported assets return a clear “No USDC-on-Base x402 quote” message.pulse_cataloguses the free/api/catalogJSON endpoint and returns complete URLs with prices and parameters; clear error if the catalog is unreachable.templates.json: shipsmain.py,pyproject.toml,.env.example,README.md, and guided next steps so deps install and wallet setup is clear.Dependencies
x402[httpx,evm],httpx,eth-account, andpython-dotenv(template includesbrowser-use).Written for commit 010b412. Summary will update on new commits.