TypeScript SDK for interacting with the Zyfai Yield Optimization Engine. This SDK provides easy-to-use methods for deploying Safe smart wallets, managing DeFi positions, and optimizing yield across multiple protocols.
You can generate an api key from here: https://sdk.zyf.ai/
- Safe Smart Wallet Deployment: Deploy Safe wallets with deterministic addresses
- Flexible Authentication: Support for private keys and modern wallet providers
- Multi-Chain Support: Works on Arbitrum, Base, and Plasma
- Yield Optimization: Access to multiple DeFi protocols and strategies
- Position Tracking: Monitor and manage your DeFi positions across chains
npm install @zyfai/sdk viem
# or
yarn add @zyfai/sdk viem
# or
pnpm add @zyfai/sdk viem- API Key: Single API key for both Execution API (Safe deployment, transactions, session keys) and Data API (earnings, opportunities, analytics)
Get your API key from Zyfai Dashboard
The SDK can be initialized with either a configuration object or just the API key string:
import { ZyfaiSDK } from "@zyfai/sdk";
// Option 1: Full configuration object
const sdk = new ZyfaiSDK({
apiKey: "your-api-key",
environment: "production", // or 'staging' (default: 'production')
});
// Option 2: Simple string initialization (API key only)
const sdk = new ZyfaiSDK("your-api-key");Configuration Options:
| Option | Required | Description |
|---|---|---|
apiKey |
Yes | API key for both Execution API and Data API (Safe deployment, transactions, session keys, analytics) |
environment |
No | "production" or "staging" (default: "production") |
The SDK accepts either a private key or a modern wallet provider. The SDK automatically authenticates the user via SIWE (Sign-In with Ethereum) when connecting.
// Option 1: With private key (chainId required)
await sdk.connectAccount("0x...", 8453);
// Option 2: With wallet provider (chainId optional - uses provider's chain)
const provider = await connector.getProvider();
await sdk.connectAccount(provider); // SDK detects chain from provider
// Option 3: With EIP-1193 provider
const provider = window.ethereum; // Client passes this from their frontend
await sdk.connectAccount(provider); // Automatically uses provider's current chain
// Now call methods with explicit user addresses
const userAddress = "0xUser...";
await sdk.deploySafe(userAddress, 8453);Note:
- When using a wallet provider, the SDK automatically detects the chain from the provider. You can optionally specify
chainIdto override. - The SDK automatically performs SIWE authentication when connecting, so you don't need to call any additional authentication methods.
Disconnect the current account and clear all authentication state:
// Disconnect account and clear JWT token
await sdk.disconnectAccount();
console.log("Account disconnected and authentication cleared");This method:
- Clears the wallet connection
- Resets authentication state
- Clears the JWT token
- Resets session key tracking
Deploy a Safe smart wallet:
const userAddress = "0xUser..."; // User's EOA address
// Get the deterministic Safe address (before deployment)
const walletInfo = await sdk.getSmartWalletAddress(userAddress, 8453);
console.log("Safe Address:", walletInfo.address);
console.log("Is Deployed:", walletInfo.isDeployed);
// Deploy the Safe (automatically checks if already deployed)
const result = await sdk.deploySafe(userAddress, 8453);
if (result.success) {
console.log("Safe Address:", result.safeAddress);
console.log("Status:", result.status); // 'deployed' | 'failed'
console.log("Transaction Hash:", result.txHash);
}Note: The SDK proactively checks if the Safe is already deployed before attempting deployment. If it exists, it returns early without making any transactions.
The SDK supports the following chains:
| Chain | Chain ID | Status |
|---|---|---|
| Arbitrum | 42161 | ✅ |
| Base | 8453 | ✅ |
| Plasma | 9745 | ✅ |
Example with different chains:
import { getSupportedChainIds, isSupportedChain } from "@zyfai/sdk";
// Get all supported chains
const chains = getSupportedChainIds();
console.log("Supported chains:", chains);
// Check if a chain is supported
if (isSupportedChain(8453)) {
const userAddress = "0xUser...";
const result = await sdk.deploySafe(userAddress, 8453); // Base
}new ZyfaiSDK(config: SDKConfig | string)Parameters:
config: Configuration object or API key string- If a string is provided, it's treated as the
apiKey - If an object is provided:
apiKey(string): Your Zyfai API key (required)environment('production' | 'staging', optional): API environment (default: 'production')rpcUrls(object, optional): Custom RPC URLs per chain to avoid rate limiting (optional, only needed for local operations likegetSmartWalletAddress)8453(string, optional): Base Mainnet RPC URL42161(string, optional): Arbitrum One RPC URL9745(string, optional): Plasma Mainnet RPC URL
- If a string is provided, it's treated as the
Examples:
// Option 1: String initialization (API key only)
const sdk = new ZyfaiSDK("your-api-key");
// Option 2: Object initialization (full configuration)
const sdk = new ZyfaiSDK({
apiKey: "your-api-key",
environment: "production",
});
// Option 3: With custom RPC URLs (recommended to avoid rate limiting)
const sdk = new ZyfaiSDK({
apiKey: "your-api-key",
environment: "production",
rpcUrls: {
8453: "https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY", // Base
42161: "https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY", // Arbitrum
9745: "https://your-plasma-rpc-provider.com", // Plasma
},
});Connect account for signing transactions and authenticate via SIWE. Accepts either a private key string or a modern wallet provider.
Parameters:
account: Private key string or wallet provider object (EIP-1193 provider, viem WalletClient, etc.)chainId: Target chain ID- Required for private key
- Optional for wallet providers (auto-detects from provider)
- Default: 8453 (Base)
Returns: Connected wallet address
Automatic Actions:
- Connects the wallet
- Authenticates via SIWE (Sign-In with Ethereum)
- Stores JWT token for authenticated endpoints
Examples:
// With private key (chainId required)
await sdk.connectAccount("0x...", 8453);
// With wallet provider (chainId optional)
const provider = await connector.getProvider();
await sdk.connectAccount(provider); // Uses provider's current chainDisconnect account and clear all authentication state.
Returns: Promise that resolves when disconnection is complete
Actions:
- Clears wallet connection
- Resets authentication state
- Clears JWT token
- Resets session key tracking
Example:
await sdk.disconnectAccount();
console.log("Disconnected and cleared");Get the Smart Wallet (Safe) address for a user.
Parameters:
userAddress: User's EOA addresschainId: Target chain ID
Returns:
{
address: Address; // Safe address
isDeployed: boolean; // Whether the Safe is deployed
}Deploy a Safe smart wallet for a user. Deployment is handled by the backend API, which manages all RPC calls and bundler interactions. This avoids rate limiting issues.
Parameters:
userAddress: User's EOA addresschainId: Target chain ID
Returns:
{
success: boolean;
safeAddress: Address;
txHash: string;
status: "deployed" | "failed";
}Note:
- User must be authenticated (automatically done via
connectAccount()) - Backend handles all RPC calls, avoiding rate limiting
Add a wallet address to the SDK API key's allowedWallets list. This endpoint requires SDK API key authentication (API key starting with "zyfai_").
Parameters:
walletAddress: Wallet address to add to the allowed list
Returns:
{
success: boolean;
message: string; // Status message
}Note: This method is only available when using an SDK API key (starts with "zyfai_"). Regular API keys cannot use this endpoint.
Session keys enable delegated transaction execution without exposing the main private key.
The SDK automatically fetches optimal session configuration from Zyfai API:
// SDK automatically:
// 1. Uses existing SIWE authentication (from connectAccount)
// 2. Checks if user already has an active session key (returns early if so)
// 3. Calculates the deterministic Safe address
// 4. Retrieves personalized config via /session-keys/config
// 5. Signs the session key
// 6. Calls /session-keys/add so the session becomes active immediately
const result = await sdk.createSessionKey(userAddress, 8453);
// Check if session key already existed
if (result.alreadyActive) {
console.log("Session key already active:", result.message);
} else {
console.log("Session created:", result.signature);
console.log("Safe address:", result.sessionKeyAddress);
console.log("Activation ID:", result.sessionActivation?.id);
}
console.log("User ID:", result.userId);Important:
- User must be authenticated (automatically done via
connectAccount()) - The SDK proactively checks if the user already has an active session key and returns early without requiring any signature if one exists
- The user record must have
smartWalletandchainIdset (automatically handled after callingdeploySafe) - When
alreadyActiveistrue,sessionKeyAddressandsignatureare not available in the response
Transfer tokens to your Safe smart wallet. Token address is automatically selected based on chain:
- Base (8453) and Arbitrum (42161): USDC
- Plasma (9745): USDT
// Deposit 100 USDC (6 decimals) to Safe on Base
const result = await sdk.depositFunds(
userAddress,
8453, // Chain ID
"100000000" // Amount: 100 USDC = 100 * 10^6
);
if (result.success) {
console.log("Deposit successful!");
console.log("Transaction Hash:", result.txHash);
}Note:
- Amount must be in least decimal units. For USDC (6 decimals): 1 USDC = 1000000
- Token address is automatically selected based on chain (USDC for Base/Arbitrum, USDT for Plasma)
- The SDK automatically authenticates via SIWE before logging the deposit with Zyfai's API, so no extra steps are required on your end once the transfer confirms
Initiate a withdrawal from your Safe. Note: Withdrawals are processed asynchronously by the backend. Funds are always withdrawn to the Safe owner's address (userAddress).
// Full withdrawal
const result = await sdk.withdrawFunds(userAddress, 8453);
// Partial withdrawal of 50 USDC (6 decimals)
const result = await sdk.withdrawFunds(
userAddress,
8453,
"50000000" // Amount: 50 USDC = 50 * 10^6
);
if (result.success) {
console.log("Withdrawal initiated!");
console.log("Message:", result.message); // e.g., "Withdrawal request sent"
if (result.txHash) {
console.log("Transaction Hash:", result.txHash);
} else {
console.log("Transaction will be processed asynchronously");
}
}Important Notes:
- Amount must be in least decimal units. For USDC (6 decimals): 1 USDC = 1000000
- The SDK authenticates via SIWE before calling the withdrawal endpoints
- Withdrawals are processed asynchronously - the
txHashmay not be immediately available - Check the
messagefield for the withdrawal status - Use
getHistory()to track the withdrawal transaction once it's processed
Retrieve all available DeFi protocols and pools for a specific chain:
const protocols = await sdk.getAvailableProtocols(8453);
console.log(`Found ${protocols.protocols.length} protocols`);
protocols.protocols.forEach((protocol) => {
console.log(`${protocol.name} (${protocol.type})`);
console.log(`Chains: ${protocol.chains.join(", ")}`);
console.log(`Strategies: ${protocol.strategies?.join(", ") ?? "n/a"}`);
console.log(`Website: ${protocol.website ?? "n/a"}`);
console.log(`Pools: ${protocol.pools?.length ?? 0}`);
});Note: This endpoint fetches protocols from /api/v1/protocols?chainId={chainId} and returns additional metadata such as type, strategies, chains, website, and an optional imageUrl.
Track all active DeFi positions for a user:
const positions = await sdk.getPositions(userAddress);
positions.positions.forEach((bundle) => {
console.log(`Chain: ${bundle.chain}, Strategy: ${bundle.strategy}`);
bundle.positions.forEach((slot) => {
console.log(`Token: ${slot.token_symbol}, Pool: ${slot.pool}`);
console.log(`Underlying Amount: ${slot.underlyingAmount}`);
});
});Note: This endpoint uses /api/v1/data/position?walletAddress={address} (Smart wallet address) and returns bundles with nested slot data. Use each slot's underlyingAmount for the canonical token balance.
The SDK provides access to various analytics and data endpoints:
const user = await sdk.getUserDetails();
console.log("Smart Wallet:", user.user.smartWallet);
console.log("Active Chains:", user.user.chains);const tvl = await sdk.getTVL();
console.log("Total TVL:", tvl.totalTvl);
const volume = await sdk.getVolume();
console.log("Total Volume:", volume.volumeInUSD);const wallets = await sdk.getActiveWallets(8453); // Base chain
console.log("Active wallet count:", wallets.count);Get the smart wallet address associated with an EOA address:
const result = await sdk.getSmartWalletByEOA("0xYourEOA...");
console.log("Smart Wallet:", result.smartWallet);
console.log("Chains:", result.chains);
console.log("EOA:", result.eoa);Returns:
{
success: boolean;
eoa: string;
smartWallet: Address | null;
chains: number[];
}Get information about the first deposit/topup for a wallet:
const firstTopup = await sdk.getFirstTopup(walletAddress, 8453);
console.log("First Topup Date:", firstTopup.date);
console.log("First Topup Amount:", firstTopup.amount);
console.log("Chain ID:", firstTopup.chainId);Returns:
{
success: boolean;
walletAddress: string;
date: string;
amount?: string;
chainId?: number;
}Note: Returns an error if the wallet has no deposits yet.
const history = await sdk.getHistory(walletAddress, 8453, {
limit: 50,
fromDate: "2024-01-01",
});
history.data.forEach((tx) => console.log(tx.type, tx.amount));const earnings = await sdk.getOnchainEarnings(walletAddress);
console.log("Total Earnings:", earnings.data.totalEarnings);
console.log("Current Earnings:", earnings.data.currentEarnings);
console.log("Lifetime Earnings:", earnings.data.lifetimeEarnings);const updated = await sdk.calculateOnchainEarnings(walletAddress);
console.log("Updated earnings:", updated.data.totalEarnings);const daily = await sdk.getDailyEarnings(
walletAddress,
"2024-01-01",
"2024-01-31"
);
daily.data.forEach((d) => console.log(d.date, d.earnings));const apyHistory = await sdk.getDailyApyHistory(walletAddress, "30D");
console.log("Average Weighted APY:", apyHistory.averageWeightedApy);const safeOpps = await sdk.getSafeOpportunities(8453);
safeOpps.data.forEach((o) => {
console.log(`${o.protocolName} - ${o.poolName}: ${o.apy}% APY`);
});const degenStrats = await sdk.getDegenStrategies(8453);
degenStrats.data.forEach((s) => {
console.log(`${s.protocolName} - ${s.poolName}: ${s.apy}% APY`);
});// Get same-chain rebalances
const rebalances = await sdk.getRebalanceInfo(false);
console.log("Rebalance events:", rebalances.count);
// Get cross-chain rebalances
const crossChain = await sdk.getRebalanceInfo(true);const frequency = await sdk.getRebalanceFrequency(walletAddress);
console.log("Tier:", frequency.tier);
console.log("Max rebalances/day:", frequency.frequency);Add a wallet address to the SDK API key's allowedWallets list. This endpoint requires SDK API key authentication (API key starting with "zyfai_").
const result = await sdk.addWalletToSdk("0x1234...");
console.log(result.message); // "Wallet successfully added to allowed list"Note: This method is only available when using an SDK API key (starts with "zyfai_"). Regular API keys cannot use this endpoint.
const portfolio = await sdk.getDebankPortfolio(walletAddress);
console.log("Total Value:", portfolio.totalValueUsd);
Object.entries(portfolio.chains).forEach(([chain, data]) => {
console.log(`${chain}: $${data.totalValueUsd}`);
});Note: The Debank portfolio endpoint is a premium feature and may require additional authorization.
All examples are available in the examples/ directory:
end-to-end.ts- Complete workflow demonstrating all SDK featuresbasic-usage.ts- Simple Safe deployment workflowcreate-session-key.ts- Session key creation + registrationdeposit.ts- Deposit funds to Safewithdraw.ts- Withdraw funds from Safedeposit-withdraw.ts- Combined fund management examples
get-protocols.ts- Fetch available protocols for a chainget-positions.ts- Get active positions for a walletget-user-details.ts- Get authenticated user detailsget-tvl-volume.ts- Get TVL and trading volumeget-active-wallets.ts- Get active wallets by chainget-smart-wallets-by-eoa.ts- Get smart wallets by EOAget-first-topup.ts- Get first deposit informationget-history.ts- Get transaction history
get-onchain-earnings.ts- Get/calculate onchain earningsget-daily-earnings.ts- Get daily earnings breakdownget-apy-history.ts- Get daily APY history with weighted averages
get-opportunities.ts- Get safe and degen yield opportunitiesget-rebalance-info.ts- Get rebalance events and frequency tier
get-debank-portfolio.ts- Get Debank multi-chain portfolio
# 1. Set up environment variables
cp .env.example .env
# Edit .env with your API keys
# 2. Build the SDK
pnpm install
pnpm build
# 3. Run the complete workflow
pnpm tsx examples/end-to-end.tsimport { ZyfaiSDK } from "@zyfai/sdk";
async function main() {
const sdk = new ZyfaiSDK({
apiKey: process.env.ZYFAI_API_KEY!,
});
// Connect account (automatically authenticates via SIWE)
await sdk.connectAccount(process.env.PRIVATE_KEY!, 8453);
const userAddress = "0xUser..."; // User's EOA address
// Check if Safe already exists
const walletInfo = await sdk.getSmartWalletAddress(userAddress, 8453);
if (walletInfo.isDeployed) {
console.log("Safe already deployed at:", walletInfo.address);
return;
}
// Deploy Safe
const result = await sdk.deploySafe(userAddress, 8453);
if (result.success) {
console.log("✅ Successfully deployed Safe");
console.log("Address:", result.safeAddress);
console.log("Tx Hash:", result.txHash);
}
}
main();import { ZyfaiSDK } from "@zyfai/sdk";
import { useState } from "react";
function SafeDeployment() {
const [sdk] = useState(
() =>
new ZyfaiSDK({
apiKey: process.env.ZYFAI_API_KEY!,
bundlerApiKey: process.env.BUNDLER_API_KEY!,
})
);
const [userAddress, setUserAddress] = useState<string>("");
const [safeAddress, setSafeAddress] = useState<string>("");
const [isDeploying, setIsDeploying] = useState(false);
const handleConnect = async (walletProvider: any) => {
try {
// Client passes the wallet provider from their frontend
// e.g., from wagmi: const provider = await connector.getProvider();
// connectAccount automatically authenticates via SIWE
const address = await sdk.connectAccount(walletProvider); // chainId auto-detected
setUserAddress(address);
console.log("Connected and authenticated:", address);
// Get Safe address for this user
const walletInfo = await sdk.getSmartWalletAddress(address, 8453);
setSafeAddress(walletInfo.address);
} catch (error) {
console.error("Connection failed:", error);
}
};
const handleDeploy = async () => {
if (!userAddress) return;
setIsDeploying(true);
try {
const result = await sdk.deploySafe(userAddress, 8453);
if (result.success) {
alert(`Safe deployed at ${result.safeAddress}`);
}
} catch (error) {
console.error("Deployment failed:", error);
} finally {
setIsDeploying(false);
}
};
return (
<div>
<button
onClick={async () => {
// Client gets provider from their wallet connection library
const provider = window.ethereum; // or from wagmi, web3-react, etc.
await handleConnect(provider);
}}
>
Connect Wallet
</button>
{userAddress && (
<>
<p>Connected: {userAddress}</p>
<p>Your Safe: {safeAddress}</p>
<button onClick={handleDeploy} disabled={isDeploying}>
{isDeploying ? "Deploying..." : "Deploy Safe"}
</button>
</>
)}
</div>
);
}Important Note: The SDK doesn't connect to wallets directly. The client integrating the SDK should handle wallet connection on their frontend and pass the provider to connectAccount().
The SDK is built on top of:
- Viem: Low-level Ethereum interactions
- Axios: HTTP client for API communication
- Deterministic Address Generation: Safe addresses are generated deterministically using CREATE2
- Explicit Parameters: All methods take explicit user addresses - the connected account is only used for signing
- Multi-User Support: One SDK instance can manage multiple users
- Backend-Friendly: Perfect for services managing Safe wallets for multiple users
try {
const userAddress = "0xUser...";
const result = await sdk.deploySafe(userAddress, 8453);
if (!result.success) {
console.error("Deployment failed:", result.status);
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
}- Store API Keys Securely: Never commit API keys to version control
- Use Environment Variables: Store keys in
.envfiles - Check Deployment Status: Always check if Safe is already deployed before deploying
- Handle Errors Gracefully: Implement proper error handling for all SDK methods
- Validate Chain IDs: Ensure you're using supported chains (Arbitrum, Base, Plasma)
- Use Explicit Parameters: Always pass explicit
userAddressandchainIdto methods
For running the examples, set up the following environment variables:
# Required: API key (used for both Execution API and Data API)
ZYFAI_API_KEY=your-api-key
# Required for examples: Private key for signing transactions
# WARNING: Never commit your private key to version control!
PRIVATE_KEY=0x...
# Optional: Chain ID (default: 8453 for Base)
# Supported: 42161 (Arbitrum), 8453 (Base), 9745 (Plasma)
CHAIN_ID=8453Make sure to call connectAccount() before calling other methods that require signing. Note that connectAccount() automatically authenticates the user via SIWE.
Check that the chain ID is in the supported chains list: Arbitrum (42161), Base (8453), or Plasma (9745).
The SDK automatically performs SIWE authentication when you call connectAccount(). The SDK automatically detects browser vs Node.js environments:
- Browser: Uses
window.location.originfor the SIWE message domain/uri to match the browser's automaticOriginheader - Node.js: Uses the API endpoint URL
If you encounter SIWE authentication failures in a browser, ensure:
- Your frontend origin is allowed by the API's CORS configuration
- You're using the correct
environmentsetting (stagingorproduction) - The user approves the SIWE signature request in their wallet
If createSessionKey returns { alreadyActive: true }, the user already has an active session key. This is not an error - the SDK proactively checks before attempting to create a new one.
If withdrawFunds returns without a txHash, the withdrawal is being processed asynchronously by the backend. You can:
- Check the
messagefield for status information - Use
getHistory()to track when the withdrawal transaction is processed - The transaction will appear in the history once it's been executed
Some Data API endpoints may require server-side CORS configuration. If you see CORS errors for endpoints like onchain-earnings, calculate-onchain-earnings, or opportunities, contact Zyfai support to ensure your origin is whitelisted.
Contributions are welcome! Please open an issue or submit a pull request.
MIT