Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .mppx-docs-sync
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
# When updating docs from mppx changes, bump this SHA to HEAD of mppx main
# after incorporating the new features/changes into the docs site.

mppx_version=0.0.0-main-20260805032439
mppx_sha=44414785ff5efb42f50fd89f06aae72e5e9af3de
mppx_version=0.0.0-main-20260805221648
mppx_sha=cd71f9e5c12bdae75d3b1ea8dd0a677c2d0af7e7
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"hono": "^4.12.27",
"lottie-web": "^5.13.0",
"mermaid": "^11.15.0",
"mppx": "0.0.0-main-20260805032439",
"mppx": "0.0.0-main-20260805221648",
"nuqs": "2.9.1",
"react": "^19",
"react-dom": "^19",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/pages.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ type Page =
| { path: '/sdk/typescript/core/Receipt.from'; render: 'static' }
| { path: '/sdk/typescript/core/Receipt.fromResponse'; render: 'static' }
| { path: '/sdk/typescript/core/Receipt.serialize'; render: 'static' }
| { path: '/sdk/typescript/core/Store.tryClaim'; render: 'static' }
| { path: '/sdk/typescript/html/custom'; render: 'static' }
| { path: '/sdk/typescript'; render: 'static' }
| { path: '/sdk/typescript/middlewares/elysia'; render: 'static' }
Expand Down
38 changes: 19 additions & 19 deletions src/pages/advanced/identity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,8 @@ console.log(response.status)
Configure the verifier on the MPP server. The resolver receives the signed directory origin and key ID. Apply your trust policy before you return a key.

```ts twoslash [server.ts]
import * as Attestation from 'mppx/attestation'
import * as WebBotAuth from 'mppx/attestation/web-bot-auth'
import { Mppx, tempo } from 'mppx/server'
import { Mppx, Store, tempo } from 'mppx/server'

declare const botPublicKey: CryptoKey

Expand All @@ -107,7 +106,7 @@ const payment = Mppx.create({
return botPublicKey
},
maxAge: 60,
nonceStore: Attestation.NonceStore.memory(),
nonceStore: Store.memory(),
}),
},
// [!code hl:end]
Expand Down Expand Up @@ -157,9 +156,8 @@ console.log(response.status)
On the server, resolve `keyId` only from agent providers you trust. The verifier checks the signature, request authority and path, lifetime, intent tag, and nonce.

```ts twoslash [server.ts]
import * as Attestation from 'mppx/attestation'
import * as Tap from 'mppx/attestation/tap'
import { Mppx, tempo } from 'mppx/server'
import { Mppx, Store, tempo } from 'mppx/server'

declare const trustedAgentKeys: ReadonlyMap<string, CryptoKey>

Expand All @@ -170,7 +168,7 @@ const payment = Mppx.create({
keyResolver({ keyId }) {
return trustedAgentKeys.get(keyId)
},
nonceStore: Attestation.NonceStore.memory(),
nonceStore: Store.memory(),
}),
},
// [!code hl:end]
Expand Down Expand Up @@ -210,7 +208,7 @@ import { Mppx, tempo } from 'mppx/server'

declare const trustedAgentKeys: ReadonlyMap<string, CryptoKey>

const nonceStore = Attestation.NonceStore.memory()
const nonceStore = Attestation.Store.memory()
const tap = Tap.Server.verifier({
keyResolver({ keyId }) {
return trustedAgentKeys.get(keyId)
Expand Down Expand Up @@ -240,23 +238,25 @@ Don't pass the same verifier to `Mppx.create` in this pattern. Verification cons

### Store nonces

`Attestation.NonceStore.memory()` is limited to one long-lived server process. In a multi-instance deployment, provide a shared store whose `consume` operation atomically inserts a nonce only when absent and retains it until `expires`.
Attestation verifiers accept the core `Store.AtomicStore`. `Store.memory()` is limited to one long-lived server process. In a multi-instance deployment, provide a shared atomic store so every instance claims nonces through their expiration time.

```ts [nonce-store.ts]
import type { NonceStore } from 'mppx/attestation'

export const nonceStore: NonceStore.Store = {
async consume(key, expires) {
const inserted = await nonceDatabase.insertIfAbsent({
expires,
key,
})
return !inserted
},
import type { Store } from 'mppx'

declare const nonceDatabase: {
insertIfAbsent(value: { expires: number; key: string }): Promise<boolean>
}
declare const sharedStore: Store.AtomicStore

export const nonceStore = {
...sharedStore,
async tryClaim(key: string, expires: number) {
return nonceDatabase.insertIfAbsent({ expires, key })
},
} satisfies Store.AtomicStore
```

Adapt the call to your storage client. `consume` returns `true` when the nonce was already present and unexpired.
Adapt `tryClaim` to your storage client's atomic insert-if-absent operation. It returns `true` when it records a new claim and `false` when an unexpired claim already exists. If your `AtomicStore` omits this optimized method, `mppx` falls back to its atomic `update` operation. See [`Store.tryClaim`](/sdk/typescript/core/Store.tryClaim).

## MPP Credential identity

Expand Down
25 changes: 25 additions & 0 deletions src/pages/advanced/payment-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@ payment.onPaymentFailed(({ error, method, submittedChallenge }) => { // [!code h
})
```

### Scope success hooks to a method

Pass `onPaymentSuccess` to a method constructor when the side effect belongs only to that payment method and intent. The hook receives the method-specific request, its Receipt, and the HTTP input when available.

```ts
import { Mppx, tempo } from 'mppx/server'

const payment = Mppx.create({
methods: [
tempo.charge({
async onPaymentSuccess({ input, receipt, request }) {
await recordCharge({
amount: request.amount,
path: input ? new URL(input.url).pathname : undefined,
reference: receipt.reference,
})
},
}),
tempo.session(),
],
})
```

`mppx` registers this as a filtered `payment.success` listener. It runs only when both the method name and intent match. The server awaits it inline and ignores thrown errors, matching instance-level server hook behavior. `input` is absent for standalone `broadcastCredential` and `verifyCredential` calls.

## Client hooks

Register client hooks on the object returned by `Mppx.create` from `mppx/client`.
Expand Down
2 changes: 2 additions & 0 deletions src/pages/blog/multi-method-discovery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ const mppx = Mppx.create({
}),
stripe.charge({
client: stripeClient,
currency: 'usd',
decimals: 2,
networkId: 'internal',
paymentMethodTypes: ['card'],
}),
Expand Down
Loading
Loading