Files
FbartoliandGitHub e3eb5e13c1 feat(web): enhance proposer functionality with nested safe support (#7028)
* 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.
2026-02-18 15:10:37 +01:00

10 KiB
Raw Permalink Blame History

API Contracts: Proposer Multisig Validation

This feature uses existing CGW/Transaction Service endpoints. No new endpoints are needed. This document describes how the existing APIs are composed to implement the multi-sig delegation flow.

Endpoints Used

1. Create Off-Chain Message (Initiate Delegation)

Endpoint: POST /v1/chains/{chainId}/safes/{parentSafeAddress}/messages

Creates a new off-chain SafeMessage on the parent Safe containing the delegate TypedData.

Request:

interface CreateMessageDto {
  message: TypedData // The delegate EIP-712 typed data
  signature: string // Initiating owner's signature on the SafeMessage
  safeAppId: null
  origin: string // JSON-encoded DelegationOrigin metadata
}

Example Request Body:

{
  "message": {
    "domain": {
      "name": "Safe Transaction Service",
      "version": "1.0",
      "chainId": 1
    },
    "types": {
      "Delegate": [
        { "name": "delegateAddress", "type": "address" },
        { "name": "totp", "type": "uint256" }
      ]
    },
    "message": {
      "delegateAddress": "0x1234...proposer",
      "totp": 497142
    },
    "primaryType": "Delegate"
  },
  "signature": "0xabc...ownerA_signature",
  "safeAppId": null,
  "origin": "{\"type\":\"proposer-delegation\",\"action\":\"add\",\"delegate\":\"0x1234...proposer\",\"nestedSafe\":\"0x5678...nested\",\"label\":\"My Proposer\"}"
}

Response: 201 Created (Message object)

Signing Process (for the initiating owner):

// 1. Generate delegate typed data
const delegateTypedData = getDelegateTypedData(chainId, proposerAddress)

// 2. Generate SafeMessage typed data wrapping the delegate hash
const safeMessageTypedData = generateSafeMessageTypedData(parentSafe, delegateTypedData)

// 3. Sign the SafeMessage with the owner's wallet
const signature = await tryOffChainMsgSigning(signer, parentSafe, delegateTypedData)

2. Confirm Off-Chain Message (Co-owner Signs)

Endpoint: POST /v1/chains/{chainId}/messages/{messageHash}/signatures

Adds a co-owner's signature to an existing delegation message.

Request:

interface UpdateMessageSignatureDto {
  signature: string // Co-owner's signature on the SafeMessage
}

Example Request Body:

{
  "signature": "0xdef...ownerB_signature"
}

Response: 200 OK

Signing Process (for confirming owners):

// 1. Fetch the message to get its content (delegate TypedData)
const message = await getMessageByHash(chainId, messageHash)

// 2. Sign the same SafeMessage that the initiator signed
const signature = await tryOffChainMsgSigning(signer, parentSafe, message.message)

3. Get Messages by Safe (Discover Pending Delegations)

Endpoint: GET /v1/chains/{chainId}/safes/{parentSafeAddress}/messages

Fetches all off-chain messages for the parent Safe. Client filters by origin.type === 'proposer-delegation'.

Query Parameters:

interface GetMessagesArgs {
  limit?: number // Pagination
  offset?: number
}

Response:

interface MessagePage {
  count: number | null
  next: string | null
  previous: string | null
  results: Message[]
}

interface Message {
  messageHash: string
  status: 'NEEDS_CONFIRMATION' | 'CONFIRMED'
  logoUri: string | null
  name: string | null
  message: string | TypedData // The delegate TypedData
  creationTimestamp: number
  modifiedTimestamp: number
  confirmationsSubmitted: number // Current signature count
  confirmationsRequired: number // Parent Safe threshold
  proposedBy: AddressInfo
  confirmations: MessageConfirmation[]
  preparedSignature: string | null // All sigs concatenated when CONFIRMED
  origin: string | null // Our DelegationOrigin JSON
}

interface MessageConfirmation {
  owner: AddressInfo
  signature: string
}

Client-Side Filtering:

const pendingDelegations = messages.results.filter((msg) => {
  try {
    const origin = JSON.parse(msg.origin || '')
    return origin.type === 'proposer-delegation' && origin.nestedSafe === currentSafeAddress
  } catch {
    return false
  }
})

4. Submit Completed Delegation (Add Proposer)

Endpoint: POST /v2/chains/{chainId}/delegates

Submits the completed multi-sig EIP-1271 signature to register the delegate.

Request:

interface CreateDelegateDto {
  safe: string // Nested Safe address
  delegate: string // Proposer address
  delegator: string // Parent Safe address
  signature: string // EIP-1271 wrapped preparedSignature
  label: string // Proposer label
}

Signature Assembly:

// 1. Get preparedSignature from confirmed message
const confirmedMessage = await getMessageByHash(chainId, messageHash)
const innerSignatures = confirmedMessage.preparedSignature!

// 2. Wrap in EIP-1271 format
const eip1271Signature = encodeEIP1271Signature(parentSafeAddress, innerSignatures)

// 3. Submit
await addDelegateV2({
  chainId,
  createDelegateDto: {
    safe: nestedSafeAddress,
    delegate: proposerAddress,
    delegator: parentSafeAddress,
    signature: eip1271Signature,
    label: proposerLabel,
  },
})

5. Submit Completed Delegation (Remove Proposer)

Endpoint: DELETE /v2/chains/{chainId}/delegates/{delegateAddress}

Submits the completed multi-sig EIP-1271 signature to remove the delegate.

Request Body:

interface DeleteDelegateV2Dto {
  delegator: string // Parent Safe address
  safe: string // Nested Safe address
  signature: string // EIP-1271 wrapped preparedSignature
}

Same signature assembly as addition — the delegate TypedData structure is the same for both add and remove operations. The only difference is the HTTP method and endpoint.


Data Flow Sequence

Owner A (Initiator)                    CGW / Transaction Service              Owner B (Co-signer)
       │                                        │                                     │
       │ 1. Sign SafeMessage(delegateTypedData) │                                     │
       │───────────────────────────────────────►│                                     │
       │   POST /safes/{parent}/messages        │                                     │
       │                                        │                                     │
       │ 2. Receive messageHash                 │                                     │
       │◄───────────────────────────────────────│                                     │
       │                                        │                                     │
       │                                        │  3. Owner B opens settings page      │
       │                                        │◄─────────────────────────────────────│
       │                                        │  GET /safes/{parent}/messages        │
       │                                        │                                     │
       │                                        │  4. Returns pending delegation       │
       │                                        │─────────────────────────────────────►│
       │                                        │  (status: NEEDS_CONFIRMATION)        │
       │                                        │                                     │
       │                                        │  5. Owner B signs & confirms         │
       │                                        │◄─────────────────────────────────────│
       │                                        │  POST /messages/{hash}/signatures    │
       │                                        │                                     │
       │                                        │  6. Returns updated message          │
       │                                        │─────────────────────────────────────►│
       │                                        │  (status: CONFIRMED,                 │
       │                                        │   preparedSignature: "0x...")         │
       │                                        │                                     │
       │                                        │  7. Wrap & submit to delegate API    │
       │                                        │◄─────────────────────────────────────│
       │                                        │  POST /delegates (EIP-1271 sig)      │
       │                                        │                                     │
       │                                        │  8. Proposer registered ✓            │
       │                                        │─────────────────────────────────────►│

EIP-1271 Signature Encoding

┌─────────────────────────────────────────────────────────────┐
│ Byte Range  │ Content                                       │
├─────────────┼───────────────────────────────────────────────│
│ 0-31        │ r: parentSafeAddress left-padded to 32 bytes  │
│ 32-63       │ s: 0x00...0041 (offset 65 to dynamic data)    │
│ 64          │ v: 0x00 (contract signature type indicator)   │
│ 65-96       │ length: byte length of preparedSignature      │
│ 97+         │ preparedSignature (N × 65 bytes, sorted)      │
└─────────────┴───────────────────────────────────────────────┘

For a 2-of-3 parent Safe:

  • Inner signatures: 2 × 65 = 130 bytes
  • Total EIP-1271 signature: 65 (header) + 32 (length) + 130 (sigs) = 227 bytes