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
4 changes: 2 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default [
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked.map((cfg) => ({
...cfg,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', 'src/adapters/**/*.ts'],
languageOptions: {
...cfg.languageOptions,
parserOptions: {
Expand All @@ -33,7 +33,7 @@ export default [
},
})),
{
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', 'src/adapters/**/*.ts'],
rules: {
'no-console': 'warn',
'@typescript-eslint/consistent-type-imports': 'warn',
Expand Down
5 changes: 5 additions & 0 deletions src/adapters/__tests__/adapter-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
L2NativeTokenVaultABI,
} from '../../core/internal/abi-registry.ts';
import type { Address } from '../../core/types/primitives';
import { GasPlanner, DEFAULT_GAS_POLICIES } from '../../core/gas';

const IBridgehub = new Interface(IBridgehubABI as any);
const IL1AssetRouter = new Interface(IL1AssetRouterABI as any);
Expand Down Expand Up @@ -390,6 +391,7 @@ export type DepositTestContext<T extends AdapterHarness> = {
gasPrice?: bigint;
gasPriceForBaseCost: bigint;
};
gas: GasPlanner<any>;
} & Record<string, unknown>;

export function makeDepositContext<T extends AdapterHarness>(
Expand All @@ -413,6 +415,7 @@ export function makeDepositContext<T extends AdapterHarness>(
refundRecipient: ADAPTER_TEST_ADDRESSES.signer,
operatorTip: 7n,
fee: baseFee,
gas: new GasPlanner(DEFAULT_GAS_POLICIES),
};

const merged = {
Expand Down Expand Up @@ -441,6 +444,7 @@ export type WithdrawalTestContext<T extends AdapterHarness> = {
l2GasLimit: bigint;
gasBufferPct: number;
fee?: Record<string, unknown>;
gas: GasPlanner<any>;
} & Record<string, unknown>;

export function makeWithdrawalContext<T extends AdapterHarness>(
Expand All @@ -461,6 +465,7 @@ export function makeWithdrawalContext<T extends AdapterHarness>(
l2GasLimit: 300_000n,
gasBufferPct: 15,
fee: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n },
gas: new GasPlanner(DEFAULT_GAS_POLICIES),
};

return {
Expand Down
13 changes: 5 additions & 8 deletions src/adapters/__tests__/deposits/erc20-nonbase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ const ERC20_TOKEN = '0x3333333333333333333333333333333333333333' as const;
const BASE_TOKEN = ADAPTER_TEST_ADDRESSES.baseTokenFor324;
const RECEIVER = '0x4444444444444444444444444444444444444444' as const;

const withBuffer = (x: bigint) => (x * 10100n) / 10000n;

function expectedMint(kind: AdapterKind, baseCost: bigint, operatorTip: bigint) {
const raw = baseCost + operatorTip;
return kind === 'viem' ? withBuffer(raw) : raw;
function expectedMint(baseCost: bigint, operatorTip: bigint) {
return baseCost + operatorTip;
}

describeForAdapters('adapters/deposits/routeErc20NonBase', (kind, factory) => {
Expand All @@ -39,7 +36,7 @@ describeForAdapters('adapters/deposits/routeErc20NonBase', (kind, factory) => {
const ctx = makeDepositContext(harness, { l2GasLimit: 600_000n });
const amount = 1_000n;
const baseCost = 3_000n;
const mintValue = expectedMint(kind, baseCost, ctx.operatorTip);
const mintValue = expectedMint(baseCost, ctx.operatorTip);

setBridgehubBaseToken(harness, ctx, FORMAL_ETH_ADDRESS);
setBridgehubBaseCost(harness, ctx, baseCost, { l2GasLimit: MIN_L2_GAS_FOR_ERC20 });
Expand Down Expand Up @@ -91,7 +88,7 @@ describeForAdapters('adapters/deposits/routeErc20NonBase', (kind, factory) => {
const ctx = makeDepositContext(harness);
const amount = 5_000n;
const baseCost = 4_000n;
const mintValue = expectedMint(kind, baseCost, ctx.operatorTip);
const mintValue = expectedMint(baseCost, ctx.operatorTip);

setBridgehubBaseToken(harness, ctx, BASE_TOKEN);
setBridgehubBaseCost(harness, ctx, baseCost, { l2GasLimit: MIN_L2_GAS_FOR_ERC20 });
Expand Down Expand Up @@ -150,7 +147,7 @@ describeForAdapters('adapters/deposits/routeErc20NonBase', (kind, factory) => {
const ctx = makeDepositContext(harness);
const amount = 2_000n;
const baseCost = 3_000n;
const mintValue = expectedMint('ethers', baseCost, ctx.operatorTip);
const mintValue = expectedMint(baseCost, ctx.operatorTip);

setBridgehubBaseToken(harness, ctx, BASE_TOKEN);
setBridgehubBaseCost(harness, ctx, baseCost, { l2GasLimit: MIN_L2_GAS_FOR_ERC20 });
Expand Down
5 changes: 5 additions & 0 deletions src/adapters/ethers/resources/deposits/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getFeeOverrides } from '../utils';
import { pickDepositRoute } from '../../../../core/resources/deposits/route';
import type { DepositParams, DepositRoute } from '../../../../core/types/flows/deposits';
import type { CommonCtx } from '../../../../core/types/flows/base';
import { GasPlanner, DEFAULT_GAS_POLICIES } from '../../../../core/gas';

// Common context for building deposit (L1-L2) transactions
export interface BuildCtx extends CommonCtx {
Expand All @@ -18,6 +19,7 @@ export interface BuildCtx extends CommonCtx {
gasPerPubdata: bigint;
operatorTip: bigint;
refundRecipient: Address;
gas: GasPlanner<TransactionRequest>;
}

// Prepare a common context for deposit operations
Expand All @@ -35,6 +37,8 @@ export async function commonCtx(p: DepositParams, client: EthersClient) {

const route = await pickDepositRoute(client, BigInt(chainId), p.token);

const gasPlanner = new GasPlanner<TransactionRequest>(DEFAULT_GAS_POLICIES);

return {
client,
l1AssetRouter,
Expand All @@ -47,5 +51,6 @@ export async function commonCtx(p: DepositParams, client: EthersClient) {
gasPerPubdata,
operatorTip,
refundRecipient,
gas: gasPlanner,
} satisfies BuildCtx & { route: DepositRoute };
}
5 changes: 4 additions & 1 deletion src/adapters/ethers/resources/deposits/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function createDepositsResource(client: EthersClient): DepositsResource {
await ROUTES[route].preflight?.(p, ctx);

const { steps, approvals, quoteExtras } = await ROUTES[route].build(p, ctx);
const { baseCost, mintValue } = quoteExtras;
const { baseCost, mintValue, gasPlan, baseToken, baseIsEth } = quoteExtras;

return {
route: ctx.route,
Expand All @@ -120,6 +120,9 @@ export function createDepositsResource(client: EthersClient): DepositsResource {
mintValue,
suggestedL2GasLimit: ctx.l2GasLimit,
gasPerPubdata: ctx.gasPerPubdata,
gasPlan,
baseToken,
baseIsEth,
},
steps,
};
Expand Down
74 changes: 50 additions & 24 deletions src/adapters/ethers/resources/deposits/routes/erc20-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ import { normalizeAddrEq, isETH } from '../../../../../core/utils/addr';
// error handling
const { wrapAs } = createErrorHandlers('deposits');

// TODO: all gas buffers need to be moved to a dedicated resource
// this is getting messy
const BASE_COST_BUFFER_BPS = 100n; // 1%
const BPS = 10_000n;
const withBuffer = (x: bigint) => (x * (BPS + BASE_COST_BUFFER_BPS)) / BPS;

// ERC20 deposit where the deposit token IS the target chain's base token (base β‰  ETH).
export function routeErc20Base(): DepositRouteStrategy {
return {
Expand Down Expand Up @@ -91,10 +85,16 @@ export function routeErc20Base(): DepositRouteStrategy {
)) as bigint;
const baseCost = BigInt(rawBaseCost);

// Direct path: mintValue must cover fee + the L2 msg.value (amount) β†’ plus a small buffer
// Direct path: mintValue must cover fee + the L2 msg.value (amount)
const l2Value = p.amount;
const rawMintValue = baseCost + ctx.operatorTip + l2Value;
const mintValue = withBuffer(rawMintValue);
const baseCostQuote = ctx.gas.applyBaseCost(
'base-cost:bridgehub:erc20-base',
'deposit.base-cost.erc20-base',
baseCost,
{ operatorTip: ctx.operatorTip, extras: l2Value },
);

const mintValue = baseCostQuote.recommended;

const approvals: ApprovalNeed[] = [];
const steps: PlanStep<TransactionRequest>[] = [];
Expand All @@ -118,11 +118,32 @@ export function routeErc20Base(): DepositRouteStrategy {
ctx.l1AssetRouter,
mintValue,
]);
const approveTx: TransactionRequest = {
to: baseToken,
data,
from: ctx.sender,
...ctx.fee,
};
const approveGas = await ctx.gas.ensure(
`approve:${baseToken}:${ctx.l1AssetRouter}`,
'deposit.approval.l1',
approveTx,
{
estimator: (request) =>
wrapAs('RPC', OP_DEPOSITS.base.estGas, () => ctx.client.l1.estimateGas(request), {
ctx: { where: 'l1.estimateGas', to: baseToken },
message: 'Failed to estimate gas for ERC-20 approval.',
}),
},
);
if (approveGas.recommended != null) {
approveTx.gasLimit = approveGas.recommended;
}
steps.push({
key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
kind: 'approve',
description: 'Approve base token for mintValue',
tx: { to: baseToken, data, from: ctx.sender, ...ctx.fee },
tx: approveTx,
});
}
}
Expand Down Expand Up @@ -151,19 +172,20 @@ export function routeErc20Base(): DepositRouteStrategy {
...ctx.fee,
};

try {
const est = await wrapAs(
'RPC',
OP_DEPOSITS.base.estGas,
() => ctx.client.l1.estimateGas(tx),
{
ctx: { where: 'l1.estimateGas', to: ctx.bridgehub },
message: 'Failed to estimate gas for Bridgehub request.',
},
);
tx.gasLimit = (BigInt(est) * 115n) / 100n;
} catch {
// ignore;
const gas = await ctx.gas.ensure(
'bridgehub:direct:erc20-base',
'deposit.bridgehub.direct.l1',
tx,
{
estimator: (request) =>
wrapAs('RPC', OP_DEPOSITS.base.estGas, () => ctx.client.l1.estimateGas(request), {
ctx: { where: 'l1.estimateGas', to: ctx.bridgehub },
message: 'Failed to estimate gas for Bridgehub request.',
}),
},
);
if (gas.recommended != null) {
tx.gasLimit = gas.recommended;
}

steps.push({
Expand All @@ -173,7 +195,11 @@ export function routeErc20Base(): DepositRouteStrategy {
tx,
});

return { steps, approvals, quoteExtras: { baseCost, mintValue } };
return {
steps,
approvals,
quoteExtras: { baseCost, mintValue, gasPlan: ctx.gas.snapshot() },
};
},
};
}
Loading
Loading