Skip to content
Merged
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
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,14 @@ When touching transaction and position flows, validation MUST include all releva
7. **UI clarity and duplication checks**: remove duplicate/redundant/low-signal data and keep only decision-critical information.
8. **Null/data-corruption resilience**: guard null/undefined/stale API/contract fields so malformed data fails gracefully.
9. **Runtime guards on optional config/routes**: avoid unsafe non-null assertions in tx-critical paths; unsupported routes/config must degrade gracefully.
10. **Bundler authorization chokepoint**: every Morpho bundler transaction path (supply, borrow, repay, rebalance, leverage/deleverage) must route through `useBundlerAuthorizationStep` rather than implementing ad hoc authorization logic per hook.
10. **Bundler authorization and transfer-authority chokepoint**: every Morpho bundler transaction path (supply, borrow, repay, rebalance, leverage/deleverage) must route through `useBundlerAuthorizationStep` rather than implementing ad hoc authorization logic per hook; Permit2/ERC20 spender scope must target the contract that actually pulls the token (never Bundler3 unless it is the transfer executor), readiness must fail closed, and auth helpers must preserve original wallet/chain errors.
11. **Locale-safe decimal inputs**: transaction-critical amount/slippage inputs must accept both `,` and `.`, preserve transient edit states (e.g. `''`, `.`) during typing, and only normalize/clamp on commit (`blur`/submit) so delete-and-retype flows never lock users into stale values.
12. **Aggregator API contract integrity**: quote-only request params must never be forwarded to transaction-build endpoints (e.g. Velora `version` on `/prices` but not `/transactions/:network`); enforce endpoint-specific payload/query builders, normalize fetch/network failures into typed API errors, and verify returned route token addresses match requested canonical token addresses before using previews/tx payloads.
12. **Aggregator API contract integrity**: quote-only request params must never be forwarded to transaction-build endpoints (e.g. Velora `version` on `/prices` but not `/transactions/:network`); enforce endpoint-specific payload/query builders, normalize fetch/network failures into typed API errors, verify returned route token addresses match requested canonical token addresses before using previews/tx payloads, and ensure aggregator `userAddress` / taker fields always match the actual on-chain swap executor (adapter contract for adapter-executed swaps, never an unrelated EOA).
13. **User-rejection error normalization**: transaction hooks must map wallet rejection payloads (EIP-1193 `4001`, `ACTION_REJECTED`, viem request-argument dumps) to a short canonical UI message (`User rejected transaction.`) and never render raw payload text in inline UI/error boxes.
14. **Input/state integrity in tx-critical UIs**: never strip unsupported numeric syntax into a different value (e.g. `1e-6` must be rejected, not rewritten), and after any balance refetch re-derive selected token objects from refreshed data before allowing `Max`/submit.
15. **Bundler3 swap route integrity**: Bundler3 swap leverage/deleverage must use adapter flashloan callbacks (not pre-swap borrow gating), with `callbackHash`/`reenter` wiring and adapter token flows matching on-chain contracts; before submit, verify aggregator quote/tx parity (trusted target, exact/min calldata offsets, and same-pair combined-sell normalization) so previewed borrow/repay/collateral amounts cannot drift from executed inputs; prefer exact-in close executors that fully consume the withdrawn collateral over max-sell refund paths that can strand shared-adapter balances, and only relax build-time allowance checks for adapter-executed paths when the failure is allowance-specific.
16. **Quote, preview, and route-state integrity**: when a preview depends on one or more aggregator legs, surface failures from every required leg and use conservative fallbacks (`0`, disable submit) instead of optimistic defaults, but optional exact-close quote legs must not block still-valid partial execution paths; if a close-out path depends on a dedicated debt-close bound (for example BUY/max-close quoting) plus a separate execution preview, full-close / repay-by-shares intent must be driven by one explicit close-route flag shared by preview and tx building, the close executor must be satisfiable under the same slippage floor shown in UI, and if the current sell quote can fully close debt while the exact close bound is still unresolved the UI must fail closed rather than silently degrading to a dust-leaving partial path; for exact-in swap deleverage routes, the exact close bound is a threshold for switching into close mode, not a universal input cap, so valid oversell/refund paths must remain available and previews must continue to match the selected exact-in amount; preview rate/slippage must come from the executable quote/config, selected route mode must never execute a different route while capability probes are in-flight, and route controls/entry CTAs must stay consistent with capability probes without duplicate low-signal UI.
17. **Permit2 time-units and adapter balance hygiene**: Permit2 `expiration`/`sigDeadline` values must always be unix seconds (never milliseconds), and every adapter-executed swap leg must sweep leftover source tokens from the adapter before bundle exit so shared-adapter balances cannot accumulate between transactions.

### REQUIRED: Regression Rule Capture

Expand Down
3 changes: 3 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
// Object shorthand - disable for gentle migration
"useConsistentObjectDefinitions": "off",

