@brian-ai/ee-portals-solver
Version:
> ⚠️ README: work in progress.
339 lines (331 loc) • 9.22 kB
JavaScript
// src/index.ts
import { Solver } from "@brian-ai/ee-core";
// src/actions/deposit.ts
import ky from "ky";
import {
createPublicClient,
http,
encodeFunctionData,
erc20Abi
} from "viem";
// src/constants.ts
var NATIVE_ADDRESS = "0x0000000000000000000000000000000000000000";
// src/utils.ts
var ADJUSTED_CHAINS_NAME_PORTALS = {
"arbitrum one": "arbitrum",
"op mainnet": "optimism",
avalanche: "avalanche",
"bnb smart chain": "bsc",
gnosis: "gnosis",
polygon: "polygon",
base: "base",
ethereum: "ethereum",
fantom: "fantom"
};
// src/actions/deposit.ts
import { getAmountToApprove } from "@brian-ai/ee-core";
var portalsDeposit = async ({
apiUrl: inputApiUrl,
apiKey,
feeReceiver,
fee,
slippage,
chain,
address,
inputToken,
outputToken,
amount
}) => {
const adjustedChainName = ADJUSTED_CHAINS_NAME_PORTALS[chain.name.toLowerCase()];
const publicClient = createPublicClient({
chain,
transport: http()
});
const blockNumber = await publicClient.getBlockNumber();
const tokens = `${adjustedChainName.toLowerCase()}:${outputToken}`;
const tokenInfoUrl = `${inputApiUrl}/tokens?ids=${tokens}&networks=${adjustedChainName.toLowerCase()}&sortBy=liquidity&sortDirection=desc`;
const tokenOut = await fetch(tokenInfoUrl, {
headers: {
Authorization: `Bearer ${apiKey}`
}
});
const tokenOutJson = await tokenOut.json();
const tokenOutAddress = tokenOutJson.tokens[0].address;
if (!tokenOut) {
return Promise.reject("Portals: token not supported");
}
const depositUrl = `${inputApiUrl}/portal?sender=${address}&inputToken=${adjustedChainName.toLowerCase()}%3A${inputToken}&inputAmount=${amount}&outputToken=${adjustedChainName.toLowerCase()}%3A${tokenOutAddress}&slippageTolerancePercentage=${slippage}&partner=${feeReceiver}&feePercentage=${fee}&validate=false`;
const data = await ky.get(depositUrl, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
}
}).json();
const steps = [
{
chainId: chain.id,
blockNumber: Number(blockNumber),
from: data.tx.from,
to: data.tx.to,
gasLimit: "100000",
gasPrice: void 0,
data: data.tx.data,
value: data.tx.value
}
];
if (inputToken !== NATIVE_ADDRESS) {
const approveAmount = await getAmountToApprove(
inputToken,
address,
data.tx.to,
amount,
chain
);
if (approveAmount > 0n) {
steps.unshift({
chainId: chain.id,
blockNumber: Number(blockNumber),
from: address,
to: inputToken,
gasLimit: "80000",
gasPrice: void 0,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [data.tx.to, approveAmount]
}),
value: "0"
});
}
}
return steps;
};
// src/actions/withdraw.ts
import ky2 from "ky";
import {
createPublicClient as createPublicClient2,
http as http2,
encodeFunctionData as encodeFunctionData2,
erc20Abi as erc20Abi2
} from "viem";
import { getAmountToApprove as getAmountToApprove2 } from "@brian-ai/ee-core";
var portalsWithdraw = async ({
apiUrl: inputApiUrl,
apiKey,
feeReceiver,
fee,
slippage,
chain,
address,
inputToken,
outputToken,
amount
}) => {
const adjustedChainName = ADJUSTED_CHAINS_NAME_PORTALS[chain.name.toLowerCase()];
const publicClient = createPublicClient2({
chain,
transport: http2()
});
const blockNumber = await publicClient.getBlockNumber();
const tokens = `${adjustedChainName.toLowerCase()}:${outputToken}`;
const tokenInfoUrl = `${inputApiUrl}/tokens?ids=${tokens}&networks=${adjustedChainName.toLowerCase()}&sortBy=liquidity&sortDirection=desc`;
const tokenOut = await fetch(tokenInfoUrl, {
headers: {
Authorization: `Bearer ${apiKey}`
}
});
const tokenOutJson = await tokenOut.json();
const tokenOutAddress = tokenOutJson.tokens[0].address;
if (!tokenOut) {
return Promise.reject("Portals: token not supported");
}
const withdrawUrl = `${inputApiUrl}/portal?sender=${address}&inputToken=${adjustedChainName.toLowerCase()}%3A${inputToken}&inputAmount=${amount}&outputToken=${adjustedChainName.toLowerCase()}%3A${tokenOutAddress}&slippageTolerancePercentage=${slippage}&partner=${feeReceiver}&feePercentage=${fee}&validate=false`;
const data = await ky2.get(withdrawUrl, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
}
}).json();
const steps = [
{
chainId: chain.id,
blockNumber: Number(blockNumber),
from: data.tx.from,
to: data.tx.to,
gasLimit: "100000",
gasPrice: void 0,
data: data.tx.data,
value: data.tx.value
}
];
if (inputToken !== NATIVE_ADDRESS) {
const approveAmount = await getAmountToApprove2(
inputToken,
address,
data.tx.to,
amount,
chain
);
if (approveAmount > 0n) {
steps.unshift({
chainId: chain.id,
blockNumber: Number(blockNumber),
from: address,
to: inputToken,
gasLimit: "80000",
gasPrice: void 0,
data: encodeFunctionData2({
abi: erc20Abi2,
functionName: "approve",
args: [data.tx.to, approveAmount]
}),
value: "0"
});
}
}
return steps;
};
// src/actions/swap.ts
import ky3 from "ky";
import {
createPublicClient as createPublicClient3,
http as http3,
encodeFunctionData as encodeFunctionData3,
erc20Abi as erc20Abi3
} from "viem";
import { getAmountToApprove as getAmountToApprove3 } from "@brian-ai/ee-core";
var portalsSwap = async ({
apiUrl: inputApiUrl,
apiKey,
feeReceiver,
fee,
slippage,
chain,
address,
inputToken,
outputToken,
amount
}) => {
const adjustedChainName = ADJUSTED_CHAINS_NAME_PORTALS[chain.name.toLowerCase()];
const publicClient = createPublicClient3({
chain,
transport: http3()
});
const blockNumber = await publicClient.getBlockNumber();
const swapUrl = `${inputApiUrl}/portal?sender=${address}&inputToken=${adjustedChainName.toLowerCase()}%3A${inputToken}&inputAmount=${amount}&outputToken=${adjustedChainName.toLowerCase()}%3A${outputToken}&slippageTolerancePercentage=${slippage}&partner=${feeReceiver}&feePercentage=${fee}&validate=false`;
const data = await ky3.get(swapUrl, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
}
}).json();
const steps = [
{
chainId: chain.id,
blockNumber: Number(blockNumber),
from: data.tx.from,
to: data.tx.to,
gasLimit: "100000",
gasPrice: void 0,
data: data.tx.data,
value: data.tx.value
}
];
if (inputToken !== NATIVE_ADDRESS) {
const approveAmount = await getAmountToApprove3(
inputToken,
address,
data.tx.to,
amount,
chain
);
if (approveAmount > 0n) {
steps.unshift({
chainId: chain.id,
blockNumber: Number(blockNumber),
from: address,
to: inputToken,
gasLimit: "80000",
gasPrice: void 0,
data: encodeFunctionData3({
abi: erc20Abi3,
functionName: "approve",
args: [data.tx.to, approveAmount]
}),
value: "0"
});
}
}
return steps;
};
// src/index.ts
var PortalsSolver = class extends Solver {
apiKey;
apiUrl;
feeReceiver;
fee = 0.1;
slippage = 0.5;
constructor(allowedChains, apiUrl, apiKey, feeReceiver, fee = 0.1, slippage = 0.5) {
super("portals", allowedChains);
this.apiUrl = apiUrl;
this.apiKey = apiKey;
this.feeReceiver = feeReceiver;
this.fee = fee;
this.slippage = slippage;
}
async execute(action, data) {
if (data.chain && !this.allowedChains.includes(data.chain.id)) {
return Promise.reject(
`[${this.name} solver] chain ${data.chain.id} not supported.`
);
}
switch (action) {
case "swap":
return {
type: "write",
solver: this.name,
data: await portalsSwap({
...data,
apiUrl: this.apiUrl,
apiKey: this.apiKey,
feeReceiver: this.feeReceiver,
fee: this.fee,
slippage: this.slippage
})
};
case "deposit":
return {
type: "write",
solver: this.name,
data: await portalsDeposit({
...data,
apiUrl: this.apiUrl,
apiKey: this.apiKey,
feeReceiver: this.feeReceiver,
fee: this.fee,
slippage: this.slippage
})
};
case "withdraw":
return {
type: "write",
solver: this.name,
data: await portalsWithdraw({
...data,
apiUrl: this.apiUrl,
apiKey: this.apiKey,
feeReceiver: this.feeReceiver,
fee: this.fee,
slippage: this.slippage
})
};
default:
return Promise.reject(
`[${this.name} solver] action "${action}" not supported.`
);
}
}
};
export {
PortalsSolver
};