ethereum-optimism / ethereum-optimism/actions

REFACTOR: Wallet layer - align with lend/borrow provider pattern

Open
#396 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
32
Forks
25
Avg merge
10h 20m
Merged PRs (30d)
16

Description

## Summary

Structural refactor of the wallet layer to align with the canonical `LendProvider` pattern, rename classes and methods for accuracy, collapse node/react duplication, and add room for `ZeroDevSmartWallet` (needed for agent session keys). Preserves `ActionsConfig` shape. Reduces ~43 wallet files to ~30 while adding two new capabilities (ZeroDev + connected wallets).

**Closed draft PR/brainstorm:** #395
**Foundation PR (ships first):** #356 — adds `toActionsWallet(LocalAccount)` support, makes `hostedWalletConfig` optional, and introduces the `ActionsContext` interface that subsequent sub-issues will consume.

## Motivation

1. **Node/react duplication.** `PrivyWallet`/`TurnkeyWallet` + providers exist twice; differ only in which SDK adapter they import (~10-30 lines).
2. **Thin wallet subclasses.** `LocalWallet`, `PrivyWallet`, `TurnkeyWallet`, `DynamicWallet` add no behavior beyond assigning `signer` and `address`.
3. **Dated abstraction shape.** Wallet layer doesn't match the `LendProvider` pattern used by lend/borrow.
4. **Misnomers.** `EOAWallet` (class doesn't require an EOA), `DefaultSmartWallet` (meaningless once ZeroDev lands), `hosted` (local isn't hosted).
5. **Method names don't match viem.** `wallet.send` is viem's `walletClient.sendTransaction` shape but uses a diverging name; `wallet.sendTokens` is a SmartWallet-only method that should be hoisted.
6. **Agent work (#354) requires ZeroDev Kernel.** Coinbase Smart Wallet isn't ERC-7579 modular — can't support session keys / Call Policy / Spending Cap Policy.

## Sub-issues

- [ ] #356 — **Foundation:** add `toActionsWallet(LocalAccount)`, make `hostedWalletConfig` optional, introduce `ActionsContext` _(ships first; no agent-work block)_
- [ ] #330 — Rename `hosted` → `embedded` _(no agent-work block)_
- [ ] #370 — Rename `wallet.send` → `wallet.sendTransaction` (viem alignment); rename `wallet.sendTokens` → `wallet.transfer`; hoist `transfer`/`signMessage`/`signTypedData` to `Wallet` base _(no agent-work block)_
- [ ] #397 — Rename `EOAWallet` → `ExternalWallet` _(no agent-work block)_
- [ ] #398 — Collapse thin wallet subclasses into concrete `ExternalWallet` _(no agent-work block)_
- [ ] #399 — Introduce `WalletProvider` base + protected `_methods` pattern _(no agent-work block)_
- [ ] #400 — Per-protocol restructure: `Base.ts` + `node/` + `react/` _(no agent-work block)_
- [ ] #401 — `ConnectedWalletProvider` (full implementation) _(no agent-work block)_
- [ ] #402 — Rename `DefaultSmartWallet*` → `CoinbaseSmartWallet*`; verify `SmartWallet` base supports second impl _(**blocks agent work**)_
- [ ] #403 — Implement `ZeroDevSmartWallet` + `ZeroDevSmartWalletProvider` _(**blocks agent work**)_

## What #356 already delivers (inherited by the refactor)

- `ActionsContext` interface — the derived-from-config bundle of providers + shared deps. Subsequent sub-issues consume this rather than redefining the shape.
- `hostedWalletConfig` is optional — no `type: 'local'` pretender; consumers wanting local-only (agent CLI, scripts) just omit the field.
- `toActionsWallet` accepts viem `LocalAccount` in addition to hosted provider params. Adaptive type: when no hosted provider is configured, only `LocalAccount` is accepted (type-enforced).
- `WalletNamespace` owns its own deps via `ActionsContext` instead of reaching into provider internals.

Sub-issues #397 / #398 / #399 build directly on this — e.g., when `LocalWallet` collapses into `ExternalWallet` (#398), the `toActionsWallet(LocalAccount)` path already flows through namespace-owned deps and just swaps the concrete class.

## Proposed final file structure

Legend: `[NEW]` new file · `[MOD]` modified · `[REN]` renamed · `[REP]` replaced · `[MV]` moved

```
packages/sdk/src/wallet/

core/
wallets/
Wallet.ts [MOD] hoist transfer/signMessage/signTypedData;
rename abstract send→sendTransaction,
sendBatch→sendBatchTransactions (#370)
ExternalWallet.ts [REN] from eoa/EOAWallet.ts (#397);
now concrete, no subclasses (#398)
smart/
SmartWallet.ts [MOD] audit for Coinbase assumptions;
rename send→sendTransaction
coinbase/CoinbaseSmartWallet.ts [REN] from default/DefaultSmartWallet.ts (#402)
zerodev/ZeroDevSmartWallet.ts [NEW] ERC-7579 Kernel; unblocks agent work (#403)

providers/
WalletProvider.ts [REP] new abstract base, mirrors
LendProvider (#399)
smart/
SmartWalletProvider.ts [MOD] audit for Coinbase assumptions
coinbase/CoinbaseSmartWalletProvider.ts [REN] from default/ (#402)
zerodev/ZeroDevSmartWalletProvider.ts [NEW] (#403)
namespace/
WalletNamespace.ts [MOD] simplified; drops unifier wrapping (#399)

providers/ [NEW DIR] mirrors lend/providers/ (#400)
privy/
PrivyWalletProviderBase.ts [NEW] all non-SDK logic
node/PrivyWalletProvider.ts [MV] thin subclass (@privy-io/node)
node/createSigner.ts [MV] SDK adapter, unchanged
react/PrivyWalletProvider.ts [MV] thin subclass (@privy-io/react-auth)
react/createSigner.ts [MV] SDK adapter, unchanged
turnkey/
TurnkeyWalletProviderBase.ts [NEW]
node/{TurnkeyWalletProvider,createSigner}.ts [MV]
react/{TurnkeyWalletProvider,createSigner}.ts [MV]
dynamic/
react/DynamicWalletProvider.ts [MV] react-only, no Base.ts
react/createSigner.ts [MV]
local/
LocalWalletProvider.ts [MV] env-agnostic
connected/ [NEW] (#401)
ConnectedWalletProviderBase.ts [NEW]
react/ConnectedWalletProvider.ts [NEW] WalletConnect / injected /
Coinbase Wallet

index.node.ts [MOD] re-exports updated paths
index.react.ts [MOD] re-exports updated paths
```

**Deleted (legacy paths subsumed above):**

```
wallet/core/wallets/eoa/ renamed (#397)
wallet/core/wallets/smart/default/ → smart/coinbase/ (#402)
wallet/core/providers/hosted/ subsumed by new WalletProvider (#399)
wallet/core/providers/smart/default/ renamed (#402)
wallet/node/wallets/ LocalWallet + subclasses collapsed (#398)
wallet/node/providers/ → wallet/providers//node/ (#400)
wallet/react/wallets/ subclasses collapsed (#398)
wallet/react/providers/ → wallet/providers//react/ (#400)
```

### Method-naming summary (post-refactor)

| Method | Today | After |
|---|---|---|
| Raw tx submission | `wallet.send(tx, chainId)` | `wallet.sendTransaction(tx, chainId)` |
| Raw batch submission | `wallet.sendBatch(txs, chainId)` | `wallet.sendBatchTransactions(txs, chainId)` |
| High-level token transfer | `wallet.sendTokens(...)` (smart only) | `wallet.transfer(amount, asset, chainId, recipient)` (on `Wallet` base, ENS-aware) |
| Message signing | `wallet.signer.signMessage(...)` | `wallet.signMessage(message)` |
| Typed-data signing | `wallet.signer.signTypedData(...)` | `wallet.signTypedData(data)` |

### Delta summary

| | Before | After |
|------------------------------------|-------:|------:|
| Total wallet files | ~43 | ~30 |
| Duplicate node/react wallet files | 6 | 0 |
| Duplicate node/react provider files| 4 | 0 |
| Thin wallet subclasses | 6 | 0 |
| Abstract provider layers | 3 | 1 |
| Concrete smart wallet impls | 1 | 2 |
| Supported wallet sources | 4 | 5 |

## Dependency graph

```
#356 (foundation — ships first)

├── #330 (hosted→embedded rename)

├── External path: #397 → #398 → #399 → #400 → #401

├── Smart path: #402 → #403 ← unblocks agent work (#354)

└── Methods: #370 (standalone, any time)
```

## Relationship to agent wallet work (#354)

The agent brainstorm in #354 defined 10 issues. Two overlap with this refactor:

- **Agent Issue 1** (`type: 'local'` provider, plus optional hosted config) — shipping in #356
- **Agent Issue 3** (Coinbase → ZeroDev migration) — becomes #402 + #403 in this refactor

Agent work can proceed in parallel with all items except #403 (ZeroDev implementation).

This refactor supersedes #354 on the `hosted`→`embedded` rename (they deferred; we're doing it as #330).

## See also

- #330 — hosted → embedded rename
- #370 — send/sendTokens rename (viem alignment) + signing hoist
- #354 — agent wallet research
- #356 — foundation PR (local account support + optional hosted config + `ActionsContext`)
- #380 — engineering principles (motivation)
- #395 — brainstorm PR

Contributor guide

Open the contributing guide

Research direction

Start with the dependency graph and foundation issue #356, then inspect packages/sdk/src/wallet/ and the proposed final file structure. Review the linked sub-issues (#330, #370, and #397–#403) to understand the staged scope and dependencies. Done means the wallet layer matches the listed structure, naming, provider pattern, and capability goals without breaking the preserved ActionsConfig shape.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
blockchain
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.