// Catch mutable declarations that can be const
"useConst": "error",

// ENABLED - Safe, auto-fixable style improvements
"useImportType": {
"level": "warn",
Expand Down
108 changes: 108 additions & 0 deletions src/abis/bundlerV3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { Abi } from 'viem';

/**
* Minimal Bundler3 ABI for multicall + callback reentry.
*/
export const bundlerV3Abi = [
{
type: 'function',
stateMutability: 'view',
name: 'callbackHash',
inputs: [{ internalType: 'bytes', name: 'data', type: 'bytes' }],
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
},
{
type: 'function',
stateMutability: 'view',
name: 'initiator',
inputs: [],
outputs: [{ internalType: 'address', name: '', type: 'address' }],
},
{
type: 'function',
stateMutability: 'view',
name: 'reenterHash',
inputs: [],
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
},
{
type: 'function',
stateMutability: 'payable',
name: 'multicall',
inputs: [
{
internalType: 'struct Call[]',
name: 'bundle',
type: 'tuple[]',
components: [
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
{
internalType: 'uint256',
name: 'value',
type: 'uint256',
},
{
internalType: 'bool',
name: 'skipRevert',
type: 'bool',
},
{
internalType: 'bytes32',
name: 'callbackHash',
type: 'bytes32',
},
],
},
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'reenter',
inputs: [
{
internalType: 'struct Call[]',
name: 'bundle',
type: 'tuple[]',
components: [
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
{
internalType: 'uint256',
name: 'value',
type: 'uint256',
},
{
internalType: 'bool',
name: 'skipRevert',
type: 'bool',
},
{
internalType: 'bytes32',
name: 'callbackHash',
type: 'bytes32',
},
],
},
],
outputs: [],
},
] as const satisfies Abi;
114 changes: 114 additions & 0 deletions src/abis/morphoGeneralAdapterV1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { Abi } from 'viem';

const marketParamsTuple = {
internalType: 'struct MarketParams',
name: 'marketParams',
type: 'tuple',
components: [
{ internalType: 'address', name: 'loanToken', type: 'address' },
{ internalType: 'address', name: 'collateralToken', type: 'address' },
{ internalType: 'address', name: 'oracle', type: 'address' },
{ internalType: 'address', name: 'irm', type: 'address' },
{ internalType: 'uint256', name: 'lltv', type: 'uint256' },
],
} as const;

/**
* Minimal GeneralAdapter1 ABI needed for swap-backed leverage.
*/
export const morphoGeneralAdapterV1Abi = [
{
type: 'function',
stateMutability: 'nonpayable',
name: 'erc20Transfer',
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'receiver', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'erc20TransferFrom',
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'receiver', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'permit2TransferFrom',
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'receiver', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'morphoSupplyCollateral',
inputs: [
marketParamsTuple,
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'address', name: 'onBehalf', type: 'address' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'morphoBorrow',
inputs: [
marketParamsTuple,
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
{ internalType: 'uint256', name: 'shares', type: 'uint256' },
{ internalType: 'uint256', name: 'minSharePriceE27', type: 'uint256' },
{ internalType: 'address', name: 'receiver', type: 'address' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'morphoRepay',
inputs: [
marketParamsTuple,
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
{ internalType: 'uint256', name: 'shares', type: 'uint256' },
{ internalType: 'uint256', name: 'maxSharePriceE27', type: 'uint256' },
{ internalType: 'address', name: 'onBehalf', type: 'address' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'morphoWithdrawCollateral',
inputs: [
marketParamsTuple,
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
{ internalType: 'address', name: 'receiver', type: 'address' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'morphoFlashLoan',
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
],
outputs: [],
},
] as const satisfies Abi;
42 changes: 42 additions & 0 deletions src/abis/paraswapAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Abi } from 'viem';

/**
* Minimal ParaswapAdapter ABI for Bundler3 swap legs.
*/
export const paraswapAdapterAbi = [
{
type: 'function',
stateMutability: 'nonpayable',
name: 'erc20Transfer',
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'receiver', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
type: 'function',
stateMutability: 'nonpayable',
name: 'sell',
inputs: [
{ internalType: 'address', name: 'augustus', type: 'address' },
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
{ internalType: 'address', name: 'srcToken', type: 'address' },
{ internalType: 'address', name: 'destToken', type: 'address' },
{ internalType: 'bool', name: 'sellEntireBalance', type: 'bool' },
{
components: [
{ internalType: 'uint256', name: 'exactAmount', type: 'uint256' },
{ internalType: 'uint256', name: 'limitAmount', type: 'uint256' },
{ internalType: 'uint256', name: 'quotedAmount', type: 'uint256' },
],
internalType: 'struct Offsets',
name: 'offsets',
type: 'tuple',
},
{ internalType: 'address', name: 'receiver', type: 'address' },
],
outputs: [],
},
] as const satisfies Abi;
Loading