Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
0d3b314
feat: swap bridge test
arthcp May 11, 2026
bbd42da
feat: swap native bridge test
arthcp May 11, 2026
8ec91bd
feat: construct openocean, stargate data and collect fee
arthcp May 11, 2026
faf8786
feat: monolithic+modular combined router
sebastiantf May 11, 2026
3d7f8b4
feat: hardhat, deploy script
sebastiantf May 11, 2026
c12f2e3
feat: gas measure and optimise
arthcp May 11, 2026
a1bfeea
feat: swapFeeBridge router
arthcp May 11, 2026
ec8a74d
feat: native token support
sebastiantf May 12, 2026
a9d6b1e
test: test scripts
sebastiantf May 12, 2026
9a86303
feat: js util for generic executor
arthcp May 12, 2026
78319ab
fix: ah calldata, approval
sebastiantf May 12, 2026
90ca9d3
fix: scripts
sebastiantf May 12, 2026
39d5750
fix: AH _pullFromUser assembly
sebastiantf May 12, 2026
c01aed7
feat: stargate native test
sebastiantf May 12, 2026
5aba998
feat: gas golf
arthcp May 12, 2026
7875b3e
Merge remote-tracking branch 'origin/refactor/openrouter-modular-mono…
arthcp May 12, 2026
7625069
feat: port latest modular executor to combined
arthcp May 12, 2026
d228d5a
feat: clean up
arthcp May 12, 2026
aac862a
Merge pull request #1 from SocketDotTech/openocean-across-modular-test
sebastiantf May 12, 2026
ac75933
fix: stargate script
sebastiantf May 12, 2026
8b8e206
feat: polygon swap and bridge compare test
arthcp May 13, 2026
57030ae
feat: use returnDataWordOffset on monolithic
arthcp May 13, 2026
c404e08
feat: gateway test
arthcp May 13, 2026
61a3419
feat: improve test scripts
sebastiantf May 14, 2026
d66f476
test: arb eth to base usdc stargate
sebastiantf May 14, 2026
c19c288
feat: native token direct router call without ah
sebastiantf May 14, 2026
035958d
feat: update swapBridgeViaStargateNative for Polygon USDT0 integration
sebastiantf May 14, 2026
c8a5492
fix: build
sebastiantf May 14, 2026
b169b66
feat: support direct deposit via arbitrum native bridge
sebastiantf May 14, 2026
c7d0c1c
feat: simple bridge function
sebastiantf May 15, 2026
e85dba5
feat: simple bridge relay test
sebastiantf May 15, 2026
e8e1074
feat: approval, balance dust during gas tests
sebastiantf May 15, 2026
17dc785
feat: introduce flags for fee and balance handling in swap functions
sebastiantf May 15, 2026
0685308
fix: swap output check after swap
sebastiantf May 15, 2026
c45a025
feat: calldata optimisations
arthcp May 15, 2026
5bed333
Merge commit '06853084fee1a7a21e1ac178c57432a99ad71f3e' into refactor…
arthcp May 15, 2026
82faec8
feat: requestHash event
sebastiantf May 15, 2026
00e3801
refactor: tests
sebastiantf May 15, 2026
08d78aa
refactor: tests
sebastiantf May 18, 2026
d24bb5a
fix: swap receiver
sebastiantf May 18, 2026
22c39b6
fix: sum amount + bd.value
sebastiantf May 18, 2026
2dc0080
fix: comments
sebastiantf May 18, 2026
c88af75
test: fix stargate tests
sebastiantf May 18, 2026
29ab099
fix: stargate tests
sebastiantf May 18, 2026
362b8be
feat: kyberswap, 0x swap scripts, fix swap scripts
sebastiantf May 18, 2026
1313cc4
feat: rescueFunds
sebastiantf May 18, 2026
c58077b
refactor: remove monolithic exec code
sebastiantf May 19, 2026
85a7d2b
feat: slither
sebastiantf May 19, 2026
9775932
fix: fork tests
arthcp May 19, 2026
3da9283
feat: mock tests
arthcp May 19, 2026
783c885
chore: format
arthcp May 19, 2026
01eb83b
refactor: renames, refactors, reorders
sebastiantf May 19, 2026
9a19a3c
build: change solc version
sebastiantf May 19, 2026
419f396
feat: balance and return data variants
arthcp May 19, 2026
578ef44
refactor: reorder function params
sebastiantf May 19, 2026
d84d648
refactor: remove old contracts, rename router, move to src root
sebastiantf May 19, 2026
b3cf268
refactor: comments
sebastiantf May 19, 2026
4eb6ab2
refactor: remove performActions return
sebastiantf May 19, 2026
e7a81b5
refactor: check and set max approval
sebastiantf May 19, 2026
5307f1b
feat: agent docs
arthcp May 19, 2026
fac7a9c
Merge remote-tracking branch 'origin/refactor/monolithic' into tests
arthcp May 19, 2026
cd64732
Merge pull request #5 from SocketDotTech/tests
arthcp May 19, 2026
8c3f4f8
fix: tests
sebastiantf May 19, 2026
2c69a6f
Merge branch 'refactor/monolithic' into refactor/infinite-approval
sebastiantf May 19, 2026
5a2b25a
test: set approvalSpender zero if enough allowance
sebastiantf May 19, 2026
b51be89
ci: remove fmt
sebastiantf May 19, 2026
d05a66b
ci: run on push to main only
sebastiantf May 19, 2026
d313ed2
refactor: rename contract name
sebastiantf May 19, 2026
02494d1
docs: update
sebastiantf May 19, 2026
2859af9
Merge pull request #6 from SocketDotTech/refactor/infinite-approval
sebastiantf May 19, 2026
7ba76fe
feat: create3
sebastiantf May 20, 2026
4a7b732
chore: comments
sebastiantf May 20, 2026
e2a09a5
test: allowance holder pull fork gas test
sebastiantf May 20, 2026
bce867e
chore: comments, function renames
sebastiantf May 25, 2026
c1c4b4a
chore: prev. audit comments
sebastiantf May 25, 2026
220c7a7
fix: prev. audit comment
sebastiantf May 25, 2026
61dce88
Merge pull request #3 from SocketDotTech/refactor/monolithic
sebastiantf May 25, 2026
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
54 changes: 54 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Private key of the deployer wallet (no 0x prefix)
DEPLOYER_PRIVATE_KEY=

