-
Notifications
You must be signed in to change notification settings - Fork 43
x402 X Turnkey demo #1093
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
x402 X Turnkey demo #1093
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit ad71151:
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
ad71151 to
fa57fc5
Compare
r-n-o
left a comment
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.
👏
Some minor things below, but overall great work!
| // This middleware will intercept requests to the protected route(s). It checks for the presence of a "payment-session" cookie, | ||
| // which indicates that the user has made a valid payment. If the cookie is not present, the user is redirected to the paywall page. | ||
|
|
||
| const paymentHeader = request.cookies.get("payment-session"); | ||
| if (!paymentHeader) { | ||
| return NextResponse.rewrite(new URL("/paywall", request.url)); | ||
| } | ||
|
|
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.
Maybe worth a comment here: this validation is really weak because it only checks for the presence of a payment-session header and doesn't validate its value. So a good TODO would be using a x402 validator to ensure the payment header is valid and contain a payment that has been made correctly, to the right 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.
Added comment
| }; | ||
|
|
||
| useEffect(() => { | ||
| setScale(100); |
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.
crank it up to 1000
I'm 50% joking 50% not joking
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.
500 is a good compromise
| const facilitatorUrl = | ||
| process.env.NEXT_PUBLIC_FACILITATOR_URL || | ||
| "https://www.x402.org/facilitator"; | ||
|
|
||
| // Call facilitator to verify | ||
| const verifyResponse = await fetch(`${facilitatorUrl}/verify`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| paymentPayload: decodedPayment, | ||
| paymentRequirements: PAYMENT_REQUIREMENTS, | ||
| }), | ||
| }); |
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.
Oh! So we do use a public facilitator already. Glorious.
| mimeType: "text/html", | ||
| payTo: process.env.NEXT_PUBLIC_RESOURCE_WALLET_ADDRESS!, | ||
| maxTimeoutSeconds: COOKIE_TIMEOUT_SECONDS, | ||
| asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // USDC on Base Sepolia |
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) you already have this in the other file (const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";). Define it in a common place and import it?
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.
Added constants.ts
705c99f to
0da27ad
Compare
0da27ad to
389eb4a
Compare
Summary & Motivation
x402 with Turnkey Embedded Wallets
This example demonstrates how to use Coinbase's x402 payment protocol with Turnkey's embedded wallets for seamless payment authentication.
Overview
This demo shows how to build a custom x402 middleware that integrates with Turnkey's embedded wallet system instead of relying on browser wallet extensions.
How It Works
1. Middleware-Based Access Control
The demo uses Next.js middleware to gate access to protected content. The middleware (
middleware.ts) intercepts requests to/protected/*routes and checks for apayment-sessioncookie:This means the server is responsible for gating the access to the protected page. The cookie only lasts for 5 minutes and can be cleared before if needed.
2. Turnkey-Powered Payment Authorization
The payment page (
app/paywall/page.tsx) uses Turnkey's embedded wallets to sign the payment authorization:@turnkey/react-wallet-kitpackage, which handles authentication with different methodsTransferWithAuthorizationmessage for USDC on Base Sepolia using the@turnkey/viempackage. This is a gasless transfer authorization that includes:from) and recipient (to) addressesx402package and submitted to the server for verification3. Payment Verification with Public Facilitator
After the payload is signed, the server route uses a public x402 facilitator to verify the payment:
x402package/verifyendpoint validates that:/settleendpoint records the payment as settledpayment-sessioncookie is set with a 5-minute expirationThe facilitator acts as a neutral third party that verifies payments without requiring the merchant to run their own verification infrastructure. You can also host your own facilitator.
Running The App
To start, ensure you have a Turnkey organization setup with Auth Proxy enabled.
Configure your
.env.localfileCopy the
.env.local.examplefile and rename it to.env.localthen fill in the following fields:You can use any Ethereum wallet address to act as a resource wallet. The Base Sepolia USDC will go there.
Start the Development Server
Install the packages and run the dev server:
You'll see the demo running on http://localhost:3000.