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/veria-292-session-fee-payer-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Applied strict session fee-payer call validation to relay-sponsored transactions.
6 changes: 6 additions & 0 deletions src/tempo/server/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export function session<const parameters extends session.Parameters>(
payload,
methodDetails,
resolvedFeePayer,
methodDetails.feePayer === true,
feePayerPolicy,
waitForConfirmation,
)
Expand All @@ -240,6 +241,7 @@ export function session<const parameters extends session.Parameters>(
payload,
methodDetails,
resolvedFeePayer,
methodDetails.feePayer === true,
feePayerPolicy,
)
lastOnChainVerified.set(sessionReceipt.channelId, Date.now())
Expand Down Expand Up @@ -635,6 +637,7 @@ async function handleOpen(
payload: SessionCredentialPayload & { action: 'open' },
methodDetails: SessionMethodDetails,
feePayer: viem_Account | undefined,
isSponsored: boolean,
feePayerPolicy: session.FeePayerPolicy | undefined,
waitForConfirmation: boolean,
): Promise<SessionReceipt> {
Expand Down Expand Up @@ -687,6 +690,7 @@ async function handleOpen(
challengeExpires: challenge.expires,
feePayerPolicy,
feePayer,
isSponsored,
beforeBroadcast: async (pendingOnChain) => {
await validateOpenVoucher(pendingOnChain)
},
Expand Down Expand Up @@ -772,6 +776,7 @@ async function handleTopUp(
payload: SessionCredentialPayload & { action: 'topUp' },
methodDetails: SessionMethodDetails,
feePayer: viem_Account | undefined,
isSponsored: boolean,
feePayerPolicy: session.FeePayerPolicy | undefined,
): Promise<SessionReceipt> {
const channelId = ChannelStore.normalizeChannelId(payload.channelId)
Expand All @@ -793,6 +798,7 @@ async function handleTopUp(
challengeExpires: challenge.expires,
feePayerPolicy,
feePayer,
isSponsored,
})

const updated = await store.updateChannel(channelId, (current) => {
Expand Down
8 changes: 4 additions & 4 deletions src/tempo/session/Chain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ describe.runIf(isLocalnet)('on-chain', () => {
expect(result.onChain.finalized).toBe(false)
})

test('fee-payer: rejects unauthorized calls', async () => {
test('fee-payer relay: rejects unauthorized open calls', async () => {
const salt = nextSalt()
const deposit = 5_000_000n

Expand Down Expand Up @@ -349,7 +349,7 @@ describe.runIf(isLocalnet)('on-chain', () => {
channelId,
recipient,
currency,
feePayer: accounts[0],
isSponsored: true,
}),
).rejects.toThrow('fee-sponsored open transaction contains an unauthorized call')
})
Expand Down Expand Up @@ -798,7 +798,7 @@ describe.runIf(isLocalnet)('on-chain', () => {
expect(result.newDeposit).toBe(deposit + topUpAmount)
})

test('fee-payer: rejects unauthorized calls', async () => {
test('fee-payer relay: rejects unauthorized topUp calls', async () => {
const salt = nextSalt()
const deposit = 5_000_000n
const topUpAmount = 3_000_000n
Expand Down Expand Up @@ -847,7 +847,7 @@ describe.runIf(isLocalnet)('on-chain', () => {
currency: asset,
declaredDeposit: topUpAmount,
previousDeposit: deposit,
feePayer: accounts[0],
isSponsored: true,
}),
).rejects.toThrow('fee-sponsored topUp transaction contains an unauthorized call')
})
Expand Down
16 changes: 10 additions & 6 deletions src/tempo/session/Chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ export async function broadcastOpenTransaction(parameters: {
challengeExpires?: string | undefined
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
feePayer?: Account | undefined
isSponsored?: boolean | undefined
beforeBroadcast?: ((onChain: OnChainChannel) => Promise<void> | void) | undefined
/** When false, simulates instead of waiting for confirmation and returns derived on-chain state. @default true */
waitForConfirmation?: boolean | undefined
Expand All @@ -480,11 +481,12 @@ export async function broadcastOpenTransaction(parameters: {
challengeExpires,
feePayerPolicy,
feePayer,
isSponsored = Boolean(feePayer),
beforeBroadcast,
waitForConfirmation = true,
} = parameters

if (feePayer && !FeePayer.isTempoTransaction(serializedTransaction))
if (isSponsored && !FeePayer.isTempoTransaction(serializedTransaction))
throw new BadRequestError({
reason: 'Only Tempo (0x76/0x78) transactions are supported',
})
Expand All @@ -493,11 +495,11 @@ export async function broadcastOpenTransaction(parameters: {
serializedTransaction as Transaction.TransactionSerializedTempo,
)

if (feePayer) assertSenderSigned(transaction)
if (isSponsored) assertSenderSigned(transaction)

const calls = transaction.calls ?? []

const sponsoredOpenCall = feePayer
const sponsoredOpenCall = isSponsored
? validateSponsoredOpenCalls({
calls,
currency,
Expand Down Expand Up @@ -669,6 +671,7 @@ export async function broadcastTopUpTransaction(parameters: {
challengeExpires?: string | undefined
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
feePayer?: Account | undefined
isSponsored?: boolean | undefined
}): Promise<{ txHash: Hex; newDeposit: bigint }> {
const {
client,
Expand All @@ -681,9 +684,10 @@ export async function broadcastTopUpTransaction(parameters: {
challengeExpires,
feePayerPolicy,
feePayer,
isSponsored = Boolean(feePayer),
} = parameters

if (feePayer && !FeePayer.isTempoTransaction(serializedTransaction))
if (isSponsored && !FeePayer.isTempoTransaction(serializedTransaction))
throw new BadRequestError({
reason: 'Only Tempo (0x76/0x78) transactions are supported',
})
Expand All @@ -692,11 +696,11 @@ export async function broadcastTopUpTransaction(parameters: {
serializedTransaction as Transaction.TransactionSerializedTempo,
)

if (feePayer) assertSenderSigned(transaction)
if (isSponsored) assertSenderSigned(transaction)

const calls = transaction.calls ?? []

const sponsoredTopUpCall = feePayer
const sponsoredTopUpCall = isSponsored
? validateSponsoredTopUpCalls({
calls,
currency,
Expand Down
Loading