Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/x402-exact-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Added x402 exact core types, header codecs, known asset metadata, client/server transports, and public client/server interfaces.
5 changes: 5 additions & 0 deletions .changeset/x402-resource-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Fixed x402 exact resource binding for submitted payment payloads.
124 changes: 124 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,130 @@ Mppx.create({
const res = await fetch('https://mpp.dev/api/ping/paid')
```

### x402 Exact

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

const mppx = Mppx.create({
methods: [
tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
}),
x402.exact({
config: {
currency: x402.assets.baseSepolia.USDC,
facilitator: 'https://x402.org/facilitator',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
},
}),
],
})

export async function GET(request: Request) {
const url = new URL(request.url)
const result =
url.pathname === '/mpp'
? await mppx.tempo.charge({ amount: '1' })(request)
: await mppx.x402.exact({
amount: '0.01',
resource: { url: request.url },
})(request)

if (result.status === 402) return result.challenge
return result.withReceipt(Response.json({ data: 'paid content' }))
}
```

Existing server adapters expose the same method. For Hono:

```ts
import { Hono } from 'hono'
import { Mppx, tempo, x402 } from 'mppx/hono'

const app = new Hono()
const mppx = Mppx.create({
methods: [
tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
}),
x402.exact({
config: {
currency: x402.assets.baseSepolia.USDC,
facilitator: 'https://x402.org/facilitator',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
},
}),
],
})

app.get('/mpp', mppx.tempo.charge({ amount: '1' }), (c) => c.json({ ok: true }))
app.get('/x402', mppx.x402.exact({ amount: '0.01' }), (c) => c.json({ ok: true }))
```

Like Tempo, x402 route `amount` values are display-unit strings; mppx converts
them to atomic token amounts from the configured currency decimals.

To offer both protocols from one Hono route, use the core HTTP composer inside
the Hono handler:

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

const app = new Hono()
const tempoCharge = tempo.charge({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
})
const x402Exact = x402.exact({
config: {
currency: x402.assets.baseSepolia.USDC,
facilitator: 'https://x402.org/facilitator',
recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
},
})
const payments = Mppx.create({
methods: [tempoCharge, x402Exact],
})

const paid = payments.compose([tempoCharge, { amount: '1' }], [x402Exact, { amount: '0.01' }])

app.get('/paid', async (c) => {
const result = await paid(c.req.raw)
if (result.status === 402) return result.challenge
return result.withReceipt(c.json({ ok: true }))
})
```

The same `compose()` handler can be used in other HTTP frameworks. Pass it the
framework's standard `Request`, return `result.challenge` on `402`, and wrap the
framework response with `result.withReceipt(...)` after payment succeeds.

```ts
import { privateKeyToAccount } from 'viem/accounts'
import { Mppx, x402 } from 'mppx/client'

const mppx = Mppx.create({
methods: [
x402.exact({
account: privateKeyToAccount('0x...'),
currencies: [x402.assets.baseSepolia.USDC],
maxAmount: '0.01',
networks: ['eip155:84532'],
}),
],
})

const res = await mppx.fetch('https://api.example.com/paid')
```

The default HTTP transport multiplexes Payment auth and x402 headers. It reads
`WWW-Authenticate` and `PAYMENT-REQUIRED`, then sends credentials through either
`Authorization` or `PAYMENT-SIGNATURE` based on the selected challenge.

## Examples

| Example | Description |
Expand Down
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@
"src": "./src/stripe/server/index.ts",
"default": "./dist/stripe/server/index.js"
},
"./x402": {
"types": "./dist/x402/index.d.ts",
"src": "./src/x402/index.ts",
"default": "./dist/x402/index.js"
},
"./x402/client": {
"types": "./dist/x402/client/index.d.ts",
"src": "./src/x402/client/index.ts",
"default": "./dist/x402/client/index.js"
},
"./x402/server": {
"types": "./dist/x402/server/index.d.ts",
"src": "./src/x402/server/index.ts",
"default": "./dist/x402/server/index.js"
},
"./tempo": {
"types": "./dist/tempo/index.d.ts",
"src": "./src/tempo/index.ts",
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

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

2 changes: 1 addition & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ overrides:
path-to-regexp@<8.4.0: '8.4.0'
tar@<=7.5.10: '7.5.11'
'@modelcontextprotocol/sdk@>=1.10.0 <=1.25.3': '1.26.0'
qs@>=6.7.0 <=6.14.1: '6.14.2'
qs@>=6.7.0 <=6.15.1: '6.15.2'
ip-address@<=10.1.0: '10.1.1'
minimatch@>=5.0.0 <5.1.8: '5.1.8'
minimatch@>=9.0.0 <9.0.7: '9.0.7'
Expand Down
1 change: 1 addition & 0 deletions src/client/Methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { stripe } from '../stripe/client/index.js'
export { subscription } from '../tempo/client/Subscription.js'
export { tempo } from '../tempo/client/index.js'
export { session } from '../tempo/client/Session.js'
export { x402 } from '../x402/client/index.js'
1 change: 1 addition & 0 deletions src/client/Mppx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export function create<
...(resolvedOnChallenge && { onChallenge: resolvedOnChallenge }),
...(orderChallenges && { orderChallenges }),
methods,
transport: transport as never,
} satisfies Fetch.from.Config<FlattenMethods<methods>>
const fetch = Fetch.from<FlattenMethods<methods>>(config_fetch)

Expand Down
Loading
Loading