Skip to content

Conversation

@humanizersequel
Copy link
Collaborator

This Provider upgrade allows any provider to be queried via HTTP and for micropayments to settle via the x402 protocol.

It binds the /xfour endpoint which accepts a GET with query parameters like /xfour?providername=name&param1=value&param2=value. These calls are then verified and settled with the facilitator.x402.rs endpoints (/verify and /settle) before the parameters are passed to make the upstream call as usual.

notes:
I have NOT yet tested this with Mainnet Base / USDC, only Sepolia. It should be fine, but EIP-3009 has been finicky so worth testing.

I have also not tested it on a provider that takes more than one parameter.

Also, this relies on features in branches of process lib and the macro that have not been merged yet, and are not necessarily going to be merged in the way that this PR implies.

not real yet, just a checkpoint commit because I'm going to have Claude get real jiggy with it
Checkpoint commit, some jank when calling out to the facilitator that I can't figure out easily
checkpoint commit. lfg
it works (on testnet at least)
Ok seems good, but need to test this with Mainnet Base / USDC (should be fine but there's some fiddly stuff with EIP-3009). Also need to ensure that a migration will be fine.
@humanizersequel humanizersequel changed the title x402 Support x402 Provider Support Oct 17, 2025
@humanizersequel
Copy link
Collaborator Author

Probably good to support the additional output schema requested here: https://www.x402scan.com/resources/register

I'll take care of that. @Gohlub it would be good if you added an ~x402 note that just had the full URL (https://nodename.hostingplatform.tld/provider:hypergrid:ware.hypr/xfour?providername=providername&param1=param1samplevalue)

info!("x402 endpoint called");

// ===== CHECK FOR X-PAYMENT HEADER =====
let x_payment_header = APP_HELPERS.with(|helpers| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably should not use APP_HELPERS, iirc its used by the macro internally so we should have some code in process-lib that enables getting the header, same comment for wherever we use it to fetch the relevant context about the http request

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorted

pub spent_tx_hashes: Vec<String>,
// TODO: Replace with persistent storage for production - tracks used payment nonces to prevent replay attacks
#[serde(skip)]
pub used_nonces: HashSet<String>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

we can put this in our key-value store process

Copy link
Collaborator

Choose a reason for hiding this comment

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

avoiding to modify the state also saves us from writing more state migration logic

Copy link
Collaborator

Choose a reason for hiding this comment

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

though since we need the new note, we might have to do it anyways lol

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Kind of annoying that we have to write migration logic for every single note we try to add (or, presumably, remove). Is there some obvious way around this or do we just have to suck it up?

Copy link
Collaborator

Choose a reason for hiding this comment

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

it's just about state persistence: it is plausible we can move provider metadata to not live in the state as it's all public info anyways, though imo changing the structure of notes on a protocol level should only happen on higher version bumps: because of the distributed nature of our deployments, for every such change we have to add extra logic that nudges, and correctly tracks what is missing in the client (provider in our case) to get on their node and sign extra transactions, I had to do such extra work for the is_live note from some time ago

Copy link
Collaborator

Choose a reason for hiding this comment

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

furthermore, we have restrictions on what notes are allowed in the namespace so that's yet another dependency we need to keep a track off

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note: check if #[serde(skip)] means this won't break state and cause a panic.

Generally, we should test the upgrade path, @Gohlub only flagged this as a potential state troll but who knows

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

to be clear it isn't super important to keep track of the nonces. EIP 3009 kind of does it for us

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got rid of it.

};

// Validate protocol version
if payment_payload.protocol_version != 1 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

unless there's a specific reason we're doing the validation here, it makes more sense to do it sooner rather than later: if the client is using an outdated protocol version (not sure if payment would go through in the first place), we don't want them to get rejected after being told to send the payment

Copy link
Collaborator

@Gohlub Gohlub Oct 20, 2025

Choose a reason for hiding this comment

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

inspecting the code further, I see this comes from 'parse_x_payment_header', which is the second step so it actually does make sense to do this here, I am not sure exactly about how x402 is set up but I feel like validating the protocol version should be done sooner

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think it's a huge deal because of how EIP-3009 works — a modified malicious client could maybe steal user funds by counterfeiting everything after receiving the signature on false pretenses, but I'm not sure about that and if it is the case it's a problem across all x402 clients not just this implementation.

payment_payload.scheme, payment_payload.network);
method
},
None => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

defensive validation (which is okay), but unclear why this would be None given the contents of build_payment_requirements as those fields are populated by constants "exact" and "X402_PAYMENT_NETWORK"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

wontfix

return Ok("".to_string());
}

info!("Payment verified for payer: {}", verify_result.payer);
Copy link
Collaborator

Choose a reason for hiding this comment

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

info! will be propagated to hypergrid_logger, so we should send this log after replay protection check
also, we should update the hypergrid_logger process to include x402 payments

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

wontfix

@Gohlub
Copy link
Collaborator

Gohlub commented Oct 20, 2025

