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.
Files changed (140) hide show
  1. package/dist/Errors.d.ts +7 -7
  2. package/dist/Errors.d.ts.map +1 -1
  3. package/dist/Errors.js +7 -7
  4. package/dist/Errors.js.map +1 -1
  5. package/dist/cli.js +98 -64
  6. package/dist/cli.js.map +1 -1
  7. package/dist/internal/env.d.ts +19 -0
  8. package/dist/internal/env.d.ts.map +1 -0
  9. package/dist/internal/env.js +55 -0
  10. package/dist/internal/env.js.map +1 -0
  11. package/dist/server/Mppx.d.ts +2 -2
  12. package/dist/server/Mppx.d.ts.map +1 -1
  13. package/dist/server/Mppx.js +2 -1
  14. package/dist/server/Mppx.js.map +1 -1
  15. package/dist/tempo/client/ChannelOps.d.ts +5 -5
  16. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  17. package/dist/tempo/client/ChannelOps.js +3 -3
  18. package/dist/tempo/client/ChannelOps.js.map +1 -1
  19. package/dist/tempo/client/Session.d.ts +2 -2
  20. package/dist/tempo/client/Session.d.ts.map +1 -1
  21. package/dist/tempo/client/Session.js +3 -3
  22. package/dist/tempo/client/Session.js.map +1 -1
  23. package/dist/tempo/client/SessionManager.d.ts +4 -4
  24. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  25. package/dist/tempo/client/SessionManager.js +4 -4
  26. package/dist/tempo/client/SessionManager.js.map +1 -1
  27. package/dist/tempo/index.d.ts +1 -1
  28. package/dist/tempo/index.d.ts.map +1 -1
  29. package/dist/tempo/index.js +1 -1
  30. package/dist/tempo/index.js.map +1 -1
  31. package/dist/tempo/server/Charge.js +1 -1
  32. package/dist/tempo/server/Charge.js.map +1 -1
  33. package/dist/tempo/server/Methods.d.ts +1 -1
  34. package/dist/tempo/server/Methods.d.ts.map +1 -1
  35. package/dist/tempo/server/Session.d.ts +8 -8
  36. package/dist/tempo/server/Session.d.ts.map +1 -1
  37. package/dist/tempo/server/Session.js +24 -24
  38. package/dist/tempo/server/Session.js.map +1 -1
  39. package/dist/tempo/server/index.d.ts +2 -2
  40. package/dist/tempo/server/index.d.ts.map +1 -1
  41. package/dist/tempo/server/index.js +2 -2
  42. package/dist/tempo/server/index.js.map +1 -1
  43. package/dist/tempo/server/internal/transport.d.ts +4 -4
  44. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  45. package/dist/tempo/server/internal/transport.js +3 -3
  46. package/dist/tempo/server/internal/transport.js.map +1 -1
  47. package/dist/tempo/session/Chain.d.ts.map +1 -0
  48. package/dist/tempo/session/Chain.js.map +1 -0
  49. package/dist/tempo/session/Channel.d.ts.map +1 -0
  50. package/dist/tempo/session/Channel.js.map +1 -0
  51. package/dist/tempo/session/ChannelStore.d.ts.map +1 -0
  52. package/dist/tempo/session/ChannelStore.js.map +1 -0
  53. package/dist/tempo/session/Receipt.d.ts +22 -0
  54. package/dist/tempo/session/Receipt.d.ts.map +1 -0
  55. package/dist/tempo/{stream → session}/Receipt.js +6 -6
  56. package/dist/tempo/session/Receipt.js.map +1 -0
  57. package/dist/tempo/{stream → session}/Sse.d.ts +7 -7
  58. package/dist/tempo/session/Sse.d.ts.map +1 -0
  59. package/dist/tempo/{stream → session}/Sse.js +4 -4
  60. package/dist/tempo/session/Sse.js.map +1 -0
  61. package/dist/tempo/{stream → session}/Types.d.ts +4 -4
  62. package/dist/tempo/session/Types.d.ts.map +1 -0
  63. package/dist/tempo/{stream → session}/Types.js.map +1 -1
  64. package/dist/tempo/session/Voucher.d.ts.map +1 -0
  65. package/dist/tempo/session/Voucher.js.map +1 -0
  66. package/dist/tempo/{stream → session}/escrow.abi.d.ts.map +1 -1
  67. package/dist/tempo/session/escrow.abi.js.map +1 -0
  68. package/dist/tempo/session/index.d.ts.map +1 -0
  69. package/dist/tempo/session/index.js.map +1 -0
  70. package/package.json +1 -1
  71. package/src/Errors.test.ts +10 -10
  72. package/src/Errors.ts +7 -7
  73. package/src/Expires.test.ts +111 -0
  74. package/src/cli.test.ts +3 -3
  75. package/src/cli.ts +125 -70
  76. package/src/internal/env.ts +54 -0
  77. package/src/mcp-sdk/server/Transport.test.ts +171 -0
  78. package/src/middlewares/express.test.ts +1 -1
  79. package/src/middlewares/hono.test.ts +1 -1
  80. package/src/middlewares/nextjs.test.ts +1 -1
  81. package/src/server/Mppx.ts +5 -4
  82. package/src/server/Transport.test.ts +1 -1
  83. package/src/tempo/client/ChannelOps.test.ts +290 -0
  84. package/src/tempo/client/ChannelOps.ts +8 -8
  85. package/src/tempo/client/Session.test.ts +467 -0
  86. package/src/tempo/client/Session.ts +9 -9
  87. package/src/tempo/client/SessionManager.test.ts +3 -3
  88. package/src/tempo/client/SessionManager.ts +9 -9
  89. package/src/tempo/index.ts +1 -1
  90. package/src/tempo/server/Charge.ts +1 -1
  91. package/src/tempo/server/Session.test.ts +9 -9
  92. package/src/tempo/server/Session.ts +47 -47
  93. package/src/tempo/server/Sse.test.ts +3 -3
  94. package/src/tempo/server/index.ts +2 -2
  95. package/src/tempo/server/internal/transport.ts +6 -6
  96. package/src/tempo/session/Chain.test.ts +511 -0
  97. package/src/tempo/session/Channel.test.ts +108 -0
  98. package/src/tempo/{stream → session}/Receipt.test.ts +16 -12
  99. package/src/tempo/{stream → session}/Receipt.ts +9 -9
  100. package/src/tempo/{stream → session}/Sse.test.ts +5 -5
  101. package/src/tempo/{stream → session}/Sse.ts +11 -11
  102. package/src/tempo/{stream → session}/Types.ts +4 -4
  103. package/dist/tempo/stream/Chain.d.ts.map +0 -1
  104. package/dist/tempo/stream/Chain.js.map +0 -1
  105. package/dist/tempo/stream/Channel.d.ts.map +0 -1
  106. package/dist/tempo/stream/Channel.js.map +0 -1
  107. package/dist/tempo/stream/ChannelStore.d.ts.map +0 -1
  108. package/dist/tempo/stream/ChannelStore.js.map +0 -1
  109. package/dist/tempo/stream/Receipt.d.ts +0 -22
  110. package/dist/tempo/stream/Receipt.d.ts.map +0 -1
  111. package/dist/tempo/stream/Receipt.js.map +0 -1
  112. package/dist/tempo/stream/Sse.d.ts.map +0 -1
  113. package/dist/tempo/stream/Sse.js.map +0 -1
  114. package/dist/tempo/stream/Types.d.ts.map +0 -1
  115. package/dist/tempo/stream/Voucher.d.ts.map +0 -1
  116. package/dist/tempo/stream/Voucher.js.map +0 -1
  117. package/dist/tempo/stream/escrow.abi.js.map +0 -1
  118. package/dist/tempo/stream/index.d.ts.map +0 -1
  119. package/dist/tempo/stream/index.js.map +0 -1
  120. /package/dist/tempo/{stream → session}/Chain.d.ts +0 -0
  121. /package/dist/tempo/{stream → session}/Chain.js +0 -0
  122. /package/dist/tempo/{stream → session}/Channel.d.ts +0 -0
  123. /package/dist/tempo/{stream → session}/Channel.js +0 -0
  124. /package/dist/tempo/{stream → session}/ChannelStore.d.ts +0 -0
  125. /package/dist/tempo/{stream → session}/ChannelStore.js +0 -0
  126. /package/dist/tempo/{stream → session}/Types.js +0 -0
  127. /package/dist/tempo/{stream → session}/Voucher.d.ts +0 -0
  128. /package/dist/tempo/{stream → session}/Voucher.js +0 -0
  129. /package/dist/tempo/{stream → session}/escrow.abi.d.ts +0 -0
  130. /package/dist/tempo/{stream → session}/escrow.abi.js +0 -0
  131. /package/dist/tempo/{stream → session}/index.d.ts +0 -0
  132. /package/dist/tempo/{stream → session}/index.js +0 -0
  133. /package/src/tempo/{stream → session}/Chain.ts +0 -0
  134. /package/src/tempo/{stream → session}/Channel.ts +0 -0
  135. /package/src/tempo/{stream → session}/ChannelStore.test.ts +0 -0
  136. /package/src/tempo/{stream → session}/ChannelStore.ts +0 -0
  137. /package/src/tempo/{stream → session}/Voucher.test.ts +0 -0
  138. /package/src/tempo/{stream → session}/Voucher.ts +0 -0
  139. /package/src/tempo/{stream → session}/escrow.abi.ts +0 -0
  140. /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/stream.js'
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/stream.js'
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/stream.js'
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) {
@@ -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 = 'MPP Payment',
76
- secretKey = 'tmp',
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). @default "MPP Payment". */
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/stream/channel-finalized')
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 '../stream/Chain.js'
22
- import * as Channel from '../stream/Channel.js'
23
- import type { StreamCredentialPayload } from '../stream/Types.js'
24
- import { signVoucher } from '../stream/Voucher.js'
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: StreamCredentialPayload,
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<StreamCredentialPayload> {
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<StreamCredentialPayload> {
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: StreamCredentialPayload }> {
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