import { useReadContract, useReadContracts } from "wagmi"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import toast from "react-hot-toast"; import { config } from "../../config"; import { BOND_DEPOSITORY_ADDRESSES, DAO_TREASURY_ADDRESSES, BONDING_CALCULATOR_ADDRESSES, } from "../../constants/addresses"; import { abi as BondAbi } from "../../abi/GhostBondDepository.json"; import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json"; import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json"; import { useFtsoPrice } from "../prices"; import { useTokenSymbol, useTokenSymbols } from "../tokens"; import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { shorten } from "../../helpers"; export const useLiveBonds = (chainId) => { const baseTokenPerUsd = useFtsoPrice(chainId); const { data: liveIndexesRaw, refetch } = useReadContract({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "liveMarkets", scopeKey: `liveMarkets-${chainId}`, chainId: chainId, }); const { data: markets } = useReadContracts({ contracts: liveIndexesRaw?.map((id) => ({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "markets", args: [id], scopeKey: `markets-${id.toString()}-${chainId}`, chainId: chainId, })) }); const { data: terms } = useReadContracts({ contracts: liveIndexesRaw?.map((id) => ({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "terms", args: [id], scopeKey: `terms-${id.toString()}-${chainId}`, chainId: chainId, })) }); const { data: metas } = useReadContracts({ contracts: liveIndexesRaw?.map((id) => ({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "metadatas", args: [id], scopeKey: `metadatas-${id.toString()}-${chainId}`, chainId: chainId, })) }); const { data: marketPrices } = useReadContracts({ contracts: liveIndexesRaw?.map((id) => ({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "marketPrice", args: [id], scopeKey: `marketPrice-${id.toString()}-${chainId}`, chainId: chainId, })) }); const { data: quotePrices } = useReadContracts({ contracts: markets?.map((_, index) => { const quoteTokenDecimals = metas?.at(index).result?.at(5) ? metas.at(index).result.at(5) : 18; const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : ""; const one = 10n ** BigInt(quoteTokenDecimals); return { abi: TreasuryAbi, address: DAO_TREASURY_ADDRESSES[chainId], functionName: "tokenValue", args: [quoteTokenAddress, one], scopeKey: `tokenValue-${quoteTokenAddress}-${one.toString()}-${chainId}`, chainId: chainId, } }) }); const { data: markdowns } = useReadContracts({ contracts: markets?.map((_, index) => { const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : ""; return { abi: BondingCalculatorAbi, address: BONDING_CALCULATOR_ADDRESSES[chainId], functionName: "markdown", args: [quoteTokenAddress], scopeKey: `markdown-${quoteTokenAddress}-${chainId}`, chainId: chainId, } }) }); const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1))); const { symbol: baseTokenSymbol } = useTokenSymbol(chainId, "FTSO"); const liveBonds = liveIndexesRaw ? liveIndexesRaw.map((bondIndex, index) => { const id = Number(bondIndex); const marketPrice = marketPrices?.at(index).result ? marketPrices.at(index).result : 0n; const marketCapacity = markets?.at(index).result?.at(0) ? markets.at(index).result.at(0) : 0n; const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : ""; const capacityInQuote = markets?.at(index).result?.at(2) ? markets.at(index).result.at(2) : 0n; const marketMaxPayout = markets?.at(index).result?.at(4) ? markets.at(index).result.at(4) : 0n; const quoteTokenDecimals = metas?.at(index).result?.at(5) ? metas.at(index).result.at(5) : 18; const quoteTokenPerUsd = quotePrices?.at(index).result ? new DecimalBigNumber(quotePrices.at(index).result * (10n ** 9n), quoteTokenDecimals) : new DecimalBigNumber(0n, quoteTokenDecimals); const markdown = markdowns?.at(index).result ? new DecimalBigNumber(markdowns.at(index).result, quoteTokenDecimals) : new DecimalBigNumber(1n, 0); const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : ""; const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9); const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown); const discount = baseTokenPerUsd._value > 0n ? baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd) : new DecimalBigNumber("0", baseTokenPerUsd._decimals); const capacityInBaseToken = capacityInQuote ? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9) : new DecimalBigNumber(marketCapacity, 9); const capacityInQuoteToken = capacityInQuote ? new DecimalBigNumber(marketCapacity, quoteTokenDecimals) : new DecimalBigNumber(marketCapacity * marketPrice, quoteTokenDecimals); const maxPayoutInBaseToken = new DecimalBigNumber(marketMaxPayout, 9); const maxPayoutInQuoteToken = new DecimalBigNumber( marketMaxPayout * marketPrice, quoteTokenDecimals ); const zero = new DecimalBigNumber(0n, 0); const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`; return { id, discount, displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress), baseToken: { name: quoteTokenSymbol, purchaseUrl: getTokenPurchaseLink(chainId, ""), icons: ["FTSO"], tokenAddress: getTokenAddress(chainId, "FTSO") }, quoteToken: { name: quoteTokenName, purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress), icons: getTokenIcons(chainId, quoteTokenAddress), decimals: quoteTokenDecimals, quoteTokenAddress, }, duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true, isSoldOut: capacityInBaseToken.eq(zero) || capacityInBaseToken.eq(zero), price: { inUsd: priceInUsd, inBaseToken: quoteTokenPerBaseToken, marketPriceInUsd: baseTokenPerUsd }, capacity: { inBaseToken: capacityInBaseToken, inQuoteToken: capacityInQuoteToken, }, maxPayout: { inBaseToken: maxPayoutInBaseToken, inQuoteToken: maxPayoutInQuoteToken, }, }; }).sort((a, b) => (a.id > b.id ? -1 : 1)) : []; return { liveBonds, refetch }; } export const useNotes = (chainId, address) => { const { data: indexesFor, refetch } = useReadContract({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "indexesFor", args: [address], scopeKey: `indexesFor-${address}-${chainId}`, chainId: chainId, }); const { data: notesRaw } = useReadContracts({ contracts: indexesFor?.map((id) => ({ abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "notes", args: [address, id], scopeKey: `notes-${id.toString()}-${address}-${chainId}`, chainId: chainId, })) }); const { data: markets } = useReadContracts({ contracts: notesRaw?.map((note) => { const marketId = note.result?.at(4) ? note.result.at(4) : ""; return { abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "markets", args: [marketId], scopeKey: `markets-${marketId.toString()}-${chainId}`, chainId: chainId, } }), }); const { data: terms } = useReadContracts({ contracts: notesRaw?.map((note) => { const marketId = note.result?.at(4) ? note.result.at(4) : ""; return { abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName: "terms", args: [marketId], scopeKey: `terms-${marketId.toString()}-${chainId}`, chainId: chainId, } }), }); const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1))); const notes = indexesFor ? indexesFor.map((noteIndex, index) => { const id = Number(noteIndex); const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : ""; const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : ""; return { id, quoteToken: { name: quoteTokenSymbol, icons: getTokenIcons(chainId, quoteTokenAddress), }, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, created: notesRaw?.at(index).result?.at(1) ? notesRaw.at(index).result.at(1) : 0, matured: notesRaw?.at(index).result?.at(2) ? notesRaw.at(index).result.at(2) : 0, payout: new DecimalBigNumber( notesRaw?.at(index).result?.at(0) ? notesRaw.at(index).result.at(0) : 0n, 18 ), } }) : []; return { notes }; } export const purchaseBond = async (chainId, bondId, amount, maxPrice, user, referral) => { const args = [ bondId, amount, maxPrice, user, referral ]; const messages = { replacedMsg: "Bond transaction was replaced. Wait for inclusion please.", successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`, errorMsg: "Bond transaction failed. Check logs for error detalization.", }; await executeOnChainTransaction( chainId, "deposit", args, user, messages ); } export const redeem = async (chainId, user, isGhst, indexes) => { const args = [ user, isGhst, indexes ]; const messages = { replacedMsg: "Redeem transaction was replaced. Wait for inclusion please.", successMsg: `Address ${shorten(user)} redeemed ${indexes.length} bonds in ${isGhst ? "GHST" : "STNK"}!`, errorMsg: `Redeem of ${indexes.length} bonds failed. Check logs for error detalization.`, }; await executeOnChainTransaction( chainId, "redeem", args, user, messages ); } const executeOnChainTransaction = async ( chainId, functionName, args, account, messages ) => { try { const { request } = await simulateContract(config, { abi: BondAbi, address: BOND_DEPOSITORY_ADDRESSES[chainId], functionName, args, account, chainId }); const txHash = await writeContract(config, request); await waitForTransactionReceipt(config, { hash: txHash, onReplaced: () => toast(messages.replacedMsg), chainId }); toast.success(messages.successMsg); } catch (err) { console.error(err); toast.error(messages.errorMsg) } }