mppx 0.4.12 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -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/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 +23 -0
- package/dist/tempo/internal/proof.d.ts.map +1 -0
- package/dist/tempo/internal/proof.js +17 -0
- package/dist/tempo/internal/proof.js.map +1 -0
- package/dist/tempo/server/Charge.d.ts +3 -0
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +32 -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/cli.test.ts +230 -1
- package/src/middlewares/elysia.test.ts +127 -4
- package/src/middlewares/express.test.ts +120 -54
- package/src/middlewares/hono.test.ts +73 -34
- package/src/middlewares/nextjs.test.ts +159 -36
- 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 +36 -0
- package/src/tempo/internal/proof.ts +19 -0
- package/src/tempo/server/Charge.test.ts +362 -1
- package/src/tempo/server/Charge.ts +40 -2
- 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
|
@@ -23,13 +23,14 @@ function createServer(app: express.Express) {
|
|
|
23
23
|
|
|
24
24
|
const secretKey = 'test-secret-key'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function createChargeHarness(feePayer: boolean) {
|
|
27
27
|
const mppx = Mppx.create({
|
|
28
28
|
methods: [
|
|
29
29
|
tempo_server({
|
|
30
30
|
getClient: () => client,
|
|
31
31
|
currency: asset,
|
|
32
32
|
account: accounts[0],
|
|
33
|
+
...(feePayer ? { feePayer: true } : {}),
|
|
33
34
|
}),
|
|
34
35
|
],
|
|
35
36
|
secretKey,
|
|
@@ -45,7 +46,39 @@ describe('charge', () => {
|
|
|
45
46
|
],
|
|
46
47
|
})
|
|
47
48
|
|
|
49
|
+
return { fetch, mppx }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createCoreChargeHarness(feePayer: boolean) {
|
|
53
|
+
const mppx = Mppx_server.create({
|
|
54
|
+
methods: [
|
|
55
|
+
tempo_server({
|
|
56
|
+
getClient: () => client,
|
|
57
|
+
currency: asset,
|
|
58
|
+
account: accounts[0],
|
|
59
|
+
...(feePayer ? { feePayer: true } : {}),
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
secretKey,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const { fetch } = Mppx_client.create({
|
|
66
|
+
polyfill: false,
|
|
67
|
+
methods: [
|
|
68
|
+
tempo_client({
|
|
69
|
+
account: accounts[1],
|
|
70
|
+
getClient: () => client,
|
|
71
|
+
}),
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return { fetch, mppx }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('charge', () => {
|
|
48
79
|
test('returns 402 when no credential', async () => {
|
|
80
|
+
const { mppx } = createChargeHarness(false)
|
|
81
|
+
|
|
49
82
|
const app = express()
|
|
50
83
|
app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
|
|
51
84
|
res.json({ fortune: 'You will be rich' })
|
|
@@ -60,6 +93,8 @@ describe('charge', () => {
|
|
|
60
93
|
})
|
|
61
94
|
|
|
62
95
|
test('returns 200 with receipt on valid payment', async () => {
|
|
96
|
+
const { fetch, mppx } = createChargeHarness(false)
|
|
97
|
+
|
|
63
98
|
const app = express()
|
|
64
99
|
app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
|
|
65
100
|
res.json({ fortune: 'You will be rich' })
|
|
@@ -82,7 +117,25 @@ describe('charge', () => {
|
|
|
82
117
|
server.close()
|
|
83
118
|
})
|
|
84
119
|
|
|
120
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
121
|
+
const { fetch, mppx } = createChargeHarness(true)
|
|
122
|
+
|
|
123
|
+
const app = express()
|
|
124
|
+
app.get('/', mppx.charge({ amount: '1' }), (_req, res) => {
|
|
125
|
+
res.json({ fortune: 'You will be rich' })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const server = await createServer(app)
|
|
129
|
+
const response = await fetch(server.url)
|
|
130
|
+
expect(response.status).toBe(200)
|
|
131
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
132
|
+
|
|
133
|
+
server.close()
|
|
134
|
+
})
|
|
135
|
+
|
|
85
136
|
test('serves /openapi.json from a handler-derived route config', async () => {
|
|
137
|
+
const { mppx } = createChargeHarness(false)
|
|
138
|
+
|
|
86
139
|
const app = express()
|
|
87
140
|
const pay = mppx.charge({ amount: '1' })
|
|
88
141
|
app.get('/', pay, (_req, res) => {
|
|
@@ -114,13 +167,7 @@ describe('charge', () => {
|
|
|
114
167
|
describe('session', () => {
|
|
115
168
|
let escrowContract: Address
|
|
116
169
|
|
|
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 () => {
|
|
170
|
+
function createSessionHarness(feePayer: boolean) {
|
|
124
171
|
const mppx = Mppx.create({
|
|
125
172
|
methods: [
|
|
126
173
|
tempo_server.session({
|
|
@@ -128,13 +175,39 @@ describe('session', () => {
|
|
|
128
175
|
account: accounts[0],
|
|
129
176
|
currency: asset,
|
|
130
177
|
escrowContract,
|
|
131
|
-
|
|
178
|
+
...(feePayer ? { feePayer: accounts[1] } : {}),
|
|
179
|
+
} as any),
|
|
132
180
|
],
|
|
133
181
|
secretKey,
|
|
134
182
|
})
|
|
135
183
|
|
|
184
|
+
const { fetch } = Mppx_client.create({
|
|
185
|
+
polyfill: false,
|
|
186
|
+
methods: [
|
|
187
|
+
sessionIntent({
|
|
188
|
+
account: accounts[2],
|
|
189
|
+
deposit: '10',
|
|
190
|
+
getClient: () => client,
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return { fetch, mppx }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
beforeAll(async () => {
|
|
199
|
+
escrowContract = await deployEscrow()
|
|
200
|
+
await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
|
|
201
|
+
await fundAccount({ address: accounts[1].address, token: asset })
|
|
202
|
+
await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
|
|
203
|
+
await fundAccount({ address: accounts[2].address, token: asset })
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('returns 402 when no credential', async () => {
|
|
207
|
+
const { mppx } = createSessionHarness(false)
|
|
208
|
+
|
|
136
209
|
const app = express()
|
|
137
|
-
app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (_req, res) => {
|
|
210
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
|
|
138
211
|
res.json({ data: 'streamed' })
|
|
139
212
|
})
|
|
140
213
|
|
|
@@ -147,32 +220,10 @@ describe('session', () => {
|
|
|
147
220
|
})
|
|
148
221
|
|
|
149
222
|
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
|
-
})
|
|
223
|
+
const { fetch, mppx } = createSessionHarness(false)
|
|
173
224
|
|
|
174
225
|
const app = express()
|
|
175
|
-
app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (_req, res) => {
|
|
226
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
|
|
176
227
|
res.json({ data: 'streamed' })
|
|
177
228
|
})
|
|
178
229
|
|
|
@@ -185,31 +236,28 @@ describe('session', () => {
|
|
|
185
236
|
|
|
186
237
|
server.close()
|
|
187
238
|
})
|
|
188
|
-
})
|
|
189
239
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
methods: [
|
|
193
|
-
tempo_server({
|
|
194
|
-
getClient: () => client,
|
|
195
|
-
currency: asset,
|
|
196
|
-
account: accounts[0],
|
|
197
|
-
}),
|
|
198
|
-
],
|
|
199
|
-
secretKey,
|
|
200
|
-
})
|
|
240
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
241
|
+
const { fetch, mppx } = createSessionHarness(true)
|
|
201
242
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
243
|
+
const app = express()
|
|
244
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (_req, res) => {
|
|
245
|
+
res.json({ data: 'streamed' })
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const server = await createServer(app)
|
|
249
|
+
const response = await fetch(server.url)
|
|
250
|
+
expect(response.status).toBe(200)
|
|
251
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
252
|
+
|
|
253
|
+
server.close()
|
|
210
254
|
})
|
|
255
|
+
})
|
|
211
256
|
|
|
257
|
+
describe('payment', () => {
|
|
212
258
|
test('returns 402 when no credential', async () => {
|
|
259
|
+
const { mppx } = createCoreChargeHarness(false)
|
|
260
|
+
|
|
213
261
|
const app = express()
|
|
214
262
|
app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
|
|
215
263
|
res.json({ fortune: 'You will be rich' })
|
|
@@ -224,6 +272,8 @@ describe('payment', () => {
|
|
|
224
272
|
})
|
|
225
273
|
|
|
226
274
|
test('returns 200 with receipt on valid payment', async () => {
|
|
275
|
+
const { fetch, mppx } = createCoreChargeHarness(false)
|
|
276
|
+
|
|
227
277
|
const app = express()
|
|
228
278
|
app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
|
|
229
279
|
res.json({ fortune: 'You will be rich' })
|
|
@@ -245,4 +295,20 @@ describe('payment', () => {
|
|
|
245
295
|
|
|
246
296
|
server.close()
|
|
247
297
|
})
|
|
298
|
+
|
|
299
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
300
|
+
const { fetch, mppx } = createCoreChargeHarness(true)
|
|
301
|
+
|
|
302
|
+
const app = express()
|
|
303
|
+
app.get('/', payment(mppx.charge, { amount: '1' }), (_req, res) => {
|
|
304
|
+
res.json({ fortune: 'You will be rich' })
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const server = await createServer(app)
|
|
308
|
+
const response = await fetch(server.url)
|
|
309
|
+
expect(response.status).toBe(200)
|
|
310
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
311
|
+
|
|
312
|
+
server.close()
|
|
313
|
+
})
|
|
248
314
|
})
|
|
@@ -23,13 +23,14 @@ function createServer(app: Hono) {
|
|
|
23
23
|
|
|
24
24
|
const secretKey = 'test-secret-key'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function createChargeHarness(feePayer: boolean) {
|
|
27
27
|
const mppx = Mppx.create({
|
|
28
28
|
methods: [
|
|
29
29
|
tempo_server.charge({
|
|
30
30
|
getClient: () => client,
|
|
31
31
|
currency: asset,
|
|
32
32
|
account: accounts[0],
|
|
33
|
+
...(feePayer ? { feePayer: true } : {}),
|
|
33
34
|
}),
|
|
34
35
|
],
|
|
35
36
|
secretKey,
|
|
@@ -45,7 +46,13 @@ describe('charge', () => {
|
|
|
45
46
|
],
|
|
46
47
|
})
|
|
47
48
|
|
|
49
|
+
return { fetch, mppx }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('charge', () => {
|
|
48
53
|
test('returns 402 when no credential', async () => {
|
|
54
|
+
const { mppx } = createChargeHarness(false)
|
|
55
|
+
|
|
49
56
|
const app = new Hono()
|
|
50
57
|
app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
|
|
51
58
|
|
|
@@ -58,6 +65,8 @@ describe('charge', () => {
|
|
|
58
65
|
})
|
|
59
66
|
|
|
60
67
|
test('returns 200 with receipt on valid payment', async () => {
|
|
68
|
+
const { fetch, mppx } = createChargeHarness(false)
|
|
69
|
+
|
|
61
70
|
const app = new Hono()
|
|
62
71
|
app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
|
|
63
72
|
|
|
@@ -75,7 +84,23 @@ describe('charge', () => {
|
|
|
75
84
|
server.close()
|
|
76
85
|
})
|
|
77
86
|
|
|
87
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
88
|
+
const { fetch, mppx } = createChargeHarness(true)
|
|
89
|
+
|
|
90
|
+
const app = new Hono()
|
|
91
|
+
app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
|
|
92
|
+
|
|
93
|
+
const server = await createServer(app)
|
|
94
|
+
const response = await fetch(server.url)
|
|
95
|
+
expect(response.status).toBe(200)
|
|
96
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
97
|
+
|
|
98
|
+
server.close()
|
|
99
|
+
})
|
|
100
|
+
|
|
78
101
|
test('serves /openapi.json via auto discovery', async () => {
|
|
102
|
+
const { mppx } = createChargeHarness(false)
|
|
103
|
+
|
|
79
104
|
const app = new Hono()
|
|
80
105
|
app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
|
|
81
106
|
discovery(app, mppx, { auto: true, info: { title: 'Auto API', version: '2.0.0' } })
|
|
@@ -101,13 +126,7 @@ describe('charge', () => {
|
|
|
101
126
|
describe('session', () => {
|
|
102
127
|
let escrowContract: Address
|
|
103
128
|
|
|
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 () => {
|
|
129
|
+
function createSessionHarness(feePayer: boolean) {
|
|
111
130
|
const mppx = Mppx.create({
|
|
112
131
|
methods: [
|
|
113
132
|
tempo_server.session({
|
|
@@ -115,13 +134,39 @@ describe('session', () => {
|
|
|
115
134
|
account: accounts[0],
|
|
116
135
|
currency: asset,
|
|
117
136
|
escrowContract,
|
|
118
|
-
|
|
137
|
+
...(feePayer ? { feePayer: accounts[1] } : {}),
|
|
138
|
+
} as any),
|
|
119
139
|
],
|
|
120
140
|
secretKey,
|
|
121
141
|
})
|
|
122
142
|
|
|
143
|
+
const { fetch } = Mppx_client.create({
|
|
144
|
+
polyfill: false,
|
|
145
|
+
methods: [
|
|
146
|
+
sessionIntent({
|
|
147
|
+
account: accounts[2],
|
|
148
|
+
deposit: '10',
|
|
149
|
+
getClient: () => client,
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return { fetch, mppx }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
beforeAll(async () => {
|
|
158
|
+
escrowContract = await deployEscrow()
|
|
159
|
+
await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
|
|
160
|
+
await fundAccount({ address: accounts[1].address, token: asset })
|
|
161
|
+
await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
|
|
162
|
+
await fundAccount({ address: accounts[2].address, token: asset })
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('returns 402 when no credential', async () => {
|
|
166
|
+
const { mppx } = createSessionHarness(false)
|
|
167
|
+
|
|
123
168
|
const app = new Hono()
|
|
124
|
-
app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (c) =>
|
|
169
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
|
|
125
170
|
c.json({ data: 'streamed' }),
|
|
126
171
|
)
|
|
127
172
|
|
|
@@ -134,32 +179,10 @@ describe('session', () => {
|
|
|
134
179
|
})
|
|
135
180
|
|
|
136
181
|
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
|
-
})
|
|
182
|
+
const { fetch, mppx } = createSessionHarness(false)
|
|
160
183
|
|
|
161
184
|
const app = new Hono()
|
|
162
|
-
app.get('/', mppx.session({ amount: '1', unitType: 'token' }), (c) =>
|
|
185
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
|
|
163
186
|
c.json({ data: 'streamed' }),
|
|
164
187
|
)
|
|
165
188
|
|
|
@@ -172,4 +195,20 @@ describe('session', () => {
|
|
|
172
195
|
|
|
173
196
|
server.close()
|
|
174
197
|
})
|
|
198
|
+
|
|
199
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
200
|
+
const { fetch, mppx } = createSessionHarness(true)
|
|
201
|
+
|
|
202
|
+
const app = new Hono()
|
|
203
|
+
app.get('/', mppx.session({ amount: '1', currency: asset, unitType: 'token' }), (c) =>
|
|
204
|
+
c.json({ data: 'streamed' }),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const server = await createServer(app)
|
|
208
|
+
const response = await fetch(server.url)
|
|
209
|
+
expect(response.status).toBe(200)
|
|
210
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
211
|
+
|
|
212
|
+
server.close()
|
|
213
|
+
})
|
|
175
214
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as http from 'node:http'
|
|
2
2
|
|
|
3
|
-
import { Receipt } from 'mppx'
|
|
3
|
+
import { Challenge, Credential, Receipt } from 'mppx'
|
|
4
4
|
import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
|
|
5
5
|
import { Mppx, discovery } from 'mppx/nextjs'
|
|
6
6
|
import { tempo as tempo_server } from 'mppx/server'
|
|
@@ -8,7 +8,7 @@ import type { Address } from 'viem'
|
|
|
8
8
|
import { Addresses } from 'viem/tempo'
|
|
9
9
|
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
10
10
|
import { deployEscrow } from '~test/tempo/session.js'
|
|
11
|
-
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
11
|
+
import { accounts, asset, chain, client, fundAccount } from '~test/tempo/viem.js'
|
|
12
12
|
|
|
13
13
|
function createServer(handler: (request: Request) => Promise<Response> | Response) {
|
|
14
14
|
return new Promise<{ url: string; close: () => void }>((resolve) => {
|
|
@@ -36,13 +36,14 @@ function createServer(handler: (request: Request) => Promise<Response> | Respons
|
|
|
36
36
|
|
|
37
37
|
const secretKey = 'test-secret-key'
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
function createChargeHarness(feePayer: boolean) {
|
|
40
40
|
const mppx = Mppx.create({
|
|
41
41
|
methods: [
|
|
42
42
|
tempo_server.charge({
|
|
43
43
|
getClient: () => client,
|
|
44
44
|
currency: asset,
|
|
45
45
|
account: accounts[0],
|
|
46
|
+
...(feePayer ? { feePayer: true } : {}),
|
|
46
47
|
}),
|
|
47
48
|
],
|
|
48
49
|
secretKey,
|
|
@@ -58,7 +59,13 @@ describe('charge', () => {
|
|
|
58
59
|
],
|
|
59
60
|
})
|
|
60
61
|
|
|
62
|
+
return { fetch, mppx }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe('charge', () => {
|
|
61
66
|
test('returns 402 when no credential', async () => {
|
|
67
|
+
const { mppx } = createChargeHarness(false)
|
|
68
|
+
|
|
62
69
|
const handler = mppx.charge({ amount: '1' })(() =>
|
|
63
70
|
Response.json({ fortune: 'You will be rich' }),
|
|
64
71
|
)
|
|
@@ -72,6 +79,8 @@ describe('charge', () => {
|
|
|
72
79
|
})
|
|
73
80
|
|
|
74
81
|
test('returns 200 with receipt on valid payment', async () => {
|
|
82
|
+
const { fetch, mppx } = createChargeHarness(false)
|
|
83
|
+
|
|
75
84
|
const handler = mppx.charge({ amount: '1' })(() =>
|
|
76
85
|
Response.json({ fortune: 'You will be rich' }),
|
|
77
86
|
)
|
|
@@ -90,7 +99,108 @@ describe('charge', () => {
|
|
|
90
99
|
server.close()
|
|
91
100
|
})
|
|
92
101
|
|
|
102
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
103
|
+
const { fetch, mppx } = createChargeHarness(true)
|
|
104
|
+
|
|
105
|
+
const handler = mppx.charge({ amount: '1' })(() =>
|
|
106
|
+
Response.json({ fortune: 'You will be rich' }),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const server = await createServer(handler)
|
|
110
|
+
const response = await fetch(server.url)
|
|
111
|
+
expect(response.status).toBe(200)
|
|
112
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
113
|
+
|
|
114
|
+
server.close()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('zero-amount charge creates a proof credential and receipt', async () => {
|
|
118
|
+
const { fetch, mppx } = createChargeHarness(false)
|
|
119
|
+
|
|
120
|
+
const handler = mppx.charge({ amount: '0' })((request) =>
|
|
121
|
+
Response.json({ payer: request.headers.get('Authorization') }),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const server = await createServer(handler)
|
|
125
|
+
|
|
126
|
+
const challengeResponse = await globalThis.fetch(server.url)
|
|
127
|
+
expect(challengeResponse.status).toBe(402)
|
|
128
|
+
|
|
129
|
+
const response = await fetch(server.url)
|
|
130
|
+
expect(response.status).toBe(200)
|
|
131
|
+
|
|
132
|
+
const body = (await response.json()) as { payer: string }
|
|
133
|
+
const credential = Credential.deserialize<{ signature: string; type: 'proof' }>(body.payer)
|
|
134
|
+
expect(credential.challenge.request.amount).toBe('0')
|
|
135
|
+
expect(credential.payload.type).toBe('proof')
|
|
136
|
+
expect(credential.source).toBe(`did:pkh:eip155:${chain.id}:${accounts[1].address}`)
|
|
137
|
+
|
|
138
|
+
const receipt = Receipt.fromResponse(response)
|
|
139
|
+
expect(receipt.reference).toBe(credential.challenge.id)
|
|
140
|
+
|
|
141
|
+
server.close()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('zero-amount charge with testnet currency omission creates a proof credential', async () => {
|
|
145
|
+
const isTestnet = true
|
|
146
|
+
const mainnetCurrency = '0x20C000000000000000000000b9537d11c60E8b50' as const
|
|
147
|
+
|
|
148
|
+
const mppx = Mppx.create({
|
|
149
|
+
methods: [
|
|
150
|
+
tempo_server.charge({
|
|
151
|
+
account: accounts[0],
|
|
152
|
+
getClient: () => client,
|
|
153
|
+
...(isTestnet ? {} : { currency: mainnetCurrency }),
|
|
154
|
+
recipient: accounts[0].address,
|
|
155
|
+
testnet: isTestnet,
|
|
156
|
+
}),
|
|
157
|
+
],
|
|
158
|
+
secretKey,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const { fetch } = Mppx_client.create({
|
|
162
|
+
polyfill: false,
|
|
163
|
+
methods: [
|
|
164
|
+
tempo_client.charge({
|
|
165
|
+
account: accounts[1],
|
|
166
|
+
getClient: () => client,
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const handler = mppx.charge({ amount: '0', chainId: chain.id })((request) =>
|
|
172
|
+
Response.json({ payer: request.headers.get('Authorization') }),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
const server = await createServer(handler)
|
|
176
|
+
|
|
177
|
+
const challengeResponse = await globalThis.fetch(server.url)
|
|
178
|
+
expect(challengeResponse.status).toBe(402)
|
|
179
|
+
|
|
180
|
+
const challenge = Challenge.fromResponse(challengeResponse, {
|
|
181
|
+
methods: [tempo_client.charge()],
|
|
182
|
+
})
|
|
183
|
+
expect(challenge.request.currency).toBe('0x20c0000000000000000000000000000000000000')
|
|
184
|
+
|
|
185
|
+
const response = await fetch(server.url)
|
|
186
|
+
expect(response.status).toBe(200)
|
|
187
|
+
|
|
188
|
+
const body = (await response.json()) as { payer: string }
|
|
189
|
+
const credential = Credential.deserialize<{ signature: string; type: 'proof' }>(body.payer)
|
|
190
|
+
expect(credential.challenge.request.amount).toBe('0')
|
|
191
|
+
expect(credential.challenge.request.currency).toBe('0x20c0000000000000000000000000000000000000')
|
|
192
|
+
expect(credential.payload.type).toBe('proof')
|
|
193
|
+
expect(credential.source).toBe(`did:pkh:eip155:${chain.id}:${accounts[1].address}`)
|
|
194
|
+
|
|
195
|
+
const receipt = Receipt.fromResponse(response)
|
|
196
|
+
expect(receipt.reference).toBe(credential.challenge.id)
|
|
197
|
+
|
|
198
|
+
server.close()
|
|
199
|
+
})
|
|
200
|
+
|
|
93
201
|
test('serves /openapi.json from a handler-derived route config', async () => {
|
|
202
|
+
const { mppx } = createChargeHarness(false)
|
|
203
|
+
|
|
94
204
|
const pay = mppx.charge({ amount: '1' })
|
|
95
205
|
const server = await createServer(
|
|
96
206
|
discovery(mppx, {
|
|
@@ -119,13 +229,7 @@ describe('charge', () => {
|
|
|
119
229
|
describe('session', () => {
|
|
120
230
|
let escrowContract: Address
|
|
121
231
|
|
|
122
|
-
|
|
123
|
-
escrowContract = await deployEscrow()
|
|
124
|
-
await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
|
|
125
|
-
await fundAccount({ address: accounts[2].address, token: asset })
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
test('returns 402 when no credential', async () => {
|
|
232
|
+
function createSessionHarness(feePayer: boolean) {
|
|
129
233
|
const mppx = Mppx.create({
|
|
130
234
|
methods: [
|
|
131
235
|
tempo_server.session({
|
|
@@ -133,12 +237,38 @@ describe('session', () => {
|
|
|
133
237
|
account: accounts[0],
|
|
134
238
|
currency: asset,
|
|
135
239
|
escrowContract,
|
|
136
|
-
|
|
240
|
+
...(feePayer ? { feePayer: accounts[1] } : {}),
|
|
241
|
+
} as any),
|
|
137
242
|
],
|
|
138
243
|
secretKey,
|
|
139
244
|
})
|
|
140
245
|
|
|
141
|
-
const
|
|
246
|
+
const { fetch } = Mppx_client.create({
|
|
247
|
+
polyfill: false,
|
|
248
|
+
methods: [
|
|
249
|
+
sessionIntent({
|
|
250
|
+
account: accounts[2],
|
|
251
|
+
deposit: '10',
|
|
252
|
+
getClient: () => client,
|
|
253
|
+
}),
|
|
254
|
+
],
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
return { fetch, mppx }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
beforeAll(async () => {
|
|
261
|
+
escrowContract = await deployEscrow()
|
|
262
|
+
await fundAccount({ address: accounts[1].address, token: Addresses.pathUsd })
|
|
263
|
+
await fundAccount({ address: accounts[1].address, token: asset })
|
|
264
|
+
await fundAccount({ address: accounts[2].address, token: Addresses.pathUsd })
|
|
265
|
+
await fundAccount({ address: accounts[2].address, token: asset })
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
test('returns 402 when no credential', async () => {
|
|
269
|
+
const { mppx } = createSessionHarness(false)
|
|
270
|
+
|
|
271
|
+
const handler = mppx.session({ amount: '1', currency: asset, unitType: 'token' })(() =>
|
|
142
272
|
Response.json({ data: 'streamed' }),
|
|
143
273
|
)
|
|
144
274
|
|
|
@@ -151,31 +281,9 @@ describe('session', () => {
|
|
|
151
281
|
})
|
|
152
282
|
|
|
153
283
|
test('returns 200 with receipt on valid payment', async () => {
|
|
154
|
-
const mppx =
|
|
155
|
-
methods: [
|
|
156
|
-
tempo_server.session({
|
|
157
|
-
getClient: () => client,
|
|
158
|
-
account: accounts[0],
|
|
159
|
-
currency: asset,
|
|
160
|
-
escrowContract,
|
|
161
|
-
feePayer: true,
|
|
162
|
-
}),
|
|
163
|
-
],
|
|
164
|
-
secretKey,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const { fetch } = Mppx_client.create({
|
|
168
|
-
polyfill: false,
|
|
169
|
-
methods: [
|
|
170
|
-
sessionIntent({
|
|
171
|
-
account: accounts[2],
|
|
172
|
-
deposit: '10',
|
|
173
|
-
getClient: () => client,
|
|
174
|
-
}),
|
|
175
|
-
],
|
|
176
|
-
})
|
|
284
|
+
const { fetch, mppx } = createSessionHarness(false)
|
|
177
285
|
|
|
178
|
-
const handler = mppx.session({ amount: '1', unitType: 'token' })(() =>
|
|
286
|
+
const handler = mppx.session({ amount: '1', currency: asset, unitType: 'token' })(() =>
|
|
179
287
|
Response.json({ data: 'streamed' }),
|
|
180
288
|
)
|
|
181
289
|
|
|
@@ -192,4 +300,19 @@ describe('session', () => {
|
|
|
192
300
|
|
|
193
301
|
server.close()
|
|
194
302
|
})
|
|
303
|
+
|
|
304
|
+
test('fee payer: returns 200 with receipt on valid payment', async () => {
|
|
305
|
+
const { fetch, mppx } = createSessionHarness(true)
|
|
306
|
+
|
|
307
|
+
const handler = mppx.session({ amount: '1', currency: asset, unitType: 'token' })(() =>
|
|
308
|
+
Response.json({ data: 'streamed' }),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const server = await createServer(handler)
|
|
312
|
+
const response = await fetch(server.url)
|
|
313
|
+
expect(response.status).toBe(200)
|
|
314
|
+
expect(Receipt.fromResponse(response).status).toBe('success')
|
|
315
|
+
|
|
316
|
+
server.close()
|
|
317
|
+
})
|
|
195
318
|
})
|