# Private key used by e2e scripts (may be the same as DEPLOYER_PRIVATE_KEY)
PRIVATE_KEY=

# Constructor arguments
OWNER_ADDRESS=

# External API keys
RELAY_API_KEY= # optional, relay.link x-api-key header
OPEN_OCEAN_API_KEY= # optional, OpenOcean API key

# RPC endpoints (public fallbacks are pre-configured in hardhat.config.ts)
ETHEREUM_RPC=
POLYGON_RPC=
ARBITRUM_RPC=
OPTIMISM_RPC=
BASE_RPC=
AVALANCHE_RPC=
BSC_RPC=
LINEA_RPC=
SCROLL_RPC=
BLAST_RPC=
MODE_RPC=
MANTLE_RPC=
GNOSIS_RPC=
SONIC_RPC=
UNICHAIN_RPC=
BERACHAIN_RPC=
INK_RPC=
SONEIUM_RPC=
WORLDCHAIN_RPC=
SEI_RPC=
ARBITRUM_SEPOLIA_RPC=
OPTIMISM_SEPOLIA_RPC=

# Block explorer API keys (for --verify)
MAINNET_ETHERSCAN_KEY=
POLYGON_ETHERSCAN_KEY=
ARBITRUM_ETHERSCAN_KEY=
OPTIMISM_ETHERSCAN_KEY=
BASE_ETHERSCAN_KEY=
BSC_ETHERSCAN_KEY=
AVALANCHE_ETHERSCAN_KEY=
LINEA_ETHERSCAN_KEY=
SCROLL_ETHERSCAN_KEY=
BLAST_ETHERSCAN_KEY=
MANTLE_ETHERSCAN_KEY=
GNOSIS_ETHERSCAN_KEY=
SONIC_ETHERSCAN_KEY=
UNICHAIN_ETHERSCAN_KEY=
BERACHAIN_ETHERSCAN_KEY=
SEI_ETHERSCAN_KEY=
5 changes: 2 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ permissions: {}

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

Expand All @@ -28,9 +30,6 @@ jobs:
- name: Show Forge version
run: forge --version

- name: Run Forge fmt
run: forge fmt --check

- name: Run Forge build
run: forge build --sizes

Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ docs/

# Dotenv file
.env


node_modules

