* 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.
10 KiB
Implementation Plan: Nested Safe Proposer Management
Branch: 001-nested-safe-proposer | Date: 2026-01-23 | Spec: spec.md
Input: Feature specification from /specs/001-nested-safe-proposer/spec.md
Summary
Enable users who are nested Safe owners (their wallet controls a parent Safe that is an owner of the target Safe) to add proposers to the nested Safe. This requires two changes: (1) replace the OnlyOwner permission gate with CheckWallet on the "Add proposer" button, and (2) implement a signing flow that wraps the connected wallet's EOA signature in EIP-1271 format with the parent Safe as delegator. Backend EIP-1271 support has been confirmed in the Safe Transaction Service source code.
Technical Context
Language/Version: TypeScript 5.x (Next.js 14.x)
Primary Dependencies: React, MUI, Redux Toolkit (RTK Query), ethers.js, @safe-global/protocol-kit, @safe-global/api-kit
Storage: N/A (backend-managed via CGW API)
Testing: Jest + React Testing Library + MSW
Target Platform: Web (Next.js, all modern browsers)
Project Type: Web (monorepo workspace apps/web)
Performance Goals: Standard web app responsiveness (<100ms UI interactions)
Constraints: Must work with existing CGW delegate API; parent Safe signing is async (multisig threshold)
Scale/Scope: ~5-7 files modified, 1-2 new utility functions
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| I. Type Safety | PASS | All new code will use proper TypeScript interfaces. No any types. |
| II. Branch Protection | PASS | Working on feature branch 001-nested-safe-proposer. Will run all quality gates before commit. |
| III. Cross-Platform Consistency | PASS | Changes are web-only (apps/web/). No shared package modifications. |
| IV. Testing Discipline | PASS | Will use MSW for API mocking, colocated test files, faker for test data. |
| V. Feature Organization | PASS | Changes are within existing src/features/proposers/ and src/components/settings/. No new feature folder needed (extending existing). Existing feature flag (FEATURES.PROPOSERS) applies. |
| VI. Theme System Integrity | PASS | No styling changes required. |
Post-Phase 1 Re-check: All gates still pass. No new patterns or dependencies introduced that violate constitution.
Project Structure
Documentation (this feature)
specs/001-nested-safe-proposer/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
│ └── delegate-api.md # API contract documentation
└── tasks.md # Phase 2 output (/speckit.tasks command)
Source Code (repository root)
apps/web/src/
├── components/
│ ├── common/
│ │ ├── CheckWallet/index.tsx # Already supports nested owners (no changes)
│ │ └── OnlyOwner/index.tsx # Reference only (being replaced in ProposersList)
│ └── settings/
│ └── ProposersList/index.tsx # MODIFY: Replace OnlyOwner with CheckWallet
├── features/
│ └── proposers/
│ ├── components/
│ │ └── UpsertProposer.tsx # MODIFY: Add nested Safe owner detection + EIP-1271 wrapping
│ └── utils/
│ └── utils.ts # MODIFY: Add encodeEIP1271Signature() helper
└── hooks/
├── useIsNestedSafeOwner.ts # Existing (no changes)
└── useNestedSafeOwners.tsx # Existing (no changes)
Structure Decision: This feature extends the existing proposers feature within apps/web/. No new directories or feature folders are needed. The changes are localized to the permission gate component and the proposer form submission logic.
Implementation Phases
Phase A: Permission Gate Fix (P1)
Goal: Enable the "Add proposer" button for nested Safe owners.
Change: In ProposersList/index.tsx, replace:
<OnlyOwner>
{(isOk) => ( ... )}
</OnlyOwner>
with:
<CheckWallet allowProposer={false}>
{(isOk) => ( ... )}
</CheckWallet>
Why allowProposer={false}: Proposers should not be able to add other proposers. Only direct owners and nested Safe owners should manage proposers.
Risk: Low. CheckWallet is a well-tested component already used throughout the app.
Tests:
- Unit test: Verify button enabled when
useIsNestedSafeOwnerreturns true - Unit test: Verify button disabled when user is only a proposer (not owner/nested owner)
- Unit test: Verify button disabled when user has no relationship to the Safe
- Unit test: Verify existing direct owner behavior unchanged
Phase B: Nested Safe Signing Flow (P2)
Goal: When a nested Safe owner submits the proposer form, sign with the connected wallet and wrap in EIP-1271 format with the parent Safe as delegator.
Confirmed: The Safe Transaction Service fully supports EIP-1271 contract signatures for delegates (verified in source code and explicit test _test_add_delegate_using_1271_signature()). No on-chain transaction (SignMessageLib) is required.
Steps:
-
Detect nested Safe owner in UpsertProposer: Use
useIsNestedSafeOwner()anduseNestedSafeOwners()to determine if the current user is a nested Safe owner and get the parent Safe address. -
Sign delegate typed data with connected wallet: Use the same
signProposerTypedData()orsignProposerData()functions. The connected wallet IS an owner of the parent Safe, so its signature is valid as an inner signature. -
Wrap in EIP-1271 format: Encode the EOA signature in the EIP-1271 contract signature format:
v=0, r=parentSafeAddress (32 bytes), s=65 (offset to dynamic data) + ABI-encoded bytes of the owner signature(s) -
Submit to delegate API: POST with
delegator: parentSafeAddressandsignature: eip1271Signature.
Flow for 1-of-1 parent Safe (initial scope):
- Single-step, synchronous — identical UX to direct owner flow
- Connected wallet signs → wrap in EIP-1271 → submit → done
Flow for multi-sig parent Safe (threshold > 1, future phase):
- Would require collecting signatures from additional parent Safe owners
- Deferred — out of scope for initial implementation
Risk: Low-Medium.
- Backend EIP-1271 support: CONFIRMED (no longer a risk)
- EIP-1271 encoding: Well-defined format, test reference available
- Scope limited to 1-of-1 parent Safes initially
New utility functions needed (in apps/web/src/features/proposers/utils/utils.ts):
encodeEIP1271Signature(parentSafeAddress: string, ownerSignature: string): string— wraps an EOA signature in EIP-1271 contract signature format- Update
UpsertProposerto detect nested Safe ownership and use the new encoding
Tests:
- Unit test:
encodeEIP1271Signatureproduces correct byte layout - Unit test: Parent Safe address is correctly identified from
useNestedSafeOwners() - Unit test: UpsertProposer uses EIP-1271 flow when user is nested Safe owner
- Unit test: UpsertProposer uses direct EOA flow when user is direct owner (no regression)
- Integration test: Full submission flow with mocked delegate API (MSW)
Dependencies & Risks
| Dependency | Risk | Status | Notes |
|---|---|---|---|
| Backend EIP-1271 support for delegates | LOW | CONFIRMED | Safe Transaction Service supports it; explicit test exists (_test_add_delegate_using_1271_signature) |
| EIP-1271 signature encoding | LOW | RESOLVED | Format documented in Transaction Service test; well-defined byte layout |
| Parent Safe threshold > 1 | MEDIUM | DEFERRED | Initial scope limited to 1-of-1 parent Safes. Multi-sig collection is a future phase. |
| TOTP expiration | LOW | RESOLVED | Backend accepts current AND previous hour's TOTP (~2 hour window). For 1-of-1 parent Safes, signing is instant. |
Open Questions (resolved)
Backend EIP-1271 support: CONFIRMED. The Safe Transaction ServiceDelegateSerializerV2.validate_delegator_signature()callssafe_signature.is_valid(ethereum_client, owner)which invokesisValidSignatureon contract delegators.- Multi-signer UX (deferred): For threshold > 1 parent Safes, a signature collection mechanism is needed. This is out of scope for the initial implementation.
TOTP window: RESOLVED. The backend tries both current and previous TOTP values, giving a ~2 hour validity window. For the synchronous 1-of-1 flow, this is not a concern.