mppx 0.7.0 → 0.8.0
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 +33 -0
- package/README.md +20 -11
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +18 -6
- package/dist/Challenge.js.map +1 -1
- package/dist/Mcp.d.ts +3 -0
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +2 -0
- package/dist/Mcp.js.map +1 -1
- package/dist/PaymentRequest.d.ts +10 -10
- package/dist/PaymentRequest.js +8 -8
- package/dist/client/Mppx.js +2 -2
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +11 -16
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +55 -75
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +46 -7
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/protocols/Mcp.d.ts +7 -0
- package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mcp.js +159 -0
- package/dist/client/internal/protocols/Mcp.js.map +1 -0
- package/dist/client/internal/protocols/Mpp.d.ts +4 -0
- package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mpp.js +18 -0
- package/dist/client/internal/protocols/Mpp.js.map +1 -0
- package/dist/client/internal/protocols/Protocol.d.ts +10 -0
- package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
- package/dist/client/internal/protocols/Protocol.js +2 -0
- package/dist/client/internal/protocols/Protocol.js.map +1 -0
- package/dist/client/internal/protocols/Shared.d.ts +5 -0
- package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
- package/dist/client/internal/protocols/Shared.js +20 -0
- package/dist/client/internal/protocols/Shared.js.map +1 -0
- package/dist/client/internal/protocols/X402.d.ts +8 -0
- package/dist/client/internal/protocols/X402.d.ts.map +1 -0
- package/dist/client/internal/protocols/X402.js +39 -0
- package/dist/client/internal/protocols/X402.js.map +1 -0
- package/dist/evm/client/index.d.ts +1 -0
- package/dist/evm/client/index.d.ts.map +1 -1
- package/dist/evm/client/index.js +1 -0
- package/dist/evm/client/index.js.map +1 -1
- package/dist/evm/index.d.ts +2 -0
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +2 -0
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/server/index.d.ts +1 -0
- package/dist/evm/server/index.d.ts.map +1 -1
- package/dist/evm/server/index.js +1 -0
- package/dist/evm/server/index.js.map +1 -1
- package/dist/mcp/client/McpClient.d.ts +101 -0
- package/dist/mcp/client/McpClient.d.ts.map +1 -0
- package/dist/mcp/client/McpClient.js +162 -0
- package/dist/mcp/client/McpClient.js.map +1 -0
- package/dist/mcp/client/index.d.ts.map +1 -0
- package/dist/mcp/client/index.js.map +1 -0
- package/dist/mcp/server/Transport.d.ts.map +1 -0
- package/dist/mcp/server/Transport.js.map +1 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/server/Mppx.d.ts +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +9 -0
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Transport.d.ts +1 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +1 -1
- package/dist/server/Transport.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/Proof.d.ts +85 -1
- package/dist/tempo/Proof.d.ts.map +1 -1
- package/dist/tempo/Proof.js +35 -0
- package/dist/tempo/Proof.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts +13 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +38 -25
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +5 -3
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Methods.js +4 -2
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/ResolveAccount.d.ts +40 -0
- package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
- package/dist/tempo/client/ResolveAccount.js +2 -0
- package/dist/tempo/client/ResolveAccount.js.map +1 -0
- package/dist/tempo/internal/fee-payer.d.ts +9 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +35 -6
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/proof.d.ts +71 -5
- package/dist/tempo/internal/proof.d.ts.map +1 -1
- package/dist/tempo/internal/proof.js +42 -6
- package/dist/tempo/internal/proof.js.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.js +10 -3
- package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +42 -18
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +4 -2
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +4 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +10 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +135 -23
- package/dist/tempo/server/Subscription.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/dist/tempo/session/client/ChannelOps.d.ts +2 -3
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +7 -10
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
- package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/client/ChannelStore.js +63 -0
- package/dist/tempo/session/client/ChannelStore.js.map +1 -0
- package/dist/tempo/session/client/CredentialState.d.ts +7 -24
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +51 -49
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +8 -2
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +22 -8
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +4 -40
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +124 -174
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/index.d.ts +3 -4
- package/dist/tempo/session/client/index.d.ts.map +1 -1
- package/dist/tempo/session/client/index.js +1 -0
- package/dist/tempo/session/client/index.js.map +1 -1
- package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
- package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Voucher.js +24 -25
- package/dist/tempo/session/precompile/Voucher.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +4 -2
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Sse.d.ts.map +1 -1
- package/dist/tempo/session/server/Sse.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.js +16 -1
- package/dist/tempo/subscription/Store.js.map +1 -1
- package/dist/x402/index.d.ts +1 -0
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +1 -0
- package/dist/x402/index.js.map +1 -1
- package/package.json +21 -10
- package/src/Challenge.test.ts +40 -0
- package/src/Challenge.ts +19 -6
- package/src/Mcp.ts +4 -0
- package/src/PaymentRequest.ts +10 -10
- package/src/cli/cli.test.ts +15 -15
- package/src/client/Mppx.test-d.ts +21 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Mppx.ts +2 -2
- package/src/client/Transport.test.ts +225 -178
- package/src/client/Transport.ts +77 -83
- package/src/client/index.ts +14 -0
- package/src/client/internal/Fetch.test.ts +207 -2
- package/src/client/internal/Fetch.ts +52 -6
- package/src/client/internal/protocols/Mcp.test.ts +220 -0
- package/src/client/internal/protocols/Mcp.ts +162 -0
- package/src/client/internal/protocols/Mpp.ts +21 -0
- package/src/client/internal/protocols/Protocol.ts +10 -0
- package/src/client/internal/protocols/Shared.ts +25 -0
- package/src/client/internal/protocols/X402.ts +42 -0
- package/src/discovery/OpenApi.test.ts +1 -1
- package/src/evm/PublicInterface.test-d.ts +1 -1
- package/src/evm/client/index.ts +1 -0
- package/src/evm/index.ts +2 -0
- package/src/evm/server/Charge.test.ts +1 -1
- package/src/evm/server/index.ts +1 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
- package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
- package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
- package/src/mcp/client/McpClient.ts +307 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
- package/src/middlewares/elysia.test.ts +1 -1
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/internal/mppx.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/proxy/Proxy.test.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +1 -1
- package/src/proxy/services/openai.test.ts +1 -1
- package/src/proxy/services/stripe.test.ts +1 -1
- package/src/server/Mppx.authorize.test.ts +1 -1
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +20 -2
- package/src/server/Mppx.ts +14 -1
- package/src/server/Transport.test.ts +6 -6
- package/src/server/Transport.ts +1 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/client/Charge.test.ts +1 -1
- package/src/stripe/server/Charge.test.ts +1 -1
- package/src/stripe/server/internal/html/package.json +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/Proof.conformance.test.ts +146 -0
- package/src/tempo/Proof.test-d.ts +15 -0
- package/src/tempo/Proof.ts +52 -1
- package/src/tempo/Subscription.integration.test.ts +1 -1
- package/src/tempo/client/Charge.test.ts +173 -0
- package/src/tempo/client/Charge.ts +65 -36
- package/src/tempo/client/Methods.ts +4 -2
- package/src/tempo/client/ResolveAccount.ts +46 -0
- package/src/tempo/internal/fee-payer.test.ts +65 -10
- package/src/tempo/internal/fee-payer.ts +42 -6
- package/src/tempo/internal/proof.test.ts +12 -4
- package/src/tempo/internal/proof.ts +55 -6
- package/src/tempo/legacy/client/SessionManager.ts +11 -3
- package/src/tempo/legacy/server/Session.test.ts +91 -26
- package/src/tempo/server/Charge.test.ts +388 -17
- package/src/tempo/server/Charge.ts +46 -24
- package/src/tempo/server/Methods.ts +4 -2
- package/src/tempo/server/Subscription.test.ts +465 -3
- package/src/tempo/server/Subscription.ts +174 -19
- package/src/tempo/server/internal/html/package.json +2 -2
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/client/ChannelOps.ts +5 -19
- package/src/tempo/session/client/ChannelStore.ts +111 -0
- package/src/tempo/session/client/CredentialState.test.ts +206 -62
- package/src/tempo/session/client/CredentialState.ts +58 -73
- package/src/tempo/session/client/Session.test.ts +41 -1
- package/src/tempo/session/client/Session.ts +36 -10
- package/src/tempo/session/client/SessionManager.test.ts +154 -65
- package/src/tempo/session/client/SessionManager.ts +141 -235
- package/src/tempo/session/client/index.ts +8 -5
- package/src/tempo/session/precompile/Voucher.test.ts +45 -7
- package/src/tempo/session/precompile/Voucher.ts +27 -25
- package/src/tempo/session/server/Session.test.ts +4 -4
- package/src/tempo/session/server/Settlement.test.ts +88 -1
- package/src/tempo/session/server/Settlement.ts +2 -1
- package/src/tempo/session/server/Sse.ts +0 -2
- package/src/tempo/session/server/Ws.ts +0 -4
- package/src/tempo/subscription/Store.ts +27 -9
- package/src/x402/Exact.e2e.test.ts +1 -1
- package/src/x402/PublicInterface.test-d.ts +1 -1
- package/src/x402/index.ts +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
- package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
- package/dist/mcp-sdk/client/McpClient.js +0 -118
- package/dist/mcp-sdk/client/McpClient.js.map +0 -1
- package/dist/mcp-sdk/client/index.d.ts.map +0 -1
- package/dist/mcp-sdk/client/index.js.map +0 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
- package/dist/mcp-sdk/server/Transport.js.map +0 -1
- package/dist/mcp-sdk/server/index.d.ts.map +0 -1
- package/dist/mcp-sdk/server/index.js.map +0 -1
- package/src/mcp-sdk/client/McpClient.ts +0 -228
- /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
- /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
|
@@ -9,14 +9,7 @@
|
|
|
9
9
|
* @see https://tips.sh/1034-1
|
|
10
10
|
*/
|
|
11
11
|
import { Hex } from 'ox'
|
|
12
|
-
import {
|
|
13
|
-
encodeFunctionData,
|
|
14
|
-
isAddress,
|
|
15
|
-
zeroAddress,
|
|
16
|
-
type Account,
|
|
17
|
-
type Address,
|
|
18
|
-
type Client,
|
|
19
|
-
} from 'viem'
|
|
12
|
+
import { encodeFunctionData, isAddress, type Account, type Address, type Client } from 'viem'
|
|
20
13
|
import { prepareTransactionRequest, signTransaction } from 'viem/actions'
|
|
21
14
|
import { Transaction } from 'viem/tempo'
|
|
22
15
|
|
|
@@ -62,10 +55,6 @@ export type ChannelEntry = {
|
|
|
62
55
|
opened: boolean
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
function voucherAuthorizedSigner(address: Address): Address | undefined {
|
|
66
|
-
return address.toLowerCase() === zeroAddress ? undefined : address
|
|
67
|
-
}
|
|
68
|
-
|
|
69
58
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
70
59
|
return typeof value === 'object' && value !== null
|
|
71
60
|
}
|
|
@@ -78,9 +67,9 @@ function readAccessKeyAddress(account: Account): Address | undefined {
|
|
|
78
67
|
return readOptionalAddress((account as AccountWithAccessKey).accessKeyAddress)
|
|
79
68
|
}
|
|
80
69
|
|
|
81
|
-
/** Resolves the voucher
|
|
82
|
-
export function resolveAuthorizedSigner(account: Account
|
|
83
|
-
return
|
|
70
|
+
/** Resolves the voucher authority address for a client account. */
|
|
71
|
+
export function resolveAuthorizedSigner(account: Account): Address {
|
|
72
|
+
return readAccessKeyAddress(account) ?? account.address
|
|
84
73
|
}
|
|
85
74
|
|
|
86
75
|
async function prepareTempoChannelTransaction(
|
|
@@ -169,7 +158,6 @@ export async function createVoucherPayload(
|
|
|
169
158
|
{ channelId, cumulativeAmount: amount },
|
|
170
159
|
escrow,
|
|
171
160
|
chainId,
|
|
172
|
-
voucherAuthorizedSigner(descriptor.authorizedSigner),
|
|
173
161
|
)
|
|
174
162
|
|
|
175
163
|
return {
|
|
@@ -223,7 +211,6 @@ export async function createOpenPayload(
|
|
|
223
211
|
client: Client,
|
|
224
212
|
account: Account,
|
|
225
213
|
parameters: {
|
|
226
|
-
authorizedSigner?: Address | undefined
|
|
227
214
|
chainId: number
|
|
228
215
|
deposit: bigint
|
|
229
216
|
escrow?: Address | undefined
|
|
@@ -234,7 +221,7 @@ export async function createOpenPayload(
|
|
|
234
221
|
token: Address
|
|
235
222
|
},
|
|
236
223
|
): Promise<OpenCredentialPayload> {
|
|
237
|
-
const authorizedSigner = resolveAuthorizedSigner(account
|
|
224
|
+
const authorizedSigner = resolveAuthorizedSigner(account)
|
|
238
225
|
const escrow = parameters.escrow ?? tip20ChannelEscrow
|
|
239
226
|
const operator = parameters.operator ?? '0x0000000000000000000000000000000000000000'
|
|
240
227
|
const salt = Hex.random(32)
|
|
@@ -282,7 +269,6 @@ export async function createOpenPayload(
|
|
|
282
269
|
{ channelId, cumulativeAmount: initialAmount },
|
|
283
270
|
escrow,
|
|
284
271
|
parameters.chainId,
|
|
285
|
-
voucherAuthorizedSigner(authorizedSigner),
|
|
286
272
|
)
|
|
287
273
|
return {
|
|
288
274
|
action: 'open',
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
|
|
3
|
+
import type { MaybePromise } from '../../../internal/types.js'
|
|
4
|
+
import type { ChannelEntry } from './ChannelOps.js'
|
|
5
|
+
|
|
6
|
+
/** Store of reusable payer session channels keyed by payment scope. */
|
|
7
|
+
export type ChannelStore = {
|
|
8
|
+
/** Returns the channel cached for `key`, when present. */
|
|
9
|
+
get(key: string): MaybePromise<ChannelEntry | undefined>
|
|
10
|
+
/** Inserts or replaces a channel entry. */
|
|
11
|
+
set(entry: ChannelEntry): MaybePromise<void>
|
|
12
|
+
/** Removes the channel cached for `key`. */
|
|
13
|
+
delete(key: string): MaybePromise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Channel persistence and update notification for credential results. */
|
|
17
|
+
export type ChannelSink = {
|
|
18
|
+
store: ChannelStore
|
|
19
|
+
notifyUpdate: (entry: ChannelEntry) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Returns the scope key for a reusable payer session channel. */
|
|
23
|
+
export function channelKey(scope: {
|
|
24
|
+
payee: Address
|
|
25
|
+
token: Address
|
|
26
|
+
escrow: Address
|
|
27
|
+
chainId: number
|
|
28
|
+
}): string {
|
|
29
|
+
const { payee, token, escrow, chainId } = scope
|
|
30
|
+
return `${payee.toLowerCase()}:${token.toLowerCase()}:${escrow.toLowerCase()}:${chainId}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Returns the scope key for a stored channel entry. */
|
|
34
|
+
export function entryKey(entry: ChannelEntry): string {
|
|
35
|
+
return channelKey({
|
|
36
|
+
payee: entry.descriptor.payee,
|
|
37
|
+
token: entry.descriptor.token,
|
|
38
|
+
escrow: entry.escrow,
|
|
39
|
+
chainId: entry.chainId,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Creates the default in-memory {@link ChannelStore}. */
|
|
44
|
+
export function createChannelStore(): ChannelStore {
|
|
45
|
+
const channels = new Map<string, ChannelEntry>()
|
|
46
|
+
return {
|
|
47
|
+
get: (key) => channels.get(key),
|
|
48
|
+
set(entry) {
|
|
49
|
+
channels.set(entryKey(entry), entry)
|
|
50
|
+
},
|
|
51
|
+
delete(key) {
|
|
52
|
+
channels.delete(key)
|
|
53
|
+
},
|
|
54
|
+
} satisfies ChannelStore
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** JSON-safe projection of a {@link ChannelEntry}, with bigint amounts as decimal strings. */
|
|
58
|
+
export type StoredChannel = Omit<ChannelEntry, 'cumulativeAmount' | 'deposit'> & {
|
|
59
|
+
/** Cumulative voucher authorization in raw token units, as a decimal string. */
|
|
60
|
+
cumulativeAmount: string
|
|
61
|
+
/** Channel deposit in raw token units, as a decimal string. */
|
|
62
|
+
deposit: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Converts a channel entry into its JSON-safe stored form. */
|
|
66
|
+
export function serializeEntry(entry: ChannelEntry): StoredChannel {
|
|
67
|
+
return {
|
|
68
|
+
...entry,
|
|
69
|
+
cumulativeAmount: entry.cumulativeAmount.toString(),
|
|
70
|
+
deposit: entry.deposit.toString(),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Restores a channel entry from its JSON-safe stored form. */
|
|
75
|
+
export function deserializeEntry(stored: StoredChannel): ChannelEntry {
|
|
76
|
+
return {
|
|
77
|
+
...stored,
|
|
78
|
+
cumulativeAmount: BigInt(stored.cumulativeAmount),
|
|
79
|
+
deposit: BigInt(stored.deposit),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Prefix for serialized channel entries persisted by {@link createJsonChannelStore}. */
|
|
84
|
+
const channelPrefix = 'chan:'
|
|
85
|
+
|
|
86
|
+
/** Plain string key-value backend a {@link createJsonChannelStore} persists into. */
|
|
87
|
+
export type JsonChannelKv = {
|
|
88
|
+
/** Returns the value stored at `key`, when present. */
|
|
89
|
+
get(key: string): MaybePromise<string | undefined>
|
|
90
|
+
/** Persists a `value` at `key`. */
|
|
91
|
+
set(key: string, value: string): MaybePromise<void>
|
|
92
|
+
/** Removes the value stored at `key`. */
|
|
93
|
+
delete(key: string): MaybePromise<void>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Wraps a string KV backend as a bigint-safe channel store. */
|
|
97
|
+
export function createJsonChannelStore(kv: JsonChannelKv): ChannelStore {
|
|
98
|
+
return {
|
|
99
|
+
async get(key) {
|
|
100
|
+
const value = await kv.get(channelPrefix + key)
|
|
101
|
+
if (value === undefined) return undefined
|
|
102
|
+
return deserializeEntry(JSON.parse(value) as StoredChannel)
|
|
103
|
+
},
|
|
104
|
+
async set(entry) {
|
|
105
|
+
await kv.set(channelPrefix + entryKey(entry), JSON.stringify(serializeEntry(entry)))
|
|
106
|
+
},
|
|
107
|
+
async delete(key) {
|
|
108
|
+
await kv.delete(channelPrefix + key)
|
|
109
|
+
},
|
|
110
|
+
} satisfies ChannelStore
|
|
111
|
+
}
|
|
@@ -11,7 +11,15 @@ import type { SessionSnapshot } from '../Snapshot.js'
|
|
|
11
11
|
import type { ChannelEntry } from './ChannelOps.js'
|
|
12
12
|
import {
|
|
13
13
|
channelKey,
|
|
14
|
-
|
|
14
|
+
createChannelStore,
|
|
15
|
+
createJsonChannelStore,
|
|
16
|
+
deserializeEntry,
|
|
17
|
+
entryKey,
|
|
18
|
+
serializeEntry,
|
|
19
|
+
type ChannelSink,
|
|
20
|
+
} from './ChannelStore.js'
|
|
21
|
+
import {
|
|
22
|
+
canSignDescriptor,
|
|
15
23
|
executeCredentialPlan,
|
|
16
24
|
hasCredentialCumulativeAmount,
|
|
17
25
|
hasManualSessionDescriptor,
|
|
@@ -26,12 +34,15 @@ import {
|
|
|
26
34
|
resolveRecoverContext,
|
|
27
35
|
resolveReusableChannel,
|
|
28
36
|
sessionContextSchema,
|
|
29
|
-
storeChannelEntry,
|
|
30
|
-
updateCachedCumulative,
|
|
31
37
|
type ChallengeContext,
|
|
32
38
|
type SessionContext,
|
|
33
39
|
} from './CredentialState.js'
|
|
34
40
|
|
|
41
|
+
/** Builds a credential sink backed by a fresh in-memory store. */
|
|
42
|
+
function sink(): ChannelSink {
|
|
43
|
+
return { store: createChannelStore(), notifyUpdate: () => {} }
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
describe('ChannelCache', () => {
|
|
36
47
|
const channelId = `0x${'11'.repeat(32)}` as Hex
|
|
37
48
|
|
|
@@ -66,16 +77,6 @@ describe('ChannelCache', () => {
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
function close(cumulativeAmount: string): SessionCredentialPayload {
|
|
70
|
-
return {
|
|
71
|
-
action: 'close',
|
|
72
|
-
channelId,
|
|
73
|
-
descriptor: channel().descriptor,
|
|
74
|
-
cumulativeAmount,
|
|
75
|
-
signature: '0x1234',
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
80
|
function topUp(additionalDeposit: string): SessionCredentialPayload {
|
|
80
81
|
return {
|
|
81
82
|
action: 'topUp',
|
|
@@ -87,41 +88,51 @@ describe('ChannelCache', () => {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
describe('precompile client
|
|
91
|
-
test('creates stable case-insensitive reusable channel keys', () => {
|
|
91
|
+
describe('precompile client ChannelStore', () => {
|
|
92
|
+
test('creates stable case-insensitive reusable channel keys scoped by chain', () => {
|
|
92
93
|
expect(
|
|
93
|
-
channelKey(
|
|
94
|
-
'0x00000000000000000000000000000000000000AA' as Address,
|
|
95
|
-
'0x20C0000000000000000000000000000000000001' as Address,
|
|
96
|
-
'0x4D50500000000000000000000000000000000000' as Address,
|
|
97
|
-
|
|
94
|
+
channelKey({
|
|
95
|
+
payee: '0x00000000000000000000000000000000000000AA' as Address,
|
|
96
|
+
token: '0x20C0000000000000000000000000000000000001' as Address,
|
|
97
|
+
escrow: '0x4D50500000000000000000000000000000000000' as Address,
|
|
98
|
+
chainId: 4217,
|
|
99
|
+
}),
|
|
98
100
|
).toBe(
|
|
99
|
-
'0x00000000000000000000000000000000000000aa:0x20c0000000000000000000000000000000000001:0x4d50500000000000000000000000000000000000',
|
|
101
|
+
'0x00000000000000000000000000000000000000aa:0x20c0000000000000000000000000000000000001:0x4d50500000000000000000000000000000000000:4217',
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('derives a stored entry key from its descriptor, escrow, and chain', () => {
|
|
106
|
+
const entry = channel()
|
|
107
|
+
expect(entryKey(entry)).toBe(
|
|
108
|
+
channelKey({
|
|
109
|
+
payee: entry.descriptor.payee,
|
|
110
|
+
token: entry.descriptor.token,
|
|
111
|
+
escrow: entry.escrow,
|
|
112
|
+
chainId: entry.chainId,
|
|
113
|
+
}),
|
|
100
114
|
)
|
|
101
115
|
})
|
|
102
116
|
|
|
103
|
-
test('stores
|
|
104
|
-
const
|
|
105
|
-
const cache = createChannelCache((entry) => updates.push(entry))
|
|
117
|
+
test('stores, gets, and deletes entries by derived key', () => {
|
|
118
|
+
const store = createChannelStore()
|
|
106
119
|
const entry = channel()
|
|
120
|
+
store.set(entry)
|
|
107
121
|
|
|
108
|
-
|
|
122
|
+
expect(store.get(entryKey(entry))).toBe(entry)
|
|
109
123
|
|
|
110
|
-
|
|
111
|
-
expect(
|
|
112
|
-
expect(updates).toEqual([entry])
|
|
124
|
+
store.delete(entryKey(entry))
|
|
125
|
+
expect(store.get(entryKey(entry))).toBeUndefined()
|
|
113
126
|
})
|
|
114
127
|
|
|
115
|
-
test('
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
expect(entry.cumulativeAmount).toBe(10n)
|
|
128
|
+
test('replaces entries that share a scope key', () => {
|
|
129
|
+
const store = createChannelStore()
|
|
130
|
+
const first = channel({ cumulativeAmount: 10n })
|
|
131
|
+
const second = channel({ cumulativeAmount: 12n })
|
|
132
|
+
store.set(first)
|
|
133
|
+
store.set(second)
|
|
122
134
|
|
|
123
|
-
|
|
124
|
-
expect(entry.cumulativeAmount).toBe(12n)
|
|
135
|
+
expect(store.get(entryKey(second))).toBe(second)
|
|
125
136
|
})
|
|
126
137
|
|
|
127
138
|
test('reads cumulative amounts only from cumulative credential payloads', () => {
|
|
@@ -130,26 +141,50 @@ describe('ChannelCache', () => {
|
|
|
130
141
|
expect(hasCredentialCumulativeAmount(topUp('12'))).toBe(false)
|
|
131
142
|
expect(readCredentialCumulativeAmount(topUp('12'))).toBeUndefined()
|
|
132
143
|
})
|
|
144
|
+
})
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const entry = channel({ cumulativeAmount:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
describe('serialization', () => {
|
|
147
|
+
test('serializes bigint amounts to decimal strings', () => {
|
|
148
|
+
const entry = channel({ cumulativeAmount: 2n ** 70n, deposit: 0n })
|
|
149
|
+
const stored = serializeEntry(entry)
|
|
150
|
+
expect(stored.cumulativeAmount).toBe((2n ** 70n).toString())
|
|
151
|
+
expect(stored.deposit).toBe('0')
|
|
152
|
+
})
|
|
140
153
|
|
|
141
|
-
|
|
154
|
+
test('round-trips a channel entry through JSON', () => {
|
|
155
|
+
const entry = channel({ cumulativeAmount: 2n ** 100n + 7n, deposit: 999n })
|
|
156
|
+
const roundtrip = deserializeEntry(
|
|
157
|
+
JSON.parse(JSON.stringify(serializeEntry(entry))) as ReturnType<typeof serializeEntry>,
|
|
158
|
+
)
|
|
159
|
+
expect(roundtrip).toEqual(entry)
|
|
142
160
|
})
|
|
161
|
+
})
|
|
143
162
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
163
|
+
describe('createJsonChannelStore', () => {
|
|
164
|
+
function jsonStore() {
|
|
165
|
+
const backend = new Map<string, string>()
|
|
166
|
+
const store = createJsonChannelStore({
|
|
167
|
+
get: (key) => backend.get(key),
|
|
168
|
+
set: (key, value) => {
|
|
169
|
+
backend.set(key, value)
|
|
170
|
+
},
|
|
171
|
+
delete: (key) => {
|
|
172
|
+
backend.delete(key)
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
return { backend, store }
|
|
176
|
+
}
|
|
148
177
|
|
|
149
|
-
|
|
178
|
+
test('persists, gets, and deletes via a string KV backend', async () => {
|
|
179
|
+
const { backend, store } = jsonStore()
|
|
180
|
+
const entry = channel({ cumulativeAmount: 2n ** 64n, deposit: 5n })
|
|
181
|
+
await store.set(entry)
|
|
150
182
|
|
|
151
|
-
expect(
|
|
152
|
-
expect(entry
|
|
183
|
+
expect(backend.size).toBe(1)
|
|
184
|
+
expect(await store.get(entryKey(entry))).toEqual(entry)
|
|
185
|
+
|
|
186
|
+
await store.delete(entryKey(entry))
|
|
187
|
+
expect(await store.get(entryKey(entry))).toBeUndefined()
|
|
153
188
|
})
|
|
154
189
|
})
|
|
155
190
|
})
|
|
@@ -346,7 +381,7 @@ describe('CredentialPlan', () => {
|
|
|
346
381
|
token,
|
|
347
382
|
})
|
|
348
383
|
expect(resolved.key).toBe(
|
|
349
|
-
`${payee.toLowerCase()}:${token.toLowerCase()}:${escrow.toLowerCase()}`,
|
|
384
|
+
`${payee.toLowerCase()}:${token.toLowerCase()}:${escrow.toLowerCase()}:42431`,
|
|
350
385
|
)
|
|
351
386
|
})
|
|
352
387
|
|
|
@@ -425,10 +460,9 @@ describe('CredentialPlan', () => {
|
|
|
425
460
|
})
|
|
426
461
|
|
|
427
462
|
test('plans manual credentials only when an explicit action includes descriptor', () => {
|
|
428
|
-
const cache = createChannelCache()
|
|
429
463
|
const plan = planCredential({
|
|
430
464
|
account,
|
|
431
|
-
|
|
465
|
+
entry: undefined,
|
|
432
466
|
context: { action: 'voucher', descriptor, cumulativeAmountRaw: '10' },
|
|
433
467
|
decimals: 6,
|
|
434
468
|
resolved: challengeContext(),
|
|
@@ -443,7 +477,7 @@ describe('CredentialPlan', () => {
|
|
|
443
477
|
expect(() =>
|
|
444
478
|
planCredential({
|
|
445
479
|
account,
|
|
446
|
-
|
|
480
|
+
entry: undefined,
|
|
447
481
|
context: { action: 'voucher', cumulativeAmountRaw: '10' },
|
|
448
482
|
decimals: 6,
|
|
449
483
|
resolved: challengeContext(),
|
|
@@ -454,7 +488,7 @@ describe('CredentialPlan', () => {
|
|
|
454
488
|
test('rejects manual descriptors that do not match the active challenge', async () => {
|
|
455
489
|
const plan = planCredential({
|
|
456
490
|
account,
|
|
457
|
-
|
|
491
|
+
entry: undefined,
|
|
458
492
|
context: {
|
|
459
493
|
action: 'voucher',
|
|
460
494
|
cumulativeAmountRaw: '10',
|
|
@@ -467,15 +501,44 @@ describe('CredentialPlan', () => {
|
|
|
467
501
|
resolved: challengeContext(),
|
|
468
502
|
})
|
|
469
503
|
|
|
470
|
-
await expect(executeCredentialPlan(plan,
|
|
504
|
+
await expect(executeCredentialPlan(plan, sink())).rejects.toThrow(
|
|
471
505
|
'context descriptor payee does not match challenge',
|
|
472
506
|
)
|
|
473
507
|
})
|
|
474
508
|
|
|
509
|
+
test('leaves stored scope entry unchanged when a manual credential targets another channel', async () => {
|
|
510
|
+
const entry = channel()
|
|
511
|
+
const originalCumulative = entry.cumulativeAmount
|
|
512
|
+
const notifications: ChannelEntry[] = []
|
|
513
|
+
const store = createChannelStore()
|
|
514
|
+
const manualDescriptor = { ...descriptor, salt: `0x${'55'.repeat(32)}` as Hex }
|
|
515
|
+
await store.set(entry)
|
|
516
|
+
|
|
517
|
+
await executeCredentialPlan(
|
|
518
|
+
planCredential({
|
|
519
|
+
account,
|
|
520
|
+
entry,
|
|
521
|
+
context: {
|
|
522
|
+
action: 'voucher',
|
|
523
|
+
descriptor: manualDescriptor,
|
|
524
|
+
cumulativeAmountRaw: '25',
|
|
525
|
+
},
|
|
526
|
+
decimals: 6,
|
|
527
|
+
resolved: challengeContext(),
|
|
528
|
+
}),
|
|
529
|
+
{ store, notifyUpdate: (updated) => notifications.push(updated) },
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
const stored = await store.get(entryKey(entry))
|
|
533
|
+
expect(stored?.channelId).toBe(entry.channelId)
|
|
534
|
+
expect(stored?.cumulativeAmount).toBe(originalCumulative)
|
|
535
|
+
expect(notifications).toEqual([])
|
|
536
|
+
})
|
|
537
|
+
|
|
475
538
|
test('plans recovery from server snapshot when no reusable cache entry exists', () => {
|
|
476
539
|
const plan = planCredential({
|
|
477
540
|
account,
|
|
478
|
-
|
|
541
|
+
entry: undefined,
|
|
479
542
|
decimals: 6,
|
|
480
543
|
resolved: challengeContext({ snapshot: snapshot() }),
|
|
481
544
|
})
|
|
@@ -487,13 +550,11 @@ describe('CredentialPlan', () => {
|
|
|
487
550
|
})
|
|
488
551
|
|
|
489
552
|
test('plans voucher reuse before snapshot recovery when cache entry is open', () => {
|
|
490
|
-
const cache = createChannelCache()
|
|
491
553
|
const entry = channel()
|
|
492
|
-
storeChannelEntry(cache, 'payee:token:escrow', entry)
|
|
493
554
|
|
|
494
555
|
const plan = planCredential({
|
|
495
556
|
account,
|
|
496
|
-
|
|
557
|
+
entry,
|
|
497
558
|
decimals: 6,
|
|
498
559
|
resolved: challengeContext({ snapshot: snapshot() }),
|
|
499
560
|
})
|
|
@@ -501,11 +562,94 @@ describe('CredentialPlan', () => {
|
|
|
501
562
|
expect(plan).toMatchObject({ type: 'voucher', entry })
|
|
502
563
|
})
|
|
503
564
|
|
|
565
|
+
test('opens fresh instead of vouchering when the account cannot sign the cached entry', () => {
|
|
566
|
+
const entry = channel({
|
|
567
|
+
descriptor: {
|
|
568
|
+
...descriptor,
|
|
569
|
+
authorizedSigner: '0x00000000000000000000000000000000000000aa' as Address,
|
|
570
|
+
},
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
const plan = planCredential({
|
|
574
|
+
account,
|
|
575
|
+
entry,
|
|
576
|
+
decimals: 6,
|
|
577
|
+
resolved: challengeContext(),
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
expect(plan.type).toBe('open')
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
test('opens fresh instead of recovering when the account cannot sign the snapshot', () => {
|
|
584
|
+
const plan = planCredential({
|
|
585
|
+
account,
|
|
586
|
+
entry: undefined,
|
|
587
|
+
decimals: 6,
|
|
588
|
+
resolved: challengeContext({
|
|
589
|
+
snapshot: snapshot({
|
|
590
|
+
descriptor: {
|
|
591
|
+
...snapshotDescriptor,
|
|
592
|
+
authorizedSigner: '0x00000000000000000000000000000000000000aa' as Address,
|
|
593
|
+
},
|
|
594
|
+
}),
|
|
595
|
+
}),
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
expect(plan.type).toBe('open')
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
test('vouchers when the account can satisfy the cached voucher authority', () => {
|
|
602
|
+
const delegatedAccount = privateKeyToAccount(
|
|
603
|
+
'0x2000000000000000000000000000000000000000000000000000000000000000',
|
|
604
|
+
)
|
|
605
|
+
const entry = channel({
|
|
606
|
+
descriptor: { ...descriptor, authorizedSigner: delegatedAccount.address },
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
const plan = planCredential({
|
|
610
|
+
account: Object.assign({}, account, { accessKeyAddress: delegatedAccount.address }),
|
|
611
|
+
entry,
|
|
612
|
+
decimals: 6,
|
|
613
|
+
resolved: challengeContext(),
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
expect(plan).toMatchObject({ type: 'voucher', entry })
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
test('canSignDescriptor matches root, zero, and delegated authorities', () => {
|
|
620
|
+
const delegatedAuthority = '0x00000000000000000000000000000000000000aa' as Address
|
|
621
|
+
expect(canSignDescriptor(account, descriptor)).toBe(true)
|
|
622
|
+
expect(
|
|
623
|
+
canSignDescriptor(account, {
|
|
624
|
+
...descriptor,
|
|
625
|
+
authorizedSigner: '0x0000000000000000000000000000000000000000' as Address,
|
|
626
|
+
}),
|
|
627
|
+
).toBe(true)
|
|
628
|
+
expect(
|
|
629
|
+
canSignDescriptor(account, { ...descriptor, authorizedSigner: delegatedAuthority }),
|
|
630
|
+
).toBe(false)
|
|
631
|
+
expect(
|
|
632
|
+
canSignDescriptor(Object.assign({}, account, { accessKeyAddress: delegatedAuthority }), {
|
|
633
|
+
...descriptor,
|
|
634
|
+
authorizedSigner: delegatedAuthority,
|
|
635
|
+
}),
|
|
636
|
+
).toBe(true)
|
|
637
|
+
const otherPayer = '0x00000000000000000000000000000000000000bb' as Address
|
|
638
|
+
expect(canSignDescriptor(account, { ...descriptor, payer: otherPayer })).toBe(false)
|
|
639
|
+
expect(
|
|
640
|
+
canSignDescriptor(Object.assign({}, account, { accessKeyAddress: delegatedAuthority }), {
|
|
641
|
+
...descriptor,
|
|
642
|
+
payer: otherPayer,
|
|
643
|
+
authorizedSigner: delegatedAuthority,
|
|
644
|
+
}),
|
|
645
|
+
).toBe(false)
|
|
646
|
+
})
|
|
647
|
+
|
|
504
648
|
test('rejects channel ID reuse without descriptor or cache entry', () => {
|
|
505
649
|
expect(() =>
|
|
506
650
|
planCredential({
|
|
507
651
|
account,
|
|
508
|
-
|
|
652
|
+
entry: undefined,
|
|
509
653
|
context: { channelId },
|
|
510
654
|
decimals: 6,
|
|
511
655
|
resolved: challengeContext(),
|