Dual Interaction Layers
isSafe prop. Safe mode uses gasless relay via Gelato; EOA mode sends transactions directly.
NMT supports two distinct ways for users to interact with DeFi protocols. Understanding which path a transaction takes is critical for working on any transaction-related code.
A deposit into Yearn on Base. The NMT modal (left) prepares the transaction; the wallet extension (right) shows the token approval for the user to sign.
The Two Modes
MODE 1: Delegate + Safe MODE 2: EOA (Direct Wallet)
======================== ===========================
User's Wallet (EOA) User's Wallet (EOA)
| |
| signs EIP-712 message | signs transaction directly
v v
DelegateBundler Blockchain (direct call)
|
| validates permissions
v
Protocol Module (e.g. AaveModule)
|
| executes via Safe
v
Gnosis Safe (holds funds)
|
v
Blockchain
Mode 1: Delegate + Safe
The user's funds live in a Gnosis Safe (smart wallet). A delegate executes DeFi strategies on their behalf, within the bounds of granted permissions.
| Aspect | Detail |
|---|---|
| Who holds the funds | Gnosis Safe contract |
| Who signs | The delegate (operator) signs an EIP-712 message |
| How it executes | DelegateBundler validates signature and permissions, then routes calls through protocol modules |
| Who pays gas | Gelato Relay (sponsored/gasless for the user) |
| Batching | Multiple operations bundled into one atomic transaction |
| Native ETH | Must be wrapped to WETH first — Safes can't use native ETH in swaps |
Use case: Asset managers controlling client wallets. Advisors executing strategies for advisees. Institutional delegation.
Mode 2: EOA (Direct Wallet)
The user trades directly from their own wallet. No Safe, no delegation, no permission checks.
| Aspect | Detail |
|---|---|
| Who holds the funds | The user's wallet (EOA) |
| Who signs | The user signs the transaction directly |
| How it executes | walletClient.sendTransaction() straight to the protocol contract |
| Who pays gas | The user pays |
| Batching | Each operation is a separate transaction |
| Native ETH | Can be used directly |
Use case: Personal trading. Users who want direct control without delegation overhead.
How the Code Branches
Throughout the frontend, the isSafe prop controls which execution path is taken. Every transaction component accepts it:
// Pattern used across all protocol components
export const AaveBalancesTable = ({
address,
isSafe = false, // defaults to EOA mode
}: {
address: string | null;
isSafe?: boolean;
}) => {
// ...
const withdrawSelected = async () => {
if (isSafe) {
await withdrawFromSafe(positions, amounts); // Bundler path
} else {
await withdrawEOA(position, amount); // Direct path
}
};
};
Components that branch on isSafe:
| Component | Location |
|---|---|
AaveBalancesTable |
features/transact/aave/aave-balances-table.tsx |
UniswapV3PositionsTable |
features/transact/uniswap-v3/uniswap-v3-positions.tsx |
UniswapV2PositionsTable |
features/transact/uniswap-v2/uniswap-v2-positions.tsx |
YearnVaultPositionsTable |
features/transact/yearn/yearn-positions.tsx |
MorphoPositionsTable |
features/transact/morpho/morpho-positions.tsx |
AerodromePositions |
features/transact/aerodrome/aerodrome-positions.tsx |
KrystalVaultPositionsTable |
features/transact/krystal/krystal-positions.tsx |
Safe Path: Step by Step
When isSafe = true, a withdrawal from Aave looks like this:
1. PREPARE — Build call array (approve + withdraw calls)
via buildAaveWithdrawCall()
2. SIGN — Delegate signs EIP-712 typed data:
{ callsDataHash, nonce, deadline }
3. SIMULATE — DelegateBundler.simulateExecuteAndRevert()
to catch errors before spending gas
4. RELAY — Submit to Gelato Relay for sponsored execution
(platform pays gas, not the user)
5. CONFIRM — Poll Gelato for task status until confirmed
Key files:
- Bundler service:
features/delegations/services/bundler/service.ts - Relay service:
features/delegations/services/relay-swap-service.ts
EOA Path: Step by Step
When isSafe = false, the same withdrawal looks like this:
1. PREPARE — Encode the withdraw transaction calldata
2. SIGN+SEND — walletClient.sendTransaction({
to: aavePoolAddress,
data: withdrawCalldata
})
3. CONFIRM — publicClient.waitForTransactionReceipt()
Key file: v2/transactions/swaps/hooks/use-execute-swap.ts
Token Swaps: Mode Differences
Token swaps have additional complexity because different swap providers have different Safe compatibility:
| Swap Provider | Safe Mode | EOA Mode | Notes |
|---|---|---|---|
| Relay (cross-chain) | Yes | Yes | Safe wraps native ETH to WETH automatically |
| Relay (same-chain) | Yes | Yes | Same WETH wrapping behavior |
| Cow Protocol | Yes | No | Requires PRESIGN signing scheme (Safe-only) |
| Direct swap | No | Yes | Standard sendTransaction |
Why Cow Protocol is Safe-Only
Cow Protocol uses a PRESIGN signing scheme — the order is pre-signed on-chain through the Safe module rather than signed off-chain by an EOA. This enables MEV protection but requires the Safe infrastructure:
// Cow Swap always uses PRESIGN for Safe execution
const advancedSettings = {
quoteRequest: {
signingScheme: SigningScheme.PRESIGN,
receiver: safeAddress,
},
};
The Cow Swap module builds calls that go through the DelegateBundler:
buildCowSwapApprovalCall()— approve token spendingbuildCowSwapSignOrderCall()— pre-sign the order on-chain
Decision Matrix
| Scenario | Mode | Why |
|---|---|---|
| Advisor manages client portfolio | Delegate + Safe | Client retains custody, advisor operates within permissions |
| User wants gasless transactions | Delegate + Safe | Gelato Relay sponsors gas |
| User wants MEV-protected swaps | Delegate + Safe | Cow Protocol requires PRESIGN via Safe |
| User wants multi-step atomic operations | Delegate + Safe | DelegateBundler batches calls |
| User trades from personal wallet | EOA | Simpler, no Safe setup needed |
| User wants to use native ETH directly | EOA | Safes require WETH wrapping |
| Quick one-off swap | EOA | Less overhead than Safe setup |