0xhamssog / evm-trading-executor
git clone https://github.com/0xhamssog/evm-trading-executor.git
—
+0
stars, 24h · #38,729
+~0
stars, 7 days · #43,911
+163
stars, 30 days · #4,654
163
★ total
0
forks · 0 open issues
Star history (as recorded here)
LanguageSolidity
LicenseMIT
Created2026-10-01 (21d ago)
Last push21d ago
Watchers1
OwnerUser
Links
README excerpt
# EVM Trading Executor
This repository demonstrates a reusable Solidity/EVM execution layer for automated trading applications.
It focuses on constrained execution, access control, slippage protection, deadline validation, event-driven state, transaction-state management, and reconciliation.
## Why I Built This
Trading applications often mix strategy, risk, and execution in one place. That makes the onchain boundary hard to audit. This project isolates the execution layer: a caller submits a trade that has already been chosen, and the contract enforces who may trade, which routers and tokens are allowed, and which output and deadline bounds must hold.
## What Problem It Solves
An execution contract that can call any address with arbitrary calldata is the wrong tool under a trading bot. This executor exposes one trade function. It pulls a known token, approves one allowlisted router for that call, checks what the recipient actually received, emits a structured event, and clears the allowance before returning.
The TypeScript side treats that event as the canonical fill. A quote is not assumed to equal the amount that landed.
## Architecture
[code]
Strategy code is intentionally absent. `src/risk/engine.ts` accepts or rejects an intent before anything is signed. `src/execution/executor.ts` encodes, submits, and tracks that intent. The contract does not know why the trade was requested.
## Smart Contract
`contracts/src/TradingExecutor.sol` is a non-upgradeable executor.
Roles:
- `ADMIN` configures routers, tokens, pause, and the executor address. Admin transfer is two-step.
- `EXECUTOR` may call `executeTrade`. That role cannot change allowlists, pause state, or its own authorization.
`executeTrade` checks, in order: caller, pause, reentrancy, operation id, router allowlist, token allowlist, amount, identical tokens, recipient, and deadline. It then pulls `tokenIn` from the caller, swaps through `ITradingRouter`, and pays `recipient`.
`ITradingRouter.swap` is a project interface used by the tests and by future adapters. It is not a universal DEX interface.
Approval model:
[code]
The authorized caller approves the executor for `amountIn`. The executor approves the allowlisted router for that same amount, calls `swap`, then sets the router allowance back to zero. If the token rejects a non-zero allowance overwrite, the executor resets to zero and retries, following the OpenZeppelin `SafeERC20.forceApprove` sequence. Tokens that deliver a different amount than requested are rejected. The recipient's `tokenOut` balance increase is the canonical output. It must match the router's report and be at least `minAmountOut`.
A successful trade consumes its `operationId`. A reverted trade does not, so the same id can be retried after a revert. The TypeScript client still does not resubmit automatically.
## Execution Flow
[code]
Offchain, an operation id is `keccak256(abi.encodePacked("trade", wallet, sequence))`. That id is the join key from the intent to `