From 64b8480c9f79ea610bd0a1feabd2903887727b4f Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:00:40 +1200 Subject: [PATCH 01/12] Don't use temp manager --- .../wallet/dapp-client/src/ChainSessionManager.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index 14fbdc329..beee832d0 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -673,27 +673,22 @@ export class ChainSessionManager { ): Promise { if (!this.provider || !this.wallet) throw new InitializationError('Manager core components not ready for explicit session.') + if (!this.sessionManager) throw new InitializationError('Main session manager is not initialized.') + + const signerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: pk })) const maxRetries = allowRetries ? 3 : 1 let lastError: Error | null = null for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - const tempManager = new Signers.SessionManager(this.wallet, { - sessionManagerAddress: Extensions.Rc3.sessions, - provider: this.provider, - }) - const topology = await tempManager.getTopology() + const topology = await this.sessionManager.getTopology() - const signerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: pk })) const permissions = SessionConfig.getSessionPermissions(topology, signerAddress) - if (!permissions) { throw new InitializationError(`Permissions not found for session key.`) } - if (!this.sessionManager) throw new InitializationError('Main session manager is not initialized.') - const explicitSigner = new Signers.Session.Explicit(pk, permissions) this.sessionManager = this.sessionManager.withExplicitSigner(explicitSigner) From 153ea2bc61dc1d2e2663830923a44b3faf313404 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:04:57 +1200 Subject: [PATCH 02/12] Remove hasPermission. Check signer is in topology only --- .../dapp-client/src/ChainSessionManager.ts | 32 ++++--------------- packages/wallet/dapp-client/src/DappClient.ts | 11 +++---- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index beee832d0..706d481f6 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -716,38 +716,20 @@ export class ChainSessionManager { } /** - * Checks if the current session has permission to execute a set of transactions. - * @param transactions The transactions to check permissions for. - * @returns A promise that resolves to true if the session has permission, false otherwise. + * Checks if the current session has a valid signer. + * @returns A promise that resolves to true if the session has a valid signer, false otherwise. */ - async hasPermission(transactions: Transaction[]): Promise { + async hasValidSigner(): Promise { if (!this.wallet || !this.sessionManager || !this.provider || !this.isInitialized) { return false } - try { - const calls: Payload.Call[] = transactions.map((tx) => ({ - to: tx.to, - value: tx.value ?? 0n, - data: tx.data ?? '0x', - gasLimit: tx.gasLimit ?? 0n, - delegateCall: tx.delegateCall ?? false, - onlyFallback: tx.onlyFallback ?? false, - behaviorOnError: tx.behaviorOnError ?? ('revert' as const), - })) - - // Directly check if there are signers with the necessary permissions for all calls. - // This will throw an error if any call is not supported. - await this.sessionManager.findSignersForCalls(this.wallet.address, this.chainId, calls) + const signerValidity = await this.sessionManager.listSignerValidity(this.chainId) + if (signerValidity.some((s) => s.isValid)) { return true - } catch (error) { - // An error from findSignersForCalls indicates a permission failure. - console.warn( - `Permission check failed for chain ${this.chainId}:`, - error instanceof Error ? error.message : String(error), - ) - return false } + // SessionSignerInvalidReason available here + return false } /** diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts index dcc7d81ce..5f905b024 100644 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ b/packages/wallet/dapp-client/src/DappClient.ts @@ -559,17 +559,16 @@ export class DappClient { } /** - * Checks if the current session has permission to execute a set of transactions on a specific chain. - * @param chainId The chain ID on which to check the permissions. - * @param transactions An array of transactions to check permissions for. - * @returns A promise that resolves to true if the session has permission, otherwise false. + * Checks if the current session has a valid signer. + * @param chainId The chain ID on which to check the signer. + * @returns A promise that resolves to true if the session has a valid signer, otherwise false. */ - async hasPermission(chainId: number, transactions: Transaction[]): Promise { + async hasValidSigner(chainId: number): Promise { const chainSessionManager = this.chainSessionManagers.get(chainId) if (!chainSessionManager || !chainSessionManager.isInitialized) { return false } - return await chainSessionManager.hasPermission(transactions) + return await chainSessionManager.hasValidSigner() } /** From 279c92c01cceaea3fd906d79d0078b335abb8c87 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:06:49 +1200 Subject: [PATCH 03/12] FeeOptions do not sign calls --- packages/wallet/dapp-client/src/ChainSessionManager.ts | 6 +++--- packages/wallet/dapp-client/src/DappClient.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index 706d481f6..eaae8439f 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -734,11 +734,12 @@ export class ChainSessionManager { /** * Fetches fee options for a set of transactions. + * @param wallet The wallet address to use for the fee options. * @param calls The transactions to estimate fees for. * @returns A promise that resolves with an array of fee options. * @throws {FeeOptionError} If fetching fee options fails. */ - async getFeeOptions(calls: Transaction[]): Promise { + async getFeeOptions(wallet: Address.Address, calls: Transaction[]): Promise { const callsToSend = calls.map((tx) => ({ to: tx.to, value: tx.value, @@ -749,8 +750,7 @@ export class ChainSessionManager { behaviorOnError: tx.behaviorOnError ?? ('revert' as const), })) try { - const signedCall = await this._buildAndSignCalls(callsToSend) - const feeOptions = await this.relayer.feeOptions(signedCall.to, this.chainId, callsToSend) + const feeOptions = await this.relayer.feeOptions(wallet, this.chainId, callsToSend) return feeOptions.options } catch (err) { throw new FeeOptionError(`Failed to get fee options: ${err instanceof Error ? err.message : String(err)}`) diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts index 5f905b024..8b3855f13 100644 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ b/packages/wallet/dapp-client/src/DappClient.ts @@ -551,11 +551,11 @@ export class DappClient { * } */ async getFeeOptions(chainId: number, transactions: Transaction[]): Promise { - if (!this.isInitialized) throw new InitializationError('Not initialized') + if (!this.isInitialized || !this.walletAddress) throw new InitializationError('Not initialized') const chainSessionManager = this.getChainSessionManager(chainId) if (!chainSessionManager.isInitialized) throw new InitializationError(`ChainSessionManager for chain ${chainId} is not initialized.`) - return await chainSessionManager.getFeeOptions(transactions) + return await chainSessionManager.getFeeOptions(this.walletAddress, transactions) } /** From 9ccb951bcd5e994e3e5148923eb09e3c6debaaa8 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:09:29 +1200 Subject: [PATCH 04/12] Reduce calls to wallet --- packages/wallet/core/src/signers/session-manager.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index 99d189131..9a28d6849 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -72,6 +72,10 @@ export class SessionManager implements SapientSigner { if (!imageHash) { throw new Error(`Session configuration not found for image hash ${imageHash}`) } + return this._getTopologyForImageHash(imageHash) + } + + private async _getTopologyForImageHash(imageHash: Hex.Hex): Promise { const tree = await this.stateProvider.getTree(imageHash) if (!tree) { throw new Error(`Session configuration not found for image hash ${imageHash}`) @@ -236,6 +240,7 @@ export class SessionManager implements SapientSigner { if ((await this.imageHash) !== imageHash) { throw new Error('Unexpected image hash') } + const topology = await this._getTopologyForImageHash(imageHash) //FIXME Test chain id // if (this._provider) { // const providerChainId = await this._provider.request({ @@ -327,7 +332,7 @@ export class SessionManager implements SapientSigner { // Perform encoding const encodedSignature = SessionSignature.encodeSessionCallSignatures( signatures, - await this.topology, + topology, identitySigner, explicitSigners, implicitSigners, From 2a3c1258272922eab7153cca4e9e0d944a484251 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:21:13 +1200 Subject: [PATCH 05/12] Use private functions that pass topology --- .../core/src/signers/session-manager.ts | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index 9a28d6849..6997ebc5b 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -135,8 +135,21 @@ export class SessionManager implements SapientSigner { } async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { + if (!Address.isEqual(this.wallet.address, wallet)) { + throw new Error('Wallet address mismatch') + } // Only use signers that match the topology const topology = await this.topology + return this._findSignersForCalls(wallet, chainId, calls, topology) + } + + private async _findSignersForCalls( + wallet: Address.Address, + chainId: number, + calls: Payload.Call[], + topology: SessionConfig.SessionsTopology, + ): Promise { + // Only use signers that match the topology const identitySigners = SessionConfig.getIdentitySigners(topology) if (identitySigners.length === 0) { throw new Error('Identity signers not found') @@ -177,11 +190,24 @@ export class SessionManager implements SapientSigner { wallet: Address.Address, chainId: number, calls: Payload.Call[], + ): Promise { + if (!Address.isEqual(wallet, this.wallet.address)) { + throw new Error('Wallet address mismatch') + } + const topology = await this.topology + return this._prepareIncrement(wallet, chainId, calls, topology) + } + + private async _prepareIncrement( + wallet: Address.Address, + chainId: number, + calls: Payload.Call[], + topology: SessionConfig.SessionsTopology, ): Promise { if (calls.length === 0) { throw new Error('No calls provided') } - const signers = await this.findSignersForCalls(wallet, chainId, calls) + const signers = await this._findSignersForCalls(wallet, chainId, calls, topology) // Create a map of signers to their associated calls const signerToCalls = new Map() @@ -259,7 +285,7 @@ export class SessionManager implements SapientSigner { throw new Error(`Space ${payload.space} is too large`) } - const signers = await this.findSignersForCalls(wallet, chainId, payload.calls) + const signers = await this._findSignersForCalls(wallet, chainId, payload.calls, topology) if (signers.length !== payload.calls.length) { throw new Error('No signer supported for call') } @@ -275,7 +301,7 @@ export class SessionManager implements SapientSigner { ) // Check if the last call is an increment usage call - const expectedIncrement = await this.prepareIncrement(wallet, chainId, payload.calls) + const expectedIncrement = await this._prepareIncrement(wallet, chainId, payload.calls, topology) if (expectedIncrement) { let actualIncrement: Payload.Call if ( From b88438b10867a043f6e101fbe131806034be87fd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:28:52 +1200 Subject: [PATCH 06/12] Log on delayed retry --- packages/wallet/dapp-client/src/ChainSessionManager.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index eaae8439f..97023a520 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -706,9 +706,10 @@ export class ChainSessionManager { return } catch (err) { lastError = err instanceof Error ? err : new Error(String(err)) - if (attempt < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) - } + } + if (attempt < maxRetries) { + console.error('Explicit session initialization failed, retrying...') + await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) } } if (lastError) From c6de3563f9f0af73f5472e1b0f27292af4a336ac Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:37:49 +1200 Subject: [PATCH 07/12] image hash for sapient signers always defined --- packages/wallet/core/src/signers/index.ts | 2 +- packages/wallet/core/src/signers/session-manager.ts | 6 +++--- packages/wallet/dapp-client/src/ChainSessionManager.ts | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/wallet/core/src/signers/index.ts b/packages/wallet/core/src/signers/index.ts index 80ccc07f1..2eb143703 100644 --- a/packages/wallet/core/src/signers/index.ts +++ b/packages/wallet/core/src/signers/index.ts @@ -20,7 +20,7 @@ export interface Signer { export interface SapientSigner { readonly address: MaybePromise - readonly imageHash: MaybePromise + readonly imageHash: MaybePromise signSapient: ( wallet: Address.Address, diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index 6997ebc5b..0dcc35e22 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -50,15 +50,15 @@ export class SessionManager implements SapientSigner { this._provider = options.provider } - get imageHash(): Promise { + get imageHash(): Promise { return this.getImageHash() } - async getImageHash(): Promise { + async getImageHash(): Promise { const { configuration } = await this.wallet.getStatus() const sessionConfigLeaf = Config.findSignerLeaf(configuration, this.address) if (!sessionConfigLeaf || !Config.isSapientSignerLeaf(sessionConfigLeaf)) { - return undefined + throw new Error(`Session configuration not found for wallet ${this.wallet.address}`) } return sessionConfigLeaf.imageHash } diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index 97023a520..7e2e44860 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -926,9 +926,7 @@ export class ChainSessionManager { ...envelope.payload, parentWallets: [this.wallet.address], } - const imageHash = await this.sessionManager.imageHash - if (imageHash === undefined) throw new SessionConfigError('Session manager image hash is undefined') - + const imageHash = await this.sessionManager.getImageHash() const signature = await this.sessionManager.signSapient( this.wallet.address, this.chainId, From 124d7337694f81cc516a29564868b847a6bdfdf0 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 08:40:46 +1200 Subject: [PATCH 08/12] Always validate wallet address --- packages/wallet/core/src/signers/session-manager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index 0dcc35e22..f9cbc0b5c 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -377,11 +377,13 @@ export class SessionManager implements SapientSigner { payload: Payload.Parented, signature: SignatureTypes.SignatureOfSapientSignerLeaf, ): Promise { + if (!Address.isEqual(wallet, this.wallet.address)) { + throw new Error('Wallet address mismatch') + } if (!Payload.isCalls(payload)) { // Only calls are supported return false } - if (!this._provider) { throw new Error('Provider not set') } From cbd854e1a3af1ced6608659bf07579d9624d467c Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 09:30:57 +1200 Subject: [PATCH 09/12] Enable chainId test --- .../core/src/signers/session-manager.ts | 35 +++++++++---------- .../wallet/core/test/session-manager.test.ts | 4 +-- packages/wallet/wdk/test/sessions.test.ts | 10 +++--- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index f9cbc0b5c..e4cba16ec 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -263,19 +263,18 @@ export class SessionManager implements SapientSigner { if (!Address.isEqual(wallet, this.wallet.address)) { throw new Error('Wallet address mismatch') } + if (this._provider) { + const providerChainId = await this._provider.request({ + method: 'eth_chainId', + }) + if (providerChainId !== Hex.fromNumber(chainId)) { + throw new Error(`Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`) + } + } if ((await this.imageHash) !== imageHash) { throw new Error('Unexpected image hash') } const topology = await this._getTopologyForImageHash(imageHash) - //FIXME Test chain id - // if (this._provider) { - // const providerChainId = await this._provider.request({ - // method: 'eth_chainId', - // }) - // if (providerChainId !== Hex.fromNumber(chainId)) { - // throw new Error(`Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`) - // } - // } if (!Payload.isCalls(payload) || payload.calls.length === 0) { throw new Error('Only calls are supported') } @@ -380,22 +379,20 @@ export class SessionManager implements SapientSigner { if (!Address.isEqual(wallet, this.wallet.address)) { throw new Error('Wallet address mismatch') } - if (!Payload.isCalls(payload)) { + if (!Payload.isCalls(payload) || payload.calls.length === 0) { // Only calls are supported return false } if (!this._provider) { throw new Error('Provider not set') } - //FIXME Test chain id - // const providerChainId = await this._provider.request({ - // method: 'eth_chainId', - // }) - // if (providerChainId !== Hex.fromNumber(chainId)) { - // throw new Error( - // `Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`, - // ) - // } + // Test chain id + const providerChainId = await this._provider.request({ + method: 'eth_chainId', + }) + if (providerChainId !== Hex.fromNumber(chainId)) { + throw new Error(`Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`) + } const encodedPayload = Payload.encodeSapient(chainId, payload) const encodedCallData = AbiFunction.encodeData(Constants.RECOVER_SAPIENT_SIGNATURE, [ diff --git a/packages/wallet/core/test/session-manager.test.ts b/packages/wallet/core/test/session-manager.test.ts index aa154df91..cbbb26fba 100644 --- a/packages/wallet/core/test/session-manager.test.ts +++ b/packages/wallet/core/test/session-manager.test.ts @@ -503,7 +503,7 @@ for (const extension of ALL_EXTENSIONS) { 'should fail to sign with an expired explicit session', async () => { const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = 0 + const chainId = Number(await provider.request({ method: 'eth_chainId' })) // Create unique identity and state provider for this test const identityPrivateKey = Secp256k1.randomPrivateKey() @@ -561,7 +561,7 @@ for (const extension of ALL_EXTENSIONS) { } // Sign the transaction - expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( + await expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( 'No signers match the topology', ) }, diff --git a/packages/wallet/wdk/test/sessions.test.ts b/packages/wallet/wdk/test/sessions.test.ts index f6d8a144b..5e3a2592b 100644 --- a/packages/wallet/wdk/test/sessions.test.ts +++ b/packages/wallet/wdk/test/sessions.test.ts @@ -151,7 +151,7 @@ describe('Sessions (via Manager)', () => { } const signature = await dapp.sessionManager.signSapient( dapp.wallet.address, - chainId ?? 1n, + chainId, parentedEnvelope, sessionImageHash, ) @@ -255,7 +255,7 @@ describe('Sessions (via Manager)', () => { // Configure mock provider ;(provider as any).request.mockImplementation(({ method, params }) => { if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) + return Promise.resolve(Hex.fromNumber(chainId)) } if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { // Undeployed wallet @@ -329,7 +329,7 @@ describe('Sessions (via Manager)', () => { // Configure mock provider ;(provider as any).request.mockImplementation(({ method, params }) => { if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) + return Promise.resolve(Hex.fromNumber(chainId)) } if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { // Undeployed wallet @@ -404,7 +404,7 @@ describe('Sessions (via Manager)', () => { // Configure mock provider ;(provider as any).request.mockImplementation(({ method, params }) => { if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) + return Promise.resolve(Hex.fromNumber(chainId)) } if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { // Undeployed wallet @@ -491,7 +491,7 @@ describe('Sessions (via Manager)', () => { // Configure mock provider ;(provider as any).request.mockImplementation(({ method, params }) => { if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) + return Promise.resolve(Hex.fromNumber(chainId)) } if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { // Undeployed wallet From 6fd52fac8fff455f9b4fd836e895aff2262fdd3b Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 23 Sep 2025 10:27:31 +1200 Subject: [PATCH 10/12] Reduce calls to wallet.getStatus --- packages/wallet/core/src/wallet.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/wallet/core/src/wallet.ts b/packages/wallet/core/src/wallet.ts index db4733e2b..fe5198d58 100644 --- a/packages/wallet/core/src/wallet.ts +++ b/packages/wallet/core/src/wallet.ts @@ -371,7 +371,6 @@ export class Wallet { // If the latest configuration does not match the onchain configuration // then we bundle the update into the transaction envelope if (!options?.noConfigUpdate) { - const status = await this.getStatus(provider) if (status.imageHash !== status.onChainImageHash) { calls.push({ to: this.address, @@ -402,7 +401,7 @@ export class Wallet { factory, factoryData, }, - ...(await this.prepareBlankEnvelope(Number(chainId))), + ...(await this.prepareBlankEnvelope(Number(chainId), status.configuration)), } } @@ -461,15 +460,15 @@ export class Wallet { } } - const [chainId, nonce] = await Promise.all([ + const [chainId, nonce, status] = await Promise.all([ provider.request({ method: 'eth_chainId' }), this.getNonce(provider, space), + this.getStatus(provider), ]) // If the latest configuration does not match the onchain configuration // then we bundle the update into the transaction envelope if (!options?.noConfigUpdate) { - const status = await this.getStatus(provider) if (status.imageHash !== status.onChainImageHash) { calls.push({ to: this.address, @@ -490,7 +489,7 @@ export class Wallet { nonce, calls, }, - ...(await this.prepareBlankEnvelope(Number(chainId))), + ...(await this.prepareBlankEnvelope(Number(chainId), status.configuration)), } } @@ -597,13 +596,15 @@ export class Wallet { return encoded } - private async prepareBlankEnvelope(chainId: number) { - const status = await this.getStatus() - + private async prepareBlankEnvelope(chainId: number, configuration?: Config.Config) { + if (!configuration) { + const status = await this.getStatus() + configuration = status.configuration + } return { wallet: this.address, - chainId: chainId, - configuration: status.configuration, + chainId, + configuration, } } } From 49289e04c5413b842387ed6a46f824f2c2bdeed8 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 24 Sep 2025 08:26:43 +1200 Subject: [PATCH 11/12] Async loading sessions --- packages/wallet/dapp-client/src/DappClient.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts index 8b3855f13..46cef9cb6 100644 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ b/packages/wallet/dapp-client/src/DappClient.ts @@ -271,9 +271,11 @@ export class DappClient { * for previously established sessions. */ private async _loadStateFromStorage(): Promise { - const implicitSession = await this.sequenceStorage.getImplicitSession() + const [implicitSession, explicitSessions] = await Promise.all([ + this.sequenceStorage.getImplicitSession(), + this.sequenceStorage.getExplicitSessions(), + ]) - const explicitSessions = await this.sequenceStorage.getExplicitSessions() const chainIdsToInitialize = new Set([ ...(implicitSession?.chainId !== undefined ? [implicitSession.chainId] : []), ...explicitSessions.map((s) => s.chainId), From a47decf1965cec0a5a50922bbe0d954500590d0a Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 24 Sep 2025 13:54:26 +1200 Subject: [PATCH 12/12] Retain hasPermission --- .../dapp-client/src/ChainSessionManager.ts | 35 +++++++++++++++++++ packages/wallet/dapp-client/src/DappClient.ts | 14 ++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index 7e2e44860..cfa89469c 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -716,6 +716,41 @@ export class ChainSessionManager { throw new InitializationError(`Explicit session init failed after ${maxRetries} attempts: ${lastError.message}`) } + /** + * Checks if the current session has permission to execute a set of transactions. + * @param transactions The transactions to check permissions for. + * @returns A promise that resolves to true if the session has permission, false otherwise. + */ + async hasPermission(transactions: Transaction[]): Promise { + if (!this.wallet || !this.sessionManager || !this.provider || !this.isInitialized) { + return false + } + + try { + const calls: Payload.Call[] = transactions.map((tx) => ({ + to: tx.to, + value: tx.value ?? 0n, + data: tx.data ?? '0x', + gasLimit: tx.gasLimit ?? 0n, + delegateCall: tx.delegateCall ?? false, + onlyFallback: tx.onlyFallback ?? false, + behaviorOnError: tx.behaviorOnError ?? ('revert' as const), + })) + + // Directly check if there are signers with the necessary permissions for all calls. + // This will throw an error if any call is not supported. + await this.sessionManager.findSignersForCalls(this.wallet.address, this.chainId, calls) + return true + } catch (error) { + // An error from findSignersForCalls indicates a permission failure. + console.warn( + `Permission check failed for chain ${this.chainId}:`, + error instanceof Error ? error.message : String(error), + ) + return false + } + } + /** * Checks if the current session has a valid signer. * @returns A promise that resolves to true if the session has a valid signer, false otherwise. diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts index 46cef9cb6..6253eaeb7 100644 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ b/packages/wallet/dapp-client/src/DappClient.ts @@ -560,6 +560,20 @@ export class DappClient { return await chainSessionManager.getFeeOptions(this.walletAddress, transactions) } + /** + * Checks if the current session has permission to execute a set of transactions on a specific chain. + * @param chainId The chain ID on which to check the permissions. + * @param transactions An array of transactions to check permissions for. + * @returns A promise that resolves to true if the session has permission, otherwise false. + */ + async hasPermission(chainId: number, transactions: Transaction[]): Promise { + const chainSessionManager = this.chainSessionManagers.get(chainId) + if (!chainSessionManager || !chainSessionManager.isInitialized) { + return false + } + return await chainSessionManager.hasPermission(transactions) + } + /** * Checks if the current session has a valid signer. * @param chainId The chain ID on which to check the signer.