/typechain
/artifacts
/cache_hardhat
/cache-hh
/artifacts-tron
/cache_hardhat-tron
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Only resolve dependency versions published at least this many days ago.
# Helps limit supply-chain risk from freshly published malicious releases.
# Supported in npm 11+ (see `npm config get min-release-age`).
min-release-age=14
Comment on lines +3 to +4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether npm/toolchain version enforcement exists in-repo.
# Expected: package.json includes engines.npm >=11 (or equivalent), and CI/tooling pins npm 11+.

set -euo pipefail

echo "== package.json engines / packageManager =="
fd -H '^package\.json$' | while read -r f; do
  echo "--- $f"
  jq '{packageManager, engines}' "$f" 2>/dev/null || cat "$f"
done

echo
echo "== engine-strict in npm configs =="
rg -n --hidden --glob '.npmrc' '^\s*engine-strict\s*=' || true

echo
echo "== CI/tooling npm version pins =="
rg -n --hidden -S 'npm-version|setup-node|corepack|volta|packageManager|npm@' \
  .github/workflows package.json .tool-versions .node-version .nvmrc 2>/dev/null || true

Repository: SocketDotTech/poc-openrouter

Length of output: 252


Enforce npm 11+ to make this control effective.

min-release-age only works on npm 11+. The repository has no npm version enforcement—no engines in package.json, no engine-strict in .npmrc, and no CI pinning. This control becomes silently ineffective when contributors or CI use npm <11.

Add "engines": {"npm": ">=11"} to package.json and set engine-strict=true in .npmrc to enforce this across all install contexts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.npmrc around lines 3 - 4, Add npm version enforcement so min-release-age is
actually effective: update package.json to include an "engines" entry (e.g.,
"engines": {"npm": ">=11"}) and enable strict enforcement by adding
engine-strict=true to .npmrc; ensure the changes reference the existing
min-release-age setting and document or update CI to use npm >=11 so installs
fail on older npm versions rather than silently ignoring the setting.

6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Project Context

For OpenRouter contract work read files which are relevant for the task. general context - `OPENROUTER_CONTEXT.md`
assumptions - `OPENROUTER_ASSUMPTIONS.md` first.

Main ship target is `src/combined/OpenRouterV2Unchecked.sol`. If its ABI changes, update the backend encoders in `bungee-backend/src/modules/dex/utils.ts` and `bungee-backend/src/modules/router/utils/directQuotesOpenRouter.ts`.
358 changes: 194 additions & 164 deletions OPENROUTER.md

Large diffs are not rendered by default.

248 changes: 248 additions & 0 deletions OPENROUTER_ASSUMPTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# OpenRouter Assumptions

Last reviewed: 2026-05-19.

Scope: `src/combined/OpenRouterV2Unchecked.sol`.

This document captures the assumptions that make the unchecked OpenRouter safe to operate. Many of these are business and integration assumptions, not guarantees enforced by the contract.

## Source Of Truth

`OpenRouterV2Unchecked` intentionally removes backend signature verification, nonces, and deadlines. Public entrypoints can be called by anyone.

Current checked-in public surface:

- `swap(...)`
- `swapAndBridge(...)`
- `bridge(...)`
- `performActions()(...)`
- `rescueFunds(...)`

`OPENROUTER_CONTEXT.md` and `scripts/e2e/utils/routerAbi.ts` may mention `performExecution(...)`; verify against the Solidity file before relying on that ABI.

## Enforcement Classes

Use this distinction when reviewing any route or integration:

- On-chain enforced: checked directly by the router.
- Operationally enforced: must be true because frontend, backend, deploy config, or runbooks enforce it.
- Policy assumption: not enforced by code. If it becomes false, the unchecked router can become unsafe.

## Critical Business Assumptions

### Router Never Holds Durable Funds

The router may temporarily hold funds during one transaction, but it should not end routes with meaningful token or native balances.

Failure mode: `performActions()` lets any caller make the router call arbitrary contracts. If the router holds ERC20s, native ETH, bridged refunds, swap dust, rebates, or protocol refunds, a public caller can move or approve those assets through modular actions before owner rescue.

Operational requirements:

- Do not use the router as a treasury, escrow, settlement account, refund address, or fee vault.
- Route calldata should send final assets to the user, bridge, or fee recipient in the same transaction.
- Monitor router token/native balances and treat non-zero balances as an incident or stuck-funds condition.
- Owner rescue is an operational recovery tool, not a security boundary.

### Users Never Directly Approve The Router

