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.
- package/CHANGELOG.md +12 -0
- package/dist/Expires.d.ts +7 -0
- package/dist/Expires.d.ts.map +1 -1
- package/dist/Expires.js +21 -0
- package/dist/Expires.js.map +1 -1
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +12 -2
- package/dist/cli/account.js.map +1 -1
- package/dist/server/Mppx.js +6 -5
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +3 -3
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/tempo/Methods.d.ts +3 -0
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts +3 -0
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +18 -2
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +3 -0
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/internal/proof.d.ts +29 -0
- package/dist/tempo/internal/proof.d.ts.map +1 -0
- package/dist/tempo/internal/proof.js +32 -0
- package/dist/tempo/internal/proof.js.map +1 -0
- package/dist/tempo/server/Charge.d.ts +11 -3
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +54 -4
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +3 -0
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Expires.ts +25 -0
- package/src/cli/account.ts +13 -2
- package/src/cli/cli.test.ts +230 -1
- package/src/middlewares/elysia.test.ts +130 -9
- package/src/middlewares/express.test.ts +123 -59
- package/src/middlewares/hono.test.ts +81 -39
- package/src/middlewares/nextjs.test.ts +162 -41
- package/src/server/Mppx.test.ts +86 -0
- package/src/server/Mppx.ts +5 -5
- package/src/stripe/server/Charge.ts +3 -7
- package/src/tempo/Methods.test.ts +26 -0
- package/src/tempo/Methods.ts +1 -0
- package/src/tempo/client/Charge.ts +26 -3
- package/src/tempo/internal/charge.test.ts +66 -0
- package/src/tempo/internal/proof.test.ts +83 -0
- package/src/tempo/internal/proof.ts +35 -0
- package/src/tempo/server/Charge.test.ts +660 -1
- package/src/tempo/server/Charge.ts +80 -5
- package/src/tempo/server/Session.test.ts +1123 -53
- package/src/tempo/server/internal/transport.test.ts +32 -0
- package/src/tempo/session/Chain.test.ts +35 -0
- 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 {
|
|
9
|
-
import {
|
|
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<
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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<
|
|
15
|
+
return new Promise<Http.TestServer>((resolve) => {
|
|
15
16
|
const server = serve({ fetch: app.fetch, port: 0 }, (info) => {
|
|
16
|
-
resolve(
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
})
|