Summary
In auto-managed Tempo sessions, mppx reuses an existing channel when the client account's authorizedSigner changes. The next credential is emitted as a voucher for the old channel instead of opening a new channel.
Repro
This only prepares/signs credentials. It does not submit a transaction.
import { Challenge, Credential } from 'mppx'
import { session } from 'mppx/client'
import { createClient, custom } from 'viem'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { Account as TempoAccount } from 'viem/tempo'
import { tempoModerato } from 'viem/tempo/chains'
const payer = privateKeyToAccount(generatePrivateKey())
const signerA = TempoAccount.fromSecp256k1(generatePrivateKey(), { access: payer })
const signerB = TempoAccount.fromSecp256k1(generatePrivateKey(), { access: payer })
const payee = privateKeyToAccount(generatePrivateKey()).address
const currency = '0x20c0000000000000000000000000000000000000'
const escrow = '0xe1c4d3dce17bc111181ddf716f75bae49e61a336'
let account = signerA
function getClient() {
return createClient({
account,
chain: tempoModerato,
transport: custom({
async request({ method }) {
if (method === 'eth_chainId') return `0x${tempoModerato.id.toString(16)}`
if (method === 'eth_fillTransaction')
return {
raw: '0x',
tx: {
chainId: `0x${tempoModerato.id.toString(16)}`,
from: payer.address,
gas: '0x5208',
input: '0x',
maxFeePerGas: '0x1',
maxPriorityFeePerGas: '0x1',
nonce: '0x0',
to: escrow,
type: '0x2',
value: '0x0',
},
}
throw new Error(`unexpected rpc method: ${method}`)
},
}),
})
}
function challenge() {
return Challenge.from({
id: `challenge-${account.accessKeyAddress}`,
intent: 'session',
method: 'tempo',
realm: 'example.test',
request: {
amount: '1000000',
currency,
methodDetails: { chainId: tempoModerato.id, escrowContract: escrow },
recipient: payee,
suggestedDeposit: '10000000',
unitType: 'token',
},
})
}
async function main() {
const method = session({ getClient, maxDeposit: '10' })
const first = Credential.deserialize(
await method.createCredential({ challenge: challenge(), context: {} }),
)
account = signerB
const second = Credential.deserialize(
await method.createCredential({ challenge: challenge(), context: {} }),
)
console.log({
first: {
action: first.payload.action,
authorizedSigner: first.payload.authorizedSigner,
channelId: first.payload.channelId,
},
second: {
action: second.payload.action,
authorizedSigner: second.payload.authorizedSigner,
channelId: second.payload.channelId,
},
})
}
main()
Actual
The second credential reuses the first channel and returns a voucher:
{
first: {
action: 'open',
authorizedSigner: '0x...',
channelId: '0x...'
},
second: {
action: 'voucher',
authorizedSigner: undefined,
channelId: '0x...' // same as first
}
}
Expected
When the account's authorizedSigner changes, auto-managed sessions should not emit a voucher for a channel opened with the previous signer. It should open a distinct channel for the new signer.
Summary
In auto-managed Tempo sessions,
mppxreuses an existing channel when the client account'sauthorizedSignerchanges. The next credential is emitted as a voucher for the old channel instead of opening a new channel.Repro
This only prepares/signs credentials. It does not submit a transaction.
Actual
The second credential reuses the first channel and returns a voucher:
Expected
When the account's
authorizedSignerchanges, auto-managed sessions should not emit a voucher for a channel opened with the previous signer. It should open a distinct channel for the new signer.