Users must not give persistent ERC20, Permit2, ERC721, ERC1155, or protocol-specific approvals directly to the router.

Failure mode: if a user directly approves the router, any caller can use `performActions()` to make the router call `transferFrom`, `approve`, or equivalent privileged token functions against that user allowance.

Operational requirements:

- User ERC20 approvals should go to 0x AllowanceHolder, not OpenRouter.
- UI copy and wallet flows must never ask users to approve OpenRouter directly.
- Monitoring should flag direct allowances from users to the router.
- If a direct approval is discovered, revoke it before treating that user as safe.

### Router Has No Privileged Role On Other Contracts

No external contract should treat OpenRouter as a privileged actor unless every public caller is allowed to exercise that privilege.

Failure mode: if another contract has `onlyRouter`, allowlists the router, grants it minter/burner/pauser/admin/operator/bridge-agent permissions, or keys permissions off `msg.sender == router`, any caller can exercise that role through modular execution.

Operational requirements:

- Do not grant OpenRouter roles in bridges, vaults, tokens, staking systems, receivers, relayers, or settlement contracts.
- Do not whitelist OpenRouter in downstream contracts as a trusted caller unless the called operation is safe for arbitrary public callers.
- Review new integrations for hidden trust checks against `msg.sender`.

### Router Is Not A User-Intent Authority

The unchecked router does not prove that a route reflects user intent. It only executes calldata.

Failure mode: a malicious UI or compromised backend can make the user call `AllowanceHolder.exec` with calldata that pays an attacker, charges an arbitrary fee, bridges to a wrong recipient, or approves a malicious spender.

Operational requirements:

- The frontend/backend must validate recipients, fee receivers, fee amounts, swap targets, bridge targets, approval spenders, destination chain/domain, bridge min amounts, and refund addresses before presenting a transaction.
- Wallet simulation and transaction review should show the actual route effects where possible.
- `requestHash` is only an event correlation id. It does not enforce uniqueness, replay protection, or user consent.

## Fund Pull Assumptions

### ERC20 Inputs Use AllowanceHolder

ERC20 input safety depends on 0x AllowanceHolder transient allowance scoping plus `_msgSender() == input.user`.

On-chain enforced:

- `_pullFromUser` reverts unless `_msgSender() == input.user` for ERC20 inputs.
- When called through AllowanceHolder, `_msgSender()` is decoded from the appended user address.

Operational assumptions:

- The user calls `AllowanceHolder.exec(operator, token, amount, target, data)`.
- `operator` is the router.
- `target` is the router.
- `token` and `amount` match the route input.
- The user has a persistent approval to AllowanceHolder, not to the router.

Failure modes:

- Direct ERC20 calls to the router fail because `_msgSender()` is not the user.
- Bad AH calldata can still execute a bad route if the user submits it.
- AH protects fund pulling for the route input, but it does not validate swap/bridge semantics.

### Native Inputs Are Not User-Bound

Native-token input routes only check that `msg.value >= inputAmount`.

Failure mode: `input.user` is not authenticated for native routes. Anyone can submit native routes if they provide the ETH. This is usually acceptable because the caller funds the transaction, but downstream analytics must not treat `input.user` as authenticated identity for native paths.

Operational requirements:

- Native route attribution should come from transaction signer / AH sender / product context, not only `input.user`.
- Excess `msg.value` is not automatically refunded by the router.

## Execution Assumptions

### External Targets Are Trusted Per Route

The router does not whitelist swap targets, bridge targets, approval spenders, manipulators, receivers, or fee recipients.

Failure modes:

- Malicious swap target can consume approved input and return misleading returndata.
- Malicious bridge target can consume approved output or native value.
- Malicious approval spender can use allowance after the route if allowance remains and the router later receives the same token.
- Malicious fee receiver can reject native fee transfers and revert the route.

Operational requirements:

- Backend/frontend must maintain target and spender allowlists or equivalent route validation.
- Approval spender should be the minimum necessary protocol spender.
- Prefer route patterns that leave no router balance and no meaningful residual allowance.

### Swap Output Measurement Matches The Aggregator

The router supports two output modes:

- Returndata mode: decode a 32-byte word at `swapData.returnDataWordOffset`.
- Balance-delta mode: measure `balanceOf(outputReceiver)` before and after the swap.

Failure modes:

