@4mica/x402 1.2.4 → 2.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +67 -82
  3. package/dist/client/scheme.d.ts +40 -4
  4. package/dist/client/scheme.js +96 -38
  5. package/dist/domain.d.ts +12 -0
  6. package/dist/domain.js +30 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/server/express/adapter.d.ts +2 -2
  9. package/dist/server/express/index.d.ts +13 -46
  10. package/dist/server/express/index.js +32 -71
  11. package/dist/server/facilitator.d.ts +2 -24
  12. package/dist/server/facilitator.js +0 -36
  13. package/dist/server/index.d.ts +2 -4
  14. package/dist/server/index.js +1 -2
  15. package/dist/server/scheme.d.ts +54 -10
  16. package/dist/server/scheme.js +118 -56
  17. package/dist/types.d.ts +36 -12
  18. package/package.json +33 -32
  19. package/.eslintrc.cjs +0 -29
  20. package/.prettierignore +0 -3
  21. package/.prettierrc +0 -6
  22. package/demo/.env.example +0 -8
  23. package/demo/README.md +0 -125
  24. package/demo/package.json +0 -26
  25. package/demo/src/client.ts +0 -54
  26. package/demo/src/deposit.ts +0 -37
  27. package/demo/src/server.ts +0 -81
  28. package/demo/tsconfig.json +0 -8
  29. package/demo/yarn.lock +0 -925
  30. package/eslint.config.mjs +0 -22
  31. package/src/client/index.ts +0 -1
  32. package/src/client/scheme.ts +0 -111
  33. package/src/index.ts +0 -9
  34. package/src/server/express/adapter.ts +0 -100
  35. package/src/server/express/index.ts +0 -499
  36. package/src/server/facilitator.ts +0 -206
  37. package/src/server/index.ts +0 -10
  38. package/src/server/scheme.ts +0 -229
  39. package/src/types.ts +0 -24
  40. package/tests/client-scheme.test.ts +0 -99
  41. package/tests/facilitator.test.ts +0 -174
  42. package/tsconfig.build.json +0 -5
  43. package/tsconfig.json +0 -17
  44. package/vitest.config.ts +0 -12
@@ -1,12 +1,43 @@
1
- export const SUPPORTED_NETWORKS = ['eip155:11155111', 'eip155:84532', 'eip155:80002'];
1
+ import { RpcProxy, resolveNetworkRpcUrl, } from '@4mica/sdk';
2
+ import { SDK_DEFAULT_ASSET_TRANSFER_METHOD } from '@x402/core/server';
3
+ import { chainIdOf, DOMAIN_EXTRA_KEYS } from '../domain.js';
4
+ export const SUPPORTED_NETWORKS = ['eip155:11155111', 'eip155:84532', 'eip155:8453'];
2
5
  /**
3
6
  * EVM server implementation for the 4mica payment scheme.
7
+ *
8
+ * A `Money` price resolves to the stablecoin core lists for the network
9
+ * (`GET /core/tokens`), so the advertised `asset` is always one core accepts a
10
+ * guarantee against. Every requirement also carries core's EIP-712 domain in
11
+ * `extra` (`name`, `version`, `verifyingContract`, from
12
+ * `GET /core/public-params`), so a payer can sign without calling core. Both
13
+ * are fetched once per network and cached for the life of the instance.
4
14
  */
