mppx 0.5.10 → 0.5.12
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 +14 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +41 -16
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/config.d.ts +6 -4
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/internal.d.ts +8 -0
- package/dist/cli/internal.d.ts.map +1 -1
- package/dist/cli/internal.js +33 -3
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/plugins/plugin.d.ts +2 -0
- package/dist/cli/plugins/plugin.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js +3 -0
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +3 -0
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/client/Mppx.d.ts +10 -1
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +17 -5
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +2 -0
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +11 -0
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +3 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +65 -19
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/internal/AcceptPayment.d.ts +72 -0
- package/dist/internal/AcceptPayment.d.ts.map +1 -0
- package/dist/internal/AcceptPayment.js +185 -0
- package/dist/internal/AcceptPayment.js.map +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +8 -4
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/server/Mppx.d.ts +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +33 -24
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Request.js +1 -1
- package/dist/server/Request.js.map +1 -1
- package/dist/stripe/internal/constants.d.ts +8 -0
- package/dist/stripe/internal/constants.d.ts.map +1 -0
- package/dist/stripe/internal/constants.js +8 -0
- package/dist/stripe/internal/constants.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +23 -5
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/tempo/Proof.d.ts +12 -0
- package/dist/tempo/Proof.d.ts.map +1 -0
- package/dist/tempo/Proof.js +10 -0
- package/dist/tempo/Proof.js.map +1 -0
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -0
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +5 -1
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +62 -3
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/cli.test.ts +278 -0
- package/src/cli/cli.ts +47 -16
- package/src/cli/config.ts +10 -4
- package/src/cli/internal.ts +59 -3
- package/src/cli/plugins/plugin.ts +3 -0
- package/src/cli/plugins/stripe.ts +3 -0
- package/src/cli/plugins/tempo.ts +3 -0
- package/src/client/Mppx.test-d.ts +33 -0
- package/src/client/Mppx.test.ts +130 -1
- package/src/client/Mppx.ts +35 -5
- package/src/client/Transport.test.ts +88 -55
- package/src/client/Transport.ts +13 -0
- package/src/client/internal/Fetch.browser.test.ts +16 -13
- package/src/client/internal/Fetch.test.ts +307 -10
- package/src/client/internal/Fetch.ts +85 -19
- package/src/internal/AcceptPayment.test.ts +211 -0
- package/src/internal/AcceptPayment.ts +304 -0
- package/src/mcp-sdk/client/McpClient.ts +11 -5
- package/src/server/Mppx.test.ts +141 -44
- package/src/server/Mppx.ts +43 -23
- package/src/server/NodeListener.test.ts +78 -0
- package/src/server/Request.ts +1 -1
- package/src/stripe/internal/constants.ts +7 -0
- package/src/stripe/server/Charge.ts +22 -4
- package/src/tempo/Proof.test-d.ts +13 -0
- package/src/tempo/Proof.test.ts +31 -0
- package/src/tempo/Proof.ts +13 -0
- package/src/tempo/client/SessionManager.test.ts +4 -7
- package/src/tempo/index.ts +1 -0
- package/src/tempo/internal/fee-payer.test.ts +1 -1
- package/src/tempo/internal/fee-payer.ts +5 -1
- package/src/tempo/server/Charge.test.ts +123 -0
- package/src/tempo/server/Charge.ts +74 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
|
@@ -25,6 +25,11 @@ import { signVoucher } from '../session/Voucher.js'
|
|
|
25
25
|
const realm = 'api.example.com'
|
|
26
26
|
const secretKey = 'test-secret-key'
|
|
27
27
|
|
|
28
|
+
type ProofAccessKeyContext = {
|
|
29
|
+
accessKey: ReturnType<typeof Account.fromSecp256k1>
|
|
30
|
+
rootAccount: (typeof accounts)[number]
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
const server = Mppx_server.create({
|
|
29
34
|
methods: [
|
|
30
35
|
tempo_server.charge({
|
|
@@ -2114,6 +2119,124 @@ describe('tempo', () => {
|
|
|
2114
2119
|
httpServer.close()
|
|
2115
2120
|
})
|
|
2116
2121
|
|
|
2122
|
+
for (const testCase of [
|
|
2123
|
+
{
|
|
2124
|
+
name: 'accepts proof signed by an authorized access key for the root source',
|
|
2125
|
+
expectedStatus: 200,
|
|
2126
|
+
async prepare({ accessKey, rootAccount }: ProofAccessKeyContext) {
|
|
2127
|
+
await Actions.accessKey.authorizeSync(client, {
|
|
2128
|
+
account: rootAccount,
|
|
2129
|
+
accessKey,
|
|
2130
|
+
feeToken: asset,
|
|
2131
|
+
})
|
|
2132
|
+
},
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
name: 'rejects proof signed by an unauthorized access key for the root source',
|
|
2136
|
+
expectedDetail: 'Proof signature does not match source.',
|
|
2137
|
+
expectedStatus: 402,
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
name: 'rejects proof signed by a revoked access key for the root source',
|
|
2141
|
+
expectedDetail: 'Proof signature does not match source.',
|
|
2142
|
+
expectedStatus: 402,
|
|
2143
|
+
async prepare({ accessKey, rootAccount }: ProofAccessKeyContext) {
|
|
2144
|
+
await Actions.accessKey.authorizeSync(client, {
|
|
2145
|
+
account: rootAccount,
|
|
2146
|
+
accessKey,
|
|
2147
|
+
feeToken: asset,
|
|
2148
|
+
})
|
|
2149
|
+
await fundAccount({ address: rootAccount.address, token: asset })
|
|
2150
|
+
await Actions.accessKey.revokeSync(client, {
|
|
2151
|
+
account: rootAccount,
|
|
2152
|
+
accessKey,
|
|
2153
|
+
feeToken: asset,
|
|
2154
|
+
})
|
|
2155
|
+
},
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
name: 'rejects proof signed by an expired access key for the root source',
|
|
2159
|
+
expectedDetail: 'Proof signature does not match source.',
|
|
2160
|
+
expectedStatus: 402,
|
|
2161
|
+
async prepare({ accessKey, rootAccount }: ProofAccessKeyContext) {
|
|
2162
|
+
await Actions.accessKey.authorizeSync(client, {
|
|
2163
|
+
account: rootAccount,
|
|
2164
|
+
accessKey,
|
|
2165
|
+
expiry: Math.floor(Date.now() / 1000) + 10,
|
|
2166
|
+
feeToken: asset,
|
|
2167
|
+
})
|
|
2168
|
+
|
|
2169
|
+
const metadata = await Actions.accessKey.getMetadata(client, {
|
|
2170
|
+
account: rootAccount.address,
|
|
2171
|
+
accessKey,
|
|
2172
|
+
})
|
|
2173
|
+
const originalNow = Date.now
|
|
2174
|
+
Date.now = () => (Number(metadata.expiry) + 5) * 1000
|
|
2175
|
+
|
|
2176
|
+
return () => {
|
|
2177
|
+
Date.now = originalNow
|
|
2178
|
+
}
|
|
2179
|
+
},
|
|
2180
|
+
},
|
|
2181
|
+
] as const) {
|
|
2182
|
+
test(`behavior: ${testCase.name}`, async () => {
|
|
2183
|
+
const rootAccount = accounts[1]
|
|
2184
|
+
const accessKey = Account.fromSecp256k1(Secp256k1.randomPrivateKey(), {
|
|
2185
|
+
access: rootAccount,
|
|
2186
|
+
})
|
|
2187
|
+
|
|
2188
|
+
let cleanup: (() => void) | undefined
|
|
2189
|
+
let httpServer: Awaited<ReturnType<typeof Http.createServer>> | undefined
|
|
2190
|
+
|
|
2191
|
+
try {
|
|
2192
|
+
const maybeCleanup = await testCase.prepare?.({ accessKey, rootAccount })
|
|
2193
|
+
cleanup = typeof maybeCleanup === 'function' ? maybeCleanup : undefined
|
|
2194
|
+
|
|
2195
|
+
httpServer = await Http.createServer(async (req, res) => {
|
|
2196
|
+
const result = await Mppx_server.toNodeListener(
|
|
2197
|
+
server.charge({ amount: '0', decimals: 6 }),
|
|
2198
|
+
)(req, res)
|
|
2199
|
+
if (result.status === 402) return
|
|
2200
|
+
res.end('OK')
|
|
2201
|
+
})
|
|
2202
|
+
|
|
2203
|
+
const response1 = await fetch(httpServer.url)
|
|
2204
|
+
expect(response1.status).toBe(402)
|
|
2205
|
+
|
|
2206
|
+
const challenge = Challenge.fromResponse(response1, {
|
|
2207
|
+
methods: [tempo_client.charge()],
|
|
2208
|
+
})
|
|
2209
|
+
|
|
2210
|
+
const signature = await signTypedData(client, {
|
|
2211
|
+
account: accessKey,
|
|
2212
|
+
domain: Proof.domain(chain.id),
|
|
2213
|
+
types: Proof.types,
|
|
2214
|
+
primaryType: 'Proof',
|
|
2215
|
+
message: Proof.message(challenge.id),
|
|
2216
|
+
})
|
|
2217
|
+
|
|
2218
|
+
const credential = Credential.from({
|
|
2219
|
+
challenge,
|
|
2220
|
+
payload: { signature, type: 'proof' as const },
|
|
2221
|
+
source: `did:pkh:eip155:${chain.id}:${rootAccount.address}`,
|
|
2222
|
+
})
|
|
2223
|
+
|
|
2224
|
+
const response2 = await fetch(httpServer.url, {
|
|
2225
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
2226
|
+
})
|
|
2227
|
+
expect(response2.status).toBe(testCase.expectedStatus)
|
|
2228
|
+
|
|
2229
|
+
if (testCase.expectedDetail) {
|
|
2230
|
+
const body = (await response2.json()) as { detail: string }
|
|
2231
|
+
expect(body.detail).toContain(testCase.expectedDetail)
|
|
2232
|
+
}
|
|
2233
|
+
} finally {
|
|
2234
|
+
cleanup?.()
|
|
2235
|
+
httpServer?.close()
|
|
2236
|
+
}
|
|
2237
|
+
})
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2117
2240
|
test('behavior: rejects replayed proof credential when store is configured', async () => {
|
|
2118
2241
|
const replayStore = Store.memory()
|
|
2119
2242
|
const server_ = Mppx_server.create({
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import * as SignatureEnvelope from 'ox/tempo/SignatureEnvelope'
|
|
1
2
|
import {
|
|
2
3
|
decodeFunctionData,
|
|
3
4
|
formatUnits,
|
|
5
|
+
hashTypedData,
|
|
4
6
|
keccak256,
|
|
5
7
|
parseEventLogs,
|
|
6
8
|
type TransactionReceipt,
|
|
@@ -227,7 +229,21 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
227
229
|
message: Proof.message(challenge.id),
|
|
228
230
|
signature: payload.signature as `0x${string}`,
|
|
229
231
|
})
|
|
230
|
-
if (!valid)
|
|
232
|
+
if (!valid) {
|
|
233
|
+
const proofSigner = recoverAuthorizedProofSigner({
|
|
234
|
+
chainId: resolvedChainId,
|
|
235
|
+
challengeId: challenge.id,
|
|
236
|
+
signature: payload.signature as `0x${string}`,
|
|
237
|
+
sourceAddress: source.address,
|
|
238
|
+
})
|
|
239
|
+
const authorized = proofSigner
|
|
240
|
+
? await isActiveAccessKey(client, {
|
|
241
|
+
accessKey: proofSigner,
|
|
242
|
+
account: source.address,
|
|
243
|
+
})
|
|
244
|
+
: false
|
|
245
|
+
if (!authorized) throw new MismatchError('Proof signature does not match source.', {})
|
|
246
|
+
}
|
|
231
247
|
|
|
232
248
|
if (proofStore && !(await markProofUsed(proofStore, challenge.id))) {
|
|
233
249
|
throw new VerificationFailedError({ reason: 'Proof credential has already been used' })
|
|
@@ -651,6 +667,63 @@ async function markProofUsed(
|
|
|
651
667
|
})
|
|
652
668
|
}
|
|
653
669
|
|
|
670
|
+
function recoverAuthorizedProofSigner(parameters: {
|
|
671
|
+
chainId: number
|
|
672
|
+
challengeId: string
|
|
673
|
+
signature: `0x${string}`
|
|
674
|
+
sourceAddress: `0x${string}`
|
|
675
|
+
}): `0x${string}` | null {
|
|
676
|
+
const { chainId, challengeId, signature, sourceAddress } = parameters
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
const envelope = SignatureEnvelope.from(signature)
|
|
680
|
+
const proofHash = hashTypedData({
|
|
681
|
+
domain: Proof.domain(chainId),
|
|
682
|
+
types: Proof.types,
|
|
683
|
+
primaryType: 'Proof',
|
|
684
|
+
message: Proof.message(challengeId),
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
if (envelope.type === 'keychain') {
|
|
688
|
+
if (!TempoAddress.isEqual(envelope.userAddress, sourceAddress)) return null
|
|
689
|
+
|
|
690
|
+
const keychainPayload =
|
|
691
|
+
envelope.version === 'v2'
|
|
692
|
+
? keccak256(`0x04${proofHash.slice(2)}${sourceAddress.slice(2)}` as `0x${string}`)
|
|
693
|
+
: proofHash
|
|
694
|
+
|
|
695
|
+
const signer = SignatureEnvelope.extractAddress({
|
|
696
|
+
payload: keychainPayload,
|
|
697
|
+
signature: envelope.inner,
|
|
698
|
+
})
|
|
699
|
+
const valid = SignatureEnvelope.verify(envelope.inner, {
|
|
700
|
+
address: signer,
|
|
701
|
+
payload: keychainPayload,
|
|
702
|
+
})
|
|
703
|
+
if (!valid) return null
|
|
704
|
+
|
|
705
|
+
return signer
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return SignatureEnvelope.extractAddress({ payload: proofHash, signature: envelope })
|
|
709
|
+
} catch {
|
|
710
|
+
return null
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async function isActiveAccessKey(
|
|
715
|
+
client: Awaited<ReturnType<ReturnType<typeof Client.getResolver>>>,
|
|
716
|
+
parameters: { account: `0x${string}`; accessKey: `0x${string}` },
|
|
717
|
+
): Promise<boolean> {
|
|
718
|
+
try {
|
|
719
|
+
const metadata = await Actions.accessKey.getMetadata(client, parameters)
|
|
720
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1000))
|
|
721
|
+
return !metadata.isRevoked && metadata.expiry > nowSeconds
|
|
722
|
+
} catch {
|
|
723
|
+
return false
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
654
727
|
/** @internal */
|
|
655
728
|
function toReceipt(receipt: TransactionReceipt) {
|
|
656
729
|
const { status, transactionHash } = receipt
|