mppx 0.4.8 → 0.4.10
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 +26 -3
- package/README.md +13 -13
- package/dist/BodyDigest.d.ts.map +1 -1
- package/dist/BodyDigest.js.map +1 -1
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Credential.js.map +1 -1
- package/dist/Errors.js +64 -67
- package/dist/Errors.js.map +1 -1
- package/dist/PaymentRequest.d.ts.map +1 -1
- package/dist/PaymentRequest.js.map +1 -1
- package/dist/Receipt.d.ts.map +1 -1
- package/dist/Receipt.js.map +1 -1
- package/dist/Store.d.ts +9 -0
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +17 -0
- package/dist/Store.js.map +1 -1
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +40 -5
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +157 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/internal.d.ts.map +1 -1
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +2 -1
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +1 -1
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/discovery/Discovery.d.ts +146 -0
- package/dist/discovery/Discovery.d.ts.map +1 -0
- package/dist/discovery/Discovery.js +60 -0
- package/dist/discovery/Discovery.js.map +1 -0
- package/dist/discovery/OpenApi.d.ts +61 -0
- package/dist/discovery/OpenApi.d.ts.map +1 -0
- package/dist/discovery/OpenApi.js +139 -0
- package/dist/discovery/OpenApi.js.map +1 -0
- package/dist/discovery/Validate.d.ts +10 -0
- package/dist/discovery/Validate.d.ts.map +1 -0
- package/dist/discovery/Validate.js +63 -0
- package/dist/discovery/Validate.js.map +1 -0
- package/dist/discovery/index.d.ts +4 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +4 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +1 -1
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
- package/dist/mcp-sdk/server/Transport.js.map +1 -1
- package/dist/middlewares/elysia.d.ts +52 -1
- package/dist/middlewares/elysia.d.ts.map +1 -1
- package/dist/middlewares/elysia.js +17 -0
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.d.ts +13 -1
- package/dist/middlewares/express.d.ts.map +1 -1
- package/dist/middlewares/express.js +23 -2
- package/dist/middlewares/express.js.map +1 -1
- package/dist/middlewares/hono.d.ts +19 -1
- package/dist/middlewares/hono.d.ts.map +1 -1
- package/dist/middlewares/hono.js +51 -0
- package/dist/middlewares/hono.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +4 -2
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +10 -3
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts +11 -0
- package/dist/middlewares/nextjs.d.ts.map +1 -1
- package/dist/middlewares/nextjs.js +15 -0
- package/dist/middlewares/nextjs.js.map +1 -1
- package/dist/proxy/Proxy.d.ts +6 -0
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +56 -80
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/Service.d.ts +16 -23
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +20 -84
- package/dist/proxy/Service.js.map +1 -1
- package/dist/proxy/internal/Route.js +1 -1
- package/dist/proxy/internal/Route.js.map +1 -1
- package/dist/proxy/services/anthropic.d.ts.map +1 -1
- package/dist/proxy/services/anthropic.js +5 -0
- package/dist/proxy/services/anthropic.js.map +1 -1
- package/dist/proxy/services/openai.d.ts.map +1 -1
- package/dist/proxy/services/openai.js +6 -3
- package/dist/proxy/services/openai.js.map +1 -1
- package/dist/proxy/services/stripe.d.ts.map +1 -1
- package/dist/proxy/services/stripe.js +6 -3
- package/dist/proxy/services/stripe.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +35 -17
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Request.d.ts.map +1 -1
- package/dist/server/Request.js.map +1 -1
- package/dist/stripe/Methods.d.ts.map +1 -1
- package/dist/stripe/Methods.js.map +1 -1
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +1 -1
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +1 -1
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +1 -1
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +18 -5
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +8 -0
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +1 -1
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/ChannelStore.js.map +1 -1
- package/dist/tempo/session/Receipt.d.ts.map +1 -1
- package/dist/tempo/session/Receipt.js.map +1 -1
- package/dist/tempo/session/Sse.d.ts.map +1 -1
- package/dist/tempo/session/Sse.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/Voucher.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/BodyDigest.test.ts +1 -1
- package/src/BodyDigest.ts +1 -0
- package/src/Challenge.fuzz.test.ts +121 -0
- package/src/Challenge.test-d.ts +2 -1
- package/src/Challenge.test.ts +1 -1
- package/src/Challenge.ts +1 -0
- package/src/Credential.fuzz.test.ts +62 -0
- package/src/Credential.test.ts +1 -1
- package/src/Credential.ts +1 -0
- package/src/Errors.test.ts +28 -40
- package/src/Expires.test.ts +2 -1
- package/src/Method.test.ts +1 -1
- package/src/PaymentRequest.test.ts +1 -1
- package/src/PaymentRequest.ts +1 -0
- package/src/Receipt.test.ts +1 -1
- package/src/Receipt.ts +1 -0
- package/src/Store.test-d.ts +2 -1
- package/src/Store.test.ts +57 -7
- package/src/Store.ts +25 -0
- package/src/cli/account.ts +65 -30
- package/src/cli/cli.test.ts +215 -2
- package/src/cli/cli.ts +166 -1
- package/src/cli/config.test.ts +1 -0
- package/src/cli/internal.ts +1 -0
- package/src/cli/plugins/stripe.ts +1 -0
- package/src/cli/plugins/tempo.ts +4 -1
- package/src/cli/utils.ts +1 -0
- package/src/client/Mppx.test-d.ts +2 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Transport.test.ts +1 -1
- package/src/client/internal/Fetch.browser.test.ts +2 -1
- package/src/client/internal/Fetch.test-d.ts +2 -1
- package/src/client/internal/Fetch.test.ts +3 -1
- package/src/client/internal/Fetch.ts +1 -1
- package/src/discovery/Discovery.test.ts +152 -0
- package/src/discovery/Discovery.ts +72 -0
- package/src/discovery/OpenApi.test.ts +425 -0
- package/src/discovery/OpenApi.ts +224 -0
- package/src/discovery/Validate.test.ts +188 -0
- package/src/discovery/Validate.ts +76 -0
- package/src/discovery/index.ts +3 -0
- package/src/internal/constantTimeEqual.test.ts +2 -1
- package/src/internal/types.ts +1 -3
- package/src/mcp-sdk/client/McpClient.test-d.ts +2 -1
- package/src/mcp-sdk/client/McpClient.test.ts +2 -1
- package/src/mcp-sdk/client/McpClient.ts +2 -0
- package/src/mcp-sdk/server/Transport.test.ts +2 -1
- package/src/mcp-sdk/server/Transport.ts +1 -0
- package/src/middlewares/elysia.test.ts +28 -2
- package/src/middlewares/elysia.ts +36 -1
- package/src/middlewares/express.test.ts +95 -7
- package/src/middlewares/express.ts +40 -2
- package/src/middlewares/hono.test.ts +28 -6
- package/src/middlewares/hono.ts +74 -1
- package/src/middlewares/internal/mppx.test.ts +2 -1
- package/src/middlewares/internal/mppx.ts +14 -6
- package/src/middlewares/nextjs.test.ts +32 -6
- package/src/middlewares/nextjs.ts +28 -0
- package/src/proxy/Proxy.test.ts +55 -270
- package/src/proxy/Proxy.ts +73 -93
- package/src/proxy/Service.test.ts +24 -1
- package/src/proxy/Service.ts +48 -88
- package/src/proxy/internal/Headers.test.ts +2 -1
- package/src/proxy/internal/Route.test.ts +9 -1
- package/src/proxy/internal/Route.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +132 -0
- package/src/proxy/services/anthropic.ts +5 -0
- package/src/proxy/services/openai.test.ts +2 -1
- package/src/proxy/services/openai.ts +6 -4
- package/src/proxy/services/stripe.test.ts +132 -0
- package/src/proxy/services/stripe.ts +6 -4
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +194 -1
- package/src/server/Mppx.ts +38 -19
- package/src/server/NodeListener.test.ts +1 -1
- package/src/server/Request.test.ts +2 -1
- package/src/server/Request.ts +1 -0
- package/src/server/Response.test.ts +2 -1
- package/src/server/Transport.test.ts +2 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/Methods.test.ts +1 -1
- package/src/stripe/Methods.ts +1 -0
- package/src/stripe/client/Charge.test.ts +2 -1
- package/src/stripe/server/Charge.test.ts +2 -1
- package/src/tempo/Attribution.test.ts +2 -1
- package/src/tempo/Methods.test.ts +1 -1
- package/src/tempo/Methods.ts +1 -0
- package/src/tempo/client/ChannelOps.test.ts +7 -3
- package/src/tempo/client/ChannelOps.ts +1 -0
- package/src/tempo/client/Charge.ts +1 -0
- package/src/tempo/client/Session.test.ts +6 -2
- package/src/tempo/client/Session.ts +1 -0
- package/src/tempo/client/SessionManager.test.ts +29 -1
- package/src/tempo/client/SessionManager.ts +2 -1
- package/src/tempo/internal/auto-swap.test.ts +2 -1
- package/src/tempo/internal/auto-swap.ts +1 -0
- package/src/tempo/internal/defaults.test.ts +2 -1
- package/src/tempo/internal/fee-payer.test.ts +2 -1
- package/src/tempo/internal/fee-payer.ts +1 -0
- package/src/tempo/server/Charge.test.ts +2 -1
- package/src/tempo/server/Charge.ts +1 -0
- package/src/tempo/server/Session.test.ts +88 -37
- package/src/tempo/server/Session.ts +26 -8
- package/src/tempo/server/Sse.test.ts +2 -1
- package/src/tempo/server/internal/transport.test.ts +25 -1
- package/src/tempo/server/internal/transport.ts +11 -0
- package/src/tempo/session/Chain.test.ts +6 -2
- package/src/tempo/session/Chain.ts +2 -1
- package/src/tempo/session/Channel.test.ts +2 -1
- package/src/tempo/session/ChannelStore.test.ts +2 -1
- package/src/tempo/session/ChannelStore.ts +1 -0
- package/src/tempo/session/Receipt.test.ts +2 -1
- package/src/tempo/session/Receipt.ts +1 -0
- package/src/tempo/session/Sse.fuzz.test.ts +138 -0
- package/src/tempo/session/Sse.test.ts +2 -1
- package/src/tempo/session/Sse.ts +1 -0
- package/src/tempo/session/Voucher.test.ts +2 -1
- package/src/tempo/session/Voucher.ts +1 -0
- package/src/viem/Account.test.ts +2 -1
- package/src/viem/Client.test.ts +2 -1
- package/src/viem/Client.ts +1 -0
- package/src/zod.test.ts +147 -0
|
@@ -4,7 +4,10 @@ import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
|
|
|
4
4
|
import { type Address, createClient, type Hex } from 'viem'
|
|
5
5
|
import { waitForTransactionReceipt } from 'viem/actions'
|
|
6
6
|
import { Addresses } from 'viem/tempo'
|
|
7
|
-
import { beforeAll, beforeEach, describe, expect, test } from '
|
|
7
|
+
import { beforeAll, beforeEach, describe, expect, expectTypeOf, test } from 'vp/test'
|
|
8
|
+
import { nodeEnv } from '~test/config.js'
|
|
9
|
+
|
|
10
|
+
const isLocalnet = nodeEnv === 'localnet'
|
|
8
11
|
import {
|
|
9
12
|
deployEscrow,
|
|
10
13
|
requestCloseChannel,
|
|
@@ -13,12 +16,12 @@ import {
|
|
|
13
16
|
topUpChannel,
|
|
14
17
|
} from '~test/tempo/session.js'
|
|
15
18
|
import { accounts, asset, chain, client, fundAccount, http } from '~test/tempo/viem.js'
|
|
19
|
+
|
|
16
20
|
import {
|
|
17
21
|
ChannelClosedError,
|
|
18
22
|
ChannelNotFoundError,
|
|
19
23
|
InsufficientBalanceError,
|
|
20
24
|
InvalidSignatureError,
|
|
21
|
-
VerificationFailedError,
|
|
22
25
|
} from '../../Errors.js'
|
|
23
26
|
import * as Store from '../../Store.js'
|
|
24
27
|
import {
|
|
@@ -32,6 +35,7 @@ import { signVoucher } from '../session/Voucher.js'
|
|
|
32
35
|
import { charge, session, settle } from './Session.js'
|
|
33
36
|
|
|
34
37
|
const payer = accounts[2]
|
|
38
|
+
const recipientAccount = accounts[0]
|
|
35
39
|
const recipient = accounts[0].address
|
|
36
40
|
const currency = asset
|
|
37
41
|
|
|
@@ -39,12 +43,13 @@ let escrowContract: Address
|
|
|
39
43
|
let saltCounter = 0
|
|
40
44
|
|
|
41
45
|
beforeAll(async () => {
|
|
46
|
+
if (!isLocalnet) return
|
|
42
47
|
escrowContract = await deployEscrow()
|
|
43
48
|
await fundAccount({ address: payer.address, token: Addresses.pathUsd })
|
|
44
49
|
await fundAccount({ address: payer.address, token: currency })
|
|
45
50
|
})
|
|
46
51
|
|
|
47
|
-
describe('session', () => {
|
|
52
|
+
describe.runIf(isLocalnet)('session', () => {
|
|
48
53
|
let rawStore: Store.Store
|
|
49
54
|
let store: ChannelStore.ChannelStore
|
|
50
55
|
|
|
@@ -57,7 +62,7 @@ describe('session', () => {
|
|
|
57
62
|
return session({
|
|
58
63
|
store: rawStore,
|
|
59
64
|
getClient: () => client,
|
|
60
|
-
account:
|
|
65
|
+
account: recipientAccount,
|
|
61
66
|
currency,
|
|
62
67
|
escrowContract,
|
|
63
68
|
chainId: chain.id,
|
|
@@ -617,7 +622,47 @@ describe('session', () => {
|
|
|
617
622
|
).rejects.toThrow(InvalidSignatureError)
|
|
618
623
|
})
|
|
619
624
|
|
|
620
|
-
test('
|
|
625
|
+
test('accepts exact replay of already-verified voucher as idempotent', async () => {
|
|
626
|
+
const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
|
|
627
|
+
const server = createServer()
|
|
628
|
+
await openServerChannel(server, channelId, serializedTransaction)
|
|
629
|
+
|
|
630
|
+
const payload = {
|
|
631
|
+
action: 'voucher' as const,
|
|
632
|
+
channelId,
|
|
633
|
+
cumulativeAmount: '2000000',
|
|
634
|
+
signature: await signTestVoucher(channelId, 2000000n),
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
await server.verify({
|
|
638
|
+
credential: {
|
|
639
|
+
challenge: makeChallenge({ id: 'challenge-2', channelId }),
|
|
640
|
+
payload,
|
|
641
|
+
},
|
|
642
|
+
request: makeRequest(),
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
const channelAfterFirstAccept = await store.getChannel(channelId)
|
|
646
|
+
|
|
647
|
+
const replayReceipt = (await server.verify({
|
|
648
|
+
credential: {
|
|
649
|
+
challenge: makeChallenge({ id: 'challenge-3', channelId }),
|
|
650
|
+
payload,
|
|
651
|
+
},
|
|
652
|
+
request: makeRequest(),
|
|
653
|
+
})) as SessionReceipt
|
|
654
|
+
|
|
655
|
+
expect(replayReceipt.status).toBe('success')
|
|
656
|
+
expect(replayReceipt.acceptedCumulative).toBe('2000000')
|
|
657
|
+
expect(replayReceipt.spent).toBe(channelAfterFirstAccept!.spent.toString())
|
|
658
|
+
expect(replayReceipt.units).toBe(channelAfterFirstAccept!.units)
|
|
659
|
+
|
|
660
|
+
const channelAfterReplay = await store.getChannel(channelId)
|
|
661
|
+
expect(channelAfterReplay).toEqual(channelAfterFirstAccept)
|
|
662
|
+
expect(channelAfterReplay!.highestVoucherAmount).toBe(2000000n)
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
test('rejects exact replay with invalid signature', async () => {
|
|
621
666
|
const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
|
|
622
667
|
const server = createServer()
|
|
623
668
|
await openServerChannel(server, channelId, serializedTransaction)
|
|
@@ -641,11 +686,14 @@ describe('session', () => {
|
|
|
641
686
|
server.verify({
|
|
642
687
|
credential: {
|
|
643
688
|
challenge: makeChallenge({ id: 'challenge-3', channelId }),
|
|
644
|
-
payload
|
|
689
|
+
payload: {
|
|
690
|
+
...payload,
|
|
691
|
+
signature: `0x${'ab'.repeat(65)}` as Hex,
|
|
692
|
+
},
|
|
645
693
|
},
|
|
646
694
|
request: makeRequest(),
|
|
647
695
|
}),
|
|
648
|
-
).rejects.toThrow(
|
|
696
|
+
).rejects.toThrow(InvalidSignatureError)
|
|
649
697
|
})
|
|
650
698
|
|
|
651
699
|
test('rejects replayed voucher at settled amount after on-chain settlement', async () => {
|
|
@@ -1190,27 +1238,29 @@ describe('session', () => {
|
|
|
1190
1238
|
expect(ch!.finalized).toBe(true)
|
|
1191
1239
|
})
|
|
1192
1240
|
|
|
1193
|
-
test('
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1241
|
+
test('session() throws at initialization when no account provided', () => {
|
|
1242
|
+
expect(() =>
|
|
1243
|
+
session({
|
|
1244
|
+
store: rawStore,
|
|
1245
|
+
getClient: () => client,
|
|
1246
|
+
account: recipient as Address,
|
|
1247
|
+
currency,
|
|
1248
|
+
escrowContract,
|
|
1249
|
+
chainId: chain.id,
|
|
1250
|
+
} as session.Parameters),
|
|
1251
|
+
).toThrow('tempo.session() requires an `account`')
|
|
1252
|
+
})
|
|
1253
|
+
|
|
1254
|
+
test('session() throws at initialization with no account at all', () => {
|
|
1255
|
+
expect(() =>
|
|
1256
|
+
session({
|
|
1257
|
+
store: rawStore,
|
|
1258
|
+
getClient: () => client,
|
|
1259
|
+
currency,
|
|
1260
|
+
escrowContract,
|
|
1261
|
+
chainId: chain.id,
|
|
1262
|
+
} as session.Parameters),
|
|
1263
|
+
).toThrow('tempo.session() requires an `account`')
|
|
1214
1264
|
})
|
|
1215
1265
|
})
|
|
1216
1266
|
|
|
@@ -2207,6 +2257,7 @@ describe('monotonicity and TOCTOU (unit tests)', () => {
|
|
|
2207
2257
|
})
|
|
2208
2258
|
|
|
2209
2259
|
describe('session default currency resolution', () => {
|
|
2260
|
+
const mockAccount = accounts[0]
|
|
2210
2261
|
const mockClient = createClient({ transport: http('http://localhost:1') })
|
|
2211
2262
|
const mockMainnetClient = createClient({
|
|
2212
2263
|
chain: {
|
|
@@ -2231,7 +2282,7 @@ describe('session default currency resolution', () => {
|
|
|
2231
2282
|
const server = session({
|
|
2232
2283
|
store: Store.memory(),
|
|
2233
2284
|
getClient: () => mockClient,
|
|
2234
|
-
account:
|
|
2285
|
+
account: mockAccount,
|
|
2235
2286
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2236
2287
|
} as session.Parameters)
|
|
2237
2288
|
expect(server.defaults?.currency).toBe('0x20C000000000000000000000b9537d11c60E8b50')
|
|
@@ -2241,7 +2292,7 @@ describe('session default currency resolution', () => {
|
|
|
2241
2292
|
const server = session({
|
|
2242
2293
|
store: Store.memory(),
|
|
2243
2294
|
getClient: () => mockClient,
|
|
2244
|
-
account:
|
|
2295
|
+
account: mockAccount,
|
|
2245
2296
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2246
2297
|
testnet: true,
|
|
2247
2298
|
} as session.Parameters)
|
|
@@ -2252,7 +2303,7 @@ describe('session default currency resolution', () => {
|
|
|
2252
2303
|
const server = session({
|
|
2253
2304
|
store: Store.memory(),
|
|
2254
2305
|
getClient: () => mockClient,
|
|
2255
|
-
account:
|
|
2306
|
+
account: mockAccount,
|
|
2256
2307
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2257
2308
|
chainId: 69420,
|
|
2258
2309
|
} as session.Parameters)
|
|
@@ -2263,7 +2314,7 @@ describe('session default currency resolution', () => {
|
|
|
2263
2314
|
const server = session({
|
|
2264
2315
|
store: Store.memory(),
|
|
2265
2316
|
getClient: () => mockClient,
|
|
2266
|
-
account:
|
|
2317
|
+
account: mockAccount,
|
|
2267
2318
|
currency: '0xcustom',
|
|
2268
2319
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2269
2320
|
chainId: 4217,
|
|
@@ -2276,7 +2327,7 @@ describe('session default currency resolution', () => {
|
|
|
2276
2327
|
const server = session({
|
|
2277
2328
|
store: Store.memory(),
|
|
2278
2329
|
getClient: () => mockClient,
|
|
2279
|
-
account:
|
|
2330
|
+
account: mockAccount,
|
|
2280
2331
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2281
2332
|
chainId: 42431,
|
|
2282
2333
|
} as session.Parameters)
|
|
@@ -2289,7 +2340,7 @@ describe('session default currency resolution', () => {
|
|
|
2289
2340
|
tempo_server.session({
|
|
2290
2341
|
store: Store.memory(),
|
|
2291
2342
|
getClient: () => mockMainnetClient,
|
|
2292
|
-
account:
|
|
2343
|
+
account: mockAccount,
|
|
2293
2344
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2294
2345
|
chainId: 4217,
|
|
2295
2346
|
testnet: false,
|
|
@@ -2316,7 +2367,7 @@ describe('session default currency resolution', () => {
|
|
|
2316
2367
|
tempo_server.session({
|
|
2317
2368
|
store: Store.memory(),
|
|
2318
2369
|
getClient: () => mockTestnetClient,
|
|
2319
|
-
account:
|
|
2370
|
+
account: mockAccount,
|
|
2320
2371
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2321
2372
|
testnet: true,
|
|
2322
2373
|
}),
|
|
@@ -2343,7 +2394,7 @@ describe('session default currency resolution', () => {
|
|
|
2343
2394
|
tempo_server.session({
|
|
2344
2395
|
store: Store.memory(),
|
|
2345
2396
|
getClient: () => mockTestnetClient,
|
|
2346
|
-
account:
|
|
2397
|
+
account: mockAccount,
|
|
2347
2398
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2348
2399
|
chainId: 69420,
|
|
2349
2400
|
}),
|
|
@@ -2369,7 +2420,7 @@ describe('session default currency resolution', () => {
|
|
|
2369
2420
|
tempo_server.session({
|
|
2370
2421
|
store: Store.memory(),
|
|
2371
2422
|
getClient: () => mockClient,
|
|
2372
|
-
account:
|
|
2423
|
+
account: mockAccount,
|
|
2373
2424
|
currency: '0xcustom',
|
|
2374
2425
|
escrowContract: '0x0000000000000000000000000000000000000002',
|
|
2375
2426
|
chainId: 4217,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type Client as viem_Client,
|
|
19
19
|
} from 'viem'
|
|
20
20
|
import { tempo as tempo_chain } from 'viem/chains'
|
|
21
|
+
|
|
21
22
|
import {
|
|
22
23
|
AmountExceedsDepositError,
|
|
23
24
|
BadRequestError,
|
|
@@ -100,6 +101,11 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
100
101
|
|
|
101
102
|
const { account, recipient, feePayer, feePayerUrl } = Account.resolve(parameters)
|
|
102
103
|
|
|
104
|
+
if (!account)
|
|
105
|
+
throw new Error(
|
|
106
|
+
'tempo.session() requires an `account` (viem Account, e.g. privateKeyToAccount("0x...")). An address string is not sufficient — the server needs a signing account for on-chain channel close and settlement.',
|
|
107
|
+
)
|
|
108
|
+
|
|
103
109
|
const getClient = Client.getResolver({
|
|
104
110
|
chain: tempo_chain,
|
|
105
111
|
feePayerUrl,
|
|
@@ -461,19 +467,12 @@ async function verifyAndAcceptVoucher(parameters: {
|
|
|
461
467
|
throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
|
|
462
468
|
}
|
|
463
469
|
|
|
464
|
-
if (voucher.cumulativeAmount
|
|
470
|
+
if (voucher.cumulativeAmount < channel.highestVoucherAmount) {
|
|
465
471
|
throw new VerificationFailedError({
|
|
466
472
|
reason: 'voucher cumulativeAmount must be strictly greater than highest accepted voucher',
|
|
467
473
|
})
|
|
468
474
|
}
|
|
469
475
|
|
|
470
|
-
const delta = voucher.cumulativeAmount - channel.highestVoucherAmount
|
|
471
|
-
if (delta < minVoucherDelta) {
|
|
472
|
-
throw new DeltaTooSmallError({
|
|
473
|
-
reason: `voucher delta ${delta} below minimum ${minVoucherDelta}`,
|
|
474
|
-
})
|
|
475
|
-
}
|
|
476
|
-
|
|
477
476
|
const isValid = await verifyVoucher(
|
|
478
477
|
methodDetails.escrowContract,
|
|
479
478
|
methodDetails.chainId,
|
|
@@ -485,6 +484,25 @@ async function verifyAndAcceptVoucher(parameters: {
|
|
|
485
484
|
throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
486
485
|
}
|
|
487
486
|
|
|
487
|
+
// Idempotent replay: equal cumulative voucher is accepted without
|
|
488
|
+
// advancing channel state or charging additional value.
|
|
489
|
+
if (voucher.cumulativeAmount === channel.highestVoucherAmount) {
|
|
490
|
+
return createSessionReceipt({
|
|
491
|
+
challengeId: challenge.id,
|
|
492
|
+
channelId,
|
|
493
|
+
acceptedCumulative: channel.highestVoucherAmount,
|
|
494
|
+
spent: channel.spent,
|
|
495
|
+
units: channel.units,
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const delta = voucher.cumulativeAmount - channel.highestVoucherAmount
|
|
500
|
+
if (delta < minVoucherDelta) {
|
|
501
|
+
throw new DeltaTooSmallError({
|
|
502
|
+
reason: `voucher delta ${delta} below minimum ${minVoucherDelta}`,
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
488
506
|
const updated = await store.updateChannel(channelId, (current) => {
|
|
489
507
|
if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
490
508
|
if (voucher.cumulativeAmount > current.highestVoucherAmount) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Address, Hex } from 'viem'
|
|
2
|
-
import { describe, expect, test } from '
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
3
4
|
import { chainId, escrowContract as escrowContractDefaults } from '../internal/defaults.js'
|
|
4
5
|
import type * as ChannelStore from '../session/ChannelStore.js'
|
|
5
6
|
import { serve, toResponse } from '../session/Sse.js'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Challenge, Credential } from 'mppx'
|
|
2
2
|
import type { Address, Hex } from 'viem'
|
|
3
|
-
import { describe, expect, test } from '
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
|
|
4
5
|
import * as Store from '../../../Store.js'
|
|
5
6
|
import { chainId, escrowContract as escrowContractDefaults } from '../../internal/defaults.js'
|
|
6
7
|
import * as ChannelStore from '../../session/ChannelStore.js'
|
|
@@ -291,6 +292,29 @@ describe('sse transport', () => {
|
|
|
291
292
|
expect(response.headers.get('Payment-Receipt')).toBeTruthy()
|
|
292
293
|
})
|
|
293
294
|
|
|
295
|
+
test('respondReceipt with 204 management response keeps null body and receipt', async () => {
|
|
296
|
+
const store = memoryStore()
|
|
297
|
+
await seedChannel(store, 10000000n)
|
|
298
|
+
const transport = sse({ store })
|
|
299
|
+
|
|
300
|
+
transport.getCredential(makeAuthorizedRequest())
|
|
301
|
+
|
|
302
|
+
const managementResponse = new Response(null, { status: 204 })
|
|
303
|
+
const response = transport.respondReceipt({
|
|
304
|
+
receipt: makeReceipt(),
|
|
305
|
+
response: managementResponse,
|
|
306
|
+
challengeId,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
expect(response.status).toBe(204)
|
|
310
|
+
expect(await response.text()).toBe('')
|
|
311
|
+
expect(response.headers.get('Payment-Receipt')).toBeTruthy()
|
|
312
|
+
|
|
313
|
+
const channel = await store.getChannel(channelId)
|
|
314
|
+
expect(channel!.spent).toBe(0n)
|
|
315
|
+
expect(channel!.units).toBe(0)
|
|
316
|
+
})
|
|
317
|
+
|
|
294
318
|
test('poll: true strips waitForUpdate from store', async () => {
|
|
295
319
|
const store = memoryStore()
|
|
296
320
|
;(store as any).waitForUpdate = async () => {}
|
|
@@ -100,6 +100,13 @@ export function sse(options: sse.Options & { store: ChannelStore.ChannelStore })
|
|
|
100
100
|
const ctx = contextMap.get(challengeId)
|
|
101
101
|
if (ctx) {
|
|
102
102
|
contextMap.delete(challengeId)
|
|
103
|
+
|
|
104
|
+
// Null-body statuses (e.g. 204 from management actions) cannot carry a
|
|
105
|
+
// response body per Fetch/HTTP semantics.
|
|
106
|
+
if (isNullBodyStatus(baseResponse.status)) {
|
|
107
|
+
return baseResponse
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
const stream = new ReadableStream<Uint8Array>({
|
|
104
111
|
async start(controller) {
|
|
105
112
|
// deduction completes before consumer reads
|
|
@@ -191,3 +198,7 @@ function isAsyncGeneratorFunction(
|
|
|
191
198
|
function isAsyncIterable(value: unknown): value is AsyncIterable<string> {
|
|
192
199
|
return value !== null && typeof value === 'object' && Symbol.asyncIterator in (value as object)
|
|
193
200
|
}
|
|
201
|
+
|
|
202
|
+
function isNullBodyStatus(status: number): boolean {
|
|
203
|
+
return [101, 204, 205, 304].includes(status)
|
|
204
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type Address, encodeFunctionData, erc20Abi, type Hex } from 'viem'
|
|
2
2
|
import { waitForTransactionReceipt } from 'viem/actions'
|
|
3
3
|
import { Addresses, Transaction } from 'viem/tempo'
|
|
4
|
-
import { beforeAll, describe, expect, test } from '
|
|
4
|
+
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
5
|
+
import { nodeEnv } from '~test/config.js'
|
|
5
6
|
import {
|
|
6
7
|
closeChannelOnChain,
|
|
7
8
|
deployEscrow,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
topUpChannel,
|
|
12
13
|
} from '~test/tempo/session.js'
|
|
13
14
|
import { accounts, asset, chain, client, fundAccount } from '~test/tempo/viem.js'
|
|
15
|
+
|
|
14
16
|
import {
|
|
15
17
|
broadcastOpenTransaction,
|
|
16
18
|
broadcastTopUpTransaction,
|
|
@@ -21,6 +23,8 @@ import {
|
|
|
21
23
|
} from './Chain.js'
|
|
22
24
|
import { signVoucher } from './Voucher.js'
|
|
23
25
|
|
|
26
|
+
const isLocalnet = nodeEnv === 'localnet'
|
|
27
|
+
|
|
24
28
|
const UINT128_MAX = 2n ** 128n - 1n
|
|
25
29
|
|
|
26
30
|
describe('assertUint128 (via settleOnChain / closeOnChain)', () => {
|
|
@@ -69,7 +73,7 @@ describe('assertUint128 (via settleOnChain / closeOnChain)', () => {
|
|
|
69
73
|
})
|
|
70
74
|
})
|
|
71
75
|
|
|
72
|
-
describe('on-chain', () => {
|
|
76
|
+
describe.runIf(isLocalnet)('on-chain', () => {
|
|
73
77
|
const payer = accounts[2]
|
|
74
78
|
const recipient = accounts[0].address
|
|
75
79
|
const currency = asset
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
writeContract,
|
|
20
20
|
} from 'viem/actions'
|
|
21
21
|
import { Transaction } from 'viem/tempo'
|
|
22
|
+
|
|
22
23
|
import { BadRequestError, ChannelClosedError, VerificationFailedError } from '../../Errors.js'
|
|
23
24
|
import * as TempoAddress from '../internal/address.js'
|
|
24
25
|
import * as defaults from '../internal/defaults.js'
|
|
@@ -131,7 +132,7 @@ export async function closeOnChain(
|
|
|
131
132
|
const resolved = account ?? client.account
|
|
132
133
|
if (!resolved)
|
|
133
134
|
throw new Error(
|
|
134
|
-
'Cannot close channel: no account available.
|
|
135
|
+
'Cannot close channel: no account available. Pass an `account` (viem Account, e.g. privateKeyToAccount("0x...")) to tempo.session(), or provide a `getClient` that returns an account-bearing client.',
|
|
135
136
|
)
|
|
136
137
|
const args = [voucher.channelId, voucher.cumulativeAmount, voucher.signature] as const
|
|
137
138
|
if (feePayer) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Address, Hex } from 'viem'
|
|
2
|
-
import { describe, expect, test } from '
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
3
4
|
import * as Store from '../../Store.js'
|
|
4
5
|
import { chainId, escrowContract as escrowContractDefaults } from '../internal/defaults.js'
|
|
5
6
|
import * as ChannelStore from './ChannelStore.js'
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as fc from 'fast-check'
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
import * as Sse from './Sse.js'
|
|
5
|
+
|
|
6
|
+
function createChunkedResponse(chunks: Uint8Array[]): Response {
|
|
7
|
+
let index = 0
|
|
8
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
9
|
+
pull(controller) {
|
|
10
|
+
if (index < chunks.length) {
|
|
11
|
+
controller.enqueue(chunks[index]!)
|
|
12
|
+
index++
|
|
13
|
+
} else {
|
|
14
|
+
controller.close()
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
return new Response(stream, {
|
|
19
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function splitAtPositions(str: string, positions: number[]): Uint8Array[] {
|
|
24
|
+
const encoder = new TextEncoder()
|
|
25
|
+
const sorted = [...new Set([0, ...positions, str.length])]
|
|
26
|
+
.filter((p) => p >= 0 && p <= str.length)
|
|
27
|
+
.sort((a, b) => a - b)
|
|
28
|
+
const chunks: Uint8Array[] = []
|
|
29
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
30
|
+
const chunk = str.slice(sorted[i], sorted[i + 1])
|
|
31
|
+
if (chunk.length > 0) chunks.push(encoder.encode(chunk))
|
|
32
|
+
}
|
|
33
|
+
return chunks
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function collectData(response: Response): Promise<string[]> {
|
|
37
|
+
const results: string[] = []
|
|
38
|
+
for await (const data of Sse.iterateData(response)) {
|
|
39
|
+
results.push(data)
|
|
40
|
+
}
|
|
41
|
+
return results
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('parseEvent', () => {
|
|
45
|
+
test('never throws on arbitrary message-type input', () => {
|
|
46
|
+
fc.assert(
|
|
47
|
+
fc.property(fc.string(), (input) => {
|
|
48
|
+
const result = Sse.parseEvent(input)
|
|
49
|
+
if (result !== null) {
|
|
50
|
+
expect(result.type).toBe('message')
|
|
51
|
+
expect(typeof result.data).toBe('string')
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
{ numRuns: 10_000 },
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('parseEvent with valid SSE format', () => {
|
|
59
|
+
const sseMessageArb = fc
|
|
60
|
+
.array(
|
|
61
|
+
fc.string().filter((s) => !s.includes('\n')),
|
|
62
|
+
{ minLength: 1, maxLength: 5 },
|
|
63
|
+
)
|
|
64
|
+
.map((lines) => lines.map((l) => `data: ${l}`).join('\n'))
|
|
65
|
+
|
|
66
|
+
fc.assert(
|
|
67
|
+
fc.property(sseMessageArb, (raw) => {
|
|
68
|
+
const result = Sse.parseEvent(raw)
|
|
69
|
+
expect(result).not.toBeNull()
|
|
70
|
+
expect(result!.type).toBe('message')
|
|
71
|
+
const expectedData = raw
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((l) => l.slice(6))
|
|
74
|
+
.join('\n')
|
|
75
|
+
expect(result!.data).toBe(expectedData)
|
|
76
|
+
}),
|
|
77
|
+
{ numRuns: 5_000 },
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('iterateData', () => {
|
|
83
|
+
const sseEventArb = fc
|
|
84
|
+
.array(
|
|
85
|
+
fc.string().filter((s) => !s.includes('\n\n') && !s.includes('\n')),
|
|
86
|
+
{ minLength: 1, maxLength: 3 },
|
|
87
|
+
)
|
|
88
|
+
.map((lines) => lines.map((l) => `data: ${l}`).join('\n'))
|
|
89
|
+
|
|
90
|
+
const sseStreamArb = fc
|
|
91
|
+
.array(sseEventArb, { minLength: 1, maxLength: 5 })
|
|
92
|
+
.map((events) => events.join('\n\n') + '\n\n')
|
|
93
|
+
|
|
94
|
+
test('chunk boundary invariance', async () => {
|
|
95
|
+
await fc.assert(
|
|
96
|
+
fc.asyncProperty(
|
|
97
|
+
sseStreamArb,
|
|
98
|
+
fc.array(fc.nat(), { minLength: 1, maxLength: 10 }),
|
|
99
|
+
async (stream, positions) => {
|
|
100
|
+
const encoder = new TextEncoder()
|
|
101
|
+
|
|
102
|
+
const singleChunk = createChunkedResponse([encoder.encode(stream)])
|
|
103
|
+
const singleResult = await collectData(singleChunk)
|
|
104
|
+
|
|
105
|
+
const boundedPositions = positions.map((p) => p % (stream.length + 1))
|
|
106
|
+
const chunks = splitAtPositions(stream, boundedPositions)
|
|
107
|
+
const multiChunk = createChunkedResponse(chunks)
|
|
108
|
+
const multiResult = await collectData(multiChunk)
|
|
109
|
+
|
|
110
|
+
expect(multiResult).toEqual(singleResult)
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
{ numRuns: 1_000 },
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('iterateData never throws on arbitrary chunked input', async () => {
|
|
118
|
+
await fc.assert(
|
|
119
|
+
fc.asyncProperty(
|
|
120
|
+
fc.array(fc.uint8Array({ minLength: 1, maxLength: 100 }), {
|
|
121
|
+
minLength: 1,
|
|
122
|
+
maxLength: 5,
|
|
123
|
+
}),
|
|
124
|
+
async (chunks) => {
|
|
125
|
+
const response = createChunkedResponse(chunks)
|
|
126
|
+
const results: string[] = []
|
|
127
|
+
for await (const data of Sse.iterateData(response)) {
|
|
128
|
+
results.push(data)
|
|
129
|
+
}
|
|
130
|
+
for (const item of results) {
|
|
131
|
+
expect(typeof item).toBe('string')
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
),
|
|
135
|
+
{ numRuns: 5_000 },
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Address, Hex } from 'viem'
|
|
2
|
-
import { describe, expect, test } from '
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
3
4
|
import { chainId, escrowContract as escrowContractDefaults } from '../internal/defaults.js'
|
|
4
5
|
import type * as ChannelStore from './ChannelStore.js'
|
|
5
6
|
import { formatNeedVoucherEvent, formatReceiptEvent, parseEvent, serve } from './Sse.js'
|
package/src/tempo/session/Sse.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createClient, http } from 'viem'
|
|
2
2
|
import { privateKeyToAccount } from 'viem/accounts'
|
|
3
|
-
import { describe, expect, test } from '
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
|
|
4
5
|
import { parseVoucherFromPayload, signVoucher, verifyVoucher } from './Voucher.js'
|
|
5
6
|
|
|
6
7
|
const account = privateKeyToAccount(
|
|
@@ -3,6 +3,7 @@ import { SignatureEnvelope } from 'ox/tempo'
|
|
|
3
3
|
import type { Account, Client, Hex } from 'viem'
|
|
4
4
|
import { recoverTypedDataAddress } from 'viem'
|
|
5
5
|
import { signTypedData } from 'viem/actions'
|
|
6
|
+
|
|
6
7
|
import * as TempoAddress from '../internal/address.js'
|
|
7
8
|
import type { SignedVoucher, Voucher } from './Types.js'
|
|
8
9
|
|