5
15
  export class FourMicaEvmScheme {
6
- constructor(advertisedTabEndpoint) {
7
- this.advertisedTabEndpoint = advertisedTabEndpoint;
16
+ constructor(options = {}) {
8
17
  this.scheme = '4mica-credit';
18
+ // No on-wire assetTransferMethod: a claim is a claim. The signed payment is
19
+ // verified before the handler runs and settled (guarantee issued) after it
20
+ // — the library's authorization flow.
21
+ this.defaultAssetTransferMethod = SDK_DEFAULT_ASSET_TRANSFER_METHOD;
22
+ this.paymentFlows = {
23
+ [SDK_DEFAULT_ASSET_TRANSFER_METHOD]: {
24
+ supported: ['authorization'],
25
+ default: 'authorization',
26
+ },
27
+ };
9
28
  this.moneyParsers = [];
29
+ this.defaultAssets = new Map();
30
+ this.domains = new Map();
31
+ this.coreUrls = options.coreUrls ?? {};
32
+ this.stablecoinSymbol = options.stablecoinSymbol ?? 'USDC';
33
+ }
34
+ /** Core's token list. Private static so tests can stub the network call. */
35
+ static loadSupportedTokens(coreUrl) {
36
+ return new RpcProxy(coreUrl).getSupportedTokens();
37
+ }
38
+ /** Core's public parameters. Private static so tests can stub the network call. */
39
+ static loadPublicParams(coreUrl) {
40
+ return new RpcProxy(coreUrl).getPublicParams();
10
41
  }
11
42
  /**
12
43
  * Register a custom money parser in the parser chain.
@@ -36,7 +67,7 @@ export class FourMicaEvmScheme {
36
67
  * Parses a price into an asset amount.
37
68
  * If price is already an AssetAmount, returns it directly.
38
69
  * If price is Money (string | number), parses to decimal and tries custom parsers.
39
- * Falls back to default conversion if all custom parsers return null.
70
+ * Falls back to the stablecoin core lists for the network if all custom parsers return null.
40
71
  *
41
72
  * @param price - The price to parse
42
73
  * @param network - The network to use
@@ -67,7 +98,12 @@ export class FourMicaEvmScheme {
67
98
  return this.defaultMoneyConversion(amount, network);
68
99
  }
69
100
  /**
70
- * Build payment requirements for this scheme/network combination
101
+ * Build payment requirements for this scheme/network combination.
102
+ *
103
+ * Adds core's EIP-712 domain to `extra` so the payer can sign without a
104
+ * round trip to core. A domain the resource server set itself is kept; only
105
+ * the missing keys are filled in. The chain id is not added: a client takes
106
+ * it from `network`.
71
107
  *
72
108
  * @param paymentRequirements - The base payment requirements
73
109
  * @param supportedKind - The supported kind from facilitator (unused)
@@ -78,15 +114,17 @@ export class FourMicaEvmScheme {
78
114
  * @param extensionKeys - Extension keys supported by the facilitator (unused)
79
115
  * @returns Payment requirements ready to be sent to clients
80
116
  */
81
- enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
82
- // Mark unused parameters to satisfy linter
117
+ async enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
83
118
  void supportedKind;
84
119
  void extensionKeys;
85
- if (!paymentRequirements.extra) {
86
- paymentRequirements.extra = {};
87
- }
88
- paymentRequirements.extra.tabEndpoint = this.advertisedTabEndpoint;
89
- return Promise.resolve(paymentRequirements);
120
+ const extra = { ...(paymentRequirements.extra ?? {}) };
121
+ const missing = DOMAIN_EXTRA_KEYS.filter((key) => typeof extra[key] !== 'string');
122
+ if (missing.length === 0)
123
+ return paymentRequirements;
124
+ const domain = await this.getDomain(paymentRequirements.network);
125
+ for (const key of missing)
126
+ extra[key] = domain[key];
127
+ return { ...paymentRequirements, extra };
90
128
  }
91
129
  /**
92
130
  * Parse Money (string | number) to a decimal number.
@@ -102,29 +140,28 @@ export class FourMicaEvmScheme {
102
140
  // Remove $ sign and whitespace, then parse
103
141
  const cleanMoney = money.replace(/^\$/, '').trim();
104
142
  const amount = parseFloat(cleanMoney);
105
- if (isNaN(amount)) {
143
+ if (Number.isNaN(amount)) {
106
144
  throw new Error(`Invalid money format: ${money}`);
107
145
  }
108
146
  return amount;
109
147
  }
110
148
  /**
111
149
  * Default money conversion implementation.
112
- * Converts decimal amount to the default stablecoin on the specified network.
150
+ * Converts a decimal amount to the stablecoin core lists for the network.
113
151
  *
114
152
  * @param amount - The decimal amount (e.g., 1.50)
115
153
  * @param network - The network to use
116
154
  * @returns The parsed asset amount in the default stablecoin
117
155
  */
118
- defaultMoneyConversion(amount, network) {
119
- const assetInfo = this.getDefaultAsset(network);
120
- const tokenAmount = this.convertToTokenAmount(amount.toString(), assetInfo.decimals);
156
+ async defaultMoneyConversion(amount, network) {
157
+ const asset = await this.getDefaultAsset(network);
121
158
  return {
122
- amount: tokenAmount,
123
- asset: assetInfo.address,
124
- extra: {
125
- name: assetInfo.name,
126
- version: assetInfo.version,
127
- },
159
+ amount: this.convertToTokenAmount(amount.toString(), asset.decimals),
160
+ asset: asset.address,
161
+ // No EIP-3009 domain hints: a 4mica-credit payer signs against core's
162
+ // guarantee domain, never the token's. `enhancePaymentRequirements` adds
163
+ // that domain for every kind of price.
164
+ extra: {},
128
165
  };
129
166
  }
130
167
  /**
@@ -136,7 +173,7 @@ export class FourMicaEvmScheme {
136
173
  */
137
174
  convertToTokenAmount(decimalAmount, decimals) {
138
175
  const amount = parseFloat(decimalAmount);
139
- if (isNaN(amount)) {
176
+ if (Number.isNaN(amount)) {
140
177
  throw new Error(`Invalid amount: ${decimalAmount}`);
141
178
  }
142
179
  // Convert to smallest unit (e.g., for USDC with 6 decimals: 0.10 * 10^6 = 100000)
@@ -145,41 +182,66 @@ export class FourMicaEvmScheme {
145
182
  const tokenAmount = (intPart + paddedDec).replace(/^0+/, '') || '0';
146
183
  return tokenAmount;
147
184
  }
185
+ coreUrlFor(network) {
186
+ const coreUrl = this.coreUrls[network] ?? resolveNetworkRpcUrl(network);
187
+ if (!coreUrl) {
188
+ throw new Error(`No core API URL known for network ${network}; pass one in coreUrls`);
189
+ }
190
+ return coreUrl;
191
+ }
148
192
  /**
149
- * Get the default asset info for a network (typically USDC)
150
- *
151
- * @param network - The network to get asset info for
152
- * @returns The asset information including address, name, version, and decimals
193
+ * The stablecoin core lists for `network`, fetched once and cached. A failed
194
+ * lookup is not cached, so the next price parse retries.
153
195
  */
154
196
  getDefaultAsset(network) {
155
- // Map of network to USDC info including EIP-712 domain parameters
156
- // Each network has the right to determine its own default stablecoin that can be expressed as a USD string by calling servers
157
- // NOTE: Currently only EIP-3009 supporting stablecoins can be used with this scheme
158
- // Generic ERC20 support via EIP-2612/permit2 is planned, but not yet implemented.
159
- const stablecoins = {
160
- 'eip155:11155111': {
161
- address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
162
- name: 'USDC',
163
- version: '2',
164
- decimals: 6,
165
- }, // Ethereum Sepolia USDC
166
- 'eip155:84532': {
167
- address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
168
- name: 'USDC',
169
- version: '2',
170
- decimals: 6,
171
- }, // Base Sepolia USDC
172
- 'eip155:80002': {
173
- address: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582',
174
- name: 'USDC',
175
- version: '2',
176
- decimals: 6,
177
- }, // Polygon PoS Amoy USDC
178
- };
179
- const assetInfo = stablecoins[network];
180
- if (!assetInfo) {
181
- throw new Error(`No default asset configured for network ${network}`);
197
+ return cachedLookup(this.defaultAssets, network, () => this.resolveDefaultAsset(network));
198
+ }
199
+ async resolveDefaultAsset(network) {
200
+ const coreUrl = this.coreUrlFor(network);
201
+ const { tokens } = await FourMicaEvmScheme.loadSupportedTokens(coreUrl);
202
+ const wanted = this.stablecoinSymbol.toLowerCase();
203
+ const token = tokens.find((entry) => entry.symbol.toLowerCase() === wanted);
204
+ if (!token) {
205
+ const listed = tokens.map((entry) => entry.symbol).join(', ') || 'none';
206
+ throw new Error(`Core at ${coreUrl} lists no ${this.stablecoinSymbol} for network ${network} (listed: ${listed})`);
207
+ }
208
+ if (token.decimals === undefined) {
209
+ throw new Error(`Core at ${coreUrl} reports no decimals for ${token.symbol} on network ${network}`);
182
210
  }
183
- return assetInfo;
211
+ return { address: token.address, decimals: token.decimals };
212
+ }
213
+ /**
214
+ * Core's EIP-712 domain for `network`, fetched once and cached. A failed
215
+ * lookup is not cached, so the next 402 retries.
216
+ */
217
+ getDomain(network) {
218
+ return cachedLookup(this.domains, network, () => this.resolveDomain(network));
219
+ }
220
+ async resolveDomain(network) {
221
+ const coreUrl = this.coreUrlFor(network);
222
+ const params = await FourMicaEvmScheme.loadPublicParams(coreUrl);
223
+ // The client derives the chain id from `network`, so a core that serves a
224
+ // different chain would hand out a domain no signature can satisfy.
225
+ const expectedChainId = chainIdOf(network);
226
+ if (expectedChainId !== undefined && params.chainId !== expectedChainId) {
227
+ throw new Error(`Core at ${coreUrl} serves chain ${params.chainId}, not ${network}; check coreUrls`);
228
+ }
229
+ return {
230
+ name: params.eip712Name,
231
+ version: params.eip712Version,
232
+ verifyingContract: params.contractAddress,
233
+ };
234
+ }
235
+ }
236
+ /** Memoise a per-network lookup, dropping a rejected entry so the next call retries. */
237
+ function cachedLookup(cache, network, resolve) {
238
+ let pending = cache.get(network);
239
+ if (!pending) {
240
+ pending = resolve().catch((err) => {
241
+ cache.delete(network);
242
+ throw err;
243
+ });
244
+ cache.set(network, pending);
184
245
  }
246
+ return pending;
185
247
  }
package/dist/types.d.ts CHANGED
@@ -1,15 +1,39 @@
1
- import { PaymentRequirements } from '@x402/core/types';
1
+ import type { PaymentRequirements } from '@x402/core/types';
2
2
  /**
3
- * Extra fields required by the 4mica V2 validation policy.
3
+ * The `extra.validation` object a resource server advertises to gate a
4
+ * payment on an external validator. Present ⇒ the payer signs the same
5
+ * requirement into their claims, and the guarantee only becomes payable once
6
+ * the validator approves it.
4
7
  */
5
- export type FourMicaV2RequirementsExtra = {
6
- validationRegistryAddress: string;
7
- validatorAddress: string;
8
- validatorAgentId: string | number;
9
- minValidationScore: number;
10
- jobHash: string;
11
- requiredValidationTag?: string;
12
- tabEndpoint?: string;
8
+ export type FourMicaValidationExtra = {
9
+ /** Validator identifier; must be on core's allowlist. */
10
+ validator: string;
11
+ /** 0x-prefixed bytes32 the validator must approve. */
12
+ subject: string;
13
+ /** Unix seconds; core tightens this to the cycle's resolution cutoff. */
14
+ deadline?: number;
15
+ /** 0x-prefixed validator-specific policy bytes. */
16
+ params?: string;
17
+ };
18
+ /**
19
+ * The EIP-712 domain of the core a payer signs against, as
20
+ * `GET /core/public-params` reports it (`eip712_name`, `eip712_version`,
21
+ * `contract_address`). The chain id is not carried: it is the CAIP-2
22
+ * reference in `network`, so the two can never disagree.
23
+ */
24
+ export type FourMicaDomainExtra = {
25
+ name: string;
26
+ version: string;
27
+ verifyingContract: string;
28
+ };
29
+ /**
30
+ * Extra fields the 4mica-credit scheme understands on `paymentRequirements`.
31
+ * There is no tab endpoint any more: clients sign their claim straight from
32
+ * the requirements, minting a random `reqId` locally.
33
+ */
34
+ export type FourMicaRequirementsExtra = Partial<FourMicaDomainExtra> & {
35
+ validation?: FourMicaValidationExtra;
36
+ /** Override the 4Mica core API URL the client signs against. */
13
37
  rpcUrl?: string;
14
38
  resource?: {
15
39
  url?: string;
@@ -17,6 +41,6 @@ export type FourMicaV2RequirementsExtra = {
17
41
  mimeType?: string;
18
42
  };
19
43
  };
20
- export type FourMicaPaymentRequirementsV2 = PaymentRequirements & {
21
- extra: FourMicaV2RequirementsExtra;
44
+ export type FourMicaPaymentRequirements = PaymentRequirements & {
45
+ extra?: FourMicaRequirementsExtra;
22
46
  };
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@4mica/x402",
3
- "version": "1.2.4",
3
+ "version": "2.0.0-alpha.2",
4
4
  "type": "module",
5
5
  "description": "TypeScript x402 utilities for interacting with the 4Mica payment network",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/4mica-Network/x402-4mica.git"
9
+ "url": "https://github.com/4mica-Network/4mica.git",
10
+ "directory": "apps/facilitator/packages/typescript/x402"
10
11
  },
11
- "homepage": "https://github.com/4mica-Network/x402-4mica#readme",
12
+ "homepage": "https://github.com/4mica-Network/4mica/tree/main/apps/facilitator/packages/typescript/x402#readme",
12
13
  "bugs": {
13
- "url": "https://github.com/4mica-Network/x402-4mica/issues"
14
+ "url": "https://github.com/4mica-Network/4mica/issues"
14
15
  },
15
16
  "main": "dist/index.js",
16
17
  "types": "dist/index.d.ts",
@@ -32,37 +33,25 @@
32
33
  "import": "./dist/client/index.js"
33
34
  }
34
35
  },
35
- "scripts": {
36
- "build": "tsc -p tsconfig.build.json",
37
- "test": "vitest run",
38
- "lint": "eslint . --ext .ts",
39
- "fmt": "prettier --check \"{src,tests}/**/*.{ts,js,json}\"",
40
- "demo:server": "cd demo && yarn server",
41
- "demo:client": "cd demo && yarn client",
42
- "demo:dev": "cd demo && yarn dev"
43
- },
36
+ "files": [
37
+ "dist",
38
+ "CHANGELOG.md"
39
+ ],
44
40
  "dependencies": {
45
- "@x402/core": "^2.9.0",
46
- "@x402/extensions": "^2.9.0",
47
- "@4mica/sdk": "^1.2.3",
48
- "viem": "^2.45.1"
41
+ "@x402/core": "2.23.0",
42
+ "@x402/extensions": "2.23.0",
43
+ "viem": "2.x",
44
+ "@4mica/sdk": "^2.0.0-alpha.0"
49
45
  },
50
46
  "devDependencies": {
51
47
  "@arethetypeswrong/cli": "^0.18.2",
52
- "@eslint/js": "^9.39.2",
53
- "@types/express": "^5.0.6",
54
- "@types/node": "^25.0.7",
55
- "@typescript-eslint/eslint-plugin": "^8.48.1",
56
- "@typescript-eslint/parser": "^8.48.1",
57
- "eslint": "^9.39.2",
58
- "eslint-config-prettier": "^10.1.8",
59
- "eslint-plugin-react": "^7.37.5",
60
- "express": "^5.2.1",
61
- "globals": "^17.0.0",
62
- "prettier": "^3.7.4",
63
- "typescript": "^5.3.3",
64
- "typescript-eslint": "^8.48.1",
65
- "vitest": "^4.0.17"
48
+ "@biomejs/biome": "^2.4.16",
49
+ "@types/express": "^5.0.0",
50
+ "@types/node": "^26.1.1",
51
+ "express": "^5.1.0",
52
+ "publint": "^0.3.14",
53
+ "typescript": "^5.9.0",
54
+ "vitest": "^4.1.8"
66
55
  },
67
56
  "peerDependencies": {
68
57
  "@x402/paywall": "^2.2.0",
@@ -75,5 +64,17 @@
75
64
  },
76
65
  "publishConfig": {
77
66
  "access": "public"
67
+ },
68
+ "scripts": {
69
+ "build": "tsc -p tsconfig.build.json",
70
+ "check:exports": "attw --pack . --profile esm-only && publint --strict",
71
+ "test": "vitest run",
72
+ "lint": "biome check .",
73
+ "lint:write": "biome check --write --diagnostic-level=error .",
74
+ "fmt": "biome format .",
75
+ "fmt:write": "biome format --write .",
76
+ "demo:server": "pnpm --filter @4mica/x402-demo run server",
77
+ "demo:client": "pnpm --filter @4mica/x402-demo run client",
78
+ "demo:dev": "pnpm --filter @4mica/x402-demo run dev"
78
79
  }
79
- }
80
+ }
package/.eslintrc.cjs DELETED
@@ -1,29 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- env: {
4
- es2020: true,
5
- node: true,
6
- },
7
- parser: "@typescript-eslint/parser",
8
- parserOptions: {
9
- sourceType: "module",
10
- ecmaVersion: 2020,
11
- project: "./tsconfig.json",
12
- },
13
- plugins: ["@typescript-eslint"],
14
- extends: [
15
- "eslint:recommended",
16
- "plugin:@typescript-eslint/recommended",
17
- "eslint-config-prettier",
18
- ],
19
- rules: {
20
- "@typescript-eslint/no-explicit-any": "off",
21
- "@typescript-eslint/explicit-module-boundary-types": "off",
22
- "@typescript-eslint/no-unused-vars": [
23
- "error",
24
- { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
25
- ],
26
- "@typescript-eslint/no-var-requires": "off",
27
- },
28
- ignorePatterns: ["dist", "node_modules"],
29
- };
package/.prettierignore DELETED
@@ -1,3 +0,0 @@
1
- dist
2
- node_modules
3
- coverage
package/.prettierrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "trailingComma": "es5",
4
- "printWidth": 100,
5
- "semi": false
6
- }
package/demo/.env.example DELETED
@@ -1,8 +0,0 @@
1
- # Server Configuration
2
- PORT=3000
3
- PAY_TO_ADDRESS=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
4
- ADVERTISED_ENDPOINT=http://localhost:3000/payment/tab
5
-
6
- # Client Configuration
7
- PRIVATE_KEY=0xYourPrivateKeyHere
8
- API_URL=http://localhost:3000
package/demo/README.md DELETED
@@ -1,125 +0,0 @@
1
- # @4mica/x402 Demo
2
-
3
- This demo shows how to use `@4mica/x402` to protect an API endpoint with 4mica payments.
4
-
5
- ## Setup
6
-
7
- 1. **Install dependencies**:
8
-
9
- First, build the parent package:
10
-
11
- ```bash
12
- cd ..
13
- yarn install
14
- yarn build
15
- ```
16
-
17
- Then install demo dependencies:
18
-
19
- ```bash
20
- cd demo
21
- yarn install
22
- ```
23
-
24
- 2. **Configure environment variables**:
25
-
26
- Copy the example file and edit it:
27
-
28
- ```bash
29
- cp .env.example .env
30
- # Edit .env with your private key and settings
31
- ```
32
-
33
- Required variables:
34
- - `PRIVATE_KEY`: Your Ethereum private key (with 0x prefix) for Sepolia testnet
35
- - `PAY_TO_ADDRESS`: Address that will receive payments
36
-
37
- ## Running the Demo
38
-
39
- ### Terminal 1: Start the server
40
-
41
- From the demo directory:
42
-
43
- ```bash
44
- yarn server
45
- ```
46
-
47
- Or from the package root:
48
-
49
- ```bash
50
- yarn demo:server
51
- ```
52
-
53
- You should see:
54
- ```
55
- x402 Demo Server running on http://localhost:3000
56
- Protected endpoint: http://localhost:3000/api/premium-data
57
- Payment required: $0.01 (4mica credit on Sepolia)
58
- ```
59
-
60
- ### Terminal 2: Run the client
61
-
62
- The client will automatically load environment variables from `.env`:
63
-
64
- From the demo directory:
65
-
66
- ```bash
67
- yarn client
68
- ```
69
-
70
- Or from the package root:
71
-
72
- ```bash
73
- yarn demo:client
74
- ```
75
-
76
- You can also set PRIVATE_KEY inline:
77
-
78
- ```bash
79
- PRIVATE_KEY=0xYourPrivateKey yarn client
80
- ```
81
-
82
- ## What Happens
83
-
84
- 1. **Server** starts with one protected endpoint: `GET /api/premium-data`
85
- - Requires a payment of $0.01 in 4mica credits on Sepolia
86
- - Uses x402 payment protocol
87
-
88
- 2. **Client** makes a request to the protected endpoint:
89
- - First receives `402 Payment Required` response
90
- - Automatically opens a payment tab via the 4mica facilitator
91
- - Signs and submits the payment
92
- - Retries the request with payment proof
93
- - Receives the protected data
94
-
95
- 3. **Payment Flow**:
96
- ```
97
- Client → GET /api/premium-data
98
- ← 402 Payment Required (with payment requirements)
99
-
100
- Client → POST /payment/tab (open payment tab)
101
- ← 200 OK (tab details)
102
-
103
- Client → Signs payment guarantee (via 4mica SDK)
104
-
105
- Client → GET /api/premium-data (with payment proof)
106
- ← 200 OK (protected data)
107
- ```
108
-
109
- ## Testing Without Running the Client
110
-
111
- You can also test the server manually using curl:
112
-
113
- ```bash
114
- # Check server status
115
- curl http://localhost:3000/
116
-
117
- # Try to access protected endpoint (will return 402)
118
- curl -v http://localhost:3000/api/premium-data
119
- ```
120
-
121
- ## Notes
122
-
123
- - Make sure your account has sufficient balance on Sepolia testnet
124
- - The demo uses the default 4mica facilitator configuration
125
- - Tab TTL is set to 1 hour (3600 seconds)
package/demo/package.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "name": "@4mica/x402-demo",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "server": "tsx src/server.ts",
8
- "client": "tsx src/client.ts",
9
- "deposit": "tsx src/deposit.ts",
10
- "dev": "tsx watch src/server.ts"
11
- },
12
- "dependencies": {
13
- "@4mica/sdk": "^0.5.3",
14
- "@4mica/x402": "file:..",
15
- "@x402/fetch": "^2.2.0",
16
- "dotenv": "^16.4.7",
17
- "express": "^5.2.1",
18
- "viem": "^2.21.54"
19
- },
20
- "devDependencies": {
21
- "@types/express": "^5.0.6",
22
- "@types/node": "^25.0.7",
23
- "tsx": "^4.19.2",
24
- "typescript": "^5.3.3"
25
- }
26
- }
@@ -1,54 +0,0 @@
1
- import 'dotenv/config'
2
- import { wrapFetchWithPaymentFromConfig } from '@x402/fetch'
3
- import { FourMicaEvmScheme } from '@4mica/x402/client'
4
- import { privateKeyToAccount } from 'viem/accounts'
5
-
6
- async function main() {
7
- const privateKey = process.env.PRIVATE_KEY
8
- if (!privateKey || !privateKey.startsWith('0x')) {
9
- console.error('Error: PRIVATE_KEY environment variable must be set and start with 0x')
10
- console.error('Example: PRIVATE_KEY=0x1234... yarn client')
11
- process.exit(1)
12
- }
13
-
14
- const apiUrl = process.env.API_URL || 'http://localhost:3000'
15
- const endpoint = `${apiUrl}/api/premium-data`
16
-
17
- console.log('Initializing x402 client...')
18
- console.log(`Target endpoint: ${endpoint}`)
19
-
20
- const account = privateKeyToAccount(privateKey as `0x${string}`)
21
- console.log(`Using account: ${account.address}`)
22
-
23
- const scheme = await FourMicaEvmScheme.create(account)
24
-
25
- const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
26
- schemes: [
27
- {
28
- network: 'eip155:11155111', // Ethereum Sepolia
29
- client: scheme,
30
- },
31
- ],
32
- })
33
-
34
- console.log('\nMaking request to protected endpoint...')
35
-
36
- try {
37
- const response = await fetchWithPayment(endpoint)
38
- const data = await response.json()
39
-
40
- console.log('Request successful!')
41
- console.log('Response:', JSON.stringify(data, null, 2))
42
- } catch (error) {
43
- console.error('Request failed:', error)
44
- if (error instanceof Error) {
45
- console.error('Message:', error.message)
46
- }
47
- process.exit(1)
48
- }
49
- }
50
-
51
- main().catch((error) => {
52
- console.error('Unhandled error:', error)
53
- process.exit(1)
54
- })