diff --git a/packages/blue-sdk/src/vault/v2/VaultV2Adapter.ts b/packages/blue-sdk/src/vault/v2/VaultV2Adapter.ts index bafa516a..3c48b8fb 100644 --- a/packages/blue-sdk/src/vault/v2/VaultV2Adapter.ts +++ b/packages/blue-sdk/src/vault/v2/VaultV2Adapter.ts @@ -3,6 +3,7 @@ import type { BigIntish } from "../../types"; import type { CapacityLimit } from "../../utils"; export interface IVaultV2Adapter { + type: string; address: Address; parentVault: Address; adapterId: Hash; @@ -10,17 +11,20 @@ export interface IVaultV2Adapter { } export abstract class VaultV2Adapter implements IVaultV2Adapter { + public readonly type: string; public readonly address: Address; public readonly parentVault: Address; public readonly adapterId: Hash; public skimRecipient: Address; constructor({ + type, address, parentVault, adapterId, skimRecipient, }: IVaultV2Adapter) { + this.type = type; this.address = address; this.parentVault = parentVault; this.adapterId = adapterId; diff --git a/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1Adapter.ts b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1Adapter.ts index faa75600..980d8d3c 100644 --- a/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1Adapter.ts +++ b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1Adapter.ts @@ -11,7 +11,8 @@ import { VaultV2Adapter } from "./VaultV2Adapter"; import type { IAccrualVaultV2Adapter, IVaultV2Adapter } from "./VaultV2Adapter"; export interface IVaultV2MorphoMarketV1Adapter - extends Omit { + extends Omit { + type?: "VaultV2MorphoMarketV1Adapter"; marketParamsList: IMarketParams[]; } @@ -19,6 +20,8 @@ export class VaultV2MorphoMarketV1Adapter extends VaultV2Adapter implements IVaultV2MorphoMarketV1Adapter { + public declare readonly type: "VaultV2MorphoMarketV1Adapter"; + static adapterId(address: Address) { return keccak256( encodeAbiParameters( @@ -54,6 +57,7 @@ export class VaultV2MorphoMarketV1Adapter }: IVaultV2MorphoMarketV1Adapter) { super({ ...vaultV2Adapter, + type: "VaultV2MorphoMarketV1Adapter", adapterId: VaultV2MorphoMarketV1Adapter.adapterId(vaultV2Adapter.address), }); diff --git a/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts index a3b0ff07..c6b6f64d 100644 --- a/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts +++ b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts @@ -6,7 +6,8 @@ import { VaultV2Adapter } from "./VaultV2Adapter"; import type { IAccrualVaultV2Adapter, IVaultV2Adapter } from "./VaultV2Adapter"; export interface IVaultV2MorphoMarketV1AdapterV2 - extends Omit { + extends Omit { + type?: "VaultV2MorphoMarketV1AdapterV2"; marketIds: MarketId[]; adaptiveCurveIrm: Address; supplyShares: Record; @@ -16,6 +17,8 @@ export class VaultV2MorphoMarketV1AdapterV2 extends VaultV2Adapter implements IVaultV2MorphoMarketV1AdapterV2 { + public declare readonly type: "VaultV2MorphoMarketV1AdapterV2"; + static adapterId(address: Address) { return keccak256( encodeAbiParameters( @@ -55,6 +58,7 @@ export class VaultV2MorphoMarketV1AdapterV2 }: IVaultV2MorphoMarketV1AdapterV2) { super({ ...vaultV2Adapter, + type: "VaultV2MorphoMarketV1AdapterV2", adapterId: VaultV2MorphoMarketV1AdapterV2.adapterId( vaultV2Adapter.address, ), diff --git a/packages/blue-sdk/src/vault/v2/VaultV2MorphoVaultV1Adapter.ts b/packages/blue-sdk/src/vault/v2/VaultV2MorphoVaultV1Adapter.ts index 0d774fa4..6a079b7d 100644 --- a/packages/blue-sdk/src/vault/v2/VaultV2MorphoVaultV1Adapter.ts +++ b/packages/blue-sdk/src/vault/v2/VaultV2MorphoVaultV1Adapter.ts @@ -3,7 +3,8 @@ import { type Address, type Hex, encodeAbiParameters, keccak256 } from "viem"; import { VaultV2Adapter } from "./VaultV2Adapter"; export interface IVaultV2MorphoVaultV1Adapter - extends Omit { + extends Omit { + type?: "VaultV2MorphoVaultV1Adapter"; morphoVaultV1: Address; } @@ -15,6 +16,8 @@ export class VaultV2MorphoVaultV1Adapter extends VaultV2Adapter implements IVaultV2MorphoVaultV1Adapter { + public declare readonly type: "VaultV2MorphoVaultV1Adapter"; + static adapterId(address: Address) { return keccak256( encodeAbiParameters( @@ -32,6 +35,7 @@ export class VaultV2MorphoVaultV1Adapter }: IVaultV2MorphoVaultV1Adapter) { super({ ...vaultV2Adapter, + type: "VaultV2MorphoVaultV1Adapter", adapterId: VaultV2MorphoVaultV1Adapter.adapterId(vaultV2Adapter.address), }); diff --git a/packages/bundler-sdk-viem/src/actions.ts b/packages/bundler-sdk-viem/src/actions.ts index 02c7ec74..be6676f5 100644 --- a/packages/bundler-sdk-viem/src/actions.ts +++ b/packages/bundler-sdk-viem/src/actions.ts @@ -22,6 +22,7 @@ import { } from "@morpho-org/blue-sdk"; import { Time, getValue } from "@morpho-org/morpho-ts"; import { + APPROVE_ONLY_ONCE_TOKENS, MAX_TOKEN_APPROVALS, type MaybeDraft, type Operation, @@ -52,6 +53,7 @@ const encodeErc20Approval = ( token: Address, spender: Address, amount: bigint, + sender: Address, data: MaybeDraft, ) => { const { chainId } = data; @@ -63,6 +65,37 @@ const encodeErc20Approval = ( const txRequirements: TransactionRequirement[] = []; + // Handle USDT-like tokens that require reset to 0 before changing allowance. + if (APPROVE_ONLY_ONCE_TOKENS[chainId]?.includes(token) && amount > 0n) { + const holding = data.tryGetHolding(sender, token); + const addresses = getChainAddresses(chainId); + // Check if there's an existing allowance to the spender. + // We need to check the raw allowance since this is a direct ERC20 approval. + const existingAllowance = + spender === addresses.bundler3.generalAdapter1 + ? holding?.erc20Allowances["bundler3.generalAdapter1"] + : spender === addresses.permit2 + ? holding?.erc20Allowances.permit2 + : spender === addresses.morpho + ? holding?.erc20Allowances.morpho + : undefined; + + if (existingAllowance != null && existingAllowance > 0n) { + txRequirements.push({ + type: "erc20Approve", + args: [token, spender, 0n], + tx: { + to: token, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "approve", + args: [spender, 0n], + }), + }, + }); + } + } + txRequirements.push({ type: "erc20Approve", args: [token, spender, amount], @@ -218,7 +251,13 @@ export const encodeOperation = ( if (!supportsSignature && spender === permit2) break; requirements.txs.push( - ...encodeErc20Approval(operation.address, spender, amount, dataBefore), + ...encodeErc20Approval( + operation.address, + spender, + amount, + sender, + dataBefore, + ), ); break; @@ -337,6 +376,7 @@ export const encodeOperation = ( operation.address, spender, amount, + sender, dataBefore, ), ); @@ -421,6 +461,7 @@ export const encodeOperation = ( operation.address, generalAdapter1, amount, + sender, dataBefore, ), ); diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index c7ccb910..5a54480b 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -134,19 +134,6 @@ export const populateInputTransfer = ( }, }); else if (useSimpleTransfer) { - if ( - APPROVE_ONLY_ONCE_TOKENS[data.chainId]?.includes(address) && - erc20Allowances["bundler3.generalAdapter1"] > 0n - ) - operations.push({ - type: "Erc20_Approve", - sender: from, - address, - args: { - amount: 0n, - spender: generalAdapter1, - }, - }); operations.push({ type: "Erc20_Approve", sender: from, @@ -172,19 +159,6 @@ export const populateInputTransfer = ( // Simple permit is not supported: fallback to Permit2. else { if (erc20Allowances.permit2 < amount) { - if ( - APPROVE_ONLY_ONCE_TOKENS[data.chainId]?.includes(address) && - erc20Allowances.permit2 > 0n - ) - operations.push({ - type: "Erc20_Approve", - sender: from, - address, - args: { - amount: 0n, - spender: permit2, - }, - }); operations.push({ type: "Erc20_Approve", sender: from,