* feat(web): enhance proposer functionality with nested safe support - Updated ProposersList component to use CheckWallet for wallet validation. - Refactored UpsertProposer to support signing for nested safes, including new utility functions for EIP-1271 signature encoding. - Added TypeScript dependencies for improved compatibility with Next.js and React. This update improves the handling of nested safes and enhances the overall proposer management experience. * feat(web): enhance proposer management with multi-sig support - Added support for multi-signature delegation in UpsertProposer and DeleteProposerDialog components. - Integrated PendingDelegationsList to display pending actions for nested safe owners. - Updated utility functions for EIP-1271 signature encoding to handle multi-owner scenarios. - Enhanced ProposersList to conditionally show pending delegations based on nested safe ownership. These changes improve the user experience for managing proposers in nested safe environments. * fix(workflows): update preview URL format in tx-builder deploy workflow * feat(web): enhance proposer management and delegation handling - Updated ProposersList to remove the New chip and conditionally display pending delegations. - Modified DeleteProposerDialog to allow nested safe owners to delete proposers. - Enhanced PendingDelegation component to include expiration date and shareable link functionality. - Implemented accordion layout in PendingDelegationsList for better organization of pending confirmations. - Updated utility functions to compute TOTP expiration dates. These changes improve the user experience for managing proposers and pending delegations in nested safe environments. * fix(web): refine wallet and delegation handling logic - Updated CheckWallet component to include a check for nested safe owners. - Changed background color in PendingDelegationCard and PendingDelegationsList for better visual consistency. - Enhanced TOTP extraction logic in usePendingDelegations hook to ensure valid number conversion. These changes improve the accuracy of wallet checks and enhance the user interface for pending delegations. * feat(web): improve edit proposer logic and UI consistency - Enhanced EditProposerDialog to allow nested safe owners to edit proposers. - Updated PendingDelegationsList to use a consistent background color for better visual clarity. These changes enhance the user experience for managing proposer edits and improve the overall interface. * feat(web): enhance pending delegation handling and UI consistency - Updated PendingDelegation component to improve shareable link functionality and streamline UI elements. - Refined PendingDelegationsList to ensure consistent background color across components. - Enhanced usePendingDelegations hook to implement polling for new signatures and optimize delegation filtering logic. These changes improve the user experience for managing pending delegations and ensure a more cohesive interface. * fix(tests): mock useChains hook to return default configs in ProposersList tests * feat(web): refactor usePendingDelegations hook for improved delegation parsing and filtering - Introduced parseMessageToDelegation function to streamline the extraction of delegation data from messages. - Implemented keepLatestPerDelegate and filterActedUponDelegations functions to enhance delegation management logic. - Optimized the usePendingDelegations hook to improve performance and clarity in handling pending delegations. These changes enhance the overall efficiency and maintainability of the delegation handling process. * feat(tests): add comprehensive tests for delegation hooks and services - Introduced unit tests for useParentSafeThreshold, usePendingDelegations, and useSubmitDelegation hooks to ensure correct functionality and edge case handling. - Added tests for delegation message creation and confirmation logic to validate proper integration with the messaging system. - Enhanced test coverage for delegation-related services, improving reliability and maintainability of the codebase. These additions strengthen the testing framework and ensure robust delegation management in the application. * refactor(web): update background color in PendingDelegationsList for UI consistency - Changed background color from a hardcoded value to a CSS variable for improved theming and maintainability. - Ensured consistent background color across all accordion components within the PendingDelegationsList. These updates enhance the visual coherence of the user interface for pending delegations. * fix(md): revert claude claude MD changes * fix(workflow): update preview URL in tx-builder deployment configuration - Modified the preview URL in the tx-builder-deploy.yml workflow to include the '/tx-builder/' path for accurate deployment previews. This change ensures that the deployment preview links direct users to the correct location. * fix(web): improve delegation handling and error logging - Updated the PendingDelegation component to include a countdown timer for expiration and enhanced error handling with logging for better debugging. - Refactored the useSubmitDelegation hook to validate TOTP expiration and improved error management by utilizing the asError utility. - Adjusted the encodeEIP1271Signature function to return a promise, ensuring consistent handling of asynchronous operations. These changes enhance the reliability and user experience of delegation management in the application. * chore(web): remove unused dependency from package.json - Deleted the "@next/swc-linux-arm64-gnu" dependency from package.json as it is no longer needed. This cleanup helps maintain a leaner and more efficient project structure. * chore(deps): remove deprecated "@next/swc-linux-arm64-gnu" entry from yarn.lock - Deleted the "@next/swc-linux-arm64-gnu" entry from yarn.lock as it is no longer required, following the recent cleanup of the package.json. This helps maintain an up-to-date and efficient dependency tree. * feat(web): enhance delegation polling and error handling in UI components - Updated constants to include a backoff multiplier for delegation polling. - Refactored PendingDelegation component to utilize the sameAddress utility for address comparison. - Introduced DelegationErrorBoundary in PendingDelegationsList to handle loading errors gracefully. - Improved usePendingDelegations hook with custom polling logic and stable key for dependency comparison. These changes enhance the reliability and user experience of delegation management in the application. * refactor(web): enhance delegation components and hooks for improved type safety and functionality - Updated DelegationErrorBoundary and PendingDelegation components to use function declarations for better readability and type inference. - Refactored useDelegationPolling and usePendingDelegations hooks to streamline polling logic and improve type definitions. - Introduced utility functions for delegation origin parsing and filtering, enhancing code clarity and maintainability. These changes improve the overall structure and reliability of delegation management in the application. * feat(web): enhance delegation management with edit functionality and improved error handling - Updated delegation-related types and constants to support 'edit' actions for better delegation management. - Refactored PendingDelegation and UpsertProposer components to handle edit actions and improve user feedback. - Streamlined error handling in delegation submission and deletion processes using the asError utility. - Removed the deprecated useDelegationPolling hook, simplifying the polling logic in usePendingDelegations. These changes improve the functionality and reliability of delegation operations within the application. * fix(tests): update delegationMessages tests to include additional properties in origin object - Enhanced the test cases for delegationMessages by adding 'delegate', 'nestedSafe', and 'label' properties to the origin object for both 'add' and 'remove' actions. This improves the accuracy and coverage of the tests related to delegation management.
6.8 KiB
Research: Proposer Multisig Validation for 2/N Parent Safes
Decision 1: Off-Chain Message Content Format
Decision: Pass the delegate EIP-712 TypedData object as the message field when creating the off-chain SafeMessage on the parent Safe.
Rationale: The off-chain message API accepts either a string or TypedData object. When TypedData is provided, the Transaction Service:
- Computes the EIP-712 hash of the TypedData →
delegateHash - Wraps in
SafeMessage { message: delegateHash }withverifyingContract = parentSafe - Each owner signs this SafeMessage typed data
This produces signatures that are valid for the Safe contract's isValidSignature(delegateHash, signatures) because the contract internally reconstructs the same SafeMessage wrapping before verifying signatures.
Alternatives considered:
- Pass raw delegate hash as string: Would be double-hashed (keccak256 of the hash), producing invalid signatures for the delegate API
- Pass custom wrapper message: Unnecessary complexity; the TypedData message is already the correct primitive
Decision 2: Off-Chain Message API Endpoint Targeting
Decision: Create off-chain messages on the parent Safe's address (not the nested Safe), using POST /v1/chains/{chainId}/safes/{parentSafeAddress}/messages.
Rationale:
- The CGW message endpoint uses the Safe's threshold as
confirmationsRequired - The parent Safe's owners are the ones who need to sign
- The Transaction Service validates that each signer is an owner of the target Safe
- Using the parent Safe address ensures correct threshold and owner validation
Alternatives considered:
- Create message on nested Safe: Incorrect — the nested Safe's owners (which include the parent Safe) wouldn't match the EOA signers
- Custom storage mechanism: Unnecessary when existing infrastructure fits perfectly
Decision 3: Identifying Delegation Messages Among All Off-Chain Messages
Decision: Include a structured origin field when creating the off-chain message to tag it as a delegation request. Format: {"type":"proposer-delegation","action":"add"|"remove","delegate":"0x...","nestedSafe":"0x...","label":"..."}.
Rationale:
- The off-chain message API has an
originfield (string, optional) designed for metadata - This allows filtering parent Safe messages to find only delegation-related ones
- The
originis stored and returned in message responses - No collision with other off-chain messages on the parent Safe (dApp messages, etc.)
Alternatives considered:
- Filter by message content (TypedData structure): Works but fragile — other dApps could use similar typed data structures
- Store message hashes locally: Defeats the purpose of cross-device discovery
- Use a unique prefix in message content: The TypedData is fixed by the delegate API format
Decision 4: EIP-1271 Signature Assembly from preparedSignature
Decision: Use the preparedSignature field from the off-chain message response directly as the inner signature data for EIP-1271 encoding.
Rationale:
- The Transaction Service's
build_signature()method concatenates all confirmations sorted by owner address (ascending) — exactly what the Safe contract requires - The CGW exposes this as
preparedSignaturewhenstatus === 'CONFIRMED' - The existing
encodeEIP1271Signaturefunction can be adapted to accept multi-owner signature bytes instead of a single EOA signature
Alternatives considered:
- Manual concatenation on client: Redundant — the backend already does this correctly
- Fetching individual confirmations and sorting: Extra work with no benefit over preparedSignature
Decision 5: TOTP Expiration Detection
Decision: Compare the TOTP value embedded in the delegate TypedData message with the current TOTP (± 1 hour tolerance). If the message's TOTP is outside this window, display as expired.
Rationale:
- The delegate TypedData contains
totp: Math.floor(Date.now() / 1000 / 3600)at creation time - The backend accepts current TOTP ± 1 previous interval (total ~2 hour window)
- Client-side detection avoids a round-trip to the backend to discover expiration
- Expired messages are shown with visual indicator; no cleanup needed
Alternatives considered:
- Server-side validation only (submit and handle 4xx): Poor UX — user collects signatures only to fail at submission
- Use
creationTimestampfrom message response: Less precise — doesn't account for exact TOTP boundaries
Decision 6: Threshold Detection for Parent Safe
Decision: Fetch the parent Safe's info using the existing useSafesGetSafeV1Query RTK Query hook with the parent Safe's address and chain ID.
Rationale:
- The
useNestedSafeOwners()hook already provides the parent Safe address - The CGW
GET /v1/chains/{chainId}/safes/{safeAddress}endpoint returnsthresholdandowners[] - Reusing the existing RTK Query hook means automatic caching and refetching
Alternatives considered:
- Direct RPC call to Safe contract: Unnecessary complexity when CGW API provides the data
- Store threshold in local state: Would go stale; better to fetch fresh from CGW
Decision 7: Auto-Submission After Threshold Met
Decision: When the current user's confirmation brings confirmationsSubmitted to equal confirmationsRequired, immediately attempt the delegate API submission. If another user completed the threshold, show a "Ready to submit" state that any owner can trigger.
Rationale:
- The user who provides the final signature has the best context to submit immediately
- For cases where another owner completed the threshold (discovered on page load), any authenticated owner should be able to trigger submission
- The
preparedSignatureis only available whenstatus === 'CONFIRMED'
Alternatives considered:
- Always manual submission: Extra click for the common case (user provides final signature)
- Background polling + auto-submit: Could race with stale TOTP; explicit user action is safer
Decision 8: Modifying encodeEIP1271Signature for Multi-Owner Signatures
Decision: The existing encodeEIP1271Signature(parentSafeAddress, ownerSignature) function already works for multi-owner signatures because the EIP-1271 format encodes the inner signature as ABI-encoded bytes regardless of length. The preparedSignature (multiple concatenated 65-byte signatures) is simply longer bytes that get ABI-encoded the same way.
Rationale:
- EIP-1271 contract signature format:
r(address) | s(offset=65) | v(0x00) | ABI.encode(bytes, innerSignatures) - The ABI encoding handles variable-length bytes naturally
- No code change needed to
encodeEIP1271Signature— just pass the fullpreparedSignatureas the signature parameter
Alternatives considered:
- Create a separate function for multi-sig: Unnecessary duplication; same encoding logic applies
- Manually construct the bytes: Error-prone and duplicates existing tested logic