mppx 0.4.12 → 0.5.1

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 (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/Expires.d.ts +7 -0
  3. package/dist/Expires.d.ts.map +1 -1
  4. package/dist/Expires.js +21 -0
  5. package/dist/Expires.js.map +1 -1
  6. package/dist/cli/account.d.ts.map +1 -1
  7. package/dist/cli/account.js +12 -2
  8. package/dist/cli/account.js.map +1 -1
  9. package/dist/server/Mppx.js +6 -5
  10. package/dist/server/Mppx.js.map +1 -1
  11. package/dist/stripe/server/Charge.d.ts.map +1 -1
  12. package/dist/stripe/server/Charge.js +3 -3
  13. package/dist/stripe/server/Charge.js.map +1 -1
  14. package/dist/tempo/Methods.d.ts +3 -0
  15. package/dist/tempo/Methods.d.ts.map +1 -1
  16. package/dist/tempo/Methods.js +1 -0
  17. package/dist/tempo/Methods.js.map +1 -1
  18. package/dist/tempo/client/Charge.d.ts +3 -0
  19. package/dist/tempo/client/Charge.d.ts.map +1 -1
  20. package/dist/tempo/client/Charge.js +18 -2
  21. package/dist/tempo/client/Charge.js.map +1 -1
  22. package/dist/tempo/client/Methods.d.ts +3 -0
  23. package/dist/tempo/client/Methods.d.ts.map +1 -1
  24. package/dist/tempo/internal/proof.d.ts +29 -0
  25. package/dist/tempo/internal/proof.d.ts.map +1 -0
  26. package/dist/tempo/internal/proof.js +32 -0
  27. package/dist/tempo/internal/proof.js.map +1 -0
  28. package/dist/tempo/server/Charge.d.ts +11 -3
  29. package/dist/tempo/server/Charge.d.ts.map +1 -1
  30. package/dist/tempo/server/Charge.js +54 -4
  31. package/dist/tempo/server/Charge.js.map +1 -1
  32. package/dist/tempo/server/Methods.d.ts +3 -0
  33. package/dist/tempo/server/Methods.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/Expires.ts +25 -0
  36. package/src/cli/account.ts +13 -2
  37. package/src/cli/cli.test.ts +230 -1
  38. package/src/middlewares/elysia.test.ts +130 -9
  39. package/src/middlewares/express.test.ts +123 -59
  40. package/src/middlewares/hono.test.ts +81 -39
  41. package/src/middlewares/nextjs.test.ts +162 -41
  42. package/src/server/Mppx.test.ts +86 -0
  43. package/src/server/Mppx.ts +5 -5
  44. package/src/stripe/server/Charge.ts +3 -7
  45. package/src/tempo/Methods.test.ts +26 -0
  46. package/src/tempo/Methods.ts +1 -0
  47. package/src/tempo/client/Charge.ts +26 -3
  48. package/src/tempo/internal/charge.test.ts +66 -0
  49. package/src/tempo/internal/proof.test.ts +83 -0
  50. package/src/tempo/internal/proof.ts +35 -0
  51. package/src/tempo/server/Charge.test.ts +660 -1
  52. package/src/tempo/server/Charge.ts +80 -5
  53. package/src/tempo/server/Session.test.ts +1123 -53
  54. package/src/tempo/server/internal/transport.test.ts +32 -0
  55. package/src/tempo/session/Chain.test.ts +35 -0
  56. package/src/tempo/session/Sse.test.ts +31 -0
@@ -2,14 +2,18 @@ import * as http from 'node:http'
2
2
 
3
3
  import { Elysia } from 'elysia'
4
4
  import { Receipt } from 'mppx'
5
- import { Mppx as Mppx_client, tempo as tempo_client } from 'mppx/client'
5
+ import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
6
6
  import { Mppx, discovery } from 'mppx/elysia'
7
7
  import { tempo as tempo_server } from 'mppx/server'
8
- import { describe, expect, test } from 'vp/test'
9
- import { accounts, asset, client } from '~test/tempo/viem.js'
8
+ import type { Address } from 'viem'
9
+ import { Addresses } from 'viem/tempo'
10
+ import { beforeAll, describe, expect, test } from 'vp/test'
11
+ import * as TestHttp from '~test/Http.js'
12
+ import { deployEscrow } from '~test/tempo/session.js'
13
+ import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
10
14
 
11
15
  function createServer(app: Elysia<any, any, any, any, any, any, any>) {
12
- return new Promise<{ url: string; close: () => void }>((resolve) => {
16
+ return new Promise<TestHttp.TestServer>((resolve) => {
13
17
  const server = http.createServer(async (req, res) => {
14
18
  const url = `http://localhost${req.url}`
15
19
  const headers = new Headers()
@@ -24,23 +28,21 @@ function createServer(app: Elysia<any, any, any, any, any, any, any>) {
24
28
  })
25
29
  server.listen(0, () => {
26
30
  const { port } = server.address() as { port: number }
27
- resolve({
28
- url: `http://localhost:${port}`,
29
- close: () => server.close(),
30
- })
31
+ resolve(TestHttp.wrapServer(server, { port, url: `http://localhost:${port}` }))
31
32
  })
32
33
  })
33
34
  }
34
35
 
35
36
  const secretKey = 'test-secret-key'
36
37
 
37
- describe('charge', () => {
38
+ function createChargeHarness(feePayer: boolean) {
38
39
  const mppx = Mppx.create({
39
40
  methods: [
40
41
  tempo_server.charge({
41
42
  getClient: () => client,
42
43
  currency: asset,
43
44
  recipient: accounts[0].address,
45
+ ...(feePayer ? { feePayer: true } : {}),
44
46
  }),
45
47
  ],
46
48
  secretKey,
@@ -56,7 +58,13 @@ describe('charge', () => {
56
58
  ],
57
59
  })
58
60
 
61
+ return { fetch, mppx }
62
+ }
63
+
64
+ describe('charge', () => {
59
65
  test('returns 402 when no credential', async () => {
66
+ const { mppx } = createChargeHarness(false)
67
+
60
68
  const app = new Elysia().guard({ beforeHandle: mppx.charge({ amount: '1' }) }, (app) =>
61
69
  app.get('/', () => ({ fortune: 'You will be rich' })),
62
70
  )
@@ -70,6 +78,8 @@ describe('charge', () => {
70
78
  })
71
79
 
72
80
  test('returns 200 with receipt on valid payment', async () => {
81
+ const { fetch, mppx } = createChargeHarness(false)
82
+
73
83
  const app = new Elysia().guard({ beforeHandle: mppx.charge({ amount: '1' }) }, (app) =>
74
84
  app.get('/', () => ({ fortune: 'You will be rich' })),
75
85
  )
@@ -88,7 +98,24 @@ describe('charge', () => {
88
98
  server.close()
89
99
  })
90
100
 
101
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
102
+ const { fetch, mppx } = createChargeHarness(true)
103
+
104
+ const app = new Elysia().guard({ beforeHandle: mppx.charge({ amount: '1' }) }, (app) =>
105
+ app.get('/', () => ({ fortune: 'You will be rich' })),
106
+ )
107
+
108
+ const server = await createServer(app)
109
+ const response = await fetch(server.url)
110
+ expect(response.status).toBe(200)
111
+ expect(Receipt.fromResponse(response).status).toBe('success')
112
+
113
+ server.close()
114
+ })
115
+
91
116
  test('serves /openapi.json from discovery plugin', async () => {
117
+ const { mppx } = createChargeHarness(false)
118
+
92
119
  const app = new Elysia().use(
93
120
  discovery(mppx, {
94
121
  info: { title: 'Elysia API', version: '1.0.0' },
@@ -113,3 +140,97 @@ describe('charge', () => {
113
140
  server.close()
114
141
  })
115
142
  })
143
+
144
+ describe('session', () => {
145
+ let escrowContract: Address
146
+
147
+ function createSessionHarness(feePayer: boolean) {
148
+ const mppx = Mppx.create({
149
+ methods: [
150
+ tempo_server.session({
151
+ getClient: () => client,
152
+ account: accounts[0],
153
+ currency: asset,
154
+ escrowContract,
155
+ ...(feePayer ? { feePayer: accounts[1] } : {}),
156
+ } as any),
157
+ ],
158
+ secretKey,
159
+ })
160
+
161
+ const { fetch } = Mppx_client.create({
162
+ polyfill: false,
163
+ methods: [
164
+ sessionIntent({
165
+ account: accounts[2],
166
+ deposit: '10',
167
+ getClient: () => client,
168
+ }),
169
+ ],
170
+ })
171
+
172
+ return { fetch, mppx }
173
+ }
174
+
175
+ beforeAll(async () => {
176
+ escrowContract = await deployEscrow()
177
+ await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
178
+ await fundAccount({ address: accounts[1].address, token: asset })
179
+ await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
180
+ await fundAccount({ address: accounts[2].address, token: asset })
181
+ })
182
+
183
+ test('returns 402 when no credential', async () => {
184
+ const { mppx } = createSessionHarness(false)
185
+
186
+ const app = new Elysia().guard(
187
+ { beforeHandle: mppx.session({ amount: '1', currency: asset, unitType: 'token' }) },
188
+ (app) => app.get('/', () => ({ data: 'streamed' })),
189
+ )
190
+
191
+ const server = await createServer(app)
192
+ const response = await globalThis.fetch(server.url)
193
+ expect(response.status).toBe(402)
194
+ expect(response.headers.get('WWW-Authenticate')).toContain('Payment')
195
+
196
+ server.close()
197
+ })
198
+
199
+ test('returns 200 with receipt on valid payment', async () => {
200
+ const { fetch, mppx } = createSessionHarness(false)
201
+
202
+ const app = new Elysia().guard(
203
+ { beforeHandle: mppx.session({ amount: '1', currency: asset, unitType: 'token' }) },
204
+ (app) => app.get('/', () => ({ data: 'streamed' })),
205
+ )
206
+
207
+ const server = await createServer(app)
208
+ const response = await fetch(server.url)
209
+ expect(response.status).toBe(200)
210
+
211
+ const body = await response.json()
212
+ expect(body).toEqual({ data: 'streamed' })
213
+
214
+ const receipt = Receipt.fromResponse(response)
215
+ expect(receipt.status).toBe('success')
216
+ expect(receipt.method).toBe('tempo')
217
+
218
+ server.close()
219
+ })
220
+
221
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
222
+ const { fetch, mppx } = createSessionHarness(true)
223
+
224
+ const app = new Elysia().guard(
225
+ { beforeHandle: mppx.session({ amount: '1', currency: asset, unitType: 'token' }) },
226
+ (app) => app.get('/', () => ({ data: 'streamed' })),
227
+ )
228
+
229
+ const server = await createServer(app)
230
+ const response = await fetch(server.url)
231
+ expect(response.status).toBe(200)
232
+ expect(Receipt.fromResponse(response).status).toBe('success')
233
+
234
+ server.close()
235
+ })
236
+ })
@@ -6,30 +6,29 @@ import { Mppx as Mppx_server, 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 'vp/test'
9
+ import * as Http from '~test/Http.js'
9
10
  import { deployEscrow } from '~test/tempo/session.js'
10
11
  import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
11
12
 
12
13
  function createServer(app: express.Express) {
13
- return new Promise<{ url: string; close: () => void }>((resolve) => {
14
+ return new Promise<Http.TestServer>((resolve) => {
14
15
  const server = app.listen(0, () => {
15
16
  const { port } = server.address() as { port: number }
16
- resolve({
17
- url: `http://localhost:${port}`,
18
- close: () => server.close(),
19
- })
17
+ resolve(Http.wrapServer(server, { port, url: `http://localhost:${port}` }))
20
18
  })
21
19
  })
22
20
  }
23
21
 
24
22
  const secretKey = 'test-secret-key'
25
23
 
26
- describe('charge', () => {
24
+ function createChargeHarness(feePayer: boolean) {
27
25
  const mppx = Mppx.create({
28
26
  methods: [
29
27
  tempo_server({
30
28
  getClient: () => client,
31
29
  currency: asset,
32
30
  account: accounts[0],
31
+ ...(feePayer ? { feePayer: true } : {}),
33
32
  }),
34
33
  ],
35
34
  secretKey,
@@ -45,7 +44,39 @@ describe('charge', () => {
45
44
  ],
46
45
  })
47
46
 
47
+ return { fetch, mppx }
48
+ }
49
+
50
+ function createCoreChargeHarness(feePayer: boolean) {
51
+ const mppx = Mppx_server.create({
52
+ methods: [
53
+ tempo_server({
54
+ getClient: () => client,
55
+ currency: asset,
56
+ account: accounts[0],
57
+ ...(feePayer ? { feePayer: true } : {}),
58
+ }),
59
+ ],
60
+ secretKey,
61
+ })
62
+
63
+ const { fetch } = Mppx_client.create({
64
+ polyfill: false,
65
+ methods: [
66
+ tempo_client({
67
+ account: accounts[1],
68
+ getClient: () => client,
69
+ }),
70
+ ],
71
+ })
72
+
73
+ return { fetch, mppx }
74
+ }
75
+
76
+ describe('charge', () => {
48
77
  test('returns 402 when no credential', async () => {
78
+ const { mppx } = createChargeHarness(false)
79
+
49
80
  const app = express()
50
81
  app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
51
82
  res.json({ fortune: 'You will be rich' })
@@ -60,6 +91,8 @@ describe('charge', () => {
60
91
  })
61
92
 
62
93
  test('returns 200 with receipt on valid payment', async () => {
94
+ const { fetch, mppx } = createChargeHarness(false)
95
+
63
96
  const app = express()
64
97
  app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
65
98
  res.json({ fortune: 'You will be rich' })
@@ -82,7 +115,25 @@ describe('charge', () => {
82
115
  server.close()
83
116
  })
84
117
 
118
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
119
+ const { fetch, mppx } = createChargeHarness(true)
120
+
121
+ const app = express()
122
+ app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
123
+ res.json({ fortune: 'You will be rich' })
124
+ })
125
+
126
+ const server = await createServer(app)
127
+ const response = await fetch(server.url)
128
+ expect(response.status).toBe(200)
129
+ expect(Receipt.fromResponse(response).status).toBe('success')
130
+
131
+ server.close()
132
+ })
133
+
85
134
  test('serves /openapi.json from a handler-derived route config', async () => {
135
+ const { mppx } = createChargeHarness(false)
136
+
86
137
  const app = express()
87
138
  const pay = mppx.charge({ amount: '1' })
88
139
  app.get('/', pay, (_req, res) => {
@@ -114,13 +165,7 @@ describe('charge', () => {
114
165
  describe('session', () => {
115
166
  let escrowContract: Address
116
167
 
117
- beforeAll(async () => {
118
- escrowContract = await deployEscrow()
119
- await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
120
- await fundAccount({ address: accounts[2].address, token: asset })
121
- })
122
-
123
- test('returns 402 when no credential', async () => {
168
+ function createSessionHarness(feePayer: boolean) {
124
169
  const mppx = Mppx.create({
125
170
  methods: [
126
171
  tempo_server.session({
@@ -128,13 +173,39 @@ describe('session', () => {
128
173
  account: accounts[0],
129
174
  currency: asset,
130
175
  escrowContract,
131
- }),
176
+ ...(feePayer ? { feePayer: accounts[1] } : {}),
177
+ } as any),
132
178
  ],
133
179
  secretKey,
134
180
  })
135
181
 
182
+ const { fetch } = Mppx_client.create({
183
+ polyfill: false,
184
+ methods: [
185
+ sessionIntent({
186
+ account: accounts[2],
187
+ deposit: '10',
188
+ getClient: () => client,
189
+ }),
190
+ ],
191
+ })
192
+
193
+ return { fetch, mppx }
194
+ }
195
+
196
+ beforeAll(async () => {
197
+ escrowContract = await deployEscrow()
198
+ await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
199
+ await fundAccount({ address: accounts[1].address, token: asset })
200
+ await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
201
+ await fundAccount({ address: accounts[2].address, token: asset })
202
+ })
203
+
204
+ test('returns 402 when no credential', async () => {
205
+ const { mppx } = createSessionHarness(false)
206
+
136
207
  const app = express()
137
- app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (_req, res) => {
208
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
138
209
  res.json({ data: 'streamed' })
139
210
  })
140
211
 
@@ -147,32 +218,10 @@ describe('session', () => {
147
218
  })
148
219
 
149
220
  test('returns 200 with receipt on valid payment', async () => {
150
- const mppx = Mppx.create({
151
- methods: [
152
- tempo_server.session({
153
- getClient: () => client,
154
- account: accounts[0],
155
- currency: asset,
156
- escrowContract,
157
- feePayer: true,
158
- }),
159
- ],
160
- secretKey,
161
- })
162
-
163
- const { fetch } = Mppx_client.create({
164
- polyfill: false,
165
- methods: [
166
- sessionIntent({
167
- account: accounts[2],
168
- deposit: '10',
169
- getClient: () => client,
170
- }),
171
- ],
172
- })
221
+ const { fetch, mppx } = createSessionHarness(false)
173
222
 
174
223
  const app = express()
175
- app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (_req, res) => {
224
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
176
225
  res.json({ data: 'streamed' })
177
226
  })
178
227
 
@@ -185,31 +234,28 @@ describe('session', () => {
185
234
 
186
235
  server.close()
187
236
  })
188
- })
189
237
 
190
- describe('payment', () => {
191
- const mppx = Mppx_server.create({
192
- methods: [
193
- tempo_server({
194
- getClient: () => client,
195
- currency: asset,
196
- account: accounts[0],
197
- }),
198
- ],
199
- secretKey,
200
- })
238
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
239
+ const { fetch, mppx } = createSessionHarness(true)
201
240
 
202
- const { fetch } = Mppx_client.create({
203
- polyfill: false,
204
- methods: [
205
- tempo_client({
206
- account: accounts[1],
207
- getClient: () => client,
208
- }),
209
- ],
241
+ const app = express()
242
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
243
+ res.json({ data: 'streamed' })
244
+ })
245
+
246
+ const server = await createServer(app)
247
+ const response = await fetch(server.url)
248
+ expect(response.status).toBe(200)
249
+ expect(Receipt.fromResponse(response).status).toBe('success')
250
+
251
+ server.close()
210
252
  })
253
+ })
211
254
 
255
+ describe('payment', () => {
212
256
  test('returns 402 when no credential', async () => {
257
+ const { mppx } = createCoreChargeHarness(false)
258
+
213
259
  const app = express()
214
260
  app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
215
261
  res.json({ fortune: 'You will be rich' })
@@ -224,6 +270,8 @@ describe('payment', () => {
224
270
  })
225
271
 
226
272
  test('returns 200 with receipt on valid payment', async () => {
273
+ const { fetch, mppx } = createCoreChargeHarness(false)
274
+
227
275
  const app = express()
228
276
  app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
229
277
  res.json({ fortune: 'You will be rich' })
@@ -245,4 +293,20 @@ describe('payment', () => {
245
293
 
246
294
  server.close()
247
295
  })
296
+
297
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
298
+ const { fetch, mppx } = createCoreChargeHarness(true)
299
+
300
+ const app = express()
301
+ app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
302
+ res.json({ fortune: 'You will be rich' })
303
+ })
304
+
305
+ const server = await createServer(app)
306
+ const response = await fetch(server.url)
307
+ expect(response.status).toBe(200)
308
+ expect(Receipt.fromResponse(response).status).toBe('success')
309
+
310
+ server.close()
311
+ })
248
312
  })
@@ -7,29 +7,33 @@ 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 'vp/test'
10
+ import * as Http from '~test/Http.js'
10
11
  import { deployEscrow } from '~test/tempo/session.js'
11
12
  import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
12
13
 
13
14
  function createServer(app: Hono) {
14
- return new Promise<{ url: string; close: () => void }>((resolve) => {
15
+ return new Promise<Http.TestServer>((resolve) => {
15
16
  const server = serve({ fetch: app.fetch, port: 0 }, (info) => {
16
- resolve({
17
- url: `http://localhost:${info.port}`,
18
- close: () => server.close(),
19
- })
17
+ resolve(
18
+ Http.wrapServer(server as unknown as import('node:http').Server, {
19
+ port: info.port,
20
+ url: `http://localhost:${info.port}`,
21
+ }),
22
+ )
20
23
  })
21
24
  })
22
25
  }
23
26
 
24
27
  const secretKey = 'test-secret-key'
25
28
 
26
- describe('charge', () => {
29
+ function createChargeHarness(feePayer: boolean) {
27
30
  const mppx = Mppx.create({
28
31
  methods: [
29
32
  tempo_server.charge({
30
33
  getClient: () => client,
31
34
  currency: asset,
32
35
  account: accounts[0],
36
+ ...(feePayer ? { feePayer: true } : {}),
33
37
  }),
34
38
  ],
35
39
  secretKey,
@@ -45,7 +49,13 @@ describe('charge', () => {
45
49
  ],
46
50
  })
47
51
 
52
+ return { fetch, mppx }
53
+ }
54
+
55
+ describe('charge', () => {
48
56
  test('returns 402 when no credential', async () => {
57
+ const { mppx } = createChargeHarness(false)
58
+
49
59
  const app = new Hono()
50
60
  app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
51
61
 
@@ -58,6 +68,8 @@ describe('charge', () => {
58
68
  })
59
69
 
60
70
  test('returns 200 with receipt on valid payment', async () => {
71
+ const { fetch, mppx } = createChargeHarness(false)
72
+
61
73
  const app = new Hono()
62
74
  app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
63
75
 
@@ -75,7 +87,23 @@ describe('charge', () => {
75
87
  server.close()
76
88
  })
77
89
 
90
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
91
+ const { fetch, mppx } = createChargeHarness(true)
92
+
93
+ const app = new Hono()
94
+ app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
95
+
96
+ const server = await createServer(app)
97
+ const response = await fetch(server.url)
98
+ expect(response.status).toBe(200)
99
+ expect(Receipt.fromResponse(response).status).toBe('success')
100
+
101
+ server.close()
102
+ })
103
+
78
104
  test('serves /openapi.json via auto discovery', async () => {
105
+ const { mppx } = createChargeHarness(false)
106
+
79
107
  const app = new Hono()
80
108
  app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
81
109
  discovery(app, mppx, { auto: true, info: { title: 'Auto API', version: '2.0.0' } })
@@ -101,13 +129,7 @@ describe('charge', () => {
101
129
  describe('session', () => {
102
130
  let escrowContract: Address
103
131
 
104
- beforeAll(async () => {
105
- escrowContract = await deployEscrow()
106
- await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
107
- await fundAccount({ address: accounts[2].address, token: asset })
108
- })
109
-
110
- test('returns 402 when no credential', async () => {
132
+ function createSessionHarness(feePayer: boolean) {
111
133
  const mppx = Mppx.create({
112
134
  methods: [
113
135
  tempo_server.session({
@@ -115,13 +137,39 @@ describe('session', () => {
115
137
  account: accounts[0],
116
138
  currency: asset,
117
139
  escrowContract,
118
- }),
140
+ ...(feePayer ? { feePayer: accounts[1] } : {}),
141
+ } as any),
119
142
  ],
120
143
  secretKey,
121
144
  })
122
145
 
146
+ const { fetch } = Mppx_client.create({
147
+ polyfill: false,
148
+ methods: [
149
+ sessionIntent({
150
+ account: accounts[2],
151
+ deposit: '10',
152
+ getClient: () => client,
153
+ }),
154
+ ],
155
+ })
156
+
157
+ return { fetch, mppx }
158
+ }
159
+
160
+ beforeAll(async () => {
161
+ escrowContract = await deployEscrow()
162
+ await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
163
+ await fundAccount({ address: accounts[1].address, token: asset })
164
+ await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
165
+ await fundAccount({ address: accounts[2].address, token: asset })
166
+ })
167
+
168
+ test('returns 402 when no credential', async () => {
169
+ const { mppx } = createSessionHarness(false)
170
+
123
171
  const app = new Hono()
124
- app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (c) =>
172
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
125
173
  c.json({ data: 'streamed' }),
126
174
  )
127
175
 
@@ -134,32 +182,10 @@ describe('session', () => {
134
182
  })
135
183
 
136
184
  test('returns 200 with receipt on valid payment', async () => {
137
- const mppx = Mppx.create({
138
- methods: [
139
- tempo_server.session({
140
- getClient: () => client,
141
- account: accounts[0],
142
- currency: asset,
143
- escrowContract,
144
- feePayer: true,
145
- }),
146
- ],
147
- secretKey,
148
- })
149
-
150
- const { fetch } = Mppx_client.create({
151
- polyfill: false,
152
- methods: [
153
- sessionIntent({
154
- account: accounts[2],
155
- deposit: '10',
156
- getClient: () => client,
157
- }),
158
- ],
159
- })
185
+ const { fetch, mppx } = createSessionHarness(false)
160
186
 
161
187
  const app = new Hono()
162
- app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (c) =>
188
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
163
189
  c.json({ data: 'streamed' }),
164
190
  )
165
191
 
@@ -172,4 +198,20 @@ describe('session', () => {
172
198
 
173
199
  server.close()
174
200
  })
201
+
202
+ test('fee payer: returns 200 with receipt on valid payment', async () => {
203
+ const { fetch, mppx } = createSessionHarness(true)
204
+
205
+ const app = new Hono()
206
+ app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
207
+ c.json({ data: 'streamed' }),
208
+ )
209
+
210
+ const server = await createServer(app)
211
+ const response = await fetch(server.url)
212
+ expect(response.status).toBe(200)
213
+ expect(Receipt.fromResponse(response).status).toBe('success')
214
+
215
+ server.close()
216
+ })
175
217
  })