@@ -56,6 +56,11 @@ export { sessionContextSchema, type SessionContext } from './CredentialState.js'
5656/** Tail promise for each payment scope, used to serialize automatic opens per store. */
5757const channelTails = new WeakMap < ChannelStore , Map < string , Promise < void > > > ( )
5858
59+ type SelectedRail = { type : 'direct' } | { type : 'machine' ; route : MachineTokenSession . Route }
60+
61+ /** Selected rail for each logical payment scope, shared by methods using one store. */
62+ const selectedRails = new WeakMap < ChannelStore , Map < string , SelectedRail > > ( )
63+
5964/** Serializes automatic opens for one payment scope across methods sharing a store. */
6065async function lockChannel ( store : ChannelStore , key : string ) {
6166 const tails = channelTails . get ( store ) ?? new Map < string , Promise < void > > ( )
@@ -199,6 +204,8 @@ export function session(parameters: session.Parameters = {}) {
199204 topUpAmountParameter !== undefined ? parseUnits ( topUpAmountParameter , decimals ) : undefined
200205 const store = channelStore ?? createChannelStore ( )
201206 const sink = { store, notifyUpdate : ( entry : ChannelEntry ) => onChannelUpdate ?.( entry ) }
207+ const rails = selectedRails . get ( store ) ?? new Map < string , SelectedRail > ( )
208+ selectedRails . set ( store , rails )
202209
203210 type ResolvedSelection = {
204211 account : ViemAccount
@@ -254,6 +261,48 @@ export function session(parameters: session.Parameters = {}) {
254261 resolved,
255262 } )
256263
264+ const rememberRail = (
265+ direct : ChallengeContext ,
266+ resolved : ChallengeContext ,
267+ entry : ChannelEntry ,
268+ ) => {
269+ if ( ! entry . opened ) return
270+ rails . set (
271+ direct . key ,
272+ resolved . machineRoute
273+ ? { type : 'machine' , route : resolved . machineRoute }
274+ : { type : 'direct' } ,
275+ )
276+ }
277+
278+ const resolveSelectedChannel = async (
279+ direct : ChallengeContext ,
280+ ) : Promise < { channel : ChannelEntry ; resolved : ChallengeContext } | undefined > => {
281+ const rail = rails . get ( direct . key )
282+ if ( ! rail ) return undefined
283+ const cached = rail . type === 'direct' ? direct : applyMachineTokenRoute ( direct , rail . route )
284+ const channel = await store . get ( cached . key )
285+ if ( ! channel ?. opened ) {
286+ rails . delete ( direct . key )
287+ return undefined
288+ }
289+ if ( rail . type === 'direct' ) return { channel, resolved : direct }
290+ if (
291+ ! MachineTokenSession . isEnabledChallenge ( direct . challenge ) ||
292+ ! isAddressEqual ( direct . escrow , tip20ChannelEscrow )
293+ )
294+ throw new Error ( 'Machine-token channel is not bound to this merchant session challenge.' )
295+ const route = await MachineTokenSession . matchRoute ( direct . client , {
296+ chainId : direct . chainId ,
297+ descriptor : rail . route ,
298+ merchant : direct . payee ,
299+ targetToken : direct . token ,
300+ } )
301+ if ( ! route )
302+ throw new Error ( 'Machine-token channel is not bound to this merchant session challenge.' )
303+ return { channel, resolved : applyMachineTokenRoute ( direct , route ) }
304+ }
305+
257306 /** Selects one rail for this channel lifecycle before any credential is signed. */
258307 const resolveSelection = async (
259308 direct : ChallengeContext ,
@@ -267,7 +316,13 @@ export function session(parameters: session.Parameters = {}) {
267316 return select ( resolved , context , await store . get ( resolved . key ) )
268317 }
269318
270- const selectDirect = async ( ) => select ( direct , context , await store . get ( direct . key ) )
319+ const selectDirect = async ( ) => {
320+ const entry = await store . get ( direct . key )
321+ if ( entry ?. opened ) rememberRail ( direct , direct , entry )
322+ return select ( direct , context , entry )
323+ }
324+ const selected = await resolveSelectedChannel ( direct )
325+ if ( selected ) return select ( selected . resolved , context , selected . channel )
271326 if (
272327 ! MachineTokenSession . isEnabledChallenge ( direct . challenge ) ||
273328 ! isAddressEqual ( direct . escrow , tip20ChannelEscrow )
@@ -283,10 +338,16 @@ export function session(parameters: session.Parameters = {}) {
283338
284339 const machine = applyMachineTokenRoute ( direct , route )
285340 const machineEntry = await store . get ( machine . key )
286- if ( machineEntry ?. opened ) return select ( machine , context , machineEntry )
341+ if ( machineEntry ?. opened ) {
342+ rememberRail ( direct , machine , machineEntry )
343+ return select ( machine , context , machineEntry )
344+ }
287345
288346 const directEntry = await store . get ( direct . key )
289- if ( directEntry ?. opened ) return select ( direct , context , directEntry )
347+ if ( directEntry ?. opened ) {
348+ rememberRail ( direct , direct , directEntry )
349+ return select ( direct , context , directEntry )
350+ }
290351
291352 const account = await resolveCredentialAccount ( machine , context , undefined )
292353 const openingDeposit = resolveOpeningDeposit ( {
@@ -320,6 +381,9 @@ export function session(parameters: session.Parameters = {}) {
320381 return { channel : await store . get ( resolved . key ) , resolved }
321382 }
322383
384+ const selected = await resolveSelectedChannel ( direct )
385+ if ( selected ) return selected
386+
323387 if (
324388 MachineTokenSession . isEnabledChallenge ( direct . challenge ) &&
325389 isAddressEqual ( direct . escrow , tip20ChannelEscrow )
@@ -449,8 +513,13 @@ export function session(parameters: session.Parameters = {}) {
449513 payload = { ...payload , authorizationSignature }
450514 }
451515 if ( pendingEntry && ! ( attempt && plan . type === 'open' ) ) {
452- if ( pendingEntry . opened ) await store . set ( pendingEntry )
453- else await store . delete ( resolved . key )
516+ if ( pendingEntry . opened ) {
517+ await store . set ( pendingEntry )
518+ rememberRail ( direct , resolved , pendingEntry )
519+ } else {
520+ await store . delete ( resolved . key )
521+ rails . delete ( direct . key )
522+ }
454523 sink . notifyUpdate ( pendingEntry )
455524 }
456525 const credential = await serializeCredential (
@@ -475,6 +544,7 @@ export function session(parameters: session.Parameters = {}) {
475544 await store . set ( opened )
476545 sink . notifyUpdate ( opened )
477546 }
547+ rememberRail ( direct , resolved , opened )
478548 }
479549 return true
480550 } finally {
0 commit comments