@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
504 lines • 25.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getQuote = getQuote;
const app_sdk_1 = require("@across-protocol/app-sdk");
const abstractjs_1 = require("@biconomy/abstractjs");
const viem_1 = require("viem");
const index_js_1 = require("../../common/index.js");
const marketTokenAbi_js_1 = __importDefault(require("../../environments/abis/marketTokenAbi.js"));
const morphoBlueAbi_js_1 = __importDefault(require("../../environments/abis/morphoBlueAbi.js"));
const morphoVaultAbi_js_1 = __importDefault(require("../../environments/abis/morphoVaultAbi.js"));
const index_js_2 = require("../../environments/index.js");
const ACROSS_ABI = (0, viem_1.parseAbi)([
"function deposit(bytes32 depositor, bytes32 recipient, bytes32 inputToken, bytes32 outputToken, uint256 inputAmount, uint256 outputAmount, uint256 destinationChainId, bytes32 exclusiveRelayer, uint32 quoteTimestamp, uint32 fillDeadline, uint32 exclusivityParameter, bytes message)",
"function depositV3(address depositor,address recipient,address inputToken,address outputToken,uint256 inputAmount,uint256 outputAmount,uint256 destinationChainId,address exclusiveRelayer,uint32 quoteTimestamp,uint32 fillDeadline,uint32 exclusivityParameter,bytes message)",
]);
const WRAP_ABI = (0, viem_1.parseAbi)(["function deposit()"]);
const MEE_CLIENT_API_KEY = "mee_3ZkX3T823ZDfwsNiFsqj5oZS";
const ACROSS_INTEGRATOR_ID = "0x008e";
const findMarketToken = (environments, chainId, underlyingTokenAddress) => {
const originEnvironment = environments.find((env) => chainId === env.chainId);
if (originEnvironment) {
const underlyingTokenConfig = Object.keys(originEnvironment.config.tokens)
.map((token) => {
const marketToken = originEnvironment.config.tokens[token];
return {
...marketToken,
key: token,
};
})
.find((token) => token.address === underlyingTokenAddress);
if (underlyingTokenConfig) {
const originMarket = Object.values(originEnvironment.config.markets).find((market) => market.underlyingToken === underlyingTokenConfig.key);
if (originMarket) {
return {
underlyingToken: originEnvironment.config.tokens[originMarket.underlyingToken],
marketToken: originEnvironment.config.tokens[originMarket.marketToken],
};
}
}
}
return undefined;
};
const findIsolatedMarketToken = (environments, chainId, underlyingTokenAddress) => {
const originEnvironment = environments.find((env) => chainId === env.chainId);
if (originEnvironment) {
const underlyingTokenConfig = Object.keys(originEnvironment.config.tokens)
.map((token) => {
const marketToken = originEnvironment.config.tokens[token];
return {
...marketToken,
key: token,
};
})
.find((token) => token.address === underlyingTokenAddress);
if (underlyingTokenConfig) {
const originMarket = Object.values(originEnvironment.config.morphoMarkets).find((market) => market.collateralToken === underlyingTokenConfig.key);
if (originMarket) {
return {
underlyingToken: originEnvironment.config.tokens[originMarket.collateralToken],
marketToken: originEnvironment.config.tokens[originMarket.loanToken],
};
}
}
}
return undefined;
};
const findVaultToken = (environments, chainId, underlyingTokenAddress) => {
const originEnvironment = environments.find((env) => chainId === env.chainId);
if (originEnvironment) {
const underlyingTokenConfig = Object.keys(originEnvironment.config.tokens)
.map((token) => {
const marketToken = originEnvironment.config.tokens[token];
return {
...marketToken,
key: token,
};
})
.find((token) => token.address === underlyingTokenAddress);
if (underlyingTokenConfig) {
const originMarket = Object.values(originEnvironment.config.vaults).find((market) => market.underlyingToken === underlyingTokenConfig.key);
if (originMarket) {
return {
underlyingToken: originEnvironment.config.tokens[originMarket.underlyingToken],
marketToken: originEnvironment.config.tokens[originMarket.vaultToken],
};
}
}
}
return undefined;
};
async function getQuote(client, args) {
const envs = (0, index_js_1.getEnvironmentsFromArgs)(client, undefined, false).filter((env) => env.chainId !== index_js_2.moonbeam.id && env.chainId !== index_js_2.moonriver.id);
const chains = Object.values(envs).map((env) => env.chain);
const transports = chains.map((chain) => (0, viem_1.http)(chain.rpcUrls.default.http[0]));
const smartAccount = await (0, abstractjs_1.toMultichainNexusAccount)({
signer: args.wallet,
chains,
transports,
});
const meeClient = await (0, abstractjs_1.createMeeClient)({
account: smartAccount,
apiKey: MEE_CLIENT_API_KEY,
});
const accrossClient = (0, app_sdk_1.createAcrossClient)({
integratorId: ACROSS_INTEGRATOR_ID,
chains,
useTestnet: false,
});
if (args.type === "supply" ||
args.type === "morpho-supply" ||
args.type === "vault-deposit") {
const tokenConfig = args.type === "supply"
? findMarketToken(envs, args.destination.chainId, args.destination.address)
: args.type === "morpho-supply"
? findIsolatedMarketToken(envs, args.destination.chainId, args.destination.address)
: findVaultToken(envs, args.destination.chainId, args.destination.address);
if (tokenConfig) {
const quotes = [];
const instructions = [];
const approvals = [];
const transfers = [];
let totalOutput = 0n;
const nativeAmounts = [];
const cleanUps = [];
for (const source of args.sources) {
const sourceChain = chains.find((r) => r.id === source.chainId);
const publicClient = (0, viem_1.createPublicClient)({
chain: sourceChain,
transport: (0, viem_1.http)(sourceChain.rpcUrls.default.http[0]),
});
const nativeBalance = await publicClient.getBalance({
address: args.wallet.account?.address,
});
nativeAmounts.push({
chainId: source.chainId,
amount: nativeBalance,
});
if (source.address === viem_1.zeroAddress) {
const nativeBalanceSmartAccount = await publicClient.getBalance({
address: smartAccount.addressOn(source.chainId),
});
if (nativeBalanceSmartAccount < source.amount) {
transfers.push({
amount: source.amount - nativeBalanceSmartAccount,
chainId: source.chainId,
to: smartAccount.addressOn(source.chainId),
});
}
cleanUps.push({
tokenAddress: source.address,
chainId: source.chainId,
recipientAddress: args.wallet.account.address,
});
}
else {
const erc20Abi = (0, viem_1.parseAbi)([
"function allowance(address owner, address spender) view returns (uint256)",
]);
const erc20Contract = (0, viem_1.getContract)({
address: source.routeTokenAddress,
abi: erc20Abi,
client: publicClient,
});
const allowance = await erc20Contract.read.allowance([
args.wallet.account?.address,
smartAccount.addressOn(source.chainId),
]);
if (allowance < source.amount) {
approvals.push({
spender: smartAccount.addressOn(source.chainId),
tokenAddress: source.routeTokenAddress,
chainId: source.chainId,
amount: source.amount,
});
}
}
let transferOrWrapInstruction = [];
if (source.address === viem_1.zeroAddress) {
const wrapData = (0, viem_1.encodeFunctionData)({
abi: WRAP_ABI,
functionName: "deposit",
args: [],
});
transferOrWrapInstruction = await smartAccount.buildComposable({
type: "rawCalldata",
data: {
calldata: wrapData,
to: source.routeTokenAddress,
chainId: source.chainId,
value: source.amount,
},
});
}
else {
transferOrWrapInstruction = await smartAccount.buildComposable({
type: "transferFrom",
data: {
sender: args.wallet.account?.address,
recipient: smartAccount.addressOn(source.chainId),
tokenAddress: source.routeTokenAddress,
amount: source.amount,
chainId: source.chainId,
},
});
}
if (source.chainId !== args.destination.chainId) {
const acrossQuote = await accrossClient.getQuote({
route: {
originChainId: source.chainId,
inputToken: source.routeTokenAddress,
destinationChainId: args.destination.chainId,
outputToken: args.destination.routeTokenAddress,
},
inputAmount: source.amount.toString(),
recipient: smartAccount.addressOn(args.destination.chainId),
});
const acrossChainInfo = await accrossClient.getChainInfo(source.chainId);
const approveAcrossSpendInstructions = await smartAccount.buildComposable({
type: "approve",
data: {
tokenAddress: source.routeTokenAddress,
amount: source.amount,
chainId: source.chainId,
spender: acrossChainInfo.spokePool,
},
});
const bridgeData = (0, viem_1.encodeFunctionData)({
abi: ACROSS_ABI,
functionName: "depositV3",
args: [
smartAccount.addressOn(source.chainId),
smartAccount.addressOn(args.destination.chainId),
acrossQuote.deposit.inputToken,
acrossQuote.deposit.outputToken,
acrossQuote.deposit.inputAmount,
acrossQuote.deposit.outputAmount,
acrossQuote.deposit.destinationChainId,
acrossQuote.deposit.exclusiveRelayer,
acrossQuote.deposit.quoteTimestamp,
acrossQuote.deposit.fillDeadline,
acrossQuote.deposit.exclusivityDeadline,
acrossQuote.deposit.message,
],
});
totalOutput += acrossQuote.deposit.outputAmount;
const bridgeInstructions = await smartAccount.buildComposable({
type: "rawCalldata",
data: {
calldata: bridgeData,
to: acrossChainInfo.spokePool,
chainId: source.chainId,
},
});
quotes.push(acrossQuote);
instructions.push(await smartAccount.buildComposable({
type: "batch",
data: {
instructions: [
transferOrWrapInstruction,
approveAcrossSpendInstructions,
bridgeInstructions,
],
},
}));
}
else {
totalOutput += source.amount;
instructions.push(transferOrWrapInstruction);
}
}
const approveSupplyInstructions = await smartAccount.buildComposable({
type: "approve",
data: {
tokenAddress: args.destination.routeTokenAddress,
amount: (0, abstractjs_1.runtimeERC20BalanceOf)({
tokenAddress: args.destination.routeTokenAddress,
targetAddress: smartAccount.addressOn(args.destination.chainId, true),
constraints: [(0, abstractjs_1.greaterThanOrEqualTo)(totalOutput)],
}),
chainId: args.destination.chainId,
spender: args.type === "morpho-supply"
? args.destination.morphoBlue
: tokenConfig.marketToken.address,
},
});
if (args.type === "supply") {
const supplyInstructions = await smartAccount.buildComposable({
type: "default",
data: {
to: tokenConfig.marketToken.address,
abi: marketTokenAbi_js_1.default,
functionName: "mint",
args: [
(0, abstractjs_1.runtimeEncodeAbiParameters)([{ name: "amount", type: "uint256" }], [
(0, abstractjs_1.runtimeERC20BalanceOf)({
targetAddress: smartAccount.addressOn(args.destination.chainId, true),
tokenAddress: args.destination
.routeTokenAddress,
constraints: [(0, abstractjs_1.greaterThanOrEqualTo)(totalOutput)],
}),
]),
],
chainId: args.destination.chainId,
},
});
const transferBack = await smartAccount.buildComposable({
type: "transfer",
data: {
amount: (0, abstractjs_1.runtimeERC20BalanceOf)({
tokenAddress: tokenConfig.marketToken.address,
targetAddress: smartAccount.addressOn(args.destination.chainId, true),
constraints: [(0, abstractjs_1.greaterThanOrEqualTo)(1n)],
}),
chainId: args.destination.chainId,
recipient: args.wallet.account.address,
tokenAddress: tokenConfig.marketToken.address,
},
});
const batchedInstructions = await smartAccount.buildComposable({
type: "batch",
data: {
instructions: [
approveSupplyInstructions,
supplyInstructions,
transferBack,
],
},
});
instructions.push(batchedInstructions);
}
else if (args.type === "morpho-supply") {
const supplyCollateralData = (0, viem_1.encodeFunctionData)({
abi: morphoBlueAbi_js_1.default,
functionName: "supplyCollateral",
args: [
{
loanToken: args.destination.marketParams?.loanToken,
collateralToken: args.destination.marketParams?.collateralToken,
oracle: args.destination.marketParams?.oracle,
irm: args.destination.marketParams?.irm,
lltv: args.destination.marketParams?.lltv,
},
totalOutput,
args.wallet.account.address,
"0x",
],
});
const supplyInstructions = await smartAccount.buildComposable({
type: "rawCalldata",
data: {
calldata: supplyCollateralData,
to: args.destination.morphoBlue,
chainId: args.destination.chainId,
value: 0n,
},
});
const batchedInstructions = await smartAccount.buildComposable({
type: "batch",
data: {
instructions: [approveSupplyInstructions, supplyInstructions],
},
});
instructions.push(batchedInstructions);
}
else if (args.type === "vault-deposit") {
const depositData = (0, viem_1.encodeFunctionData)({
abi: morphoVaultAbi_js_1.default,
functionName: "deposit",
args: [totalOutput, args.wallet.account.address],
});
const depositInstructions = await smartAccount.buildComposable({
type: "rawCalldata",
data: {
calldata: depositData,
to: tokenConfig.marketToken.address,
chainId: args.destination.chainId,
value: 0n,
},
});
const batchedInstructions = await smartAccount.buildComposable({
type: "batch",
data: {
instructions: [approveSupplyInstructions, depositInstructions],
},
});
instructions.push(batchedInstructions);
}
const fusionQuote = await meeClient.getFusionQuote({
trigger: {
tokenAddress: viem_1.zeroAddress,
chainId: args.destination.chainId,
amount: 1n,
},
feeToken: {
address: viem_1.zeroAddress,
chainId: args.destination.chainId,
},
instructions,
cleanUps: [
...cleanUps,
{
tokenAddress: args.destination.routeTokenAddress,
chainId: args.destination.chainId,
recipientAddress: args.wallet.account.address,
},
],
});
return {
status: "success",
checks: {
approvals,
transfers,
},
account: args.wallet.account,
instructions,
quote: {
hash: fusionQuote.quote.hash,
node: fusionQuote.quote.node,
commitment: fusionQuote.quote.commitment,
fee: {
tokenAmount: fusionQuote.quote.paymentInfo.tokenAmount,
tokenWeiAmount: fusionQuote.quote.paymentInfo.tokenWeiAmount,
tokenValue: fusionQuote.quote.paymentInfo.tokenValue,
},
userOps: fusionQuote.quote.userOps.map((operation) => ({
sender: operation.userOp.sender,
nonce: operation.userOp.nonce,
initCode: operation.userOp.initCode,
callData: operation.userOp.callData,
})),
fusionQuote,
},
execute: async () => {
try {
const superTx = await meeClient.executeFusionQuote({
fusionQuote,
});
return {
hash: superTx.hash,
wait: async (confirmations) => {
await meeClient.waitForSupertransactionReceipt({
hash: superTx.hash,
confirmations,
});
return;
},
status: async () => {
const receipt = await meeClient.getSupertransactionReceipt({
hash: superTx.hash,
});
if (args.type === "supply" ||
args.type === "morpho-supply" ||
args.type === "vault-deposit") {
try {
receipt.userOps.splice(receipt.userOps.length - (1 + cleanUps.length));
const supplyOp = receipt.userOps.splice(receipt.userOps.length - 1);
const acrossOps = [...receipt.userOps];
const acrossOpsPending = acrossOps.filter((op) => op.executionStatus === "MINING" ||
op.executionStatus === "PENDING").length > 0;
const acrossOpsFailed = acrossOps.filter((op) => op.executionStatus === "FAILED" ||
op.executionStatus === "MINED_FAIL").length > 0;
const acrossOpsSucceed = acrossOps.filter((op) => op.executionStatus === "MINED_SUCCESS" ||
op.executionStatus === "SUCCESS").length === acrossOps.length;
const supplyOpPending = supplyOp.filter((op) => op.executionStatus === "MINING" ||
op.executionStatus === "PENDING").length > 0;
const supplyOpFailed = supplyOp.filter((op) => op.executionStatus === "FAILED" ||
op.executionStatus === "MINED_FAIL").length > 0;
const supplyOpSucceed = supplyOp.filter((op) => op.executionStatus === "MINED_SUCCESS" ||
op.executionStatus === "SUCCESS").length === supplyOp.length;
if (acrossOpsFailed || supplyOpFailed) {
return "reverted";
}
if (acrossOpsPending || supplyOpPending) {
return "processing";
}
if (acrossOpsSucceed || supplyOpSucceed) {
return "success";
}
}
catch (ex) {
return "pending";
}
}
return "pending";
},
};
}
catch (ex) {
console.log(ex);
throw ex;
}
},
};
}
}
return {
status: "error",
error: "Token not found",
};
}
//# sourceMappingURL=common.js.map