diff --git a/wallet-earn/.env.example b/wallet-earn/.env.example index 42bdc465..0fb56cdf 100644 --- a/wallet-earn/.env.example +++ b/wallet-earn/.env.example @@ -1,2 +1,3 @@ +COMPASS_WALLET_ADDRESS=<> PRIVATE_KEY=<> COMPASS_API_KEY=<> \ No newline at end of file diff --git a/wallet-earn/README.md b/wallet-earn/README.md index fe1c128f..e93c26b2 100644 --- a/wallet-earn/README.md +++ b/wallet-earn/README.md @@ -21,6 +21,7 @@ npm run start ## Environment variables ``` +COMPASS_WALLET_ADDRESS=<> PRIVATE_KEY=<> COMPASS_API_KEY=<> ``` diff --git a/wallet-earn/app/api/deposit/route.ts b/wallet-earn/app/api/deposit/route.ts index c2d04440..46c21392 100644 --- a/wallet-earn/app/api/deposit/route.ts +++ b/wallet-earn/app/api/deposit/route.ts @@ -32,54 +32,29 @@ export async function POST(request: Request) { apiKeyAuth: process.env.COMPASS_API_KEY, }); - const allowance = await compassApiSDK.universal.genericAllowance({ + const deposit = await compassApiSDK.earn.earnManage({ + owner: account.address, chain: CHAIN, - user: account.address, - token, - contract: vaultAddress, + venue: { + type: "VAULT", + vaultAddress, + }, + action: "DEPOSIT", + amount, }); - if (Number(allowance.amount) < amount) { - const allowance = await compassApiSDK.universal.genericAllowanceSet({ - chain: CHAIN, - sender: account.address, - contract: vaultAddress, - amount, - token, - }); - - const transaction = allowance.transaction as UnsignedTransaction; - - const setAllowanceTxHash = await walletClient.sendTransaction({ - ...(transaction as any), - value: BigInt(transaction.value), - gas: BigInt(transaction.gas), - maxFeePerGas: BigInt(transaction.maxFeePerGas), - maxPriorityFeePerGas: BigInt(transaction.maxPriorityFeePerGas), - }); + const transaction = deposit.transaction as UnsignedTransaction; - const tx = await publicClient.waitForTransactionReceipt({ - hash: setAllowanceTxHash, - }); + console.log(transaction) - if (tx.status !== "success") { - throw new Error("Allowance transaction reverted."); - } + if (!transaction) { + throw new Error("No transaction returned from earnManage"); } - const deposit = await compassApiSDK.erc4626Vaults.vaultsDeposit({ - chain: CHAIN, - sender: account.address, - vaultAddress, - amount, - }); - - const transaction = deposit.transaction as UnsignedTransaction; - const depositTxHash = await walletClient.sendTransaction({ ...(transaction as any), value: BigInt(transaction.value), - gas: BigInt(transaction.gas), + gas: transaction.gas ? BigInt(transaction.gas) : undefined, maxFeePerGas: BigInt(transaction.maxFeePerGas), maxPriorityFeePerGas: BigInt(transaction.maxPriorityFeePerGas), }); diff --git a/wallet-earn/app/api/positions/route.ts b/wallet-earn/app/api/positions/route.ts new file mode 100644 index 00000000..e74814fd --- /dev/null +++ b/wallet-earn/app/api/positions/route.ts @@ -0,0 +1,44 @@ +import { CHAIN } from "@/utils/constants"; +import { getWalletAddress } from "@/utils/utils"; +import { CompassApiSDK } from "@compass-labs/api-sdk"; + +export async function GET() { + try { + const walletAddress = getWalletAddress(); + + const compassApiSDK = new CompassApiSDK({ + apiKeyAuth: process.env.COMPASS_API_KEY, + }); + + // Fetch user's earn positions + const positionsResponse = await compassApiSDK.earn.earnPositions({ + chain: CHAIN, + userAddress: walletAddress, + offset: 0, + limit: 100, + days: 30, + }); + + return new Response(JSON.stringify(positionsResponse), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error: any) { + console.error("Error fetching earn positions:", error); + + return new Response( + JSON.stringify({ + error: "Failed to fetch earn positions", + details: error instanceof Error ? error.message : String(error) + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + } + ); + } +} diff --git a/wallet-earn/app/api/token/[token]/route.ts b/wallet-earn/app/api/token/[token]/route.ts index 401712e5..3f62d91e 100644 --- a/wallet-earn/app/api/token/[token]/route.ts +++ b/wallet-earn/app/api/token/[token]/route.ts @@ -1,15 +1,14 @@ import { CHAIN } from "@/utils/constants"; +import { getWalletAddress } from "@/utils/utils"; import { CompassApiSDK } from "@compass-labs/api-sdk"; -import { TokenEnum } from "@compass-labs/api-sdk/models/components"; -import { privateKeyToAccount } from "viem/accounts"; export async function GET( _: Request, - { params }: { params: Promise<{ token: TokenEnum | `0x${string}` }> } + { params }: { params: Promise<{ token: string }> } ) { const { token } = await params; - const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + const walletAddress = getWalletAddress(); const compassApiSDK = new CompassApiSDK({ apiKeyAuth: process.env.COMPASS_API_KEY, @@ -18,7 +17,7 @@ export async function GET( const tokenBalance = compassApiSDK.token.tokenBalance({ chain: CHAIN, token, - user: account.address, + user: walletAddress, }); const tokenPrice = compassApiSDK.token.tokenPrice({ diff --git a/wallet-earn/app/api/vault/[address]/route.ts b/wallet-earn/app/api/vault/[address]/route.ts index 7fce284e..ed8ee467 100644 --- a/wallet-earn/app/api/vault/[address]/route.ts +++ b/wallet-earn/app/api/vault/[address]/route.ts @@ -1,6 +1,6 @@ import { CHAIN } from "@/utils/constants"; +import { getWalletAddress } from "@/utils/utils"; import { CompassApiSDK } from "@compass-labs/api-sdk"; -import { privateKeyToAccount } from "viem/accounts"; export async function GET( _: Request, @@ -8,7 +8,7 @@ export async function GET( ) { const { address } = await params; - const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + const walletAddress = getWalletAddress(); const compassApiSDK = new CompassApiSDK({ apiKeyAuth: process.env.COMPASS_API_KEY, @@ -17,7 +17,7 @@ export async function GET( const vaultResponse = await compassApiSDK.erc4626Vaults.vaultsVault({ chain: CHAIN, vaultAddress: address, - userAddress: account.address, + userAddress: walletAddress, }); return new Response(JSON.stringify(vaultResponse), { diff --git a/wallet-earn/app/api/vaults/route.ts b/wallet-earn/app/api/vaults/route.ts new file mode 100644 index 00000000..ba92f3c4 --- /dev/null +++ b/wallet-earn/app/api/vaults/route.ts @@ -0,0 +1,58 @@ +import { CHAIN } from "@/utils/constants"; +import { CompassApiSDK } from "@compass-labs/api-sdk"; + +export async function GET() { + try { + const compassApiSDK = new CompassApiSDK({ + apiKeyAuth: process.env.COMPASS_API_KEY, + }); + + // Call the SDK method - it will validate the response + const vaultsResponse = await compassApiSDK.earn.earnVaults({ + chain: CHAIN, + orderBy: "one_month_returns", + direction: "desc", + offset: 0, + limit: 50, + }); + + return new Response(JSON.stringify(vaultsResponse), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error: any) { + console.error("Error fetching vaults:", error); + + // If it's a validation error from the SDK, extract the raw response body + if (error?.rawResponse) { + console.log("Validation failed, extracting raw response body"); + try { + const clonedResponse = error.rawResponse.clone(); + const rawData = await clonedResponse.json(); + return new Response(JSON.stringify(rawData), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (parseError) { + console.error("Failed to parse raw response:", parseError); + } + } + + return new Response( + JSON.stringify({ + error: "Failed to fetch vaults", + details: error instanceof Error ? error.message : String(error) + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + } + ); + } +} diff --git a/wallet-earn/app/api/withdraw/route.ts b/wallet-earn/app/api/withdraw/route.ts index ff21963b..855551e2 100644 --- a/wallet-earn/app/api/withdraw/route.ts +++ b/wallet-earn/app/api/withdraw/route.ts @@ -32,62 +32,33 @@ export async function POST(request: Request) { apiKeyAuth: process.env.COMPASS_API_KEY, }); - const auth = - await compassApiSDK.transactionBundler.transactionBundlerAuthorization({ - chain: CHAIN, - sender: account.address, - }); - - const signedAuth = await walletClient.signAuthorization({ - account, - contractAddress: auth.address as `0x${string}`, - nonce: auth.nonce, + const withdraw = await compassApiSDK.earn.earnManage({ + owner: account.address, + chain: CHAIN, + venue: { + type: "VAULT", + vaultAddress, + }, + action: "WITHDRAW", + amount: isAll ? "ALL" : amount, }); - const withdrawWithFee = - await compassApiSDK.transactionBundler.transactionBundlerExecute({ - chain: CHAIN, - sender: account.address, - signedAuthorization: { - nonce: signedAuth.nonce, - address: signedAuth.address, - chainId: signedAuth.chainId, - r: signedAuth.r, - s: signedAuth.s, - yParity: signedAuth.yParity as number, - }, - actions: [ - { - body: { - actionType: "VAULT_WITHDRAW", - vaultAddress, - amount: isAll ? "ALL" : amount, - }, - }, - { - // Extract 1% fee - body: { - actionType: "TOKEN_TRANSFER", - to: "0xd92710ffFF5c6449ADc1b0B86283eb7dbF37567d", - token, - amount: amount * 0.01, - }, - }, - ], - }); + const transaction = withdraw.transaction as UnsignedTransaction; - const transaction = withdrawWithFee.transaction as UnsignedTransaction; + if (!transaction) { + throw new Error("No transaction returned from earnManage"); + } - const withdrawWithFeeTxHash = await walletClient.sendTransaction({ + const withdrawTxHash = await walletClient.sendTransaction({ ...(transaction as any), value: BigInt(transaction.value), - gas: BigInt(transaction.gas), + gas: transaction.gas ? BigInt(transaction.gas) : undefined, maxFeePerGas: BigInt(transaction.maxFeePerGas), maxPriorityFeePerGas: BigInt(transaction.maxPriorityFeePerGas), }); const tx = await publicClient.waitForTransactionReceipt({ - hash: withdrawWithFeeTxHash, + hash: withdrawTxHash, }); console.log("tx", tx); diff --git a/wallet-earn/app/page.tsx b/wallet-earn/app/page.tsx index 906ff597..12b0d450 100644 --- a/wallet-earn/app/page.tsx +++ b/wallet-earn/app/page.tsx @@ -1,9 +1,8 @@ import Screens from "@/components/Screens"; -import { cn, generateWalletGradient } from "@/utils/utils"; -import { privateKeyToAccount } from "viem/accounts"; +import { cn, generateWalletGradient, getWalletAddress } from "@/utils/utils"; export default async function Home() { - const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + const walletAddress = getWalletAddress(); return (
@@ -13,11 +12,11 @@ export default async function Home() { className={cn( "w-6 h-6 border border-neutral-200 rounded-full mr-2 outline -outline-offset-2 outline-neutral-900/15" )} - style={{ background: generateWalletGradient(account.address) }} + style={{ background: generateWalletGradient(walletAddress) }} /> - {account.address.slice(0, 6)} + {walletAddress.slice(0, 6)} ●●●● - {account.address.slice(-4)} + {walletAddress.slice(-4)}
diff --git a/wallet-earn/components/EarnItem.tsx b/wallet-earn/components/EarnItem.tsx index b5ba6cb4..6765e80b 100644 --- a/wallet-earn/components/EarnItem.tsx +++ b/wallet-earn/components/EarnItem.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { TokenData, VaultData } from "./Screens"; -import { TrendingUp, User } from "lucide-react"; +import { TokenData } from "./Screens"; +import { EnrichedVaultData } from "./TokenScreen"; +import { TrendingUp, Copy, Check } from "lucide-react"; import { cn } from "@/utils/utils"; import { Slider } from "./primitives/Slider"; import { Spinner } from "@geist-ui/core"; @@ -11,7 +12,7 @@ export default function EarnItem({ setIsOpen, handleRefresh, }: { - vaultData: VaultData; + vaultData: EnrichedVaultData; token: TokenData; setIsOpen: (value: boolean) => void; handleRefresh: () => void; @@ -37,7 +38,7 @@ export default function EarnItem({ setOpen(true); setIsOpen(true); }} - key={vaultData.symbol} + key={vaultData.address} >
@@ -50,9 +51,9 @@ export default function EarnItem({ className="absolute -translate-x-full -left-1 text-green-600" size={14} /> - {Number(vaultData.apy.apy7Day).toFixed(2)}% + {(Number(vaultData.oneMonthReturns) * 100).toFixed(2)}%
-
7 day
+
1 month
@@ -63,15 +64,15 @@ export default function EarnItem({
$ {( - Number(vaultData.userPosition?.amountInUnderlyingToken) * + Number(vaultData.userPosition?.amountInUnderlyingToken || 0) * Number(token.price) ).toFixed(2)}
{Number( - vaultData.userPosition?.amountInUnderlyingToken + vaultData.userPosition?.amountInUnderlyingToken || 0 ).toFixed(3)}{" "} - {vaultData.underlyingToken.symbol} + {vaultData.denomination}
@@ -122,7 +123,7 @@ function EarnForm({ isLoading, setIsClosing, }: { - vaultData: VaultData; + vaultData: EnrichedVaultData; token: TokenData; handleRefresh: () => void; setIsLoading: (v: boolean) => void; @@ -130,34 +131,31 @@ function EarnForm({ setIsClosing: (v: boolean) => void; }) { const [amount, setAmount] = React.useState( - Number(vaultData.userPosition?.amountInUnderlyingToken) + Number(vaultData.userPosition?.amountInUnderlyingToken || 0) ); const submitEarnTransaction = async () => { setIsLoading(true); try { let response: Response; - if (amount > Number(vaultData.userPosition?.amountInUnderlyingToken)) { - const depositAmount = ( - amount - Number(vaultData.userPosition?.amountInUnderlyingToken) - ).toFixed(token.decimals); + const currentPosition = Number(vaultData.userPosition?.amountInUnderlyingToken || 0); + if (amount > currentPosition) { + const depositAmount = (amount - currentPosition).toFixed(token.decimals); response = await fetch("/api/deposit", { method: "POST", body: JSON.stringify({ - vaultAddress: vaultData.vaultAddress, + vaultAddress: vaultData.address, amount: depositAmount, token: token.tokenSymbol, }), }); } else { - const withdrawAmount = ( - Number(vaultData.userPosition?.amountInUnderlyingToken) - amount - ).toFixed(token.decimals); + const withdrawAmount = (currentPosition - amount).toFixed(token.decimals); console.log("amount", amount); response = await fetch("/api/withdraw", { method: "POST", body: JSON.stringify({ - vaultAddress: vaultData.vaultAddress, + vaultAddress: vaultData.address, amount: withdrawAmount, isAll: amount === 0 ? true : false, token: token.tokenSymbol, @@ -176,30 +174,57 @@ function EarnForm({ } }; + const [copied, setCopied] = React.useState(false); + + const copyAddress = () => { + navigator.clipboard.writeText(vaultData.address); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + return (
+
+

+ {vaultData.name} +

+ +

- Historical Earnings + Performance Metrics

    - {Object.entries(vaultData.apy).map( - ([key, value]) => - key !== "current" && ( -
  • -
    - - {Number(value).toFixed(2)}% -
    -
    - {key.replace("apy", "").replace("Day", " day")} -
    -
  • - ) - )} +
  • +
    + + {(Number(vaultData.cagr) * 100).toFixed(2)}% +
    +
    CAGR
    +
  • +
  • +
    + {(Number(vaultData.lifetimeReturn) * 100).toFixed(2)}% +
    +
    + Lifetime +
    +
@@ -232,12 +257,7 @@ function EarnForm({