@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.
- package/CHANGELOG.md +63 -1
- package/README.md +67 -82
- package/dist/client/scheme.d.ts +40 -4
- package/dist/client/scheme.js +96 -38
- package/dist/domain.d.ts +12 -0
- package/dist/domain.js +30 -0
- package/dist/index.d.ts +2 -2
- package/dist/server/express/adapter.d.ts +2 -2
- package/dist/server/express/index.d.ts +13 -46
- package/dist/server/express/index.js +32 -71
- package/dist/server/facilitator.d.ts +2 -24
- package/dist/server/facilitator.js +0 -36
- package/dist/server/index.d.ts +2 -4
- package/dist/server/index.js +1 -2
- package/dist/server/scheme.d.ts +54 -10
- package/dist/server/scheme.js +118 -56
- package/dist/types.d.ts +36 -12
- package/package.json +33 -32
- package/.eslintrc.cjs +0 -29
- package/.prettierignore +0 -3
- package/.prettierrc +0 -6
- package/demo/.env.example +0 -8
- package/demo/README.md +0 -125
- package/demo/package.json +0 -26
- package/demo/src/client.ts +0 -54
- package/demo/src/deposit.ts +0 -37
- package/demo/src/server.ts +0 -81
- package/demo/tsconfig.json +0 -8
- package/demo/yarn.lock +0 -925
- package/eslint.config.mjs +0 -22
- package/src/client/index.ts +0 -1
- package/src/client/scheme.ts +0 -111
- package/src/index.ts +0 -9
- package/src/server/express/adapter.ts +0 -100
- package/src/server/express/index.ts +0 -499
- package/src/server/facilitator.ts +0 -206
- package/src/server/index.ts +0 -10
- package/src/server/scheme.ts +0 -229
- package/src/types.ts +0 -24
- package/tests/client-scheme.test.ts +0 -99
- package/tests/facilitator.test.ts +0 -174
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -12
package/dist/server/scheme.js
CHANGED
|
@@ -1,12 +1,43 @@
|
|
|
1
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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
|
|
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:
|
|
123
|
-
asset:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
21
|
-
extra
|
|
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": "
|
|
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/
|
|
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
|
|
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/
|
|
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
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
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": "
|
|
46
|
-
"@x402/extensions": "
|
|
47
|
-
"
|
|
48
|
-
"
|
|
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
|
-
"@
|
|
53
|
-
"@types/express": "^5.0.
|
|
54
|
-
"@types/node": "^
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
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
package/.prettierrc
DELETED
package/demo/.env.example
DELETED
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
|
-
}
|
package/demo/src/client.ts
DELETED
|
@@ -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
|
-
})
|