Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/session-require-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

`tempo.session()` now throws immediately at initialization if no viem `Account` is provided, instead of failing later with an opaque error during channel close. The error message includes an example fix.
1 change: 1 addition & 0 deletions src/client/internal/Fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const server = Mppx_server.create({
methods: [
tempo_server({
getClient: () => client,
account: accounts[0],
}),
],
realm,
Expand Down
10 changes: 5 additions & 5 deletions src/middlewares/express.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('charge', () => {
tempo_server({
getClient: () => client,
currency: asset,
recipient: accounts[0].address,
account: accounts[0],
}),
],
secretKey,
Expand Down Expand Up @@ -97,7 +97,7 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
}),
Expand All @@ -123,10 +123,10 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
feePayer: accounts[0],
feePayer: true,
}),
],
secretKey,
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('payment', () => {
tempo_server({
getClient: () => client,
currency: asset,
recipient: accounts[0].address,
account: accounts[0],
}),
],
secretKey,
Expand Down
8 changes: 4 additions & 4 deletions src/middlewares/hono.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('charge', () => {
tempo_server.charge({
getClient: () => client,
currency: asset,
recipient: accounts[0].address,
account: accounts[0],
}),
],
secretKey,
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
}),
Expand All @@ -116,10 +116,10 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
feePayer: accounts[0],
feePayer: true,
}),
],
secretKey,
Expand Down
8 changes: 4 additions & 4 deletions src/middlewares/nextjs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('charge', () => {
tempo_server.charge({
getClient: () => client,
currency: asset,
recipient: accounts[0].address,
account: accounts[0],
}),
],
secretKey,
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
}),
Expand All @@ -130,10 +130,10 @@ describe('session', () => {
methods: [
tempo_server.session({
getClient: () => client,
recipient: accounts[0].address,
account: accounts[0],
currency: asset,
escrowContract,
feePayer: accounts[0],
feePayer: true,
}),
],
secretKey,
Expand Down
1 change: 1 addition & 0 deletions src/server/Mppx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const secretKey = 'test-secret-key'

const method = tempo({
getClient: () => client,
account: accounts[0],
})

describe('create', () => {
Expand Down
66 changes: 35 additions & 31 deletions src/tempo/server/Session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { signVoucher } from '../session/Voucher.js'
import { charge, session, settle } from './Session.js'

const payer = accounts[2]
const recipientAccount = accounts[0]
const recipient = accounts[0].address
const currency = asset

Expand All @@ -61,7 +62,7 @@ describe.runIf(isLocalnet)('session', () => {
return session({
store: rawStore,
getClient: () => client,
account: recipient,
account: recipientAccount,
currency,
escrowContract,
chainId: chain.id,
Expand Down Expand Up @@ -1237,27 +1238,29 @@ describe.runIf(isLocalnet)('session', () => {
expect(ch!.finalized).toBe(true)
})

test('close throws when client has no account', async () => {
const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
const server = createServer({
getClient: () => createClient({ chain, transport: http() }),
})
await openServerChannel(server, channelId, serializedTransaction)

await expect(
server.verify({
credential: {
challenge: makeChallenge({ id: 'challenge-2', channelId }),
payload: {
action: 'close' as const,
channelId,
cumulativeAmount: '1000000',
signature: await signTestVoucher(channelId, 1000000n),
},
},
request: makeRequest(),
}),
).rejects.toThrow('Cannot close channel: no account available')
test('session() throws at initialization when no account provided', () => {
expect(() =>
session({
store: rawStore,
getClient: () => client,
account: recipient as Address,
currency,
escrowContract,
chainId: chain.id,
} as session.Parameters),
).toThrow('tempo.session() requires an `account`')
})

test('session() throws at initialization with no account at all', () => {
expect(() =>
session({
store: rawStore,
getClient: () => client,
currency,
escrowContract,
chainId: chain.id,
} as session.Parameters),
).toThrow('tempo.session() requires an `account`')
})
})

Expand Down Expand Up @@ -2254,6 +2257,7 @@ describe('monotonicity and TOCTOU (unit tests)', () => {
})

describe('session default currency resolution', () => {
const mockAccount = accounts[0]
const mockClient = createClient({ transport: http('http://localhost:1') })
const mockMainnetClient = createClient({
chain: {
Expand All @@ -2278,7 +2282,7 @@ describe('session default currency resolution', () => {
const server = session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
} as session.Parameters)
expect(server.defaults?.currency).toBe('0x20C000000000000000000000b9537d11c60E8b50')
Expand All @@ -2288,7 +2292,7 @@ describe('session default currency resolution', () => {
const server = session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
testnet: true,
} as session.Parameters)
Expand All @@ -2299,7 +2303,7 @@ describe('session default currency resolution', () => {
const server = session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 69420,
} as session.Parameters)
Expand All @@ -2310,7 +2314,7 @@ describe('session default currency resolution', () => {
const server = session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
currency: '0xcustom',
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 4217,
Expand All @@ -2323,7 +2327,7 @@ describe('session default currency resolution', () => {
const server = session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 42431,
} as session.Parameters)
Expand All @@ -2336,7 +2340,7 @@ describe('session default currency resolution', () => {
tempo_server.session({
store: Store.memory(),
getClient: () => mockMainnetClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 4217,
testnet: false,
Expand All @@ -2363,7 +2367,7 @@ describe('session default currency resolution', () => {
tempo_server.session({
store: Store.memory(),
getClient: () => mockTestnetClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
testnet: true,
}),
Expand All @@ -2390,7 +2394,7 @@ describe('session default currency resolution', () => {
tempo_server.session({
store: Store.memory(),
getClient: () => mockTestnetClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 69420,
}),
Expand All @@ -2416,7 +2420,7 @@ describe('session default currency resolution', () => {
tempo_server.session({
store: Store.memory(),
getClient: () => mockClient,
account: '0x0000000000000000000000000000000000000001',
account: mockAccount,
currency: '0xcustom',
escrowContract: '0x0000000000000000000000000000000000000002',
chainId: 4217,
Expand Down
5 changes: 5 additions & 0 deletions src/tempo/server/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ export function session<const parameters extends session.Parameters>(p?: paramet

const { account, recipient, feePayer, feePayerUrl } = Account.resolve(parameters)

if (!account)
throw new Error(
'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.',
)

const getClient = Client.getResolver({
chain: tempo_chain,
feePayerUrl,
Expand Down
2 changes: 1 addition & 1 deletion src/tempo/session/Chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export async function closeOnChain(
const resolved = account ?? client.account
if (!resolved)
throw new Error(
'Cannot close channel: no account available. Provide an `account` in the session config or a `getClient` that returns an account-bearing client.',
'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.',
)
const args = [voucher.channelId, voucher.cumulativeAmount, voucher.signature] as const
if (feePayer) {
Expand Down
Loading