mppx 0.3.2 → 0.3.4
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/dist/Errors.d.ts +7 -7
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +7 -7
- package/dist/Errors.js.map +1 -1
- package/dist/cli.js +98 -64
- package/dist/cli.js.map +1 -1
- package/dist/internal/env.d.ts +19 -0
- package/dist/internal/env.d.ts.map +1 -0
- package/dist/internal/env.js +55 -0
- package/dist/internal/env.js.map +1 -0
- package/dist/server/Mppx.d.ts +2 -2
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +2 -1
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts +5 -5
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +3 -3
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Session.d.ts +2 -2
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +3 -3
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +4 -4
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +4 -4
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +1 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Session.d.ts +8 -8
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +24 -24
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +2 -2
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +2 -2
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +4 -4
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +3 -3
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -0
- package/dist/tempo/session/Chain.js.map +1 -0
- package/dist/tempo/session/Channel.d.ts.map +1 -0
- package/dist/tempo/session/Channel.js.map +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/ChannelStore.js.map +1 -0
- package/dist/tempo/session/Receipt.d.ts +22 -0
- package/dist/tempo/session/Receipt.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Receipt.js +6 -6
- package/dist/tempo/session/Receipt.js.map +1 -0
- package/dist/tempo/{stream → session}/Sse.d.ts +7 -7
- package/dist/tempo/session/Sse.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Sse.js +4 -4
- package/dist/tempo/session/Sse.js.map +1 -0
- package/dist/tempo/{stream → session}/Types.d.ts +4 -4
- package/dist/tempo/session/Types.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Types.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -0
- package/dist/tempo/session/Voucher.js.map +1 -0
- package/dist/tempo/{stream → session}/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/session/escrow.abi.js.map +1 -0
- package/dist/tempo/session/index.d.ts.map +1 -0
- package/dist/tempo/session/index.js.map +1 -0
- package/package.json +1 -1
- package/src/Errors.test.ts +10 -10
- package/src/Errors.ts +7 -7
- package/src/Expires.test.ts +111 -0
- package/src/cli.test.ts +3 -3
- package/src/cli.ts +125 -70
- package/src/internal/env.ts +54 -0
- package/src/mcp-sdk/server/Transport.test.ts +171 -0
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/server/Mppx.ts +5 -4
- package/src/server/Transport.test.ts +1 -1
- package/src/tempo/client/ChannelOps.test.ts +290 -0
- package/src/tempo/client/ChannelOps.ts +8 -8
- package/src/tempo/client/Session.test.ts +467 -0
- package/src/tempo/client/Session.ts +9 -9
- package/src/tempo/client/SessionManager.test.ts +3 -3
- package/src/tempo/client/SessionManager.ts +9 -9
- package/src/tempo/index.ts +1 -1
- package/src/tempo/server/Charge.ts +1 -1
- package/src/tempo/server/Session.test.ts +9 -9
- package/src/tempo/server/Session.ts +47 -47
- package/src/tempo/server/Sse.test.ts +3 -3
- package/src/tempo/server/index.ts +2 -2
- package/src/tempo/server/internal/transport.ts +6 -6
- package/src/tempo/session/Chain.test.ts +511 -0
- package/src/tempo/session/Channel.test.ts +108 -0
- package/src/tempo/{stream → session}/Receipt.test.ts +16 -12
- package/src/tempo/{stream → session}/Receipt.ts +9 -9
- package/src/tempo/{stream → session}/Sse.test.ts +5 -5
- package/src/tempo/{stream → session}/Sse.ts +11 -11
- package/src/tempo/{stream → session}/Types.ts +4 -4
- package/dist/tempo/stream/Chain.d.ts.map +0 -1
- package/dist/tempo/stream/Chain.js.map +0 -1
- package/dist/tempo/stream/Channel.d.ts.map +0 -1
- package/dist/tempo/stream/Channel.js.map +0 -1
- package/dist/tempo/stream/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/stream/ChannelStore.js.map +0 -1
- package/dist/tempo/stream/Receipt.d.ts +0 -22
- package/dist/tempo/stream/Receipt.d.ts.map +0 -1
- package/dist/tempo/stream/Receipt.js.map +0 -1
- package/dist/tempo/stream/Sse.d.ts.map +0 -1
- package/dist/tempo/stream/Sse.js.map +0 -1
- package/dist/tempo/stream/Types.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.js.map +0 -1
- package/dist/tempo/stream/escrow.abi.js.map +0 -1
- package/dist/tempo/stream/index.d.ts.map +0 -1
- package/dist/tempo/stream/index.js.map +0 -1
- /package/dist/tempo/{stream → session}/Chain.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Chain.js +0 -0
- /package/dist/tempo/{stream → session}/Channel.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Channel.js +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.d.ts +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.js +0 -0
- /package/dist/tempo/{stream → session}/Types.js +0 -0
- /package/dist/tempo/{stream → session}/Voucher.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Voucher.js +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.d.ts +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.js +0 -0
- /package/dist/tempo/{stream → session}/index.d.ts +0 -0
- /package/dist/tempo/{stream → session}/index.js +0 -0
- /package/src/tempo/{stream → session}/Chain.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.test.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.test.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.ts +0 -0
- /package/src/tempo/{stream → session}/escrow.abi.ts +0 -0
- /package/src/tempo/{stream → session}/index.ts +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import type { Challenge } from '../../Challenge.js'
|
|
3
|
+
import type { Credential } from '../../Credential.js'
|
|
4
|
+
import { VerificationFailedError } from '../../Errors.js'
|
|
5
|
+
import * as Mcp from '../../Mcp.js'
|
|
6
|
+
import { type Extra, mcpSdk } from './Transport.js'
|
|
7
|
+
|
|
8
|
+
const challenge: Challenge = {
|
|
9
|
+
id: 'test-challenge-id',
|
|
10
|
+
realm: 'api.example.com',
|
|
11
|
+
method: 'tempo',
|
|
12
|
+
intent: 'session',
|
|
13
|
+
request: { amount: '1000000' },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const credential: Credential = {
|
|
17
|
+
challenge,
|
|
18
|
+
payload: {
|
|
19
|
+
action: 'voucher',
|
|
20
|
+
channelId: '0xabc',
|
|
21
|
+
cumulativeAmount: '1000000',
|
|
22
|
+
signature: '0x1234',
|
|
23
|
+
},
|
|
24
|
+
source: 'did:pkh:eip155:42431:0x1111111111111111111111111111111111111111',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('mcpSdk', () => {
|
|
28
|
+
describe('getCredential', () => {
|
|
29
|
+
test('returns credential from _meta', () => {
|
|
30
|
+
const transport = mcpSdk()
|
|
31
|
+
const extra: Extra = {
|
|
32
|
+
_meta: {
|
|
33
|
+
[Mcp.credentialMetaKey]: credential,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
const result = transport.getCredential(extra)
|
|
37
|
+
expect(result).toEqual(credential)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('returns null when _meta is undefined', () => {
|
|
41
|
+
const transport = mcpSdk()
|
|
42
|
+
const extra: Extra = {}
|
|
43
|
+
const result = transport.getCredential(extra)
|
|
44
|
+
expect(result).toBeNull()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('returns null when credential key is missing', () => {
|
|
48
|
+
const transport = mcpSdk()
|
|
49
|
+
const extra: Extra = { _meta: { otherKey: 'value' } }
|
|
50
|
+
const result = transport.getCredential(extra)
|
|
51
|
+
expect(result).toBeNull()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('respondChallenge', () => {
|
|
56
|
+
test('creates McpError with correct code and challenge data', async () => {
|
|
57
|
+
const transport = mcpSdk()
|
|
58
|
+
const result = await transport.respondChallenge({
|
|
59
|
+
challenge,
|
|
60
|
+
input: {} as Extra,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(result).toBeInstanceOf(Error)
|
|
64
|
+
const err = result as any
|
|
65
|
+
expect(err.code).toBe(Mcp.paymentRequiredCode)
|
|
66
|
+
expect(err.message).toContain('Payment Required')
|
|
67
|
+
expect(err.data?.httpStatus).toBe(402)
|
|
68
|
+
expect(err.data?.challenges).toEqual([challenge])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('includes problem details when error is provided', async () => {
|
|
72
|
+
const transport = mcpSdk()
|
|
73
|
+
const error = new VerificationFailedError({ reason: 'bad signature' })
|
|
74
|
+
const result = await transport.respondChallenge({
|
|
75
|
+
challenge,
|
|
76
|
+
error,
|
|
77
|
+
input: {} as Extra,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const err = result as any
|
|
81
|
+
expect(err.code).toBe(Mcp.paymentRequiredCode)
|
|
82
|
+
expect(err.message).toContain('verification failed')
|
|
83
|
+
expect(err.data?.problem).toBeDefined()
|
|
84
|
+
expect(err.data?.problem?.type).toBe(error.type)
|
|
85
|
+
expect(err.data?.problem?.challengeId).toBe(challenge.id)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('uses default message when no error provided', async () => {
|
|
89
|
+
const transport = mcpSdk()
|
|
90
|
+
const result = await transport.respondChallenge({
|
|
91
|
+
challenge,
|
|
92
|
+
input: {} as Extra,
|
|
93
|
+
})
|
|
94
|
+
const err = result as any
|
|
95
|
+
expect(err.message).toContain('Payment Required')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('respondReceipt', () => {
|
|
100
|
+
test('attaches receipt to response _meta', () => {
|
|
101
|
+
const transport = mcpSdk()
|
|
102
|
+
const receipt = {
|
|
103
|
+
method: 'tempo',
|
|
104
|
+
status: 'success' as const,
|
|
105
|
+
timestamp: '2025-06-15T12:00:00.000Z',
|
|
106
|
+
reference: '0xabc',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const response = {
|
|
110
|
+
content: [{ type: 'text' as const, text: 'hello' }],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = transport.respondReceipt({
|
|
114
|
+
challengeId: 'test-challenge-id',
|
|
115
|
+
receipt,
|
|
116
|
+
response,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
expect(result._meta?.[Mcp.receiptMetaKey]).toEqual({
|
|
120
|
+
...receipt,
|
|
121
|
+
challengeId: 'test-challenge-id',
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('preserves existing _meta fields', () => {
|
|
126
|
+
const transport = mcpSdk()
|
|
127
|
+
const receipt = {
|
|
128
|
+
method: 'tempo',
|
|
129
|
+
status: 'success' as const,
|
|
130
|
+
timestamp: '2025-06-15T12:00:00.000Z',
|
|
131
|
+
reference: '0xabc',
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const response = {
|
|
135
|
+
_meta: { existingKey: 'value' },
|
|
136
|
+
content: [{ type: 'text' as const, text: 'hello' }],
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = transport.respondReceipt({
|
|
140
|
+
challengeId: 'cid',
|
|
141
|
+
receipt,
|
|
142
|
+
response,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
expect(result._meta?.existingKey).toBe('value')
|
|
146
|
+
expect(result._meta?.[Mcp.receiptMetaKey]).toBeDefined()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('preserves response content', () => {
|
|
150
|
+
const transport = mcpSdk()
|
|
151
|
+
const receipt = {
|
|
152
|
+
method: 'tempo',
|
|
153
|
+
status: 'success' as const,
|
|
154
|
+
timestamp: '2025-06-15T12:00:00.000Z',
|
|
155
|
+
reference: '0xabc',
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const response = {
|
|
159
|
+
content: [{ type: 'text' as const, text: 'result data' }],
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result = transport.respondReceipt({
|
|
163
|
+
challengeId: 'cid',
|
|
164
|
+
receipt,
|
|
165
|
+
response,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
expect(result.content).toEqual(response.content)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
})
|
|
@@ -6,7 +6,7 @@ import { tempo as tempo_server } from 'mppx/server'
|
|
|
6
6
|
import type { Address } from 'viem'
|
|
7
7
|
import { Addresses } from 'viem/tempo'
|
|
8
8
|
import { beforeAll, describe, expect, test } from 'vitest'
|
|
9
|
-
import { deployEscrow } from '~test/tempo/
|
|
9
|
+
import { deployEscrow } from '~test/tempo/session.js'
|
|
10
10
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
11
11
|
|
|
12
12
|
function createServer(app: express.Express) {
|
|
@@ -7,7 +7,7 @@ import { tempo as tempo_server } from 'mppx/server'
|
|
|
7
7
|
import type { Address } from 'viem'
|
|
8
8
|
import { Addresses } from 'viem/tempo'
|
|
9
9
|
import { beforeAll, describe, expect, test } from 'vitest'
|
|
10
|
-
import { deployEscrow } from '~test/tempo/
|
|
10
|
+
import { deployEscrow } from '~test/tempo/session.js'
|
|
11
11
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
12
12
|
|
|
13
13
|
function createServer(app: Hono) {
|
|
@@ -6,7 +6,7 @@ import { tempo as tempo_server } from 'mppx/server'
|
|
|
6
6
|
import type { Address } from 'viem'
|
|
7
7
|
import { Addresses } from 'viem/tempo'
|
|
8
8
|
import { beforeAll, describe, expect, test } from 'vitest'
|
|
9
|
-
import { deployEscrow } from '~test/tempo/
|
|
9
|
+
import { deployEscrow } from '~test/tempo/session.js'
|
|
10
10
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
11
11
|
|
|
12
12
|
function createServer(handler: (request: Request) => Promise<Response> | Response) {
|
package/src/server/Mppx.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
|
2
2
|
import * as Challenge from '../Challenge.js'
|
|
3
3
|
import type * as Credential from '../Credential.js'
|
|
4
4
|
import * as Errors from '../Errors.js'
|
|
5
|
+
import * as Env from '../internal/env.js'
|
|
5
6
|
import type * as Method from '../Method.js'
|
|
6
7
|
import type * as Receipt from '../Receipt.js'
|
|
7
8
|
import type * as z from '../zod.js'
|
|
@@ -72,8 +73,8 @@ export function create<
|
|
|
72
73
|
const transport extends Transport.AnyTransport = Transport.Http,
|
|
73
74
|
>(config: create.Config<methods, transport>): Mppx<methods, transport> {
|
|
74
75
|
const {
|
|
75
|
-
realm = '
|
|
76
|
-
secretKey = '
|
|
76
|
+
realm = Env.get('realm'),
|
|
77
|
+
secretKey = Env.get('secretKey'),
|
|
77
78
|
transport = Transport.http() as transport,
|
|
78
79
|
} = config
|
|
79
80
|
|
|
@@ -104,9 +105,9 @@ export declare namespace create {
|
|
|
104
105
|
> = {
|
|
105
106
|
/** Array of configured methods. @example [tempo()] */
|
|
106
107
|
methods: methods
|
|
107
|
-
/** Server realm (e.g., hostname).
|
|
108
|
+
/** Server realm (e.g., hostname). Auto-detected from environment variables (`MPP_REALM`, `VERCEL_URL`, `RAILWAY_PUBLIC_DOMAIN`, `RENDER_EXTERNAL_HOSTNAME`, `HOST`, `HOSTNAME`), falling back to `"localhost"`. */
|
|
108
109
|
realm?: string | undefined
|
|
109
|
-
/** Secret key for HMAC-bound challenge IDs for stateless verification. */
|
|
110
|
+
/** Secret key for HMAC-bound challenge IDs for stateless verification. Auto-detected from `MPP_SECRET_KEY` environment variable, falling back to a random key. */
|
|
110
111
|
secretKey?: string | undefined
|
|
111
112
|
/** Transport to use. @default Transport.http() */
|
|
112
113
|
transport?: transport | undefined
|
|
@@ -124,7 +124,7 @@ describe('http', () => {
|
|
|
124
124
|
|
|
125
125
|
expect(response.status).toBe(410)
|
|
126
126
|
const body = await response.json()
|
|
127
|
-
expect(body.type).toBe('https://paymentauth.org/problems/
|
|
127
|
+
expect(body.type).toBe('https://paymentauth.org/problems/session/channel-finalized')
|
|
128
128
|
expect(body.status).toBe(410)
|
|
129
129
|
})
|
|
130
130
|
})
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Hex } from 'ox'
|
|
2
|
+
import { type Address, createClient } from 'viem'
|
|
3
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
4
|
+
import { Addresses } from 'viem/tempo'
|
|
5
|
+
import { beforeAll, describe, expect, test } from 'vitest'
|
|
6
|
+
import { deployEscrow, openChannel } from '~test/tempo/session.js'
|
|
7
|
+
import { accounts, asset, chain, client, fundAccount, http } from '~test/tempo/viem.js'
|
|
8
|
+
import type { Challenge } from '../../Challenge.js'
|
|
9
|
+
import * as Credential from '../../Credential.js'
|
|
10
|
+
import { verifyVoucher } from '../session/Voucher.js'
|
|
11
|
+
import {
|
|
12
|
+
createClosePayload,
|
|
13
|
+
createOpenPayload,
|
|
14
|
+
createVoucherPayload,
|
|
15
|
+
resolveEscrow,
|
|
16
|
+
serializeCredential,
|
|
17
|
+
tryRecoverChannel,
|
|
18
|
+
} from './ChannelOps.js'
|
|
19
|
+
|
|
20
|
+
const escrow42431 = '0x542831e3E4Ace07559b7C8787395f4Fb99F70787' as Address
|
|
21
|
+
|
|
22
|
+
const localAccount = privateKeyToAccount(
|
|
23
|
+
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
|
|
24
|
+
)
|
|
25
|
+
const localClient = createClient({
|
|
26
|
+
account: localAccount,
|
|
27
|
+
transport: http('http://127.0.0.1'),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
|
|
31
|
+
const escrowContract = '0x1234567890abcdef1234567890abcdef12345678' as Address
|
|
32
|
+
const chainId = 42431
|
|
33
|
+
|
|
34
|
+
function makeChallenge(overrides?: Partial<Challenge>): Challenge {
|
|
35
|
+
return {
|
|
36
|
+
id: 'test-id',
|
|
37
|
+
realm: 'test.com',
|
|
38
|
+
method: 'tempo',
|
|
39
|
+
intent: 'session',
|
|
40
|
+
request: { amount: '1000000' },
|
|
41
|
+
...overrides,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('resolveEscrow', () => {
|
|
46
|
+
test('prefers challenge.request.methodDetails.escrowContract', () => {
|
|
47
|
+
const challenge = {
|
|
48
|
+
request: { methodDetails: { escrowContract: '0xChallengeEscrow' } },
|
|
49
|
+
}
|
|
50
|
+
const result = resolveEscrow(challenge, 42431, '0xOverride' as Address)
|
|
51
|
+
expect(result).toBe('0xChallengeEscrow')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('falls back to escrowContractOverride', () => {
|
|
55
|
+
const challenge = { request: { methodDetails: {} } }
|
|
56
|
+
const result = resolveEscrow(challenge, 42431, '0xOverride' as Address)
|
|
57
|
+
expect(result).toBe('0xOverride')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('falls back to defaults when no override', () => {
|
|
61
|
+
const challenge = { request: {} }
|
|
62
|
+
const result = resolveEscrow(challenge, 42431)
|
|
63
|
+
expect(result).toBe(escrow42431)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('throws when no escrow available', () => {
|
|
67
|
+
const challenge = { request: {} }
|
|
68
|
+
expect(() => resolveEscrow(challenge, 99999)).toThrow('No `escrowContract` available')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('falls back to defaults when methodDetails is undefined', () => {
|
|
72
|
+
const challenge = { request: { methodDetails: undefined } }
|
|
73
|
+
const result = resolveEscrow(challenge, 42431)
|
|
74
|
+
expect(result).toBe(escrow42431)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('serializeCredential', () => {
|
|
79
|
+
test('produces correct DID source string', () => {
|
|
80
|
+
const challenge = makeChallenge()
|
|
81
|
+
const payload = {
|
|
82
|
+
action: 'voucher' as const,
|
|
83
|
+
channelId,
|
|
84
|
+
cumulativeAmount: '1000000',
|
|
85
|
+
signature: '0xsig' as `0x${string}`,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = serializeCredential(challenge, payload, 42431, localAccount)
|
|
89
|
+
|
|
90
|
+
expect(result).toMatch(/^Payment /)
|
|
91
|
+
const deserialized = Credential.deserialize(result)
|
|
92
|
+
expect(deserialized.source).toBe(`did:pkh:eip155:42431:${localAccount.address}`)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('encodes chainId in DID source', () => {
|
|
96
|
+
const challenge = makeChallenge()
|
|
97
|
+
const payload = {
|
|
98
|
+
action: 'voucher' as const,
|
|
99
|
+
channelId,
|
|
100
|
+
cumulativeAmount: '1000000',
|
|
101
|
+
signature: '0xsig' as `0x${string}`,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = serializeCredential(challenge, payload, 4217, localAccount)
|
|
105
|
+
const deserialized = Credential.deserialize(result)
|
|
106
|
+
expect(deserialized.source).toContain('did:pkh:eip155:4217:')
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('createVoucherPayload', () => {
|
|
111
|
+
test('returns voucher payload with valid signature', async () => {
|
|
112
|
+
const result = await createVoucherPayload(
|
|
113
|
+
localClient,
|
|
114
|
+
localAccount,
|
|
115
|
+
channelId,
|
|
116
|
+
5_000_000n,
|
|
117
|
+
escrowContract,
|
|
118
|
+
chainId,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
expect(result.action).toBe('voucher')
|
|
122
|
+
expect(result.channelId).toBe(channelId)
|
|
123
|
+
if (result.action !== 'voucher') throw new Error('unexpected action')
|
|
124
|
+
expect(result.cumulativeAmount).toBe('5000000')
|
|
125
|
+
expect(result.signature).toMatch(/^0x[0-9a-f]{130}$/)
|
|
126
|
+
|
|
127
|
+
const valid = await verifyVoucher(
|
|
128
|
+
escrowContract,
|
|
129
|
+
chainId,
|
|
130
|
+
{ channelId, cumulativeAmount: 5_000_000n, signature: result.signature },
|
|
131
|
+
localAccount.address,
|
|
132
|
+
)
|
|
133
|
+
expect(valid).toBe(true)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('createClosePayload', () => {
|
|
138
|
+
test('returns close payload with valid signature', async () => {
|
|
139
|
+
const result = await createClosePayload(
|
|
140
|
+
localClient,
|
|
141
|
+
localAccount,
|
|
142
|
+
channelId,
|
|
143
|
+
5_000_000n,
|
|
144
|
+
escrowContract,
|
|
145
|
+
chainId,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
expect(result.action).toBe('close')
|
|
149
|
+
expect(result.channelId).toBe(channelId)
|
|
150
|
+
if (result.action !== 'close') throw new Error('unexpected action')
|
|
151
|
+
expect(result.cumulativeAmount).toBe('5000000')
|
|
152
|
+
expect(result.signature).toMatch(/^0x[0-9a-f]{130}$/)
|
|
153
|
+
|
|
154
|
+
const valid = await verifyVoucher(
|
|
155
|
+
escrowContract,
|
|
156
|
+
chainId,
|
|
157
|
+
{ channelId, cumulativeAmount: 5_000_000n, signature: result.signature },
|
|
158
|
+
localAccount.address,
|
|
159
|
+
)
|
|
160
|
+
expect(valid).toBe(true)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('createOpenPayload', () => {
|
|
165
|
+
const payer = accounts[2]
|
|
166
|
+
const payee = accounts[1].address
|
|
167
|
+
const currency = asset
|
|
168
|
+
|
|
169
|
+
let escrow: Address
|
|
170
|
+
|
|
171
|
+
beforeAll(async () => {
|
|
172
|
+
escrow = await deployEscrow()
|
|
173
|
+
await fundAccount({ address: payer.address, token: Addresses.pathUsd })
|
|
174
|
+
await fundAccount({ address: payer.address, token: currency })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('returns entry with correct fields and valid payload', async () => {
|
|
178
|
+
const payerClient = createClient({
|
|
179
|
+
account: payer,
|
|
180
|
+
chain,
|
|
181
|
+
transport: http(),
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const result = await createOpenPayload(payerClient, payer, {
|
|
185
|
+
escrowContract: escrow,
|
|
186
|
+
payee,
|
|
187
|
+
currency,
|
|
188
|
+
deposit: 10_000_000n,
|
|
189
|
+
initialAmount: 1_000_000n,
|
|
190
|
+
chainId: chain.id,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
expect(result.entry.opened).toBe(true)
|
|
194
|
+
expect(result.entry.cumulativeAmount).toBe(1_000_000n)
|
|
195
|
+
expect(result.entry.escrowContract).toBe(escrow)
|
|
196
|
+
expect(result.entry.chainId).toBe(chain.id)
|
|
197
|
+
expect(result.entry.channelId).toMatch(/^0x[0-9a-f]{64}$/)
|
|
198
|
+
expect(result.entry.salt).toMatch(/^0x/)
|
|
199
|
+
|
|
200
|
+
expect(result.payload.action).toBe('open')
|
|
201
|
+
expect(result.payload).toHaveProperty('type', 'transaction')
|
|
202
|
+
expect(result.payload).toHaveProperty('transaction')
|
|
203
|
+
expect(result.payload).toHaveProperty('signature')
|
|
204
|
+
expect(result.payload.channelId).toBe(result.entry.channelId)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('defaults authorizedSigner to account.address', async () => {
|
|
208
|
+
const payerClient = createClient({
|
|
209
|
+
account: payer,
|
|
210
|
+
chain,
|
|
211
|
+
transport: http(),
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const result = await createOpenPayload(payerClient, payer, {
|
|
215
|
+
escrowContract: escrow,
|
|
216
|
+
payee,
|
|
217
|
+
currency,
|
|
218
|
+
deposit: 10_000_000n,
|
|
219
|
+
initialAmount: 1_000_000n,
|
|
220
|
+
chainId: chain.id,
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
expect((result.payload as any).authorizedSigner).toBe(payer.address)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('uses custom authorizedSigner when provided', async () => {
|
|
227
|
+
const customSigner = accounts[5].address
|
|
228
|
+
const payerClient = createClient({
|
|
229
|
+
account: payer,
|
|
230
|
+
chain,
|
|
231
|
+
transport: http(),
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const result = await createOpenPayload(payerClient, payer, {
|
|
235
|
+
authorizedSigner: customSigner,
|
|
236
|
+
escrowContract: escrow,
|
|
237
|
+
payee,
|
|
238
|
+
currency,
|
|
239
|
+
deposit: 10_000_000n,
|
|
240
|
+
initialAmount: 1_000_000n,
|
|
241
|
+
chainId: chain.id,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
expect((result.payload as any).authorizedSigner).toBe(customSigner)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe('tryRecoverChannel', () => {
|
|
249
|
+
const payer = accounts[3]
|
|
250
|
+
const payee = accounts[1].address
|
|
251
|
+
const currency = asset
|
|
252
|
+
|
|
253
|
+
let escrow: Address
|
|
254
|
+
let existingChannelId: `0x${string}`
|
|
255
|
+
|
|
256
|
+
beforeAll(async () => {
|
|
257
|
+
escrow = await deployEscrow()
|
|
258
|
+
await fundAccount({ address: payer.address, token: Addresses.pathUsd })
|
|
259
|
+
await fundAccount({ address: payer.address, token: currency })
|
|
260
|
+
|
|
261
|
+
const salt = Hex.random(32) as `0x${string}`
|
|
262
|
+
const result = await openChannel({
|
|
263
|
+
escrow,
|
|
264
|
+
payer,
|
|
265
|
+
payee,
|
|
266
|
+
token: currency,
|
|
267
|
+
deposit: 10_000_000n,
|
|
268
|
+
salt,
|
|
269
|
+
})
|
|
270
|
+
existingChannelId = result.channelId
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('returns entry when channel has positive deposit and not finalized', async () => {
|
|
274
|
+
const result = await tryRecoverChannel(client, escrow, existingChannelId, chain.id)
|
|
275
|
+
|
|
276
|
+
expect(result).not.toBeUndefined()
|
|
277
|
+
expect(result!.channelId).toBe(existingChannelId)
|
|
278
|
+
expect(result!.cumulativeAmount).toBe(0n)
|
|
279
|
+
expect(result!.opened).toBe(true)
|
|
280
|
+
expect(result!.escrowContract).toBe(escrow)
|
|
281
|
+
expect(result!.chainId).toBe(chain.id)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('returns undefined for non-existent channel', async () => {
|
|
285
|
+
const fakeChannelId =
|
|
286
|
+
'0x0000000000000000000000000000000000000000000000000000000000000099' as `0x${string}`
|
|
287
|
+
const result = await tryRecoverChannel(client, escrow, fakeChannelId, chain.id)
|
|
288
|
+
expect(result).toBeUndefined()
|
|
289
|
+
})
|
|
290
|
+
})
|
|
@@ -18,10 +18,10 @@ import { Abis } from 'viem/tempo'
|
|
|
18
18
|
import type { Challenge } from '../../Challenge.js'
|
|
19
19
|
import * as Credential from '../../Credential.js'
|
|
20
20
|
import * as defaults from '../internal/defaults.js'
|
|
21
|
-
import { escrowAbi, getOnChainChannel } from '../
|
|
22
|
-
import * as Channel from '../
|
|
23
|
-
import type {
|
|
24
|
-
import { signVoucher } from '../
|
|
21
|
+
import { escrowAbi, getOnChainChannel } from '../session/Chain.js'
|
|
22
|
+
import * as Channel from '../session/Channel.js'
|
|
23
|
+
import type { SessionCredentialPayload } from '../session/Types.js'
|
|
24
|
+
import { signVoucher } from '../session/Voucher.js'
|
|
25
25
|
|
|
26
26
|
export type ChannelEntry = {
|
|
27
27
|
channelId: Hex.Hex
|
|
@@ -57,7 +57,7 @@ export function resolveEscrow(
|
|
|
57
57
|
|
|
58
58
|
export function serializeCredential(
|
|
59
59
|
challenge: Challenge,
|
|
60
|
-
payload:
|
|
60
|
+
payload: SessionCredentialPayload,
|
|
61
61
|
chainId: number,
|
|
62
62
|
account: viem_Account,
|
|
63
63
|
): string {
|
|
@@ -76,7 +76,7 @@ export async function createVoucherPayload(
|
|
|
76
76
|
escrowContract: Address,
|
|
77
77
|
chainId: number,
|
|
78
78
|
authorizedSigner?: Address | undefined,
|
|
79
|
-
): Promise<
|
|
79
|
+
): Promise<SessionCredentialPayload> {
|
|
80
80
|
const signature = await signVoucher(
|
|
81
81
|
client,
|
|
82
82
|
account,
|
|
@@ -101,7 +101,7 @@ export async function createClosePayload(
|
|
|
101
101
|
escrowContract: Address,
|
|
102
102
|
chainId: number,
|
|
103
103
|
authorizedSigner?: Address | undefined,
|
|
104
|
-
): Promise<
|
|
104
|
+
): Promise<SessionCredentialPayload> {
|
|
105
105
|
const signature = await signVoucher(
|
|
106
106
|
client,
|
|
107
107
|
account,
|
|
@@ -131,7 +131,7 @@ export async function createOpenPayload(
|
|
|
131
131
|
chainId: number
|
|
132
132
|
feePayer?: boolean | undefined
|
|
133
133
|
},
|
|
134
|
-
): Promise<{ entry: ChannelEntry; payload:
|
|
134
|
+
): Promise<{ entry: ChannelEntry; payload: SessionCredentialPayload }> {
|
|
135
135
|
const { escrowContract, payee, currency, deposit, initialAmount, chainId, feePayer } = options
|
|
136
136
|
const authorizedSigner = options.authorizedSigner ?? account.address
|
|
137
137
|
|