Files
zeekay 7200114387 Merge upstream/dev (52 commits) + sweep yarn → pnpm
Upstream merge brings 52 commits from safe-global/safe-wallet-monorepo
into the luxfi/safe-wallet@dev fork.

Conflicts resolved:
- yarn.lock: deleted (we are on pnpm)
- apps/web/package.json: kept pnpm version + brand build scripts
  (build:safe / build:lux / build:hanzo) plus upstream's new
  generate-version.mjs step
- apps/mobile/.../Signer/appKit.ts: took upstream's import pattern
  for shared/projectId, shared/metadata, shared/mmkvStorageAdapter

Yarn sweep applied to 42 files (docs, github-actions, workflows, AGENTS,
specs, codemods, scripts, READMEs). Net result: zero yarn-* command
references anywhere outside node_modules.

  ❯ pnpm is the law in this fork. No yarn.
2026-05-27 03:25:16 -07:00

2.9 KiB

Quickstart: Nested Safe Proposer Management

Date: 2026-01-23 Feature: 001-nested-safe-proposer

Prerequisites

  • Node.js (version per .nvmrc)
  • Yarn 4 (via corepack)
  • Repository cloned and dependencies installed: pnpm install

Development Setup

# Checkout feature branch
git checkout 001-nested-safe-proposer

# Install dependencies
pnpm install

# Run web app in development mode
pnpm --filter @safe-global/web dev

Key Files to Modify

1. Permission Gate (P1 - Button enablement)

File: apps/web/src/components/settings/ProposersList/index.tsx

  • Replace OnlyOwner wrapper with CheckWallet component
  • Configure: allowProposer={false} (nested owners yes, proposers no)

File: apps/web/src/components/common/CheckWallet/index.tsx

  • No changes needed — already supports isNestedSafeOwner

2. Proposer Form (P2 - Signing flow)

File: apps/web/src/features/proposers/components/UpsertProposer.tsx

  • Add conditional logic: if user is nested Safe owner, wrap EOA signature in EIP-1271 format
  • Determine parent Safe address from useNestedSafeOwners()
  • Use parent Safe address as delegator in the API call

3. Signing Utilities

File: apps/web/src/features/proposers/utils/utils.ts

  • Add encodeEIP1271Signature(parentSafeAddress: string, ownerSignature: string): string
  • Encodes: v=0, r=parentSafeAddress (32 bytes), s=65, + ABI-encoded owner signature

Testing

# Run unit tests
pnpm --filter @safe-global/web test

# Run specific test file
pnpm --filter @safe-global/web test --testPathPattern="ProposersList"

# Type checking
pnpm --filter @safe-global/web type-check

# Linting
pnpm --filter @safe-global/web lint

Test Scenarios

  1. Nested Safe owner sees enabled button: Connect wallet → Open nested Safe → Settings > Setup → Verify "Add proposer" enabled
  2. Non-owner sees disabled button: Connect unrelated wallet → Same page → Verify disabled with tooltip
  3. Direct owner flow unchanged: Connect direct owner → Add proposer → Verify existing flow works
  4. Nested Safe owner initiates proposer addition: Click "Add proposer" → Fill form → Submit → Verify parent Safe tx created

Architecture Notes

  • The permission fix (replacing OnlyOwner with CheckWallet) is a simple, low-risk change
  • The signing flow is simpler than initially expected:
    • No on-chain transaction (SignMessageLib) needed
    • No async multisig approval needed (for 1-of-1 parent Safes)
    • Backend EIP-1271 support confirmed in Safe Transaction Service source code
    • The connected wallet signs the delegate typed data (same as direct owner), then the signature is wrapped in EIP-1271 format
  • For multi-sig parent Safes (threshold > 1), collecting multiple owner signatures is needed — deferred to future phase
  • Both phases can be implemented together since there are no blocking dependencies