Zyfai Yield Automation
Earn yield on any Ethereum wallet on Base, Arbitrum, and Plasma. Use when a user wants passive DeFi yield on their funds. Deploys a non-custodial determinist...
Earn yield on any Ethereum wallet on Base, Arbitrum, and Plasma. Use when a user wants passive DeFi yield on their funds. Deploys a non-custodial determinist...
Real data. Real impact.
Growing
Developers
Per week
Open source
Skills give you superpowers. Install in 30 seconds.
Turn any Ethereum wallet into a yield-generating account.
| Feature | Vault | Smart Wallet |
|---|---|---|
| Complexity | Simple deposit/withdraw | Full setup (deploy, session key) |
| Chains | Base only | Base, Arbitrum, Plasma |
| Assets | USDC | USDC, WETH |
| Strategy Control | None (managed) | Conservative/Aggressive |
| Auto-rebalancing | Yes (vault-managed) | Yes (session key) |
| Best for | Quick yield, no setup | Multi-chain, custom strategies |
npm install @zyfai/sdk viem
Get one manually at sdk.zyf.ai or programmatically:
POST https://sdk.zyf.ai/api/sdk-api-keys/create Content-Type: application/json{ "clientName": "my-agent", "walletAddress": "0x...", "email": "agent@example.com" }
Response:
{ "success": true, "data": { "apiKey": "zyfai_361ad41d083c2fe.....", "clientName": "my-agent" } }
Important: Store the
securely — it cannot be retrieved later.apiKey
import { ZyfaiSDK } from "@zyfai/sdk";const sdk = new ZyfaiSDK({ apiKey: "zyfai_...", referralSource: "openclaw-skill" }); await sdk.connectAccount(window.ethereum, 8453);
import { ZyfaiSDK } from "@zyfai/sdk"; import { createWalletClient, http } from "viem"; import { base } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts";const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY as
), chain: base, transport: http(), });0x${string}const sdk = new ZyfaiSDK({ apiKey: "zyfai_...", referralSource: "openclaw-skill" }); await sdk.connectAccount(walletClient, 8453);
For production, use KMS (AWS, GCP) or Wallet-as-a-Service (Turnkey, Privy) instead of raw private keys.
For users who just want to deposit and earn — no setup required. Currently supports USDC on Base only.
// Deposit 100 USDC into the vault const deposit = await sdk.vaultDeposit("100", "USDC"); console.log("Deposited:", deposit.txHash);// Check your shares const shares = await sdk.getVaultShares(); console.log(
);Balance: ${shares.shares} ${shares.symbol}// Withdraw (async process) const withdraw = await sdk.vaultWithdraw(); console.log("Withdraw key:", withdraw.withdrawKey);
// Later, when claimable: const claim = await sdk.vaultClaim(withdraw.withdrawKey); console.log("Claimed:", claim.txHash);
| Method | Description |
|---|---|
| Deposit into vault (amount in human readable, e.g., "100") |
| Request withdrawal (returns withdrawKey) |
| Claim completed withdrawal |
| Get vault share balance |
For users who want multi-chain support, custom strategies, and automated rebalancing.
┌─────────────────┐ ┌──────────────────────┐ │ User's EOA │ ───► │ Zyfai Subaccount │ │ (their wallet) │ │ (Safe smart wallet) │ │ │ │ │ │ Owns & controls│ │ • Auto-rebalancing │ │ │ │ • Yield optimization│ │ │ │ • Non-custodial │ └─────────────────┘ └──────────────────────┘
Key points:
| Chain | ID |
|---|---|
| Base | 8453 |
| Arbitrum | 42161 |
| Plasma | 9745 |
When calling SDK methods, always pass the EOA address (the user's wallet address) as
userAddress — never the subaccount/Safe address. The SDK derives the subaccount address automatically from the EOA.
const userAddress = "0x..."; // User's EOA (NOT the subaccount address!) const chainId = 8453; // Base// Check if subaccount exists const wallet = await sdk.getSmartWalletAddress(userAddress, chainId); console.log(
); console.log(Subaccount: ${wallet.address});Deployed: ${wallet.isDeployed}// Deploy if needed if (!wallet.isDeployed) { const result = await sdk.deploySafe(userAddress, chainId, "conservative"); console.log("Subaccount deployed:", result.safeAddress); }
Strategies:
"conservative" — Stable yield, lower risk"aggressive" — Higher yield, higher riskawait sdk.createSessionKey(userAddress, chainId);// Always verify the session key was activated const user = await sdk.getUserDetails(); if (!user.hasActiveSessionKey) { // Session key not active — retry the process console.log("Session key not active, retrying..."); await sdk.createSessionKey(userAddress, chainId);
// Verify again const userRetry = await sdk.getUserDetails(); if (!userRetry.hasActiveSessionKey) { throw new Error("Session key activation failed after retry. Contact support."); } } console.log("Session key active:", user.hasActiveSessionKey);
This allows Zyfai to rebalance funds automatically. Session keys cannot withdraw to arbitrary addresses — only optimize within the protocol.
Important: Always verify the session key is active by checking
after callinggetUserDetails().hasActiveSessionKey. If it returnscreateSessionKey, retry the process. A session key must be active for automated yield optimization to work.false
// Deposit 10 USDC (6 decimals) - default asset await sdk.depositFunds(userAddress, chainId, "10000000");// Deposit 0.5 WETH (18 decimals) // IMPORTANT: User must have WETH, not ETH. Wrap ETH to WETH first if needed. await sdk.depositFunds(userAddress, chainId, "500000000000000000", "WETH");
Funds move from EOA -> Subaccount and start earning yield immediately.
// Withdraw all USDC (default) await sdk.withdrawFunds(userAddress, chainId);// Partial USDC withdrawal (5 USDC) await sdk.withdrawFunds(userAddress, chainId, "5000000");
// Withdraw all WETH await sdk.withdrawFunds(userAddress, chainId, undefined, "WETH");
Funds return to the user's EOA. Withdrawals are processed asynchronously.
await sdk.disconnectAccount();
import { ZyfaiSDK } from "@zyfai/sdk"; import { createWalletClient, http } from "viem"; import { base } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts";async function startEarningYield(userAddress: string) { const sdk = new ZyfaiSDK({ apiKey: process.env.ZYFAI_API_KEY! }); const chainId = 8453; // Base
// Connect using WalletClient (recommended for server agents) const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY as
), chain: base, transport: http(), }); await sdk.connectAccount(walletClient, chainId);0x${string}// Deploy subaccount if needed (always pass EOA as userAddress) const wallet = await sdk.getSmartWalletAddress(userAddress, chainId); if (!wallet.isDeployed) { await sdk.deploySafe(userAddress, chainId, "conservative"); console.log("Subaccount created:", wallet.address); }
// Enable automated optimization await sdk.createSessionKey(userAddress, chainId);
// Verify session key is active const user = await sdk.getUserDetails(); if (!user.hasActiveSessionKey) { console.log("Session key not active, retrying..."); await sdk.createSessionKey(userAddress, chainId); const userRetry = await sdk.getUserDetails(); if (!userRetry.hasActiveSessionKey) { throw new Error("Session key activation failed. Contact support."); } }
// Deposit 100 USDC await sdk.depositFunds(userAddress, chainId, "100000000"); console.log("Deposited! Now earning yield.");
await sdk.disconnectAccount(); }
async function withdrawYield(userAddress: string, amount?: string) { const sdk = new ZyfaiSDK({ apiKey: process.env.ZYFAI_API_KEY! }); const chainId = 8453; // Base
// Connect using WalletClient const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY as
), chain: base, transport: http(), }); await sdk.connectAccount(walletClient, chainId);0x${string}// Withdraw funds (pass EOA as userAddress) if (amount) { // Partial withdrawal await sdk.withdrawFunds(userAddress, chainId, amount); console.log(
); } else { // Full withdrawal await sdk.withdrawFunds(userAddress, chainId); console.log("Withdrawn all funds to EOA"); }Withdrawn ${amount} (6 decimals) to EOAawait sdk.disconnectAccount(); }
| Method | Params | Description |
|---|---|---|
| | Authenticate with Zyfai |
| | Get subaccount address & status |
| | Create subaccount |
| | Enable auto-optimization |
| | Deposit USDC or WETH |
| | Withdraw USDC or WETH |
| | Get active DeFi positions |
| | Get available protocols & pools |
| | Get APY by strategy and token |
| | Get authenticated user details |
| | Get earnings data |
| | Update strategy, protocols, splitting, cross-chain settings |
| | Register agent on ERC-8004 Identity Registry |
| | End session |
Note: All methods that take
userAddress expect the EOA address, not the subaccount/Safe address.
Get all active DeFi positions for a user across protocols. Optionally filter by chain.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| userAddress | string | Yes | User's EOA address |
| chainId | SupportedChainId | No | Optional: Filter by specific chain ID |
Example:
// Get all positions across all chains const positions = await sdk.getPositions("0xUser...");// Get positions on Arbitrum only const arbPositions = await sdk.getPositions("0xUser...", 42161);
Returns:
interface PositionsResponse { success: boolean; userAddress: string; positions: Position[]; }
Get available DeFi protocols and pools for a specific chain with APY data.
const protocols = await sdk.getAvailableProtocols(42161); // Arbitrumprotocols.protocols.forEach((protocol) => { console.log(); if (protocol.pools) { protocol.pools.forEach((pool) => { console.log(${protocol.name} (ID: ${protocol.id})); }); } });Pool: ${pool.name} - APY: ${pool.apy || "N/A"}%
Returns:
interface ProtocolsResponse { success: boolean; chainId: SupportedChainId; protocols: Protocol[]; }
Get current authenticated user details including smart wallet, chains, protocols, and settings. Requires SIWE authentication.
await sdk.connectAccount(walletClient, chainId); const user = await sdk.getUserDetails();console.log("Smart Wallet:", user.smartWallet); console.log("Chains:", user.chains); console.log("Has Active Session:", user.hasActiveSessionKey);
Returns
UpdateUserProfileResponse (same as updateUserProfile).
Update the authenticated user's profile settings including strategy, protocols, splitting, and cross-chain options. Requires SIWE authentication.
sdk.updateUserProfile(params: UpdateUserProfileRequest): Promise<UpdateUserProfileResponse>
Parameters:
interface UpdateUserProfileRequest { /** Investment strategy: "conservative" or "aggressive" */ strategy?: string; /** Array of protocol IDs to use */ protocols?: string[]; /** Enable auto-selection of protocols */ autoSelectProtocols?: boolean; /** Enable omni-account for cross-chain operations */ omniAccount?: boolean; /** Array of chain IDs to operate on */ chains?: number[]; /** Enable automatic compounding (default: true) */ autocompounding?: boolean; /** Custom name for your agent */ agentName?: string; /** Enable cross-chain strategy execution */ crosschainStrategy?: boolean; /** Enable position splitting across multiple protocols */ splitting?: boolean; /** Minimum number of splits (1-4) */ minSplits?: number; /** Asset to update: "usdc" (default) or "eth" */ asset?: "USDC" | "WETH"; }
Note on
: Each asset has its own configuration. Use asset
asset: "WETH" to update WETH settings separately from USDC.
Returns:
interface UpdateUserProfileResponse { success: boolean; smartWallet?: string; chains?: number[]; strategy?: string; protocols?: string[]; autoSelectProtocols?: boolean; omniAccount?: boolean; autocompounding?: boolean; agentName?: string; crosschainStrategy?: boolean; executorProxy?: boolean; hasActiveSessionKey?: boolean; splitting?: boolean; minSplits?: number; customization?: Record<string, any>; asset?: "USDC" | "WETH"; }
Examples:
// Update strategy from conservative to aggressive await sdk.updateUserProfile({ strategy: "aggressive", });// Configure specific protocols const protocolsResponse = await sdk.getAvailableProtocols(8453); const selectedProtocols = protocolsResponse.protocols .filter(p => ["Aave", "Compound", "Moonwell"].includes(p.name)) .map(p => p.id);
await sdk.updateUserProfile({ protocols: selectedProtocols, });
// Enable position splitting (distribute across multiple protocols) await sdk.updateUserProfile({ splitting: true, minSplits: 3, // Split across at least 3 protocols });
// Verify changes const userDetails = await sdk.getUserDetails(); console.log("Strategy:", userDetails.strategy); console.log("Splitting:", userDetails.splitting);
Cross-chain strategies: Only enable cross-chain when the user explicitly requests it. For cross-chain to work, both
andcrosschainStrategymust be set toomniAccount. Never enable cross-chain settings by default.true
// Enable cross-chain ONLY when explicitly requested by the user await sdk.updateUserProfile({ crosschainStrategy: true, omniAccount: true, });// Now funds can be rebalanced across configured chains const user = await sdk.getUserDetails(); console.log("Operating on chains:", user.chains);
Notes:
getAvailableProtocols(chainId) to get valid protocol IDs before updating.minSplits is set to 2, 3, or 4, funds are always distributed across at least that many pools for improved risk diversification (up to 4 DeFi pools). This guarantees your funds will be split regardless of market conditions.crosschainStrategy: true AND omniAccount: true. Only activate when the user explicitly asks for cross-chain yield optimization. Chains are configured during initial setup and cannot be changed via this method.true, yields are reinvested automatically.executorProxy cannot be updated via this method.Get global APY by strategy type, time period, chain, and token. Use this to compare expected returns between strategies before deploying.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| crossChain | boolean | No | If , returns APY for cross-chain strategies; if , single-chain (default: ) |
| days | number | No | Period over which APY is calculated: , , , (default: ) |
| strategy | string | No | Strategy risk profile: or (default: ) |
| chainId | number | No | Filter by specific chain ID (e.g., for Base) |
| tokenSymbol | string | No | Filter by token: or |
Example:
// Get 7-day APY for USDC conservative strategy const usdcApy = await sdk.getAPYPerStrategy(false, 7, "conservative", undefined, "USDC"); console.log("USDC APY:", usdcApy.data);// Get 30-day APY for WETH aggressive strategy on Base const wethApy = await sdk.getAPYPerStrategy(false, 30, "aggressive", 8453, "WETH"); console.log("WETH APY on Base:", wethApy.data);
// Compare strategies const conservative = await sdk.getAPYPerStrategy(false, 30, "conservative"); const aggressive = await sdk.getAPYPerStrategy(false, 30, "aggressive"); console.log(); console.log(Conservative 30d APY: ${conservative.data[0]?.average_apy}%);Aggressive 30d APY: ${aggressive.data[0]?.average_apy}%
Returns:
interface APYPerStrategyResponse { success: boolean; count: number; data: APYPerStrategy[]; }interface APYPerStrategy { id: string; timestamp: string; amount: number; fee_threshold: number; days: number; chain_id: number; is_cross_chain: boolean; average_apy: number; average_apy_with_rzfi: number; total_rebalances: number; created_at: string; strategy: string; token_symbol?: string; average_apy_with_fee: number; average_apy_with_rzfi_with_fee: number; average_apy_without_fee?: number; average_apy_with_rzfi_without_fee?: number; events_average_apy?: Record<string, number>; }
Get onchain earnings for a wallet with per-token breakdown (USDC, WETH).
const earnings = await sdk.getOnchainEarnings(smartWalletAddress);console.log("Total earnings by token:", earnings.data.totalEarningsByToken); // { "USDC": 150.50, "WETH": 0.05 }
console.log("USDC earnings:", earnings.data.totalEarningsByToken["USDC"]); console.log("WETH earnings:", earnings.data.totalEarningsByToken["WETH"]);
Returns:
// TokenEarnings is a record of token symbols to amounts type TokenEarnings = Record<string, number>; // e.g., { "USDC": 100.5, "WETH": 0.025 }interface OnchainEarningsResponse { success: boolean; data: OnchainEarnings; }
interface OnchainEarnings { walletAddress: string; totalEarningsByToken: TokenEarnings; lastCheckTimestamp?: string; lastLogDate?: Record<string, string | null>; }
Register your Zyfai deployed agent on the Identity Registry following the ERC-8004 standard. This is used for OpenClaw agent registration. The method fetches a tokenUri containing the agent's metadata stored on IPFS, then registers it on-chain.
Supported Chains:
| Chain | Chain ID |
|---|---|
| Base | 8453 |
| Arbitrum | 42161 |
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| smartWallet | string | Yes | The Zyfai deployed smart wallet address to register as an agent |
| chainId | SupportedChainId | Yes | Chain ID (only 8453 or 42161) |
Example:
const sdk = new ZyfaiSDK({ apiKey: "your-api-key" }); await sdk.connectAccount(walletClient, 8453);// Get smart wallet address const walletInfo = await sdk.getSmartWalletAddress(userAddress, 8453); const smartWallet = walletInfo.address;
// Register agent on Identity Registry const result = await sdk.registerAgentOnIdentityRegistry(smartWallet, 8453);
console.log("Registration successful:"); console.log(" Tx Hash:", result.txHash); console.log(" Chain ID:", result.chainId); console.log(" Smart Wallet:", result.smartWallet);
Returns:
interface RegisterAgentResponse { success: boolean; txHash: string; chainId: number; smartWallet: string; }
How It Works:
tokenUri from the Zyfai API (agent metadata stored on IPFS)register(tokenUri) call for the Identity Registry contractFor production autonomous agents, we recommend:
The subaccount address should be identical across all chains for the same EOA. If you see different addresses:
// Check addresses on both chains const baseWallet = await sdk.getSmartWalletAddress(userAddress, 8453); const arbWallet = await sdk.getSmartWalletAddress(userAddress, 42161);if (baseWallet.address !== arbWallet.address) { console.error("Address mismatch! Contact support."); }
If addresses don't match:
This means the wallet isn't registered in the backend. Solution:
deploySafe() first — even if the Safe is already deployed on-chain, this registers it with the backendcreateSessionKey()This typically means:
Verify you're using the correct wallet for the EOA.
POST /api/sdk-api-keys/createMIT License
Copyright (c) 2024 Zyfai
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
No automatic installation available. Please visit the source repository for installation instructions.
View Installation Instructions1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.