- Returndata mode is unsafe if the target return word is not the actual output amount.
- Balance-delta mode is unsafe if unrelated balance changes occur during the call, or if the token has rebasing/fee-on-transfer behavior that breaks expected deltas.
- In standalone pre-fee/no-fee swaps, the swap calldata must send output directly to `receiver`; the router will not forward output afterward.
- In standalone post-fee swaps and all `swapAndBridge` paths, the swap output must land on the router.

Operational requirements:

- Choose output mode per aggregator and route.
- Verify `returnDataWordOffset` against the concrete swap target ABI.
- Verify output recipient encoded in `swapCallData` matches the router mode.
- Treat `minOutput` as gross swap output, not guaranteed net-to-user output after post-fee or bridge fees.

### Fee Semantics Are Caller-Defined

The router does not enforce fee policy.

Assumptions:

- Pre-fee amounts are denominated in the input token.
- Post-fee amounts are denominated in the output token.
- `fee.receiver` is trusted and product-approved.
- `fee.amount` is within product policy.

Failure modes:

- A malicious caller can set an arbitrary fee receiver and amount if the user submits the calldata.
- Post-fee is applied after gross `minOutput` validation, so net user proceeds can be lower than `minOutput`.

### Bridge Calldata Is Semantically Correct

The router does not understand bridge-specific fields.

Assumptions:

- Destination chain/domain is correct.
- Recipient is correct.
- Refund address is not the router unless intentionally safe.
- Bridge min amount / slippage fields are correct.
- Bridge fee quote and native fee buffer are current enough.
- Token and amount fields in calldata match the route.

Failure modes:

- `bridge()` performs no runtime amount splicing; the amount must already be encoded.
- `swapAndBridge()` can splice one 32-byte amount word only.
- The bridge-value flag forwards `finalAmount + bridgeData.value` as native value. It must only be used when the bridge expects the bridged asset itself as native value plus a static fee.
- Excess native fee behavior depends on the bridge target and refund address, not OpenRouter.

## Modular Execution Assumptions

`performActions()` is the broadest surface. It makes the router a public generic call executor.

Assumptions:

- The router has no durable funds.
- No user has directly approved the router.
- No external contract gives the router privileged rights.
- Each action target is safe for the router to call.
- Splice offsets and lengths are generated by trusted tooling.
- Actions that are splice sources store their returndata.

Failure modes:

- Any public caller can transfer, approve, or spend assets already held by the router.
- Any public caller can exercise downstream privileges granted to the router.
- `CALL_WITH_NATIVE` can spend native ETH already sitting in the router.
- Invalid `callType` values fall through to normal `CALL`; encoders must emit only known call types.
- Splices are bounds-checked but not semantically validated. A bad splice can write a valid but wrong bridge amount, recipient field, fee field, or payload word.

## Token Assumptions

Assumptions:

- ERC20s follow sane `transfer`, `transferFrom`, `approve`, and `balanceOf` behavior.
- The native token sentinel is exactly `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.
- Tokens do not rebase or charge transfer fees in ways that invalidate route amounts, unless route tooling explicitly accounts for that.
- Approval reset/retry behavior in Solady `safeApproveWithRetry` is acceptable for the token.

Failure modes:

- Fee-on-transfer tokens can cause bridge approvals or calldata amounts to exceed actual received balances.
- Rebasing tokens can corrupt balance-delta output measurement.
- Non-standard tokens can revert, return false, or have allowance quirks.

## Operational Checklist

Before enabling a route or integration, confirm:

- Users approve AllowanceHolder only.
- The router has no direct user allowances.
- The router has no privileged roles on any touched contract.
- The router is not used as recipient, refund address, treasury, or settlement vault unless public draining is acceptable.
- Swap target, bridge target, approval spenders, manipulators, fee receiver, and receiver are validated.
- Swap output mode and `returnDataWordOffset` are correct for the aggregator.
- Standalone swap recipient is correct for pre-fee/no-fee versus post-fee mode.
- Bridge calldata encodes the correct recipient, destination, min amount, refund address, and fees.
- Bridge amount splice offset is correct for the exact calldata shape.
- Native `msg.value` covers input amount plus all downstream native call values.
- Excess native value and bridge refunds do not end up on the router.
- Monitoring exists for router balances, direct allowances to router, and unexpected downstream roles.

If any critical business assumption is false, do not rely on `OpenRouterV2Unchecked` as-is. Add access control, use a signed variant, or remove the downstream privilege/funds/allowance that makes the public call surface dangerous.
Loading