Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export function insertDelegationDb({
}: InsertDelegationDbParams) {
return db.transaction(async (tx) => {
// Insert the delegation
await tx.insert(delegationsDb).values(delegation).returning();
await tx
.insert(delegationsDb)
.values(delegation)
.returning()
.onConflictDoNothing();

if (caveats.length > 0) {
const caveatsWithDelegationHash = caveats.map((caveat) => ({
Expand All @@ -25,7 +29,11 @@ export function insertDelegationDb({
}));

// Insert the caveats
await tx.insert(caveatsDb).values(caveatsWithDelegationHash).returning();
await tx
.insert(caveatsDb)
.values(caveatsWithDelegationHash)
.returning()
.onConflictDoNothing();
}
});
}
4 changes: 4 additions & 0 deletions apps/api-delegations/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
import 'dotenv/config';
import { isHex } from 'viem';

export const env = createEnv({
server: {
DELEGATIONS_DATABASE_URL: z.string().url(),
RESOLVER_PRIVATE_KEY: z.string().refine(isHex),
RPC_URL_BASE: z.string().url(),
RPC_URL_BASE_SEPOLIA: z.string().url(),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
Expand Down
131 changes: 131 additions & 0 deletions apps/api-delegations/src/processing/limit-order/get-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { stablecoinTokenList } from 'universal-data';
import type { Address, Hex } from 'viem';
import {
getDepositAaveV3HookData,
getWithdrawAaveV3HookData,
} from './protocols/aave-v3.js';
import {
getDepositPoolTogetherV5HookData,
getWithdrawPoolTogetherV5HookData,
} from './protocols/pool-together-v5.js';
import { getDepositUnderlyingAssetHookData } from './protocols/underlying-asset.js';
import {
getDepositCompoundV3HookData,
getWithdrawCompoundV3HookData,
} from './protocols/compound-v3.js';

type HookActions = {
target: Address;
value: bigint;
callData: Hex;
}[];

type GetHookActionsParams = {
tokenIn: Address;
tokenOut: Address;
amountIn: bigint;
amountOut: bigint;
delegator: Address;
};

type GetHookActionsReturnType = {
withdrawActions: HookActions;
depositActions: HookActions;
};

export function getHookActions({
tokenIn,
tokenOut,
amountIn,
amountOut,
delegator,
}: GetHookActionsParams): GetHookActionsReturnType {
let withdrawActions: HookActions = [];
let depositActions: HookActions = [];

const tokenInData = stablecoinTokenList.tokens.find(
(token) => token.address.toLowerCase() === tokenIn.toLowerCase(),
);
const tokenOutData = stablecoinTokenList.tokens.find(
(token) => token.address.toLowerCase() === tokenOut.toLowerCase(),
);
const underlyingAssetData = stablecoinTokenList.tokens.find(
(token) => !token.extensions?.protocol,
);

if (!tokenInData || !tokenOutData || !underlyingAssetData) {
throw new Error('Token not found');
}

const tokenOutProtocol = tokenOutData.extensions?.protocol;

// Withdraw actions
switch (tokenOutProtocol) {
case 'aave-v3':
withdrawActions = getWithdrawAaveV3HookData({
// Always use the underlying asset as the tokenIn for compound
tokenIn: underlyingAssetData.address as Address,
amountOut,
});
break;
case 'pool-together-v5':
withdrawActions = getWithdrawPoolTogetherV5HookData({
amountOut,
tokenOut,
});
break;
case 'compound-v3':
withdrawActions = getWithdrawCompoundV3HookData({
// Always use the underlying asset as the tokenIn for compound
tokenIn: underlyingAssetData.address as Address,

tokenOut,
});
break;
}

// If there are withdraw calls, the tokenOut should be replaced with the underlying asset
const updatedTokenOut = withdrawActions.length
? (underlyingAssetData.address as Address)
: tokenOut;

const tokenInProtocol = tokenInData.extensions?.protocol;
// Deposit actions
switch (tokenInProtocol) {
case 'aave-v3':
depositActions = getDepositAaveV3HookData({
amountOut,
delegator,
tokenOut: updatedTokenOut,
});
break;
case 'pool-together-v5':
depositActions = getDepositPoolTogetherV5HookData({
amountOut,
delegator,
tokenIn,
tokenOut: updatedTokenOut,
});
break;
case 'compound-v3':
depositActions = getDepositCompoundV3HookData({
amountOut,
delegator,
tokenIn,
tokenOut: updatedTokenOut,
});
break;
case undefined:
depositActions = getDepositUnderlyingAssetHookData({
amountIn,
delegator,
tokenIn,
});
break;
}

return {
withdrawActions,
depositActions,
};
}
89 changes: 89 additions & 0 deletions apps/api-delegations/src/processing/limit-order/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Delegation, DelegationExecution } from 'universal-types';
import {
decodeERC20BalanceGteWrapEnforcerTerms,
decodeEnforcerERC20TransferAmount,
encodeDelegation,
encodeExternalCallEnforcerArgs,
encodeSingleExecution,
getERC20BalanceGteWrapEnforcerFromDelegation,
getErc20TransferAmountEnforcerFromDelegation,
getExternalHookEnforcerFromDelegation,
} from './utils.js';
import { getResolverWalletClient } from '../../resolver/resolver-wallet-client.js';
import type { ValidChain } from 'universal-data';
import {
SINGLE_EXECUTION_MODE,
delegationManagerAbi,
multicallAbi,
universalDeployments,
} from 'universal-data';
import { encodeFunctionData, erc20Abi } from 'viem';
import { getHookActions } from './get-actions.js';

export async function processLimitOrderDelegation({
chainId,
delegation,
}: { chainId: ValidChain['id']; delegation: Delegation }) {
const { erc20TransferAmountEnforcer } =
getErc20TransferAmountEnforcerFromDelegation(delegation);
const { token: tokenOut, amount: amountOut } =
decodeEnforcerERC20TransferAmount(erc20TransferAmountEnforcer.terms);

const { erc20BalanceGteWrapEnforcer } =
getERC20BalanceGteWrapEnforcerFromDelegation(delegation);

const { token: tokenIn, amount: amountIn } =
decodeERC20BalanceGteWrapEnforcerTerms(erc20BalanceGteWrapEnforcer.terms);

const { externalHookEnforcer, index: externalHookEnforcerIndex } =
getExternalHookEnforcerFromDelegation(delegation);

const { withdrawActions, depositActions } = getHookActions({
amountIn,
amountOut,
delegator: delegation.delegator,
tokenIn,
tokenOut,
});

delegation.caveats[externalHookEnforcerIndex] = {
...externalHookEnforcer,
// Update the args of the external hook enforcer to include the data for the Aave V3 deposit
args: encodeExternalCallEnforcerArgs({
target: universalDeployments.Multicall,
callData: encodeFunctionData({
abi: multicallAbi,
functionName: 'multicall',
args: [[...withdrawActions, ...depositActions]],
}),
}),
};

// Get resolver wallet client
const resolverWalletClient = getResolverWalletClient(chainId);

// Set the delegation execution to transfer the delegator tokens to the multicall contract
const execution: DelegationExecution = {
value: 0n,
target: tokenOut,
calldata: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [universalDeployments.Multicall, amountOut],
}),
};

const permissionContexts = [encodeDelegation(delegation)];
const executionCallData = [encodeSingleExecution(execution)];
const executionModes = SINGLE_EXECUTION_MODE;

// Redeem the delegation
const txHash = await resolverWalletClient.writeContract({
address: universalDeployments.DelegationManager,
abi: delegationManagerAbi,
functionName: 'redeemDelegations',
args: [permissionContexts, executionModes, executionCallData],
});

return txHash;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { encodeFunctionData, erc20Abi, type Address, type Hex } from 'viem';
import { aaveV3PoolAbi, universalDeployments } from 'universal-data';

const AAVE_V3_POOL_BASE = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5';

type GetDepositAaveV3HookDataReturnType = {
target: Address;
value: bigint;
callData: Hex;
}[];

export function getDepositAaveV3HookData({
amountOut,
delegator,
tokenOut,
}: {
amountOut: bigint;
delegator: Address;
tokenOut: Address;
}): GetDepositAaveV3HookDataReturnType {
return [
// Approves the token to the Aave Pool
{
target: tokenOut,
value: 0n,
callData: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [AAVE_V3_POOL_BASE, amountOut],
}),
},
// Deposits the token to the Aave Pool on behalf of the delegator
{
target: AAVE_V3_POOL_BASE,
value: 0n,
callData: encodeFunctionData({
abi: aaveV3PoolAbi,
functionName: 'supply',
args: [tokenOut, amountOut, delegator, 0],
}),
},
];
}

type GetWithdrawAaveV3HookDataReturnType = GetDepositAaveV3HookDataReturnType;
export function getWithdrawAaveV3HookData({
amountOut,
tokenIn,
}: {
amountOut: bigint;
tokenIn: Address;
}): GetWithdrawAaveV3HookDataReturnType {
return [
// Withdraws the token from the Aave Pool on behalf of the delegator
{
target: AAVE_V3_POOL_BASE,
value: 0n,
callData: encodeFunctionData({
abi: aaveV3PoolAbi,
functionName: 'withdraw',
args: [tokenIn, amountOut, universalDeployments.Multicall],
}),
},
];
}
Loading
Loading