mppx 0.5.0 → 0.5.3
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 +19 -0
- package/dist/Credential.d.ts +12 -0
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Credential.js +22 -4
- package/dist/Credential.js.map +1 -1
- package/dist/Method.d.ts +4 -0
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +2 -1
- package/dist/Method.js.map +1 -1
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +12 -2
- package/dist/cli/account.js.map +1 -1
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +52 -8
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/internal/Route.d.ts.map +1 -1
- package/dist/proxy/internal/Route.js +7 -3
- package/dist/proxy/internal/Route.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +90 -71
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Transport.d.ts +5 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +52 -7
- package/dist/server/Transport.js.map +1 -1
- package/dist/server/internal/html/config.d.ts +7 -0
- package/dist/server/internal/html/config.d.ts.map +1 -0
- package/dist/server/internal/html/config.js +3 -0
- package/dist/server/internal/html/config.js.map +1 -0
- package/dist/server/internal/html/serviceWorker.gen.d.ts +2 -0
- package/dist/server/internal/html/serviceWorker.gen.d.ts.map +1 -0
- package/dist/server/internal/html/serviceWorker.gen.js +3 -0
- package/dist/server/internal/html/serviceWorker.gen.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts +5 -0
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +14 -6
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +2 -0
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -0
- package/dist/stripe/server/internal/html.gen.js +3 -0
- package/dist/stripe/server/internal/html.gen.js.map +1 -0
- package/dist/tempo/internal/proof.d.ts +6 -0
- package/dist/tempo/internal/proof.d.ts.map +1 -1
- package/dist/tempo/internal/proof.js +15 -0
- package/dist/tempo/internal/proof.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +10 -3
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +38 -10
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +3 -2
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +2 -0
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -0
- package/dist/tempo/server/internal/html.gen.js +3 -0
- package/dist/tempo/server/internal/html.gen.js.map +1 -0
- package/dist/tempo/server/internal/transport.d.ts +1 -1
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +45 -58
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/package.json +2 -2
- package/src/Credential.ts +28 -4
- package/src/Method.ts +6 -1
- package/src/cli/account.ts +13 -2
- package/src/env.d.ts +1 -0
- package/src/mcp-sdk/server/Transport.test.ts +6 -0
- package/src/middlewares/elysia.test.ts +3 -5
- package/src/middlewares/express.test.ts +3 -5
- package/src/middlewares/hono.test.ts +8 -5
- package/src/middlewares/nextjs.test.ts +3 -5
- package/src/proxy/Proxy.test.ts +188 -1
- package/src/proxy/Proxy.ts +58 -9
- package/src/proxy/internal/Route.test.ts +9 -0
- package/src/proxy/internal/Route.ts +5 -2
- package/src/server/Mppx.test.ts +171 -18
- package/src/server/Mppx.ts +120 -79
- package/src/server/Transport.test.ts +16 -2
- package/src/server/Transport.ts +61 -7
- package/src/server/internal/html/config.ts +8 -0
- package/src/server/internal/html/serviceWorker.client.ts +28 -0
- package/src/server/internal/html/serviceWorker.gen.ts +2 -0
- package/src/server/internal/html/serviceWorker.ts +27 -0
- package/src/server/internal/html/tsconfig.worker.client.json +8 -0
- package/src/server/internal/html/tsconfig.worker.json +8 -0
- package/src/stripe/server/Charge.ts +19 -5
- package/src/stripe/server/internal/html/main.ts +106 -0
- package/src/stripe/server/internal/html/node_modules/.bin/mppx.src +21 -0
- package/src/stripe/server/internal/html/package.json +9 -0
- package/src/stripe/server/internal/html/stripe-js-pure.d.ts +7 -0
- package/src/stripe/server/internal/html/tsconfig.json +8 -0
- package/src/stripe/server/internal/html.gen.ts +2 -0
- package/src/tempo/internal/proof.test.ts +47 -0
- package/src/tempo/internal/proof.ts +16 -0
- package/src/tempo/server/Charge.test.ts +298 -0
- package/src/tempo/server/Charge.ts +61 -12
- package/src/tempo/server/Session.ts +3 -2
- package/src/tempo/server/internal/html/main.ts +71 -0
- package/src/tempo/server/internal/html/node_modules/.bin/mppx.src +21 -0
- package/src/tempo/server/internal/html/package.json +10 -0
- package/src/tempo/server/internal/html/tsconfig.json +8 -0
- package/src/tempo/server/internal/html.gen.ts +2 -0
- package/src/tempo/server/internal/transport.test.ts +37 -31
- package/src/tempo/server/internal/transport.ts +44 -58
- package/src/tsconfig.json +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { tempo as tempo_chain } from 'viem/chains'
|
|
11
11
|
import { Abis, Transaction } from 'viem/tempo'
|
|
12
12
|
|
|
13
|
+
import { PaymentError, VerificationFailedError } from '../../Errors.js'
|
|
13
14
|
import * as Expires from '../../Expires.js'
|
|
14
15
|
import type { LooseOmit, NoExtraKeys } from '../../internal/types.js'
|
|
15
16
|
import * as Method from '../../Method.js'
|
|
@@ -24,6 +25,7 @@ import * as Proof from '../internal/proof.js'
|
|
|
24
25
|
import * as Selectors from '../internal/selectors.js'
|
|
25
26
|
import type * as types from '../internal/types.js'
|
|
26
27
|
import * as Methods from '../Methods.js'
|
|
28
|
+
import { html as htmlContent } from './internal/html.gen.js'
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Creates a Tempo charge method intent for usage on the server.
|
|
@@ -47,10 +49,12 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
47
49
|
decimals = defaults.decimals,
|
|
48
50
|
description,
|
|
49
51
|
externalId,
|
|
52
|
+
html,
|
|
50
53
|
memo,
|
|
51
54
|
waitForConfirmation = true,
|
|
52
55
|
} = parameters
|
|
53
56
|
const store = (parameters.store ?? Store.memory()) as Store.Store<charge.StoreItemMap>
|
|
57
|
+
const proofStore = parameters.store as Store.Store<charge.StoreItemMap> | undefined
|
|
54
58
|
|
|
55
59
|
const { recipient, feePayer, feePayerUrl } = Account.resolve(parameters)
|
|
56
60
|
|
|
@@ -73,6 +77,8 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
73
77
|
recipient,
|
|
74
78
|
} as unknown as Defaults,
|
|
75
79
|
|
|
80
|
+
html: html ? { config: {}, content: htmlContent } : undefined,
|
|
81
|
+
|
|
76
82
|
// TODO: dedupe `{charge,session}.request`
|
|
77
83
|
async request({ credential, request }) {
|
|
78
84
|
const chainId = await (async () => {
|
|
@@ -109,16 +115,17 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
109
115
|
|
|
110
116
|
async verify({ credential, request }) {
|
|
111
117
|
const { challenge } = credential
|
|
112
|
-
const
|
|
118
|
+
const resolvedRequest = Methods.charge.schema.request.parse(request)
|
|
119
|
+
const chainId = resolvedRequest.methodDetails?.chainId ?? request.chainId
|
|
120
|
+
const feePayer = request.feePayer
|
|
113
121
|
|
|
114
122
|
const client = await getClient({ chainId })
|
|
115
123
|
|
|
116
|
-
const {
|
|
117
|
-
const { amount, methodDetails } = challengeRequest
|
|
124
|
+
const { amount, methodDetails } = resolvedRequest
|
|
118
125
|
const expires = challenge.expires
|
|
119
126
|
|
|
120
|
-
const currency =
|
|
121
|
-
const recipient =
|
|
127
|
+
const currency = resolvedRequest.currency as `0x${string}`
|
|
128
|
+
const recipient = resolvedRequest.recipient as `0x${string}`
|
|
122
129
|
|
|
123
130
|
Expires.assert(expires, challenge.id)
|
|
124
131
|
|
|
@@ -159,11 +166,15 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
159
166
|
if (!expectedSource)
|
|
160
167
|
throw new MismatchError('Proof credential must include a source.', {})
|
|
161
168
|
|
|
162
|
-
const sourceAddress = expectedSource.split(':').pop() as `0x${string}`
|
|
163
169
|
const resolvedChainId = challenge.request.methodDetails?.chainId ?? chainId!
|
|
170
|
+
const source = Proof.parseProofSource(expectedSource)
|
|
171
|
+
|
|
172
|
+
if (!source || source.chainId !== resolvedChainId) {
|
|
173
|
+
throw new MismatchError('Proof credential source is invalid.', {})
|
|
174
|
+
}
|
|
164
175
|
|
|
165
176
|
const valid = await verifyTypedData(client, {
|
|
166
|
-
address:
|
|
177
|
+
address: source.address,
|
|
167
178
|
domain: Proof.domain(resolvedChainId),
|
|
168
179
|
types: Proof.types,
|
|
169
180
|
primaryType: 'Proof',
|
|
@@ -172,6 +183,11 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
172
183
|
})
|
|
173
184
|
if (!valid) throw new MismatchError('Proof signature does not match source.', {})
|
|
174
185
|
|
|
186
|
+
if (proofStore) {
|
|
187
|
+
await assertProofUnused(proofStore, challenge.id)
|
|
188
|
+
await markProofUsed(proofStore, challenge.id)
|
|
189
|
+
}
|
|
190
|
+
|
|
175
191
|
return {
|
|
176
192
|
method: 'tempo',
|
|
177
193
|
status: 'success',
|
|
@@ -282,13 +298,20 @@ export declare namespace charge {
|
|
|
282
298
|
type Defaults = LooseOmit<Method.RequestDefaults<typeof Methods.charge>, 'feePayer' | 'recipient'>
|
|
283
299
|
|
|
284
300
|
type Parameters = {
|
|
301
|
+
/** Render payment page when Accept header is text/html (e.g. in browsers) */
|
|
302
|
+
html?: boolean | undefined
|
|
285
303
|
/** Testnet mode. */
|
|
286
304
|
testnet?: boolean | undefined
|
|
287
305
|
/**
|
|
288
|
-
* Store for
|
|
306
|
+
* Store for charge replay protection.
|
|
289
307
|
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
308
|
+
* Non-zero charge flows default to an in-memory store if omitted. For
|
|
309
|
+
* zero-dollar proof auth, replay prevention is enabled only when a store
|
|
310
|
+
* is explicitly provided; otherwise proofs remain reusable until the
|
|
311
|
+
* challenge expires.
|
|
312
|
+
*
|
|
313
|
+
* Use a shared store in multi-instance deployments so consumed hashes and
|
|
314
|
+
* proofs are visible across all server instances.
|
|
292
315
|
*/
|
|
293
316
|
store?: Store.Store | undefined
|
|
294
317
|
/**
|
|
@@ -504,13 +527,19 @@ function getHashStoreKey(hash: `0x${string}`): `mppx:charge:${string}` {
|
|
|
504
527
|
return `mppx:charge:${hash.toLowerCase()}`
|
|
505
528
|
}
|
|
506
529
|
|
|
530
|
+
/** @internal */
|
|
531
|
+
function getProofStoreKey(challengeId: string): `mppx:charge:${string}` {
|
|
532
|
+
return `mppx:charge:proof:${challengeId}`
|
|
533
|
+
}
|
|
534
|
+
|
|
507
535
|
/** @internal */
|
|
508
536
|
async function assertHashUnused(
|
|
509
537
|
store: Store.Store<charge.StoreItemMap>,
|
|
510
538
|
hash: `0x${string}`,
|
|
511
539
|
): Promise<void> {
|
|
512
540
|
const seen = await store.get(getHashStoreKey(hash))
|
|
513
|
-
if (seen !== null)
|
|
541
|
+
if (seen !== null)
|
|
542
|
+
throw new VerificationFailedError({ reason: 'Transaction hash has already been used' })
|
|
514
543
|
}
|
|
515
544
|
|
|
516
545
|
/** @internal */
|
|
@@ -521,6 +550,24 @@ async function markHashUsed(
|
|
|
521
550
|
await store.put(getHashStoreKey(hash), Date.now())
|
|
522
551
|
}
|
|
523
552
|
|
|
553
|
+
/** @internal */
|
|
554
|
+
async function assertProofUnused(
|
|
555
|
+
store: Store.Store<charge.StoreItemMap>,
|
|
556
|
+
challengeId: string,
|
|
557
|
+
): Promise<void> {
|
|
558
|
+
const seen = await store.get(getProofStoreKey(challengeId))
|
|
559
|
+
if (seen !== null)
|
|
560
|
+
throw new VerificationFailedError({ reason: 'Proof credential has already been used' })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** @internal */
|
|
564
|
+
async function markProofUsed(
|
|
565
|
+
store: Store.Store<charge.StoreItemMap>,
|
|
566
|
+
challengeId: string,
|
|
567
|
+
): Promise<void> {
|
|
568
|
+
await store.put(getProofStoreKey(challengeId), Date.now())
|
|
569
|
+
}
|
|
570
|
+
|
|
524
571
|
/** @internal */
|
|
525
572
|
function toReceipt(receipt: TransactionReceipt) {
|
|
526
573
|
const { status, transactionHash } = receipt
|
|
@@ -536,8 +583,10 @@ function toReceipt(receipt: TransactionReceipt) {
|
|
|
536
583
|
}
|
|
537
584
|
|
|
538
585
|
/** @internal */
|
|
539
|
-
class MismatchError extends
|
|
586
|
+
class MismatchError extends PaymentError {
|
|
540
587
|
override readonly name = 'MismatchError'
|
|
588
|
+
readonly title = 'Verification Failed'
|
|
589
|
+
readonly type = 'https://paymentauth.org/problems/verification-failed'
|
|
541
590
|
|
|
542
591
|
constructor(reason: string, details: Record<string, string>) {
|
|
543
592
|
super([reason, ...Object.entries(details).map(([k, v]) => ` - ${k}: ${v}`)].join('\n'))
|
|
@@ -179,10 +179,11 @@ export function session<const parameters extends session.Parameters>(
|
|
|
179
179
|
}
|
|
180
180
|
},
|
|
181
181
|
|
|
182
|
-
async verify({ credential }) {
|
|
182
|
+
async verify({ credential, request }) {
|
|
183
183
|
const { challenge, payload } = credential as Credential.Credential<SessionCredentialPayload>
|
|
184
184
|
|
|
185
|
-
const
|
|
185
|
+
const resolvedRequest = Methods.session.schema.request.parse(request)
|
|
186
|
+
const methodDetails = resolvedRequest.methodDetails as SessionMethodDetails
|
|
186
187
|
const client = await getClient({ chainId: methodDetails.chainId })
|
|
187
188
|
|
|
188
189
|
const resolvedFeePayer = methodDetails.feePayer === true ? feePayer : undefined
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { local, Provider } from 'accounts'
|
|
2
|
+
import { Json } from 'ox'
|
|
3
|
+
import { createClient, custom, http } from 'viem'
|
|
4
|
+
import { tempoModerato, tempoLocalnet } from 'viem/chains'
|
|
5
|
+
|
|
6
|
+
import type * as Challenge from '../../../../Challenge.js'
|
|
7
|
+
import { tempo } from '../../../../client/index.js'
|
|
8
|
+
import * as Html from '../../../../server/internal/html/config.js'
|
|
9
|
+
import { submitCredential } from '../../../../server/internal/html/serviceWorker.client.js'
|
|
10
|
+
import type * as Methods from '../../../Methods.js'
|
|
11
|
+
|
|
12
|
+
const data = Json.parse(document.getElementById(Html.dataId)!.textContent) as {
|
|
13
|
+
challenge: Challenge.FromMethods<[typeof Methods.charge]>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const root = document.getElementById('root')!
|
|
17
|
+
|
|
18
|
+
const h2 = document.createElement('h2')
|
|
19
|
+
h2.textContent = 'tempo'
|
|
20
|
+
root.appendChild(h2)
|
|
21
|
+
|
|
22
|
+
const provider = Provider.create({
|
|
23
|
+
// Dead code eliminated from production bundle (including top-level imports)
|
|
24
|
+
...(import.meta.env.MODE === 'test'
|
|
25
|
+
? {
|
|
26
|
+
adapter: local({
|
|
27
|
+
async loadAccounts() {
|
|
28
|
+
const { generatePrivateKey } = await import('viem/accounts')
|
|
29
|
+
const { Account, Actions } = await import('viem/tempo')
|
|
30
|
+
const privateKey = generatePrivateKey()
|
|
31
|
+
const account = Account.fromSecp256k1(privateKey)
|
|
32
|
+
const client = createClient({
|
|
33
|
+
chain: [tempoModerato, tempoLocalnet].find(
|
|
34
|
+
(x) => x.id === data.challenge.request.methodDetails?.chainId,
|
|
35
|
+
),
|
|
36
|
+
transport: http(),
|
|
37
|
+
})
|
|
38
|
+
await Actions.faucet.fundSync(client, { account })
|
|
39
|
+
return {
|
|
40
|
+
accounts: [account],
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
}
|
|
45
|
+
: {}),
|
|
46
|
+
testnet:
|
|
47
|
+
data.challenge.request.methodDetails?.chainId === tempoModerato.id ||
|
|
48
|
+
data.challenge.request.methodDetails?.chainId === tempoLocalnet.id,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const button = document.createElement('button')
|
|
52
|
+
button.textContent = 'Continue with Tempo'
|
|
53
|
+
button.onclick = async () => {
|
|
54
|
+
try {
|
|
55
|
+
button.disabled = true
|
|
56
|
+
|
|
57
|
+
const chain = [...(provider?.chains ?? []), tempoModerato, tempoLocalnet].find(
|
|
58
|
+
(x) => x.id === data.challenge.request.methodDetails?.chainId,
|
|
59
|
+
)
|
|
60
|
+
const client = createClient({ chain, transport: custom(provider) })
|
|
61
|
+
const result = await provider.request({ method: 'wallet_connect' })
|
|
62
|
+
const account = result.accounts[0]?.address
|
|
63
|
+
const method = tempo({ account, getClient: () => client })[0]
|
|
64
|
+
|
|
65
|
+
const credential = await method.createCredential({ challenge: data.challenge, context: {} })
|
|
66
|
+
await submitCredential(credential)
|
|
67
|
+
} finally {
|
|
68
|
+
button.disabled = false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
root.appendChild(button)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/mppx/mppx/src/node_modules:/home/runner/work/mppx/mppx/node_modules:/home/runner/work/mppx/node_modules:/home/runner/work/node_modules:/home/runner/node_modules:/home/node_modules:/node_modules:/home/runner/work/mppx/mppx/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/mppx/mppx/src/node_modules:/home/runner/work/mppx/mppx/node_modules:/home/runner/work/mppx/node_modules:/home/runner/work/node_modules:/home/runner/node_modules:/home/node_modules:/node_modules:/home/runner/work/mppx/mppx/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../mppx/src/bin.ts" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../mppx/src/bin.ts" "$@"
|
|
21
|
+
fi
|