> For the complete documentation index, see [llms.txt](https://agentdex.gitbook.io/agentdex-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://agentdex.gitbook.io/agentdex-docs/under-the-hood/smart-contracts.md).

# Smart contracts

This page documents the on-chain surface — the contracts users interact with, the operator roles that manage them, and the event signatures indexers should track. Contract source, ABIs, and verified-source links are available via each network's block explorer.

### See also

* [Deposits and withdrawals](/agentdex-docs/account/deposits-and-withdrawals.md)
* [Validator and verifiability](/agentdex-docs/under-the-hood/validator-and-verifiability.md)
* [Risks](/agentdex-docs/reference/risks.md)

### Inventory

| Contract                     | Purpose                                      | Notes                                             |
| ---------------------------- | -------------------------------------------- | ------------------------------------------------- |
| **BridgeUpgradeable** v1.3.0 | Custody, deposits, withdrawals, escape hatch | UUPS-upgradeable. Deployed behind `ERC1967Proxy`. |
| **USDC**                     | Mainnet ERC-20, 6 decimals                   | Deposit USDC from Base                            |

### Deployed addresses

#### Base Mainnet (Chain ID 8543)

<table><thead><tr><th width="316.34375">Role</th><th>Address</th></tr></thead><tbody><tr><td>Bridge proxy (<code>ERC1967Proxy</code>)</td><td><code>0x4c3FEB124542386cF0C9CE9153443f8BF629F6fc</code></td></tr><tr><td>USDC</td><td><code>0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913</code></td></tr></tbody></table>

Current implementation address behind the proxy is resolvable via the ERC-1967 implementation storage slot:

{% code title="Read the ERC-1967 implementation slot" %}

```bash
cast storage 0x4c3FEB124542386cF0C9CE9153443f8BF629F6fc \
  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \
  --rpc-url <https://mainnet.base.org>
```

{% endcode %}

(The constant is the ERC-1967 implementation slot defined by the standard.)

#### Planned networks

| Network          | Status  |
| ---------------- | ------- |
| Ethereum mainnet | Planned |
| Arbitrum         | Planned |

Addresses will appear here as each network goes live.

ABIs and verified-source links are available via each network's block explorer.

### Roles

BridgeUpgradeable has three distinct roles enforced at the contract level:

| Role         | Powers                                                                                                                                                      |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Owner**    | Add / remove supported tokens; change Treasury; configure timelock duration (1–14 days); set wrapped native token; pause / unpause; upgrade implementation. |
| **Treasury** | Finalise withdrawals (single + batch + accept/cancel split); update on-chain available balances.                                                            |
| **User**     | Deposit, request withdrawal, cancel own pending withdrawal, force withdrawal after timelock.                                                                |

Owner key custody (single vs. multisig, any timelock on upgrades, public announcement window): **TBD by the protocol team.** Until that policy is published, treat upgrades as Owner-discretionary.

{% hint style="warning" %}
Until a concrete policy is published, treat upgrades as Owner-discretionary.
{% endhint %}

### Storage model

{% code title="Bridge storage model" %}

```solidity
struct UserAccount {
    uint256 available;   // credited balance (deposits + L2 balance syncs)
    uint256 claimed;     // reserved for pending withdrawals
}

struct Withdrawal {
    address user;
    address token;
    uint256 amount;
    uint64  requestedAt;
    uint8   status;      // 0=PENDING 1=CANCELLED 2=FORCED 3=REDEPOSITED
}
```

{% endcode %}

Top-level state:

* `userAccount[user][token] → UserAccount` — per-user per-token accounting.
* `withdrawals[uint64 id] → Withdrawal` — pending withdrawal records.
* `supportedTokens[token] → bool` — Owner-managed allowlist.
* `lastBatch[token] → bytes` — opaque fingerprint of the last balance-update batch.
* `updatedAt → uint64` — global timestamp of the last `updateAvailableBalance`. **Resets the force-withdrawal timer.**
* `treasury → address` — current Treasury operator.
* `wrappedNativeToken → address` — WETH-like wrapper (used only as a config field today; native deposits not active).
* `timelockDuration → uint256` — current force-withdrawal delay, bounded `[MIN_TIMELOCK = 1 day, MAX_TIMELOCK = 14 days]`.
* `withdrawalNonce → uint64` — monotonically-increasing withdrawal id counter.

### User methods

#### `depositToken(address token, uint256 amount)`

`nonReentrant`, `whenNotPaused`.

1. Reverts `TokenNotSupported` if not on the allowlist.
2. Reverts `InvalidAmount` if `amount == 0`.
3. `safeTransferFrom(msg.sender, address(this), amount)`.
4. `userAccount[sender][token].available += amount`.
5. Emits `Deposit(user, token, amount, withdrawalNonce)`.

Note: `Deposit`'s `nonce` field carries `withdrawalNonce` (not a deposit counter). Indexers should not interpret it as a deposit-sequence number.

#### `requestWithdrawal(address token, uint256 amount) → uint64 withdrawalId`

`nonReentrant`, `whenNotPaused`. **User-initiated** — `msg.sender` is the user; there is no separate `user` argument. The signature takes two arguments: `token` and `amount`.

1. Reverts `InvalidAmount` if `amount == 0`.
2. Reverts `TokenNotSupported` if token not allowlisted.
3. Assigns `withdrawalId = withdrawalNonce++`.
4. Records `Withdrawal { user, token, amount, requestedAt = block.timestamp, status = PENDING }`.
5. `userAccount[sender][token].claimed += amount`; reverts `InsufficientBalance` if `claimed > available`.
6. Emits `WithdrawalRequested(id, user, token, amount, requestedAt)`.

#### `cancelWithdrawal(uint64 id)`

Caller-only; the user cancels their own pending withdrawal.

1. Reverts `WithdrawalNotFound` if the record is empty or belongs to someone else.
2. Reverts `WithdrawalIsCancelled` if status is not `PENDING`.
3. Sets status to `CANCELLED`.
4. Emits `WithdrawalCancelled(id)`.

`claimed` is decremented later, when the Treasury finalises the withdrawal (which, given `CANCELLED`, books no transfer).

#### `forceWithdrawal(uint64 id)` — escape hatch

`nonReentrant`, `whenNotPaused`.

1. Computes `requestedAt = max(w.requestedAt, updatedAt)`.
2. Reverts `TimelockNotPassed` if `block.timestamp < requestedAt + timelockDuration`.
3. Reverts `InsufficientBalance` if `available < w.amount`.
4. `available -= w.amount`.
5. Internally calls `_finalizeWithdrawal(id, WITHDRAWAL_STATUS_FORCED)`, which transfers tokens out and emits `WithdrawalFinalized`.

The structural guarantee: **every `updateAvailableBalance` call from Treasury refreshes `updatedAt`, resetting the timer.** The escape hatch opens only when the off-chain engine is fully silent for the entire `timelockDuration` (default 7 days; bounded 1–14).

### Treasury (operator) methods

#### `finalizeWithdrawal(uint64 id, uint8 status)`

Single-withdrawal finalisation. `status`:

| Value |       Constant      |                                Effect                                |
| :---: | :-----------------: | :------------------------------------------------------------------: |
|  `0`  | `WITHDRAWAL_ACCEPT` | Transfer tokens to user; emit `WithdrawalFinalized(.., ACCEPT, ..)`. |
|  `1`  | `WITHDRAWAL_CANCEL` |                     No transfer; record removed.                     |

Race condition: if Treasury sends ACCEPT against a withdrawal the user has already `CANCELLED`, the contract treats it as a **redeposit** — emits a `Deposit` event, marks status `REDEPOSITED`, no token transfer.

#### `batchFinalizeWithdrawals(uint64[] ids, uint8 status)`

Same status applied to all ids in the batch.

#### `batchFinalizeWithdrawals2(uint64[] accept, uint64[] cancel)`

Two arrays — accept and cancel — in one transaction. The variant the state-service uses by default.

#### `updateAvailableBalance(bytes batch, address token, BalanceUpdate[] updates)`

For each `BalanceUpdate { user, available }` overwrites `userAccount[user][token].available` with the **absolute value** (not a delta). Records the opaque `batch` fingerprint and refreshes `updatedAt`, resetting the force-withdrawal timer for all users.

Emits `AvailableBalanceUpdated(batch)`.

### Owner (admin) methods

| Method                             | Effect                                                                                                                         |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `addToken(token)`                  | Mark token as supported (allowlist).                                                                                           |
| `removeToken(token)`               | Remove from the allowlist (existing balances unaffected).                                                                      |
| `changeTreasury(newTreasury)`      | Swap the Treasury operator. No timelock.                                                                                       |
| `setWrappedNativeToken(addr)`      | Update the WETH-like address (config only today).                                                                              |
| `setTimelockDuration(newDuration)` | Adjust force-withdrawal delay; reverts `InvalidTimelock` outside `[1 day, 14 days]`.                                           |
| `pause()` / `unpause()`            | Global emergency stop — blocks `depositToken`, `requestWithdrawal`, `forceWithdrawal`, `finalizeWithdrawal`, `batchFinalize*`. |
| `_authorizeUpgrade(impl)`          | UUPS implementation swap.                                                                                                      |

### Events

| Event                         | Signature                                                                                              |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ |
| `Deposit`                     | `(address indexed user, address indexed token, uint256 amount, uint64 indexed nonce)`                  |
| `WithdrawalRequested`         | `(uint64 indexed id, address indexed user, address indexed token, uint256 amount, uint64 requestedAt)` |
| `WithdrawalFinalized`         | `(uint64 indexed id, uint8 status, address indexed user, address indexed token, uint256 amount)`       |
| `WithdrawalCancelled`         | `(uint64 indexed id)`                                                                                  |
| `AvailableBalanceUpdated`     | `(bytes batch)`                                                                                        |
| `TokenAdded` / `TokenRemoved` | `(address indexed token)`                                                                              |
| `TimelockDurationChanged`     | `(uint256 oldDuration, uint256 newDuration)`                                                           |
| `WrappedNativeTokenUpdated`   | `(address indexed oldToken, address indexed newToken)`                                                 |

These are the canonical signals indexers and explorers should pin to.

### Custom errors

`InvalidAmount`, `InvalidAddress`, `InvalidTimelock`, `WithdrawalNotFound`, `WithdrawalIsCancelled`, `TimelockNotPassed`, `InsufficientBalance`, `UnknownStatus`, `TokenNotSupported`, `ForceWithdrawalNotReady`, `NativeTransferFailed`.

The contract also surfaces OpenZeppelin's `OwnableUnauthorizedAccount` when the caller is not the Treasury / Owner.

### Upgrade policy

The bridge is UUPS-upgradeable; only Owner can upgrade. Concrete policy on upgrades (multisig threshold, timelock on upgrades, announcement window) is **TBD**. Until then: monitor on-chain `Upgraded` events on the proxy and the team's announcement channels.

### Security properties

| Property                        | How it's enforced                                                                                                                                   |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Custody on-chain                | All deposits sit on the bridge proxy.                                                                                                               |
| Force-withdrawal escape         | Timelock 1–14 days (default 7); opens only on full L2 silence.                                                                                      |
| Operator authority bounded      | Treasury can finalise withdrawals and overwrite `available`. It cannot transfer arbitrary user funds out.                                           |
| Token allowlist                 | Only Owner-approved tokens accept `depositToken`.                                                                                                   |
| Upgradeability                  | UUPS — implementation can be replaced by Owner only.                                                                                                |
| Global emergency stop           | Owner can `pause()` user-facing and Treasury entry points.                                                                                          |
| Reentrancy                      | All token-moving methods are `nonReentrant`.                                                                                                        |
| State commitment / fraud proofs | **None today.** Path to public log + open validator in [Validator and verifiability](/agentdex-docs/under-the-hood/validator-and-verifiability.md). |

### Known limitations

{% hint style="warning" %}
These gaps are known and explicit. They matter for production-risk evaluation.
{% endhint %}

| Limitation                                  | Status                                                                                                                                                   |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Deflationary / fee-on-transfer ERC-20s      | Not handled — `depositToken` records the `transferFrom` amount, not the actual delta. Don't deposit such tokens.                                         |
| Native-token deposit                        | `receive()` exists; no credit path. Don't send native to the bridge.                                                                                     |
| `WithdrawalCancelled` events                | Logged on-chain; not handled by state-service today.                                                                                                     |
| Out-of-order `WithdrawalFinalized` messages | Currently silently dropped if the in-memory pending map hasn't caught up.                                                                                |
| `claimedWithdrawals` mapping                | Declared but unused — dead storage from an earlier design.                                                                                               |
| `Deposit.nonce` uses `withdrawalNonce`      | Each `Deposit` carries the current withdrawal counter as `nonce`. Semantically not a deposit-sequence number; useful only for uniqueness within a chain. |

### Security review

An internal Phase 1 security review of the bridge contracts has been completed. It is positioned as an early focused review, **not** a substitute for an independent audit. Key takeaway:

* **Workable as an early trusted-operator bridge.** Not production-ready for material user funds without further hardening.
* Highest-severity finding: accepted withdrawals do not reduce on-chain `available` — the bridge's internal accounting is overstated until the next Treasury balance update reconciles. The protocol team has acknowledged this as intentional: the off-chain engine is the source of truth for available balance; the bridge's value is restored on the next `updateAvailableBalance`.
* Strong recommendation to move to proof-based or independently-verifiable balance updates over time. The roadmap to this is [Validator and verifiability](/agentdex-docs/under-the-hood/validator-and-verifiability.md).

A formal third-party security audit of the bridge contracts was completed by **Decurity** (audit period 18–22 May 2026). It surfaced one medium, one low, and two informational findings; **all were fixed and re-tested** during the engagement. The medium finding — `removeToken` could strand already-deposited balances of a delisted token — and the escape-hatch issue (`pause()` blocking `forceWithdrawal`) were both remediated. The full report and the finding-by-finding status are on the [Risks](/agentdex-docs/reference/risks.md) page.

{% file src="/files/it3pALsqFTZQ70aAJpce" %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://agentdex.gitbook.io/agentdex-docs/under-the-hood/smart-contracts.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
