336 lines
13 KiB
JavaScript
336 lines
13 KiB
JavaScript
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)
|
|
}
|
|
}
|