haven't ran the code but it looks good (will require small modifications), will probably want to abstract the logic from the handler and put it in util.rs (just for better readability)

@Gohlub
Copy link
Collaborator

Gohlub commented Oct 20, 2025

Probably good to support the additional output schema requested here: https://www.x402scan.com/resources/register

I'll take care of that. @Gohlub it would be good if you added an ~x402 note that just had the full URL (https://nodename.hostingplatform.tld/provider:hypergrid:ware.hypr/xfour?providername=providername&param1=param1samplevalue)

I might just be spitballing but I need to check in with Nick since I get the feeling that this might not work for self-hosted indirect nodes.

/// Build PaymentRequirements structure from provider and resource URL
fn build_payment_requirements(provider: &RegisteredProvider, resource_url: &str) -> PaymentRequirements {
// Convert USDC price to atomic units (6 decimals)
let max_amount_atomic = ((provider.price * 1_000_000.0) as u64).to_string();
Copy link
Collaborator

Choose a reason for hiding this comment

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

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I had flagged this previously but just got lazy about it, will fix.

@Gohlub
Copy link
Collaborator

Gohlub commented Oct 20, 2025

Probably good to support the additional output schema requested here: https://www.x402scan.com/resources/register
I'll take care of that. @Gohlub it would be good if you added an ~x402 note that just had the full URL (https://nodename.hostingplatform.tld/provider:hypergrid:ware.hypr/xfour?providername=providername&param1=param1samplevalue)

I might just be spitballing but I need to check in with Nick since I get the feeling that this might not work for self-hosted indirect nodes.

just checked in with Nick: the note should be optional as it would otherwise doxx indirect nodes

@Gohlub
Copy link
Collaborator

Gohlub commented Oct 20, 2025

Do you mind sharing more info about sepolia/base discrepency RE EIP-3009?

@humanizersequel
Copy link
Collaborator Author

RE sepolia/base there's a few: EIP-3009 expects a network name, token addresses, and then crucially the onchain name of the token matters. On Base it is "USD Coin" and on Sepolia it is "USDC" (or maybe the other way around I can't remember) and you need to specify that.

RE doxxing indirect nodes: does that mean that indirect nodes can never field inbound HTTP requests? I'm not understanding what the problem here is. If it's really just "doxxing" (in the sense that you know what URL someone hosts their node at) it's a nonissue since you don't have to use Hypergrid if you're that sensitive about opsec. If there's an actual risk that's different, but I can't imagine how that would be the case.

@Gohlub
Copy link
Collaborator

Gohlub commented Oct 20, 2025

RE sepolia/base there's a few: EIP-3009 expects a network name, token addresses, and then crucially the onchain name of the token matters. On Base it is "USD Coin" and on Sepolia it is "USDC" (or maybe the other way around I can't remember) and you need to specify that.

RE doxxing indirect nodes: does that mean that indirect nodes can never field inbound HTTP requests? I'm not understanding what the problem here is. If it's really just "doxxing" (in the sense that you know what URL someone hosts their node at) it's a nonissue since you don't have to use Hypergrid if you're that sensitive about opsec. If there's an actual risk that's different, but I can't imagine how that would be the case.

it's doxxing in the sense of that providers will post their home IP address on-chain, and I think it might even require them to fiddle with their routers to expose ports to the internet (it's why the node boot-sequence defaults to indirect and warns you do leave it like that unless you know what you're doing). Indirect nodes can handle inbound HTTP requests, but in that case if you're willing to share your IP address with the service that is trying to reach your node

@humanizersequel
Copy link
Collaborator Author

I strongly feel this is a non-issue because Hypergrid itself is opt-in.

However, given the absolute state of Hypergrid GTM right now, I don't think it even matters if we try to rush an ~x402 link into prod. We can implement this without it, and lean on 3p x402 registries to start off with.

@Gohlub insisting I abstract my shit. smdh.
not necessary, EIP-3009 doesn't give much surface area for this kind of thing anyway
you know what it is
should still work with orthodox x402 clients (whatever that means) and also now x402scans more involved schema will let them generate a UI to make calls in-app
@humanizersequel
Copy link
Collaborator Author

humanizersequel commented Oct 21, 2025

Ok, this is the last featureful commit. x402 + the extended schema for x402scan is done.

To do:

@humanizersequel
Copy link
Collaborator Author

upgrade path is fine. the whole state management thing in the app framework is a huge pain though, wish it would handle it more gracefully

@humanizersequel
Copy link
Collaborator Author

and the sh build.sh --env production path works just fine

@Gohlub I want to get this out today. If you've like to break some of the business logic out into a separate file now's the time

@humanizersequel
Copy link
Collaborator Author

Let's make it so that provider:hypergrid:ware.hypr takes priority in the UI (when you click the Hypergrid button, it should open straight to that interface).

@humanizersequel
Copy link
Collaborator Author

"I approve"

@humanizersequel humanizersequel merged commit af40cc6 into develop Oct 23, 2025
@humanizersequel humanizersequel deleted the x402 branch October 23, 2025 17:49
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.

3 participants