mppx 0.11.0 → 0.13.1
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 +83 -0
- package/README.md +22 -1
- package/dist/Method.d.ts +7 -1
- package/dist/Method.js +7 -2
- package/dist/Receipt.d.ts +2 -0
- package/dist/Receipt.js +2 -0
- package/dist/cli/agent.d.ts +3 -0
- package/dist/cli/agent.js +20 -0
- package/dist/cli/cli.d.ts +9 -1
- package/dist/cli/cli.js +134 -136
- package/dist/cli/plugins/index.js +12 -14
- package/dist/cli/plugins/tempo.js +9 -2
- package/dist/cli/utils.d.ts +11 -1
- package/dist/cli/utils.js +15 -2
- package/dist/cli/validate/challenge.d.ts +2 -0
- package/dist/cli/validate/challenge.js +19 -4
- package/dist/cli/validate/discovery.d.ts +1 -0
- package/dist/cli/validate/discovery.js +10 -1
- package/dist/cli/validate/payment.js +63 -24
- package/dist/client/Methods.d.ts +0 -2
- package/dist/client/Methods.js +0 -2
- package/dist/client/Mppx.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.js +8 -2
- package/dist/client/internal/MethodChallenge.d.ts +1 -0
- package/dist/evm/Types.js +1 -1
- package/dist/evm/server/Charge.js +5 -0
- package/dist/evm/server/Methods.d.ts +164 -3
- package/dist/evm/server/Methods.js +50 -2
- package/dist/integrations/x402/Evm.js +4 -3
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.js +4 -7
- package/dist/middlewares/elysia.d.ts +11 -8
- package/dist/middlewares/elysia.js +67 -14
- package/dist/middlewares/express.js +13 -6
- package/dist/middlewares/hono.js +4 -4
- package/dist/middlewares/nextjs.js +9 -5
- package/dist/proxy/internal/Headers.js +65 -0
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/server/Mppx.d.ts +18 -2
- package/dist/server/Mppx.js +204 -92
- package/dist/server/NodeListener.js +71 -6
- package/dist/server/Request.d.ts +11 -1
- package/dist/server/Request.js +132 -13
- package/dist/server/Transport.d.ts +14 -1
- package/dist/server/Transport.js +1 -0
- package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
- package/dist/stripe/server/Methods.d.ts +10 -3
- package/dist/stripe/server/Methods.js +19 -6
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +1 -0
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/client/Charge.js +17 -13
- package/dist/tempo/client/Methods.d.ts +1 -5
- package/dist/tempo/client/Methods.js +10 -5
- package/dist/tempo/client/ResolveAccount.d.ts +3 -0
- package/dist/tempo/client/index.d.ts +0 -3
- package/dist/tempo/client/index.js +0 -2
- package/dist/tempo/internal/currencies.d.ts +19 -0
- package/dist/tempo/internal/currencies.js +41 -0
- package/dist/tempo/internal/defaults.d.ts +5 -3
- package/dist/tempo/internal/defaults.js +6 -2
- package/dist/tempo/internal/fee-payer.d.ts +11 -7
- package/dist/tempo/internal/fee-payer.js +30 -11
- package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
- package/dist/tempo/internal/machine-token-charge.js +2 -1
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/server/Charge.d.ts +12 -1
- package/dist/tempo/server/Charge.js +109 -78
- package/dist/tempo/server/Methods.d.ts +5048 -40
- package/dist/tempo/server/Methods.js +104 -13
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.js +31 -4
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.js +28 -11
- package/dist/tempo/session/client/ChannelOps.js +5 -0
- package/dist/tempo/session/client/Session.d.ts +1 -0
- package/dist/tempo/session/client/Session.js +7 -4
- package/dist/tempo/session/client/SessionManager.js +3 -0
- package/dist/tempo/session/client/Transports.d.ts +8 -1
- package/dist/tempo/session/client/Transports.js +12 -3
- package/dist/tempo/session/precompile/Chain.js +16 -1
- package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
- package/dist/tempo/session/precompile/Voucher.js +48 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +22 -2
- package/dist/tempo/session/server/ChannelStore.js +37 -7
- package/dist/tempo/session/server/CredentialVerification.d.ts +6 -1
- package/dist/tempo/session/server/CredentialVerification.js +56 -19
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
- package/dist/tempo/session/server/MeteredStream.js +85 -29
- package/dist/tempo/session/server/RequestState.d.ts +6 -0
- package/dist/tempo/session/server/RequestState.js +11 -4
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +10 -2
- package/dist/tempo/session/server/Settlement.d.ts +23 -0
- package/dist/tempo/session/server/Settlement.js +105 -25
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.js +105 -32
- package/dist/tempo/session/server/Transports.d.ts +24 -19
- package/dist/tempo/session/server/Transports.js +169 -58
- package/dist/tempo/session/server/Ws.d.ts +5 -1
- package/dist/tempo/session/server/Ws.js +75 -20
- package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.js +3 -0
- package/dist/validation/core.js +2 -1
- package/dist/x402/server/EvmCharge.d.ts +3 -1
- package/dist/x402/server/EvmCharge.js +14 -1
- package/package.json +8 -8
- package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
- package/dist/tempo/legacy/client/ChannelOps.js +0 -168
- package/dist/tempo/legacy/client/Session.d.ts +0 -185
- package/dist/tempo/legacy/client/Session.js +0 -279
- package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
- package/dist/tempo/legacy/client/SessionManager.js +0 -706
- package/dist/tempo/legacy/client/index.d.ts +0 -7
- package/dist/tempo/legacy/client/index.js +0 -5
|
@@ -12,7 +12,7 @@ import * as Constants from '../../Constants.js';
|
|
|
12
12
|
import * as Credential from '../../Credential.js';
|
|
13
13
|
import { tempo as tempoMethods } from '../../tempo/client/index.js';
|
|
14
14
|
import { tip20ChannelEscrow } from '../../tempo/session/precompile/Protocol.js';
|
|
15
|
-
import { signVoucher } from '../../tempo/session/precompile/Voucher.js';
|
|
15
|
+
import { signCloseAuthorization, signVoucher } from '../../tempo/session/precompile/Voucher.js';
|
|
16
16
|
import { createDefaultStore, createKeychain, resolveAccountName } from '../account.js';
|
|
17
17
|
import { fetchTokenInfo, fmtBalance, isTempoAccount, isTestnet, link, pc, resolveChain, resolveRpcUrl, } from '../utils.js';
|
|
18
18
|
import { createPlugin } from './plugin.js';
|
|
@@ -227,14 +227,21 @@ export function tempo() {
|
|
|
227
227
|
const descriptor = _session?.descriptor;
|
|
228
228
|
if (!descriptor)
|
|
229
229
|
throw new Error('session descriptor not available');
|
|
230
|
+
const voucher = { channelId: channelId, cumulativeAmount };
|
|
231
|
+
const signature = await signVoucher(client, account, voucher, escrowContract, chainId);
|
|
230
232
|
return Credential.serialize({
|
|
231
233
|
challenge,
|
|
232
234
|
payload: {
|
|
233
235
|
action,
|
|
234
236
|
channelId,
|
|
237
|
+
...(action === 'close'
|
|
238
|
+
? {
|
|
239
|
+
closeSignature: await signCloseAuthorization(client, account, voucher, escrowContract, chainId),
|
|
240
|
+
}
|
|
241
|
+
: {}),
|
|
235
242
|
descriptor,
|
|
236
243
|
cumulativeAmount: cumulativeAmount.toString(),
|
|
237
|
-
signature
|
|
244
|
+
signature,
|
|
238
245
|
},
|
|
239
246
|
source: `did:pkh:eip155:${chainId}:${account.address}`,
|
|
240
247
|
});
|
package/dist/cli/utils.d.ts
CHANGED
|
@@ -96,7 +96,17 @@ export declare const usdc: Address;
|
|
|
96
96
|
export declare const mainnetTokens: readonly [`0x${string}`, `0x${string}`];
|
|
97
97
|
export declare const testnetTokens: readonly ['0x20c0000000000000000000000000000000000000', '0x20c0000000000000000000000000000000000001', '0x20c0000000000000000000000000000000000002', '0x20c0000000000000000000000000000000000003'];
|
|
98
98
|
export declare function isTestnet(chain: Chain): boolean;
|
|
99
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Fetches a Tempo token balance and display metadata.
|
|
101
|
+
*
|
|
102
|
+
* By default, unavailable decimals fall back to six for display compatibility.
|
|
103
|
+
* Set `requireDecimals` to throw instead when trusted on-chain decimals cannot
|
|
104
|
+
* be resolved.
|
|
105
|
+
*/
|
|
106
|
+
export declare function fetchTokenInfo(client: ReturnType<typeof createClient>, token: Address, account: Address, options?: {
|
|
107
|
+
/** Throws instead of using six decimals when token metadata is unavailable. */
|
|
108
|
+
requireDecimals?: boolean;
|
|
109
|
+
}): Promise<{
|
|
100
110
|
balance: bigint;
|
|
101
111
|
decimals: number;
|
|
102
112
|
symbol: string;
|
package/dist/cli/utils.js
CHANGED
|
@@ -259,14 +259,25 @@ function toBaseUnitAmount(value) {
|
|
|
259
259
|
export function isTestnet(chain) {
|
|
260
260
|
return chain.id !== tempoMainnet.id;
|
|
261
261
|
}
|
|
262
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Fetches a Tempo token balance and display metadata.
|
|
264
|
+
*
|
|
265
|
+
* By default, unavailable decimals fall back to six for display compatibility.
|
|
266
|
+
* Set `requireDecimals` to throw instead when trusted on-chain decimals cannot
|
|
267
|
+
* be resolved.
|
|
268
|
+
*/
|
|
269
|
+
export async function fetchTokenInfo(client, token, account, options = {}) {
|
|
263
270
|
const { Actions } = await import('viem/tempo');
|
|
264
271
|
const [balance, metadata] = await Promise.all([
|
|
265
272
|
Actions.token
|
|
266
273
|
.getBalance(client, { account, token })
|
|
267
274
|
.then(toBaseUnitAmount)
|
|
268
275
|
.catch(() => 0n),
|
|
269
|
-
Actions.token.getMetadata(client, { token }).catch(() =>
|
|
276
|
+
Actions.token.getMetadata(client, { token }).catch((error) => {
|
|
277
|
+
if (options.requireDecimals)
|
|
278
|
+
throw error;
|
|
279
|
+
return { symbol: token };
|
|
280
|
+
}),
|
|
270
281
|
]);
|
|
271
282
|
const knownSymbols = {
|
|
272
283
|
[pathUsd]: 'PathUSD',
|
|
@@ -274,6 +285,8 @@ export async function fetchTokenInfo(client, token, account) {
|
|
|
274
285
|
};
|
|
275
286
|
const symbol = knownSymbols[token] ?? metadata.symbol;
|
|
276
287
|
const decimals = 'decimals' in metadata && typeof metadata.decimals === 'number' ? metadata.decimals : 6;
|
|
288
|
+
if (options.requireDecimals && !('decimals' in metadata && typeof metadata.decimals === 'number'))
|
|
289
|
+
throw new Error('Token decimals are unavailable.');
|
|
277
290
|
return { balance, symbol, decimals, token };
|
|
278
291
|
}
|
|
279
292
|
export async function fetchBalanceLines(address, opts) {
|
|
@@ -11,6 +11,8 @@ export declare function validateChallenge(baseUrl: string, endpoint: EndpointSpe
|
|
|
11
11
|
resolvedBody?: string | undefined;
|
|
12
12
|
challenges?: Challenge.Challenge[] | undefined;
|
|
13
13
|
}>;
|
|
14
|
+
/** Validates that challenge realms cover the server hostname. */
|
|
15
|
+
export declare function validateRealmMatchesHost(challenges: Challenge.Challenge[], serverHost: string, results: CheckResult[]): void;
|
|
14
16
|
export declare function validateErrorHandling(baseUrl: string, endpoint: EndpointSpec, options?: {
|
|
15
17
|
body?: string | undefined;
|
|
16
18
|
query?: string[] | undefined;
|
|
@@ -15,7 +15,14 @@ function detectTestnet(challenge) {
|
|
|
15
15
|
}
|
|
16
16
|
export async function validateChallenge(baseUrl, endpoint, verbose, options) {
|
|
17
17
|
const results = [];
|
|
18
|
-
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = buildUrl(baseUrl, endpoint, options?.query);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
results.push(fail('Request URL valid', error.message));
|
|
24
|
+
return { results };
|
|
25
|
+
}
|
|
19
26
|
const fetchHeaders = parseHeaders(options?.extraHeaders);
|
|
20
27
|
let fetchBody;
|
|
21
28
|
// If the user explicitly provided a body, include it on the first request
|
|
@@ -167,11 +174,19 @@ function validateExpiration(challenges, results) {
|
|
|
167
174
|
const soonest = Math.min(...challenges.map((ch) => new Date(ch.expires).getTime() - now.getTime()));
|
|
168
175
|
results.push(check('Challenge expires in the future', `${Math.round(soonest / 60000)}m from now`));
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
/** Validates that challenge realms cover the server hostname. */
|
|
178
|
+
export function validateRealmMatchesHost(challenges, serverHost, results) {
|
|
179
|
+
const normalizeHostname = (value) => value.toLowerCase().replace(/\.$/, '');
|
|
180
|
+
const normalizedServerHost = normalizeHostname(serverHost);
|
|
171
181
|
const realms = [...new Set(challenges.map((ch) => ch.realm ?? ''))];
|
|
172
|
-
const badRealms = realms.filter((
|
|
182
|
+
const badRealms = realms.filter((realm) => {
|
|
183
|
+
const normalizedRealm = normalizeHostname(realm);
|
|
184
|
+
return (!normalizedRealm ||
|
|
185
|
+
(normalizedRealm !== normalizedServerHost &&
|
|
186
|
+
!normalizedServerHost.endsWith(`.${normalizedRealm}`)));
|
|
187
|
+
});
|
|
173
188
|
if (badRealms.length > 0) {
|
|
174
|
-
results.push(
|
|
189
|
+
results.push(fail('Realm matches server hostname', `realm="${badRealms[0]}" vs host="${serverHost}"`, 'Set the realm to your production hostname (or base domain) in the challenge.'));
|
|
175
190
|
}
|
|
176
191
|
else {
|
|
177
192
|
results.push(check('Realm matches server hostname'));
|
|
@@ -9,5 +9,6 @@ export declare function fetchDiscoveryDoc(baseUrl: string): Promise<{
|
|
|
9
9
|
export declare function validateLlmsDoc(baseUrl: string): Promise<CheckResult>;
|
|
10
10
|
export declare function extractEndpointsFromDiscovery(doc: Record<string, unknown>): EndpointSpec[];
|
|
11
11
|
export declare function extractRequestBodyFromDiscovery(doc: Record<string, unknown>, endpoint: EndpointSpec): string | undefined;
|
|
12
|
+
/** Builds an endpoint URL and rejects paths that could escape the discovery document's origin. */
|
|
12
13
|
export declare function buildUrl(baseUrl: string, endpoint: EndpointSpec, query?: string[]): string;
|
|
13
14
|
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -117,14 +117,23 @@ export function extractRequestBodyFromDiscovery(doc, endpoint) {
|
|
|
117
117
|
}
|
|
118
118
|
return undefined;
|
|
119
119
|
}
|
|
120
|
+
/** Builds an endpoint URL and rejects paths that could escape the discovery document's origin. */
|
|
120
121
|
export function buildUrl(baseUrl, endpoint, query) {
|
|
122
|
+
if (!endpoint.path.startsWith('/') ||
|
|
123
|
+
endpoint.path.startsWith('//') ||
|
|
124
|
+
endpoint.path.includes('\\'))
|
|
125
|
+
throw new Error(`OpenAPI path must be an origin-relative path: ${endpoint.path}`);
|
|
121
126
|
let path = endpoint.path;
|
|
122
127
|
if (endpoint.parameters) {
|
|
123
128
|
path = substitutePathParams(path, endpoint.parameters);
|
|
124
129
|
}
|
|
125
130
|
// Strip leading slash so URL resolves relative to baseUrl's path, not root.
|
|
126
131
|
const relativePath = path.startsWith('/') ? path.slice(1) : path;
|
|
127
|
-
|
|
132
|
+
const base = new URL(baseUrl.replace(/\/?$/, '/'));
|
|
133
|
+
const resolved = new URL(relativePath, base);
|
|
134
|
+
if (resolved.origin !== base.origin)
|
|
135
|
+
throw new Error(`OpenAPI path must be an origin-relative path: ${endpoint.path}`);
|
|
136
|
+
let url = resolved.href;
|
|
128
137
|
if (query) {
|
|
129
138
|
const u = new URL(url);
|
|
130
139
|
for (const q of query) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as childProcess from 'node:child_process';
|
|
2
|
-
import { createClient, erc20Abi, http } from 'viem';
|
|
2
|
+
import { createClient, erc20Abi, http, isAddress, zeroAddress, } from 'viem';
|
|
3
3
|
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
|
|
4
4
|
import { readContract, waitForTransactionReceipt } from 'viem/actions';
|
|
5
5
|
import * as viemChains from 'viem/chains';
|
|
@@ -15,6 +15,7 @@ import { isTempoSessionChallenge } from '../../tempo/session/client/Transports.j
|
|
|
15
15
|
import { resolveAccount, resolveAccountName } from '../account.js';
|
|
16
16
|
import { assertSamePaymentRequest, flattenConfigMethods, loadConfig, preparePayment, resolvePlugin, } from '../internal.js';
|
|
17
17
|
import { fetchTokenInfo, confirm, pc } from '../utils.js';
|
|
18
|
+
import { validateRealmMatchesHost } from './challenge.js';
|
|
18
19
|
import { buildUrl } from './discovery.js';
|
|
19
20
|
import { check, fail, fetchWithTimeout, formatBytes, isValidIntegerAmount, parseHeaders, skip, warn, } from './helpers.js';
|
|
20
21
|
async function provisionAndPayTestnet(challenge, verbose, silent) {
|
|
@@ -24,6 +25,16 @@ async function provisionAndPayTestnet(challenge, verbose, silent) {
|
|
|
24
25
|
const key = generatePrivateKey();
|
|
25
26
|
const account = privateKeyToAccount(key);
|
|
26
27
|
const client = createClient({ chain: tempoModerato, transport: http() });
|
|
28
|
+
if (isTempoSessionChallenge(challenge)) {
|
|
29
|
+
const { chainId, operator } = challenge.request.methodDetails ?? {};
|
|
30
|
+
if (chainId === tempoModerato.id &&
|
|
31
|
+
typeof operator === 'string' &&
|
|
32
|
+
isAddress(operator) &&
|
|
33
|
+
operator !== zeroAddress) {
|
|
34
|
+
const hashes = await Actions.faucet.fund(client, { account: operator });
|
|
35
|
+
await Promise.all(hashes.map((hash) => waitForTransactionReceipt(client, { hash })));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
27
38
|
const hashes = await Actions.faucet.fund(client, { account });
|
|
28
39
|
await Promise.all(hashes.map((hash) => waitForTransactionReceipt(client, { hash })));
|
|
29
40
|
if (!silent)
|
|
@@ -85,16 +96,19 @@ function resolveStripeKey(verbose) {
|
|
|
85
96
|
}
|
|
86
97
|
async function fetchEvmTokenInfo(chain, token, account) {
|
|
87
98
|
const client = createClient({ chain, transport: http() });
|
|
88
|
-
const [balance, symbol] = await Promise.all([
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
const [balance, decimals, symbol] = await Promise.all([
|
|
100
|
+
account
|
|
101
|
+
? readContract(client, {
|
|
102
|
+
address: token,
|
|
103
|
+
abi: erc20Abi,
|
|
104
|
+
functionName: 'balanceOf',
|
|
105
|
+
args: [account],
|
|
106
|
+
})
|
|
107
|
+
: 0n,
|
|
108
|
+
readContract(client, { address: token, abi: erc20Abi, functionName: 'decimals' }),
|
|
95
109
|
readContract(client, { address: token, abi: erc20Abi, functionName: 'symbol' }).catch(() => undefined),
|
|
96
110
|
]);
|
|
97
|
-
return { balance, symbol: symbol ?? undefined };
|
|
111
|
+
return { balance, decimals, symbol: symbol ?? undefined };
|
|
98
112
|
}
|
|
99
113
|
function resolveEvmChain(chainId) {
|
|
100
114
|
const all = Object.values(viemChains);
|
|
@@ -152,6 +166,14 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
|
|
|
152
166
|
results.push(fail('Payment: parse challenge', 'No Payment challenges in response'));
|
|
153
167
|
return results;
|
|
154
168
|
}
|
|
169
|
+
const realmResults = [];
|
|
170
|
+
validateRealmMatchesHost(challenges, new URL(url).hostname, realmResults);
|
|
171
|
+
const realmFailures = realmResults.filter((result) => result.severity === 'fail');
|
|
172
|
+
if (realmFailures.length > 0) {
|
|
173
|
+
results.push(...realmFailures);
|
|
174
|
+
options.onResults?.(realmFailures);
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
155
177
|
const loaded = await loadConfig().catch(() => undefined);
|
|
156
178
|
// Testnet Tempo: always use ephemeral wallet (zero-setup, free money)
|
|
157
179
|
const tempoTestnetChallenge = challenges.find((ch) => {
|
|
@@ -166,18 +188,25 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
|
|
|
166
188
|
if (tempoTestnetChallenge) {
|
|
167
189
|
const provisioned = await provisionAndPayTestnet(tempoTestnetChallenge, verbose, options.silent);
|
|
168
190
|
if (provisioned) {
|
|
169
|
-
const fakeResp = new Response(null, {
|
|
170
|
-
status: 402,
|
|
171
|
-
headers: {
|
|
172
|
-
[Constants.Headers.wwwAuthenticate]: Challenge.serialize(tempoTestnetChallenge),
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
191
|
try {
|
|
176
|
-
const mppx = Mppx.create({
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
const mppx = Mppx.create({
|
|
193
|
+
methods: provisioned.methods,
|
|
194
|
+
polyfill: false,
|
|
195
|
+
async onChallenge(challenge, { createCredential }) {
|
|
196
|
+
assertSamePaymentRequest(tempoTestnetChallenge, challenge);
|
|
197
|
+
const context = await preparePayment(challenge, loaded?.config.extensions);
|
|
198
|
+
const credential = await createCredential(context);
|
|
199
|
+
results.push(check('Payment: submitted', 'ephemeral testnet wallet'));
|
|
200
|
+
return credential;
|
|
201
|
+
},
|
|
202
|
+
fetch: (input, init) => fetchWithTimeout(input, init ?? {}, 30_000),
|
|
203
|
+
});
|
|
204
|
+
const response = await mppx.fetch(url, {
|
|
205
|
+
method: endpoint.method,
|
|
206
|
+
headers: fetchHeaders,
|
|
207
|
+
body: fetchBody ?? null,
|
|
208
|
+
});
|
|
209
|
+
await validatePaymentResponse(results, response, verbose, tempoModerato);
|
|
181
210
|
}
|
|
182
211
|
catch (error) {
|
|
183
212
|
results.push(fail('Payment: create credential', error.message));
|
|
@@ -257,7 +286,7 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
257
286
|
const requiredAmount = isValidIntegerAmount(request.amount)
|
|
258
287
|
? BigInt(request.amount)
|
|
259
288
|
: undefined;
|
|
260
|
-
|
|
289
|
+
let decimals = methodDetails?.decimals ?? request.decimals ?? 6;
|
|
261
290
|
const currency = request.currency;
|
|
262
291
|
const { plugin, method: directMethod } = resolvePlugin(challenge, loaded?.config);
|
|
263
292
|
if (!plugin && !directMethod) {
|
|
@@ -289,22 +318,30 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
289
318
|
if (chainId)
|
|
290
319
|
paymentChain = resolveEvmChain(chainId);
|
|
291
320
|
}
|
|
321
|
+
if (requiredAmount && currency && !paymentChain) {
|
|
322
|
+
results.push(skip(tag, 'unable to resolve payment chain and verify token decimals'));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
292
325
|
let tokenSymbol;
|
|
293
|
-
if (
|
|
326
|
+
if (requiredAmount && currency && paymentChain) {
|
|
294
327
|
try {
|
|
295
328
|
let balance;
|
|
296
329
|
if (challenge.method === Constants.Methods.tempo) {
|
|
297
330
|
const client = createClient({ chain: paymentChain, transport: http() });
|
|
298
|
-
const info = await fetchTokenInfo(client, currency, walletAddress)
|
|
331
|
+
const info = await fetchTokenInfo(client, currency, (walletAddress ?? zeroAddress), {
|
|
332
|
+
requireDecimals: true,
|
|
333
|
+
});
|
|
299
334
|
balance = info.balance;
|
|
335
|
+
decimals = info.decimals;
|
|
300
336
|
tokenSymbol = info.symbol;
|
|
301
337
|
}
|
|
302
338
|
else {
|
|
303
339
|
const info = await fetchEvmTokenInfo(paymentChain, currency, walletAddress);
|
|
304
340
|
balance = info.balance;
|
|
341
|
+
decimals = info.decimals;
|
|
305
342
|
tokenSymbol = info.symbol;
|
|
306
343
|
}
|
|
307
|
-
if (balance < requiredAmount) {
|
|
344
|
+
if (walletAddress && balance < requiredAmount) {
|
|
308
345
|
const requiredDisplay = formatAmount(requiredAmount, decimals);
|
|
309
346
|
const balanceDisplay = formatAmount(balance, decimals);
|
|
310
347
|
const symbol = tokenSymbol ?? 'tokens';
|
|
@@ -315,6 +352,8 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
315
352
|
catch (e) {
|
|
316
353
|
if (verbose)
|
|
317
354
|
console.log(pc.dim(` Balance check skipped: ${e.message}`));
|
|
355
|
+
results.push(skip(tag, 'unable to verify token balance and decimals from the payment chain'));
|
|
356
|
+
return;
|
|
318
357
|
}
|
|
319
358
|
}
|
|
320
359
|
// Prompt
|
package/dist/client/Methods.d.ts
CHANGED
|
@@ -4,6 +4,4 @@ export { subscription } from '../tempo/client/Subscription.js';
|
|
|
4
4
|
export { session, tempo } from '../tempo/client/index.js';
|
|
5
5
|
export { session as sessionMethod } from '../tempo/session/client/Session.js';
|
|
6
6
|
export { sessionManager } from '../tempo/session/client/SessionManager.js';
|
|
7
|
-
export { session as sessionLegacy } from '../tempo/legacy/client/index.js';
|
|
8
|
-
export { sessionManager as sessionLegacyManager } from '../tempo/legacy/client/index.js';
|
|
9
7
|
//# sourceMappingURL=Methods.d.ts.map
|
package/dist/client/Methods.js
CHANGED
|
@@ -4,6 +4,4 @@ export { subscription } from '../tempo/client/Subscription.js';
|
|
|
4
4
|
export { session, tempo } from '../tempo/client/index.js';
|
|
5
5
|
export { session as sessionMethod } from '../tempo/session/client/Session.js';
|
|
6
6
|
export { sessionManager } from '../tempo/session/client/SessionManager.js';
|
|
7
|
-
export { session as sessionLegacy } from '../tempo/legacy/client/index.js';
|
|
8
|
-
export { sessionManager as sessionLegacyManager } from '../tempo/legacy/client/index.js';
|
|
9
7
|
//# sourceMappingURL=Methods.js.map
|
package/dist/client/Mppx.js
CHANGED
package/dist/client/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * as Constants from '../Constants.js';
|
|
2
2
|
export * as Expires from '../Expires.js';
|
|
3
3
|
export * as Fetch from './internal/Fetch.js';
|
|
4
|
-
export { evm, session, sessionManager, sessionMethod,
|
|
4
|
+
export { evm, session, sessionManager, sessionMethod, stripe, tempo } from './Methods.js';
|
|
5
5
|
export { createChannelStore, createJsonChannelStore, entryKey, type ChannelStore, type JsonChannelKv, } from '../tempo/session/client/ChannelStore.js';
|
|
6
6
|
export type { ChargeContext } from '../tempo/client/Charge.js';
|
|
7
7
|
export type { ResolveAccount, ResolveAccountCall, ResolveAccountInfo, ResolveAccountOperation, } from '../tempo/client/ResolveAccount.js';
|
package/dist/client/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * as Constants from '../Constants.js';
|
|
2
2
|
export * as Expires from '../Expires.js';
|
|
3
3
|
export * as Fetch from './internal/Fetch.js';
|
|
4
|
-
export { evm, session, sessionManager, sessionMethod,
|
|
4
|
+
export { evm, session, sessionManager, sessionMethod, stripe, tempo } from './Methods.js';
|
|
5
5
|
export { createChannelStore, createJsonChannelStore, entryKey, } from '../tempo/session/client/ChannelStore.js';
|
|
6
6
|
export * as Mppx from './Mppx.js';
|
|
7
7
|
export * as Transport from './Transport.js';
|
|
@@ -171,6 +171,8 @@ export declare function createEventDispatcher<methods extends readonly Method.An
|
|
|
171
171
|
/** @internal */
|
|
172
172
|
export declare function unwrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch;
|
|
173
173
|
/** @internal */
|
|
174
|
+
export declare function resolveRequestSignal(input: RequestInfo | URL, init: RequestInit | undefined): AbortSignal | undefined;
|
|
175
|
+
/** @internal */
|
|
174
176
|
export declare function validateCredentialHeaderValue(credential: string): void;
|
|
175
177
|
/** @internal */
|
|
176
178
|
/** @internal */
|
|
@@ -122,6 +122,7 @@ export function from(config) {
|
|
|
122
122
|
context: credentialContext,
|
|
123
123
|
fetch: baseFetch,
|
|
124
124
|
input: paymentInput,
|
|
125
|
+
signal: resolveRequestSignal(input, init),
|
|
125
126
|
}),
|
|
126
127
|
};
|
|
127
128
|
const promise = attempt
|
|
@@ -576,8 +577,13 @@ function cloneRequestInput(input) {
|
|
|
576
577
|
return input;
|
|
577
578
|
}
|
|
578
579
|
}
|
|
579
|
-
|
|
580
|
-
|
|
580
|
+
/** @internal */
|
|
581
|
+
export function resolveRequestSignal(input, init) {
|
|
582
|
+
if (init?.signal === null)
|
|
583
|
+
return undefined;
|
|
584
|
+
if (init?.signal !== undefined)
|
|
585
|
+
return init.signal;
|
|
586
|
+
return input instanceof Request ? input.signal : undefined;
|
|
581
587
|
}
|
|
582
588
|
/** @internal */
|
|
583
589
|
function isWrappedFetch(fetch) {
|
|
@@ -7,6 +7,7 @@ export type HandlerParameters = {
|
|
|
7
7
|
context?: unknown;
|
|
8
8
|
fetch: typeof globalThis.fetch;
|
|
9
9
|
input: RequestInfo | URL;
|
|
10
|
+
signal?: AbortSignal | undefined;
|
|
10
11
|
};
|
|
11
12
|
/** Internal client-method challenge hook. */
|
|
12
13
|
export type Handler = (parameters: HandlerParameters) => MaybePromise<void>;
|
package/dist/evm/Types.js
CHANGED
|
@@ -87,7 +87,7 @@ export function authorizationDomain(parameters) {
|
|
|
87
87
|
}
|
|
88
88
|
/** Computes the Payment-auth EVM challenge hash used as the EIP-3009 nonce. */
|
|
89
89
|
export function challengeHash(challenge) {
|
|
90
|
-
return Hash.keccak256(Bytes.fromString(
|
|
90
|
+
return Hash.keccak256(Bytes.fromString(JSON.stringify([challenge.id, challenge.realm])), {
|
|
91
91
|
as: 'Hex',
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -171,6 +171,7 @@ function createPaths(config) {
|
|
|
171
171
|
function createMppPath() {
|
|
172
172
|
const transport = ServerTransport.http();
|
|
173
173
|
return {
|
|
174
|
+
credentialHeaders: transport.credentialHeaders ?? [],
|
|
174
175
|
bindCredential: (options) => transport.bindCredential?.(options) ?? options.credential,
|
|
175
176
|
captureRequest: transport.captureRequest,
|
|
176
177
|
getCredential: transport.getCredential,
|
|
@@ -181,10 +182,14 @@ function createMppPath() {
|
|
|
181
182
|
function httpTransport(paths) {
|
|
182
183
|
return ServerTransport.from({
|
|
183
184
|
name: 'evm-http',
|
|
185
|
+
credentialHeaders: [
|
|
186
|
+
...new Set([...paths.mpp.credentialHeaders, ...paths.x402.credentialHeaders]),
|
|
187
|
+
],
|
|
184
188
|
captureRequest: paths.mpp.captureRequest,
|
|
185
189
|
getCredential(input) {
|
|
186
190
|
return paths.mpp.getCredential(input) ?? paths.x402.getCredential(input);
|
|
187
191
|
},
|
|
192
|
+
matchCredential: paths.x402.matchCredential,
|
|
188
193
|
bindCredential(options) {
|
|
189
194
|
if (X402.isPendingCredential(options.credential))
|
|
190
195
|
return paths.x402.bindCredential(options);
|
|
@@ -2,8 +2,158 @@ import type { NoExtraKeys } from '../../internal/types.js';
|
|
|
2
2
|
import * as Assets from '../Assets.js';
|
|
3
3
|
import * as Chains from '../Chains.js';
|
|
4
4
|
import { charge as charge_ } from './Charge.js';
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Creates ordered EVM charge offers from shared parameters.
|
|
7
|
+
*
|
|
8
|
+
* Pass `currencies: [token]` for one asset or an ordered list for multiple assets.
|
|
9
|
+
* Viem token definitions resolve against `chainId`; known assets carry their own
|
|
10
|
+
* network. Unsupported chain entries are skipped and duplicate chain/address
|
|
11
|
+
* pairs are removed. An empty resolved list throws.
|
|
12
|
+
*
|
|
13
|
+
* Token definitions need explicit EIP-3009 domain metadata. Use
|
|
14
|
+
* `evm.assets.fromToken(token, { chainId, transfer })` for per-token domains.
|
|
15
|
+
* A token registry entry alone does not establish EIP-3009 support.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { evm } from 'mppx/server'
|
|
20
|
+
*
|
|
21
|
+
* evm({
|
|
22
|
+
* currencies: [evm.assets.base.USDC],
|
|
23
|
+
* recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
|
|
24
|
+
* settle: async ({ payload }) => settleAuthorization(payload),
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function evm<const parameters extends evm.Parameters>(parameters: NoExtraKeys<parameters, evm.Parameters>): [import("../../Method.js").Server<{
|
|
29
|
+
readonly name: "evm";
|
|
30
|
+
readonly intent: "charge";
|
|
31
|
+
readonly schema: {
|
|
32
|
+
credential: {
|
|
33
|
+
payload: import("zod/mini").ZodMiniObject<{
|
|
34
|
+
from: import("zod/mini").ZodMiniString<string>;
|
|
35
|
+
nonce: import("zod/mini").ZodMiniString<string>;
|
|
36
|
+
signature: import("zod/mini").ZodMiniString<string>;
|
|
37
|
+
to: import("zod/mini").ZodMiniString<string>;
|
|
38
|
+
type: import("zod/mini").ZodMiniLiteral<"authorization">;
|
|
39
|
+
validAfter: import("zod/mini").ZodMiniString<string>;
|
|
40
|
+
validBefore: import("zod/mini").ZodMiniString<string>;
|
|
41
|
+
value: import("zod/mini").ZodMiniString<string>;
|
|
42
|
+
}, import("zod/v4/core").$strip>;
|
|
43
|
+
};
|
|
44
|
+
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
45
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
46
|
+
chainId: import("zod/mini").ZodMiniNumber<number>;
|
|
47
|
+
currency: import("zod/mini").ZodMiniString<string>;
|
|
48
|
+
credentialTypes: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniEnum<{
|
|
49
|
+
authorization: "authorization";
|
|
50
|
+
}>>>;
|
|
51
|
+
decimals: import("zod/mini").ZodMiniNumber<number>;
|
|
52
|
+
description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
53
|
+
externalId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
54
|
+
permit2Address: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
55
|
+
recipient: import("zod/mini").ZodMiniString<string>;
|
|
56
|
+
splits: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniObject<{
|
|
57
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
58
|
+
recipient: import("zod/mini").ZodMiniString<string>;
|
|
59
|
+
}, import("zod/v4/core").$strip>>>;
|
|
60
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
61
|
+
description?: string | undefined;
|
|
62
|
+
externalId?: string | undefined;
|
|
63
|
+
amount: string;
|
|
64
|
+
currency: `0x${string}`;
|
|
65
|
+
methodDetails: {
|
|
66
|
+
chainId: number;
|
|
67
|
+
credentialTypes: string[];
|
|
68
|
+
decimals: number;
|
|
69
|
+
permit2Address?: `0x${string}` | undefined;
|
|
70
|
+
splits?: {
|
|
71
|
+
amount: string;
|
|
72
|
+
recipient: string;
|
|
73
|
+
}[] | undefined;
|
|
74
|
+
};
|
|
75
|
+
recipient: `0x${string}`;
|
|
76
|
+
}, {
|
|
77
|
+
amount: string;
|
|
78
|
+
chainId: number;
|
|
79
|
+
currency: string;
|
|
80
|
+
credentialTypes?: "authorization"[] | undefined;
|
|
81
|
+
decimals: number;
|
|
82
|
+
description?: string | undefined;
|
|
83
|
+
externalId?: string | undefined;
|
|
84
|
+
permit2Address?: string | undefined;
|
|
85
|
+
recipient: string;
|
|
86
|
+
splits?: {
|
|
87
|
+
amount: string;
|
|
88
|
+
recipient: string;
|
|
89
|
+
}[] | undefined;
|
|
90
|
+
}>>;
|
|
91
|
+
};
|
|
92
|
+
}, charge_.Defaults, import("../../server/Transport.js").Http, {}, string | undefined>, ...import("../../Method.js").Server<{
|
|
93
|
+
readonly name: "evm";
|
|
94
|
+
readonly intent: "charge";
|
|
95
|
+
readonly schema: {
|
|
96
|
+
credential: {
|
|
97
|
+
payload: import("zod/mini").ZodMiniObject<{
|
|
98
|
+
from: import("zod/mini").ZodMiniString<string>;
|
|
99
|
+
nonce: import("zod/mini").ZodMiniString<string>;
|
|
100
|
+
signature: import("zod/mini").ZodMiniString<string>;
|
|
101
|
+
to: import("zod/mini").ZodMiniString<string>;
|
|
102
|
+
type: import("zod/mini").ZodMiniLiteral<"authorization">;
|
|
103
|
+
validAfter: import("zod/mini").ZodMiniString<string>;
|
|
104
|
+
validBefore: import("zod/mini").ZodMiniString<string>;
|
|
105
|
+
value: import("zod/mini").ZodMiniString<string>;
|
|
106
|
+
}, import("zod/v4/core").$strip>;
|
|
107
|
+
};
|
|
108
|
+
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
109
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
110
|
+
chainId: import("zod/mini").ZodMiniNumber<number>;
|
|
111
|
+
currency: import("zod/mini").ZodMiniString<string>;
|
|
112
|
+
credentialTypes: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniEnum<{
|
|
113
|
+
authorization: "authorization";
|
|
114
|
+
}>>>;
|
|
115
|
+
decimals: import("zod/mini").ZodMiniNumber<number>;
|
|
116
|
+
description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
117
|
+
externalId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
118
|
+
permit2Address: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
119
|
+
recipient: import("zod/mini").ZodMiniString<string>;
|
|
120
|
+
splits: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniObject<{
|
|
121
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
122
|
+
recipient: import("zod/mini").ZodMiniString<string>;
|
|
123
|
+
}, import("zod/v4/core").$strip>>>;
|
|
124
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
125
|
+
description?: string | undefined;
|
|
126
|
+
externalId?: string | undefined;
|
|
127
|
+
amount: string;
|
|
128
|
+
currency: `0x${string}`;
|
|
129
|
+
methodDetails: {
|
|
130
|
+
chainId: number;
|
|
131
|
+
credentialTypes: string[];
|
|
132
|
+
decimals: number;
|
|
133
|
+
permit2Address?: `0x${string}` | undefined;
|
|
134
|
+
splits?: {
|
|
135
|
+
amount: string;
|
|
136
|
+
recipient: string;
|
|
137
|
+
}[] | undefined;
|
|
138
|
+
};
|
|
139
|
+
recipient: `0x${string}`;
|
|
140
|
+
}, {
|
|
141
|
+
amount: string;
|
|
142
|
+
chainId: number;
|
|
143
|
+
currency: string;
|
|
144
|
+
credentialTypes?: "authorization"[] | undefined;
|
|
145
|
+
decimals: number;
|
|
146
|
+
description?: string | undefined;
|
|
147
|
+
externalId?: string | undefined;
|
|
148
|
+
permit2Address?: string | undefined;
|
|
149
|
+
recipient: string;
|
|
150
|
+
splits?: {
|
|
151
|
+
amount: string;
|
|
152
|
+
recipient: string;
|
|
153
|
+
}[] | undefined;
|
|
154
|
+
}>>;
|
|
155
|
+
};
|
|
156
|
+
}, charge_.Defaults, import("../../server/Transport.js").Http, {}, string | undefined>[]] | readonly [import("../../Method.js").Server<{
|
|
7
157
|
readonly name: "evm";
|
|
8
158
|
readonly intent: "charge";
|
|
9
159
|
readonly schema: {
|
|
@@ -69,7 +219,18 @@ export declare function evm<const parameters extends evm.Parameters>(parameters:
|
|
|
69
219
|
};
|
|
70
220
|
}, charge_.Defaults, import("../../server/Transport.js").Http, {}, string | undefined>];
|
|
71
221
|
export declare namespace evm {
|
|
72
|
-
|
|
222
|
+
/** Accepted EVM currency metadata, including chain-indexed definitions from `viem/tokens`. */
|
|
223
|
+
type Currency = Assets.Currency;
|
|
224
|
+
/** Shared EVM settlement parameters and ordered accepted currencies. */
|
|
225
|
+
type Parameters = Omit<charge_.Parameters, 'currency'> & ({
|
|
226
|
+
/** Ordered accepted currencies. A one-element list accepts a single asset. */
|
|
227
|
+
currencies: readonly Currency[];
|
|
228
|
+
currency?: undefined;
|
|
229
|
+
} | {
|
|
230
|
+
/** @deprecated Use `currencies: [currency]` instead. */
|
|
231
|
+
currency: Currency;
|
|
232
|
+
currencies?: undefined;
|
|
233
|
+
});
|
|
73
234
|
/** Creates an EVM `charge` server method. */
|
|
74
235
|
const charge: typeof charge_;
|
|
75
236
|
/** Known EVM asset metadata for public config. */
|