@molecularlabs/nucleus-frontend
Version:
Nucleus BoringVault Frontend SDK
211 lines • 6.47 kB
JavaScript
import { parseUnits } from "viem";
import { getRateInQuoteAndSharesAndWantAssetDecimals } from "../../api/accountant";
import { getErc20Decimals } from "../../api/erc20";
import { getAssetSlippage } from "../../api/solver-slippage";
import { toChainId } from "../../api/vault-config";
import {
ATOMIC_QUEUE_CONTRACT_ADDRESS,
DEFAULT_APPROVAL_AMOUNT,
DEFAULT_DEADLINE,
DEFAULT_WITHDRAW_SLIPPAGE
} from "../../constants";
import { AtomicQueueAbi } from "../../contracts/atomic-queue-abi";
import { BoringVaultAbi } from "../../contracts/boring-vault-abi";
import { tryCatch } from "../../utils/try-catch";
import {
prepareBridgeTransactionData
} from "../bridge";
import { getVaultByKey } from "../index";
import { calculateAtomicPrice, prepareUserRequest } from "./utils";
const prepareWithdrawTransactionData = async ({
vaultKey,
chainId,
wantTokenSymbol,
offerAmount,
deadline = DEFAULT_DEADLINE,
slippage
}) => {
const vault = await getVaultByKey(vaultKey);
const normalizedChainId = toChainId(chainId);
if (!vault) {
throw new Error(`Invalid vault key: ${vaultKey}`);
}
if (!vault.contracts) {
throw new Error(`Contracts not configured for vault ${vaultKey}`);
}
if (!vault.contracts.boringVault) {
throw new Error(
`BoringVault contract not configured for vault ${vaultKey}`
);
}
const boringVaultAddress = vault.contracts.boringVault;
if (!vault.contracts.accountant) {
throw new Error(`Accountant contract not configured for vault ${vaultKey}`);
}
const accountantAddress = vault.contracts.accountant;
const destinationChain = vault.withdraw.sourceChains[normalizedChainId].destinationChains[normalizedChainId];
const wantTokens = destinationChain.wantTokens;
if (!!wantTokens && !wantTokens[wantTokenSymbol]) {
throw new Error(
`Invalid want token "${wantTokenSymbol}" for vault ${vaultKey}. Allowed tokens are: ${Object.keys(wantTokens).join(", ")}`
);
}
const wantAssetAddress = wantTokens[wantTokenSymbol].address;
if (!wantAssetAddress) {
throw new Error(`Invalid want token symbol: ${wantTokenSymbol}`);
}
const [wantAssetDecimals, sharesAssetDecimals, rateInQuote] = await getRateInQuoteAndSharesAndWantAssetDecimals({
sharesAssetAddress: boringVaultAddress,
wantAssetAddress,
accountantAddress,
chainId: normalizedChainId
});
if (wantAssetDecimals.status === "failure") {
throw new Error(wantAssetDecimals.error.message);
}
if (rateInQuote.status === "failure") {
throw new Error(rateInQuote.error.message);
}
if (sharesAssetDecimals.status === "failure") {
throw new Error(sharesAssetDecimals.error.message);
}
const formattedOfferAmount = parseUnits(
offerAmount,
sharesAssetDecimals.result
);
const {
data: solverSlippage,
error: solverSlippageError,
success: solverSlippageSuccess
} = await tryCatch(
getAssetSlippage(normalizedChainId, boringVaultAddress, wantAssetAddress)
);
if (!solverSlippageSuccess) {
throw new Error(
`Error getting solver slippage: ${solverSlippageError.message}`
);
}
const effectiveSlippage = slippage ?? solverSlippage;
const atomicPrice = calculateAtomicPrice(
rateInQuote.result,
wantAssetDecimals.result,
effectiveSlippage
);
const userRequest = prepareUserRequest(
formattedOfferAmount,
atomicPrice,
deadline
);
return {
abi: AtomicQueueAbi,
address: ATOMIC_QUEUE_CONTRACT_ADDRESS,
functionName: "updateAtomicRequest",
args: [boringVaultAddress, wantAssetAddress, userRequest],
chainId: normalizedChainId
};
};
const prepareBridgeAndWithdrawTransactionData = async ({
deadline = DEFAULT_DEADLINE,
destinationChainId,
offerAmount,
slippage = DEFAULT_WITHDRAW_SLIPPAGE,
sourceChainId,
userAddress,
vaultKey,
wantTokenSymbol
}) => {
const vault = await getVaultByKey(vaultKey);
if (!vault) {
throw new Error(`Invalid vault key: ${vaultKey}`);
}
const {
data: withdrawTransactionData,
error: withdrawError,
success: withdrawSuccess
} = await tryCatch(
prepareWithdrawTransactionData({
vaultKey,
chainId: destinationChainId,
userAddress,
wantTokenSymbol,
offerAmount,
deadline,
slippage
})
);
if (!withdrawSuccess) {
throw new Error(withdrawError.message);
}
const { args } = withdrawTransactionData;
const [, , userRequest] = args;
const { offerAmount: bridgeAmount } = userRequest;
const {
data: bridgeTransactionData,
error: bridgeError,
success: bridgeSuccess
} = await tryCatch(
prepareBridgeTransactionData({
vaultKey,
bridgeAmount,
sourceChainId,
destinationChainId,
userAddress
})
);
if (!bridgeSuccess) {
throw new Error(bridgeError.message);
}
return {
bridgeTransactionData,
withdrawTransactionData
};
};
const prepareApproveWithdrawToken = async ({
vaultKey,
withdrawAmount,
chainId
}) => {
const normalizedChainId = toChainId(chainId);
const vault = await getVaultByKey(vaultKey);
if (!vault) {
throw new Error(`Invalid vault key: ${vaultKey}`);
}
if (!vault.contracts) {
throw new Error(`Contracts not configured for vault ${vaultKey}`);
}
if (!vault.contracts.boringVault) {
throw new Error(`Teller contract not configured for vault ${vaultKey}`);
}
const withdrawAssetAddress = vault.contracts.boringVault;
const {
data: withdrawAssetDecimals,
error: withdrawAssetDecimalsError,
success: withdrawAssetDecimalsSuccess
} = await tryCatch(
getErc20Decimals({
chainId: normalizedChainId,
tokenAddress: withdrawAssetAddress
})
);
if (!withdrawAssetDecimalsSuccess) {
throw new Error(
`Error getting withdraw asset decimals. ${withdrawAssetDecimalsError.name}: ${withdrawAssetDecimalsError.message}`
);
}
if (!withdrawAssetDecimals) {
throw new Error(`Invalid withdraw token symbol: ${withdrawAssetAddress}`);
}
const withdrawAmountAsBitInt = withdrawAmount ? parseUnits(withdrawAmount, withdrawAssetDecimals) : DEFAULT_APPROVAL_AMOUNT;
return {
abi: BoringVaultAbi,
address: withdrawAssetAddress,
functionName: "approve",
args: [ATOMIC_QUEUE_CONTRACT_ADDRESS, withdrawAmountAsBitInt]
};
};
export {
prepareApproveWithdrawToken,
prepareBridgeAndWithdrawTransactionData,
prepareWithdrawTransactionData
};
//# sourceMappingURL=withdraw.mjs.map