Skip to content

Commit e68af06

Browse files
Merge pull request #2402 from Web3Auth/feat/x402
Feat/x402
2 parents cbea0ec + 6e011f7 commit e68af06

35 files changed

+20340
-15039
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ typings/
4949

5050
# Optional npm cache directory
5151
.npm
52+
.npmrc
5253

5354
# Optional eslint cache
5455
.eslintcache

demo/vue-app-new/.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ VITE_APP_SOLANA_MAINNET_RPC=""
33
VITE_APP_SOLANA_DEVNET_RPC=""
44
VITE_APP_SOLANA_TESTNET_RPC=""
55
VITE_APP_AUTH_BUILD_ENV=""
6+
VITE_APP_X402_TEST_CONTENT_URL=""
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# x402 Testing Plan
2+
3+
## Goal
4+
5+
Validate the end-to-end x402 payment flow used by the Vue demo against the local x402 test server.
6+
7+
Success means:
8+
9+
- protected endpoints return a payment challenge when no payment is attached
10+
- a funded Base Sepolia wallet can complete the paid retry successfully
11+
- `X402Tester` writes results and errors to the shared dashboard UI console
12+
- common failure modes are easy to reproduce and diagnose
13+
14+
## Scope
15+
16+
This plan covers the main x402 demo path across:
17+
18+
- `demo/vue-app-new/src/components/X402Tester.vue`
19+
- `demo/vue-app-new/src/components/AppDashboard.vue`
20+
- `demo/x402-test-server/src/index.ts`
21+
- the `useX402Fetch` flow exposed through `@web3auth/modal/x402/vue`
22+
23+
It does not try to fully validate every wallet provider or every facilitator implementation.
24+
25+
## Test Environment
26+
27+
### Backend
28+
29+
Start the local x402 server:
30+
31+
```bash
32+
cd demo/x402-test-server
33+
cp .env.example .env
34+
# Set EVM_ADDRESS and SVM_ADDRESS
35+
npm install
36+
npm run dev
37+
```
38+
39+
Expected default server URL:
40+
41+
```text
42+
http://localhost:4021
43+
```
44+
45+
### Frontend
46+
47+
Start the Vue demo and point it at the local server:
48+
49+
```bash
50+
cd demo/vue-app-new
51+
cp .env.sample .env
52+
```
53+
54+
Set at least:
55+
56+
```bash
57+
VITE_APP_X402_TEST_CONTENT_URL=http://localhost:4021/weather
58+
```
59+
60+
Then run:
61+
62+
```bash
63+
npm install
64+
npm run dev
65+
```
66+
67+
## Preconditions
68+
69+
- Web3Auth login is working in the Vue demo
70+
- an EVM wallet is connected
71+
- the wallet can switch to Base Sepolia (`0x14a34`)
72+
- the wallet has enough Base Sepolia gas
73+
- the wallet has enough Base Sepolia USDC for micro-payments
74+
75+
## Endpoints Under Test
76+
77+
| Method | Path | Expected behavior |
78+
| ------ | ---- | ----------------- |
79+
| `GET` | `/health` | Free health check |
80+
| `GET` | `/weather` | Standard paid x402 route |
81+
| `GET` | `/premium-data` | Paid route with higher price |
82+
| `GET` | `/weather-plain` | Debug-friendly route that returns payment requirements in the body when unpaid |
83+
84+
## Manual Test Cases
85+
86+
| ID | Scenario | Steps | Expected result |
87+
| -- | -------- | ----- | --------------- |
88+
| `X402-01` | Server health | Call `GET /health` in a browser or with `curl` | `200 OK` with JSON status payload |
89+
| `X402-02` | Unpaid challenge for `/weather` | Call `GET /weather` without payment headers | `402 Payment Required` and x402 payment requirements |
90+
| `X402-03` | Successful paid fetch for `/weather` in Vue UI | Log in, connect wallet, switch to Base Sepolia, keep the tester URL on `/weather`, and press `Fetch with Payment` | shared UI console shows `x402 response`, `status: 200`, and a weather payload in `body` |
91+
| `X402-04` | Successful paid fetch for `/premium-data` in Vue UI | Change the tester URL to `/premium-data` and press `Fetch with Payment` | shared UI console shows `x402 response`, `status: 200`, and premium dataset JSON |
92+
| `X402-05` | Debug route without payment | Call `GET /weather-plain` without payment headers | `402` body includes `accepts` array and resource metadata |
93+
| `X402-06` | Debug route through Vue UI | Set the tester URL to `/weather-plain` and press `Fetch with Payment` | shared UI console shows `x402 response`, `status: 200`, and the weather payload |
94+
| `X402-07` | Wrong-network recovery | Open the Vue demo while connected to an EVM chain other than Base Sepolia | tester shows `Not on Base Sepolia`; after pressing `Switch to Base Sepolia`, the badge updates and fetch can be retried |
95+
| `X402-08` | Insufficient balance failure | Use a wallet without enough USDC or gas and attempt `Fetch with Payment` | fetch fails cleanly; UI console shows an actionable error or a 402-style response that can be inspected |
96+
97+
## Focus Areas During Testing
98+
99+
### UI behavior
100+
101+
- the `Fetch with Payment` button is disabled when no wallet is connected
102+
- loading state is shown while the request is in flight
103+
- results are printed in the dashboard console, not inline inside `X402Tester`
104+
- errors are also routed to the dashboard console
105+
106+
### Payment behavior
107+
108+
- unpaid requests are challenged instead of silently failing
109+
- paid retries use the connected wallet on Base Sepolia
110+
- the same URL can be retested multiple times without refreshing the page
111+
- successful payments return the protected resource body
112+
113+
### Debuggability
114+
115+
- `/weather-plain` remains available as a browser-friendly inspection route
116+
- server logs are sufficient to distinguish challenge, retry, and settlement failures
117+
- console output includes enough context to see which URL was tested and whether the response was successful
118+
119+
## Suggested cURL Checks
120+
121+
Use these checks before testing the Vue flow:
122+
123+
```bash
124+
curl -i http://localhost:4021/health
125+
curl -i http://localhost:4021/weather
126+
curl -i http://localhost:4021/weather-plain
127+
curl -i http://localhost:4021/premium-data
128+
```
129+
130+
Expected outcomes:
131+
132+
- `/health` returns `200`
133+
- paid routes return `402` when no payment is attached
134+
- `/weather-plain` returns a readable JSON challenge body
135+
136+
## Failure Cases To Watch
137+
138+
Common issues worth validating explicitly:
139+
140+
- wallet is connected, but not on Base Sepolia
141+
- wallet has insufficient USDC for the requested payment
142+
- wallet has insufficient gas to complete the transaction
143+
- a stale or expired payment proof is retried
144+
- the configured tester URL points to the wrong server or port
145+
- the frontend shows no result because console output wiring regressed
146+
147+
## Exit Criteria
148+
149+
The testing cycle is complete when:
150+
151+
- `X402-01` through `X402-06` pass
152+
- at least one negative case from `X402-07` or `X402-08` is exercised
153+
- success and failure states are both visible in the dashboard console
154+
- no inline result panel is required to inspect x402 responses

