Aperture’s Intents API
Institutions and advanced users can go beyond Aperture’s front-end with our intents API for enhanced, declarative strategies that fit into your existing workflow.
import {
Action,
ActionTypeEnum,
AutomanFragment,
ApertureSupportedChainId,
IUniV3Automan__factory,
PermitInfo,
RebalanceAction,
UniV3Automan,
UniV3Automan__factory,
getAutomanDecreaseLiquidityCallInfo,
getAutomanRebalanceCallInfo,
getAutomanReinvestCallInfo,
getChainInfo,
normalizeTicks,
optimalRebalance,
} from '@aperture_finance/uniswap-v3-automation-sdk';
import { BlockTag, JsonRpcProvider, Provider } from '@ethersproject/providers';
import { Percent } from '@uniswap/sdk-core';
import { Position } from '@uniswap/v3-sdk';
import Big from 'big.js';
import {
BigNumber,
BigNumberish,
PopulatedTransaction,
Signer,
ethers,
} from 'ethers';
import JSBI from 'jsbi';
import { GAS_LIMIT_L2_MULTIPLIER, GAS_LIMIT_MULTIPLIER } from '../../constants';
import { getChainIdFromPrimaryKey, isRecurringAction } from '../../db_helper';
import { StateTableItem } from '../../interfaces/schema';
import { logger } from '../../logger';
function isRebalanceAction(action: Action): boolean {
return (
action.type === ActionTypeEnum.enum.Rebalance || isRecurringAction(action)
);
}
export async function populateTriggerActionTx(
task: StateTableItem,
position: Position,
positionRawEtherValue: Big,
permitInfo: PermitInfo | undefined,
signer: Signer,
provider: JsonRpcProvider | Provider,
blockNumber?: number,
) {
const chainId = getChainIdFromPrimaryKey(task.primary_key);
const signerAddress = await signer.getAddress();
const automanContract = UniV3Automan__factory.connect(
getChainInfo(chainId).aperture_uniswap_v3_automan,
signer,
);
// Find the gas deduction ratio and compare with user-specified ceiling.
let gasUnits: BigNumber;
let gasDeductionPips: BigNumber;
let amount0Min: JSBI;
let amount1Min: JSBI;
let swapData: string | undefined;
if (isRebalanceAction(task.action)) {
// get swap data using 1inch
swapData = await get1inchSwapData(
position,
task,
/*feePips=*/ 0,
signerAddress,
provider,
blockNumber,
);
logger.info(`Using 1inch swap data: ${swapData}`);
}
if (swapData !== undefined) {
const [poolEstimate, routerEstimate] = await Promise.all([
estimateGasAndSimulate(
task,
position,
positionRawEtherValue,
automanContract,
signerAddress,
permitInfo,
/**swapData= */ undefined,
blockNumber,
),
estimateGasAndSimulate(
task,
position,
positionRawEtherValue,
automanContract,
signerAddress,
permitInfo,
swapData,
blockNumber,
),
]);
logger.info(
`Pool estimate: ${poolEstimate.liquidity!.toString()}, gas: ${poolEstimate.gasUnits.toString()}`,
);
logger.info(
`Router estimate: ${routerEstimate.liquidity!.toString()}, gas: ${routerEstimate.gasUnits.toString()}`,
);
if (poolEstimate.liquidity!.gte(routerEstimate.liquidity!)) {
({ gasUnits, gasDeductionPips, amount0Min, amount1Min } = poolEstimate);
// Clear swap data since we are using the pool to rebalance; otherwise, the populated tx would still use 1inch.
swapData = undefined;
logger.info(`Use the same pool to rebalance`);
} else {
({ gasUnits, gasDeductionPips, amount0Min, amount1Min } = routerEstimate);
logger.info(`Use 1inch to rebalance`);
}
} else {
({ gasUnits, gasDeductionPips, amount0Min, amount1Min } =
await estimateGasAndSimulate(
task,
position,
positionRawEtherValue,
automanContract,
signerAddress,
permitInfo,
/*swapData=*/ undefined,
blockNumber,
));
}
// Execute action.
const populatedTx = await generateTxPromise(
position,
task,
permitInfo,
automanContract,
/*mode=*/ 'populate',
amount0Min.toString(),
amount1Min.toString(),
/*feePips=*/ gasDeductionPips,
signerAddress,
swapData,
blockNumber,
);
// Populating gas limit based on estimated gas units multiplied by a multiplier.
populatedTx.gasLimit = gasUnits.mul(GAS_LIMIT_MULTIPLIER).div(100);
return populatedTx;
}
async function get1inchSwapData(
position: Position,
task: StateTableItem,
feePips: BigNumberish,
signerAddress: string,
provider: JsonRpcProvider | Provider,
blockNumber?: number,
) {
const { tickLower, tickUpper } = normalizeTicks(task.action, position.pool);
try {
const { swapData } = await optimalRebalance(
position.pool.chainId,
task.nft_id,
tickLower,
tickUpper,
feePips,
false,
signerAddress,
(task.action as RebalanceAction).slippage,
provider,
blockNumber,
);
return swapData;
} catch (err) {
logger.error(`Failed to get 1inch swap data: ${err}`);
}
}
async function estimateGasAndSimulate(
task: StateTableItem,
position: Position,
positionRawEtherValue: Big,
automanContract: UniV3Automan,
signerAddress: string,
permitInfo?: PermitInfo,
swapData = '0x',
blockTag: BlockTag = 'latest',
) {
const { gasUnits, gasDeductionPips } = await estimateGasDeductionPips(
positionRawEtherValue,
position,
task,
permitInfo,
automanContract,
signerAddress,
getChainIdFromPrimaryKey(task.primary_key),
swapData,
blockTag,
);
const amounts = await getAmountsAfterSlippage(
position,
task,
permitInfo,
automanContract,
signerAddress,
gasDeductionPips,
swapData,
blockTag,
);
return {
...amounts,
gasUnits,
gasDeductionPips,
};
}
type Mode = {
estimateGas: BigNumber;
callStatic: { liquidity?: BigNumber; amount0: BigNumber; amount1: BigNumber };
populate: PopulatedTransaction;
};
// Generate tx that triggers `action`.
// If `mode` is `estimateGas`, then a Promise<BigNumber> is returned representing the estimated tx cost.
// If `mode` is `callStatic`, then a Promise<any> is returned with the return value of the invoked function.
// Otherwise, a Promise<TransactionResponse> is returned with the response for the tx after it is sent to the network.
async function generateTxPromise<K extends keyof Mode>(
position: Position,
task: StateTableItem,
permitInfo: PermitInfo | undefined,
automanContract: UniV3Automan,
mode: K,
amount0Min: BigNumberish,
amount1Min: BigNumberish,
feePips: BigNumberish,
signerAddress: string,
swapData = '0x',
blockTag?: BlockTag,
): Promise<Mode[K]> {
const deadline = Math.floor(Date.now() / 1000) + 20 * 60; // Twenty min from now.
let functionFragment: AutomanFragment;
let data: string;
switch (task.action.type) {
case 'Close':
case 'LimitOrderClose': {
({ functionFragment, data } = getAutomanDecreaseLiquidityCallInfo(
task.nft_id,
position.liquidity.toString(),
deadline,
amount0Min,
amount1Min,
feePips,
permitInfo,
));
break;
}
case 'Reinvest': {
({ functionFragment, data } = getAutomanReinvestCallInfo(
task.nft_id,
deadline,
amount0Min,
amount1Min,
feePips,
permitInfo,
));
break;
}
case 'Rebalance':
case 'RecurringPercentage':
case 'RecurringPrice':
case 'RecurringRatio': {
const pool = position.pool;
const { tickLower, tickUpper } = normalizeTicks(task.action, pool);
if (
tickLower === position.tickLower &&
tickUpper === position.tickUpper
) {
throw new Error('No need to rebalance');
}
({ functionFragment, data } = getAutomanRebalanceCallInfo(
{
token0: pool.token0.address,
token1: pool.token1.address,
fee: pool.fee,
tickLower,
tickUpper,
amount0Desired: 0, // Param value ignored by Automan.
amount1Desired: 0, // Param value ignored by Automan.
amount0Min: amount0Min,
amount1Min: amount1Min,
recipient: ethers.constants.AddressZero, // Param value ignored by Automan.
deadline: deadline,
},
task.nft_id,
feePips,
permitInfo,
swapData,
));
break;
}
default:
throw new Error('Invalid action type');
}
switch (mode) {
case 'estimateGas':
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return automanContract.provider.estimateGas({
from: signerAddress,
to: automanContract.address,
data,
});
case 'callStatic':
const returnData = await automanContract.provider.call(
{
from: signerAddress,
to: automanContract.address,
data,
},
blockTag,
);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return IUniV3Automan__factory.createInterface().decodeFunctionResult(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
functionFragment,
returnData,
);
default:
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return automanContract.signer.populateTransaction({
to: automanContract.address,
data,
});
}
}
automated position rebalancing, closure & compounding
We offer support on various DeFi strategies, including active liquidity management — automated position rebalancing, closure, and compounding. You can choose among JavaScript, Python, Rust, and other languages of your choice to leverage this arsenal of automation features.