Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/demo/backend/src/config/verbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type VerbsConfig,
type VerbsInterface,
} from '@eth-optimism/verbs-sdk'
import { unichain } from 'viem/chains'
import { baseSepolia } from 'viem/chains'

import { env } from './env.js'

Expand All @@ -20,9 +20,13 @@ export function createVerbsConfig(): VerbsConfig {
type: 'morpho',
},
chains: [
// {
// chainId: unichain.id,
// rpcUrl: unichain.rpcUrls.default.http[0],
// },
{
chainId: unichain.id,
rpcUrl: env.RPC_URL,
chainId: baseSepolia.id,
rpcUrl: baseSepolia.rpcUrls.default.http[0],
},
],
}
Expand Down
4 changes: 4 additions & 0 deletions packages/demo/backend/src/controllers/lend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class LendController {
)
return c.json({ vaults: formattedVaults })
} catch (error) {
console.error('[LendController.getVaults] Error:', error)
return c.json(
{
error: 'Failed to get vaults',
Expand All @@ -67,6 +68,7 @@ export class LendController {
const formattedVault = await lendService.formatVaultResponse(vaultInfo)
return c.json({ vault: formattedVault })
} catch (error) {
console.error('[LendController.getVault] Error:', error)
return c.json(
{
error: 'Failed to get vault info',
Expand Down Expand Up @@ -96,6 +98,7 @@ export class LendController {
await lendService.formatVaultBalanceResponse(balance)
return c.json(formattedBalance)
} catch (error) {
console.error('[LendController.getVaultBalance] Error:', error)
return c.json(
{
error: 'Failed to get vault balance',
Expand Down Expand Up @@ -136,6 +139,7 @@ export class LendController {
},
})
} catch (error) {
console.error('[LendController.deposit] Error:', error)
return c.json(
{
error: 'Failed to deposit',
Expand Down
6 changes: 6 additions & 0 deletions packages/demo/backend/src/controllers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class WalletController {
userId,
} satisfies CreateWalletResponse)
} catch (error) {
console.error('[WalletController.createWallet] Error:', error)
return c.json(
{
error: 'Failed to create wallet',
Expand Down Expand Up @@ -103,6 +104,7 @@ export class WalletController {
userId,
} satisfies GetWalletResponse)
} catch (error) {
console.error('[WalletController.getWallet] Error:', error)
return c.json(
{
error: 'Failed to get wallet',
Expand Down Expand Up @@ -134,6 +136,7 @@ export class WalletController {
count: wallets.length,
} satisfies GetAllWalletsResponse)
} catch (error) {
console.error('[WalletController.getAllWallets] Error:', error)
return c.json(
{
error: 'Failed to get wallets',
Expand All @@ -159,6 +162,7 @@ export class WalletController {

return c.json({ balance: serializeBigInt(balance) })
} catch (error) {
console.error('[WalletController.getBalance] Error:', error)
return c.json(
{
error: 'Failed to get balance',
Expand Down Expand Up @@ -186,6 +190,7 @@ export class WalletController {

return c.json(result)
} catch (error) {
console.error('[WalletController.fundWallet] Error:', error)
return c.json(
{
error: 'Failed to fund wallet',
Expand Down Expand Up @@ -222,6 +227,7 @@ export class WalletController {
},
})
} catch (error) {
console.error('[WalletController.sendTokens] Error:', error)
return c.json(
{
error: 'Failed to send tokens',
Expand Down
13 changes: 7 additions & 6 deletions packages/demo/backend/src/services/lend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export async function executeLendTransaction(
throw new Error('No transaction data available for execution')
}

const publicClient = verbs.chainManager.getPublicClient(130)
const publicClient = verbs.chainManager.getPublicClient(84532) // Base Sepolia
const ethBalance = await publicClient.getBalance({ address: wallet.address })

const gasEstimate = await estimateGasCost(
Expand All @@ -121,24 +121,25 @@ export async function executeLendTransaction(
lendTransaction,
)

// Skip gas check when using gas sponsorship
// TODO: Add proper gas sponsorship detection
if (ethBalance < gasEstimate) {
throw new Error('Insufficient ETH for gas fees')
// Proceed with gas sponsorship - Privy will handle gas fees
// throw new Error('Insufficient ETH for gas fees')
}

let depositHash: Address = '0x0'

if (lendTransaction.transactionData.approval) {
const approvalSignedTx = await wallet.sign(
const approvalHash = await wallet.signAndSend(
lendTransaction.transactionData.approval,
)
const approvalHash = await wallet.send(approvalSignedTx, publicClient)
await publicClient.waitForTransactionReceipt({ hash: approvalHash })
}

const depositSignedTx = await wallet.sign(
depositHash = await wallet.signAndSend(
lendTransaction.transactionData.deposit,
)
depositHash = await wallet.send(depositSignedTx, publicClient)
await publicClient.waitForTransactionReceipt({ hash: depositHash })

return { ...lendTransaction, hash: depositHash }
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/backend/src/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function getBalance(userId: string): Promise<TokenBalance[]> {
totalFormattedBalance: formattedBalance,
chainBalances: [
{
chainId: 130 as const, // Unichain
chainId: 84532 as const, // Base Sepolia
balance: vaultBalance.balance,
formattedBalance: formattedBalance,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/frontend/src/components/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ How much would you like to lend?`

Vault: ${promptData.selectedVault.name}
Amount: ${amount} USDC
Tx: https://uniscan.xyz/tx/${result.transaction.hash || 'pending'}`,
Tx: https://base-sepolia.blockscout.com/tx/${result.transaction.hash || 'pending'}`,
timestamp: new Date(),
}
setLines((prev) => [...prev.slice(0, -1), successLine])
Expand Down
9 changes: 7 additions & 2 deletions packages/sdk/src/constants/supportedChains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { base, mainnet, unichain } from 'viem/chains'
import { base, baseSepolia, mainnet, unichain } from 'viem/chains'

export const SUPPORTED_CHAIN_IDS = [mainnet.id, unichain.id, base.id] as const
export const SUPPORTED_CHAIN_IDS = [
mainnet.id,
unichain.id,
base.id,
baseSepolia.id,
] as const

export type SupportedChainId = (typeof SUPPORTED_CHAIN_IDS)[number]
10 changes: 10 additions & 0 deletions packages/sdk/src/lend/providers/morpho/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@ describe('LendProviderMorpho', () => {
transport: http(),
})

const mockChainManager = {
getPublicClient: () => mockPublicClient,
} as any

provider = new LendProviderMorpho(
mockConfig,
mockPublicClient as unknown as PublicClient,
mockChainManager,
)
})

Expand All @@ -74,9 +79,14 @@ describe('LendProviderMorpho', () => {
...mockConfig,
defaultSlippage: undefined,
}
const mockChainManager = {
getPublicClient: () => mockPublicClient,
} as any

const providerWithDefaults = new LendProviderMorpho(
configWithoutSlippage,
mockPublicClient as unknown as PublicClient,
mockChainManager,
)
expect(providerWithDefaults).toBeInstanceOf(LendProviderMorpho)
})
Expand Down
23 changes: 18 additions & 5 deletions packages/sdk/src/lend/providers/morpho/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MetaMorphoAction } from '@morpho-org/blue-sdk-viem'
import type { Address, PublicClient } from 'viem'
import { encodeFunctionData, erc20Abi, formatUnits } from 'viem'

import type { ChainManager } from '../../../services/ChainManager.js'
import type {
LendOptions,
LendTransaction,
Expand All @@ -24,6 +25,11 @@ export const SUPPORTED_NETWORKS = {
name: 'Unichain',
morphoAddress: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb' as Address,
},
BASE_SEPOLIA: {
chainId: 84532,
name: 'Base Sepolia',
morphoAddress: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb' as Address, // Using same address as Morpho typically uses same deployment address
},
} as const

/**
Expand All @@ -37,21 +43,28 @@ export class LendProviderMorpho extends LendProvider {
private morphoAddress: Address
private defaultSlippage: number
private publicClient: PublicClient
private chainManager: ChainManager

/**
* Create a new Morpho lending provider
* @param config - Morpho lending configuration
* @param publicClient - Viem public client for blockchain interactions // TODO: remove this
* @param chainManager - Chain manager for multi-chain support
*/
constructor(config: MorphoLendConfig, publicClient: PublicClient) {
constructor(
config: MorphoLendConfig,
publicClient: PublicClient,
chainManager: ChainManager,
) {
super()

// Use Unichain as the default network for now
const network = SUPPORTED_NETWORKS.UNICHAIN
// Use Base Sepolia as the default network for testing
const network = SUPPORTED_NETWORKS.BASE_SEPOLIA

this.morphoAddress = network.morphoAddress
this.defaultSlippage = config.defaultSlippage || 50 // 0.5% default
this.publicClient = publicClient
this.chainManager = chainManager
}

/**
Expand Down Expand Up @@ -172,15 +185,15 @@ export class LendProviderMorpho extends LendProvider {
* @returns Promise resolving to vault information
*/
async getVault(vaultAddress: Address): Promise<LendVaultInfo> {
return getVaultInfoHelper(vaultAddress, this.publicClient)
return getVaultInfoHelper(vaultAddress, this.chainManager)
}

/**
* Get list of available vaults
* @returns Promise resolving to array of vault information
*/
async getVaults(): Promise<LendVaultInfo[]> {
return getVaultsHelper(this.publicClient)
return getVaultsHelper(this.chainManager)
}

/**
Expand Down
62 changes: 48 additions & 14 deletions packages/sdk/src/lend/providers/morpho/vaults.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { AccrualPosition, IToken } from '@morpho-org/blue-sdk'
import { fetchAccrualVault } from '@morpho-org/blue-sdk-viem'
import type { Address, PublicClient } from 'viem'
import type { Address } from 'viem'

import { getTokenAddress, SUPPORTED_TOKENS } from '../../../supported/tokens.js'
import type { SupportedChainId } from '../../../constants/supportedChains.js'
import type { ChainManager } from '../../../services/ChainManager.js'
import { SUPPORTED_TOKENS } from '../../../supported/tokens.js'
import type { ApyBreakdown, LendVaultInfo } from '../../../types/lend.js'
import { fetchRewards, type RewardsBreakdown } from './api.js'

Expand All @@ -13,22 +15,35 @@ export interface VaultConfig {
address: Address
name: string
asset: IToken & { address: Address }
chainId: SupportedChainId
}

/**
* Supported vaults on Unichain for Morpho lending
* Supported vaults for Morpho lending
*/
export const SUPPORTED_VAULTS: VaultConfig[] = [
// {
// // Gauntlet USDC vault on Unichain
// address: '0x38f4f3B6533de0023b9DCd04b02F93d36ad1F9f9' as Address,
// name: 'Gauntlet USDC (Unichain)',
// asset: {
// address: getTokenAddress('USDC', 130)!, // USDC on Unichain
// symbol: SUPPORTED_TOKENS.USDC.symbol,
// decimals: BigInt(SUPPORTED_TOKENS.USDC.decimals),
// name: SUPPORTED_TOKENS.USDC.name,
// },
// },
{
// Gauntlet USDC vault - primary supported vault
address: '0x38f4f3B6533de0023b9DCd04b02F93d36ad1F9f9' as Address,
name: 'Gauntlet USDC',
// USDC vault on Base Sepolia
address: '0x99067e5D73b1d6F1b5856E59209e12F5a0f86DED' as Address,
name: 'USDC Vault (Base Sepolia)',
asset: {
address: getTokenAddress('USDC', 130)!, // USDC on Unichain
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e' as Address, // USDC on Base Sepolia
symbol: SUPPORTED_TOKENS.USDC.symbol,
decimals: BigInt(SUPPORTED_TOKENS.USDC.decimals),
name: SUPPORTED_TOKENS.USDC.name,
},
chainId: 84532 as SupportedChainId, // Base Sepolia
},
]

Expand Down Expand Up @@ -109,12 +124,12 @@ export function calculateBaseApy(vault: any): number {
/**
* Get detailed vault information with enhanced rewards data
* @param vaultAddress - Vault address
* @param publicClient - Viem public client
* @param chainManager - Chain manager for multi-chain support
* @returns Promise resolving to detailed vault information
*/
export async function getVaultInfo(
vaultAddress: Address,
publicClient: PublicClient,
chainManager: ChainManager,
): Promise<LendVaultInfo> {
try {
// 1. Find vault configuration for validation
Expand All @@ -125,10 +140,29 @@ export async function getVaultInfo(
}

// 2. Fetch live vault data from Morpho SDK
const vault = await fetchAccrualVault(vaultAddress, publicClient)
const vault = await fetchAccrualVault(
vaultAddress,
chainManager.getPublicClient(config.chainId),
).catch((error) => {
console.error('Failed to fetch vault info:', error)
return {
totalAssets: 0n,
totalSupply: 0n,
owner: '0x' as Address,
curator: '0x' as Address,
}
})

// 3. Fetch rewards data from API
const rewardsBreakdown = await fetchAndCalculateRewards(vaultAddress)
const rewardsBreakdown = await fetchAndCalculateRewards(vaultAddress).catch(
(error) => {
console.error('Failed to fetch rewards data:', error)
return {
other: 0,
totalRewardsApr: 0,
}
},
)

// 4. Calculate APY breakdown
const apyBreakdown = calculateApyBreakdown(vault, rewardsBreakdown)
Expand Down Expand Up @@ -160,15 +194,15 @@ export async function getVaultInfo(

/**
* Get list of available vaults
* @param publicClient - Viem public client
* @param chainManager - Chain manager for multi-chain support
* @returns Promise resolving to array of vault information
*/
export async function getVaults(
publicClient: PublicClient,
chainManager: ChainManager,
): Promise<LendVaultInfo[]> {
try {
const vaultInfoPromises = SUPPORTED_VAULTS.map((config) =>
getVaultInfo(config.address, publicClient),
getVaultInfo(config.address, chainManager),
)
return await Promise.all(vaultInfoPromises)
} catch (error) {
Expand Down
Loading