demo/vue-app-new/package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/vue-app-new/src/components/AppDashboard.vue

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
useWalletUI,
1313
useWeb3Auth,
1414
useWeb3AuthUser,
15-
1615
} from "@web3auth/modal/vue";
1716
import { CONNECTOR_INITIAL_AUTHENTICATION_MODE, type CustomChainConfig } from "@web3auth/no-modal";
1817
import { useI18n } from "petite-vue-i18n";
@@ -30,9 +29,10 @@ import {
3029
import { getCapabilities, getCallsStatus, sendCalls, showCallsStatus } from "@wagmi/core";
3130
import { parseEther } from "viem";
3231
import { createWalletTransactionSigner, toAddress } from "@solana/client";
33-
import { address as solanaAddress, } from "@solana/kit";
32+
import { address as solanaAddress } from "@solana/kit";
3433
import { getTransferSolInstruction } from "@solana-program/system";
3534
import { computed, ref, watch } from "vue";
35+
import X402Tester from "./X402Tester.vue";
3636
import { getPrivateKey, sendEth, sendEthWithSmartAccount, signTransaction as signEthTransaction } from "../services/ethHandlers";
3737
import { formDataStore } from "../store/form";
3838
@@ -108,10 +108,7 @@ const isDisplay = (name: "dashboard" | "ethServices" | "solServices" | "walletSe
108108
return Boolean(conn?.solanaWallet);
109109
110110
case "walletServices":
111-
return (
112-
web3Auth.value?.connectedConnectorName === WALLET_CONNECTORS.AUTH &&
113-
Boolean(conn?.ethereumProvider || conn?.solanaWallet)
114-
);
111+
return web3Auth.value?.connectedConnectorName === WALLET_CONNECTORS.AUTH && Boolean(conn?.ethereumProvider || conn?.solanaWallet);
115112
116113
default: {
117114
return false;
@@ -394,8 +391,6 @@ const onSwitchChain = async () => {
394391
printToConsole("switchedChain error", error);
395392
}
396393
};
397-
398-
399394
</script>
400395

401396
<template>
@@ -496,20 +491,15 @@ const onSwitchChain = async () => {
496491

497492
<!-- EIP-5792 -->
498493
<div class="mb-2 mt-4 text-xl font-bold leading-tight text-left">EIP-5792</div>
499-
<Button block size="xs" pill class="mb-2" @click="onGetCapabilities">
500-
Get Capabilities
501-
</Button>
502-
<Button block size="xs" pill class="mb-2" @click="onSendBatchCalls">
503-
Send Batch Calls
504-
</Button>
505-
<Button v-if="trackedCallsId" block size="xs" pill class="mb-2" @click="onRefetchCallsStatus">
506-
Refresh Calls Status
507-
</Button>
508-
<Button v-if="trackedCallsId" block size="xs" pill class="mb-2" @click="onShowCallsStatusInWallet">
509-
Show Calls Status in Wallet
510-
</Button>
494+
<Button block size="xs" pill class="mb-2" @click="onGetCapabilities">Get Capabilities</Button>
495+
<Button block size="xs" pill class="mb-2" @click="onSendBatchCalls">Send Batch Calls</Button>
496+
<Button v-if="trackedCallsId" block size="xs" pill class="mb-2" @click="onRefetchCallsStatus">Refresh Calls Status</Button>
497+
<Button v-if="trackedCallsId" block size="xs" pill class="mb-2" @click="onShowCallsStatusInWallet">Show Calls Status in Wallet</Button>
511498
</Card>
512499

500+
<!-- x402 -->
501+
<X402Tester v-if="isDisplay('ethServices') || isDisplay('solServices')" class="mb-2" @print-to-console="printToConsole" />
502+
513503
<!-- SOLANA -->
514504
<Card v-if="isDisplay('solServices')" class="h-auto gap-4 px-4 py-4 mb-2" :shadow="false">
515505
<div class="mb-2 text-xl font-bold leading-tight text-left">Solana Transaction</div>

0 commit comments

Comments
 (0)