> For the complete documentation index, see [llms.txt](https://docs.kairosswap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kairosswap.com/protocol/authorization.md).

# Authorization

Several SwapCore functions take an `onBehalfOf` parameter — the address that owns the resulting state change — which is separate from `msg.sender`, the address that pays for the call. This is the integration hook for **bundlers, routers, vault adapters, and wrapper contracts**: the end user signs and funds the transaction (perhaps through a bundler), while a contract address takes the action on their behalf.

For every `onBehalfOf` entry point except `buySwap`, `msg.sender` must be pre-authorized by `onBehalfOf`. The check is a single mapping lookup (`isAuthorized`) plus a self-path fallback.

Source: `SwapCore.sol`. Note that SwapCore is deployed as a stateless logic module behind the **`SwapStore`** proxy — all storage (including the `isAuthorized` mapping), all protocol-held tokens, and all events live at the SwapStore address. Token approvals and calls like `setAuthorization` must target the **SwapStore proxy address**, not the SwapCore implementation.

## The `onBehalfOf` pattern

Internally, SwapCore runs every guarded call through this helper:

```solidity
function _isSenderAuthorized(address onBehalfOf) internal view returns (bool) {
    return msg.sender == onBehalfOf || isAuthorized[onBehalfOf][msg.sender];
}
```

So the rule is simple: **you can always act on your own behalf, and you can delegate that right to any other address by calling `setAuthorization(delegate, true)`**. Revoke with `setAuthorization(delegate, false)`.

A failure on this check reverts with `E206`.

### Functions that require authorization

| Function                                                          | Who the caller can be                                                                                                               |
| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [`supplyCollateral`](/protocol/liquidity.md#supplycollateral)     | `onBehalfOf`, or a delegate they've authorized                                                                                      |
| [`withdrawCollateral`](/protocol/liquidity.md#withdrawcollateral) | `onBehalfOf`, or a delegate they've authorized                                                                                      |
| [`exitSwapEarly`](/protocol/swaps.md#exitswapearly)               | `onBehalfOf`, or a delegate they've authorized                                                                                      |
| [`transferSwapPosition`](/protocol/swaps.md#transferswapposition) | Current swap owner, or a delegate they've authorized                                                                                |
| [`claimEscrow`](/protocol/swaps.md#claimescrow)                   | Current `swap.userAddress` (i.e. the buyer, or whoever the swap was most recently transferred to), or a delegate they've authorized |

For the swap-scoped functions, authorization is **necessary but not sufficient**: `exitSwapEarly` and `transferSwapPosition` additionally require that `onBehalfOf` actually owns the swap (`swap.userAddress == onBehalfOf`), reverting with `E203` otherwise. Being someone's delegate lets you act on **their** positions — it never lets them (or you) touch a swap they don't own.

### Functions that do NOT require authorization

* [`buySwap`](/protocol/swaps.md#buyswap) — opening a new position is beneficial to `onBehalfOf`, and the caller is the one paying collateral and fees. Allowing anyone to open a swap for anyone else is safe and useful (bundlers, pre-funded relayers). The only restriction: `onBehalfOf == address(0)` reverts with `E103`, so a position can never be minted to the zero address.
* [`makePayment`](/protocol/swaps.md#makepayment), [`liquidateSwap`](/protocol/swaps.md#liquidateswap), [`updateMarketRateIndex`](/protocol/views.md#updatemarketrateindex) — all permissionless, no per-user auth.
* [`createMarket`](/protocol/market-admin.md#createmarket) — fully permissionless; anyone can create a market pair and becomes its `marketOwner`. Only the market-admin functions `transferMarketOwnership`, `terminateMarket`, and `setMarketLpWhitelist` are gated on `marketOwner` (the `marketOwner` modifier), which is a separate mechanism from per-user authorization.

### Why `supplyCollateral` requires auth even though it "helps" `onBehalfOf`

A dust deposit from an attacker would stamp `lpPositions[marketId][victim].lastDepositBlock = block.number`, which makes the victim's own `withdrawCollateral` revert with `E413` in the same block. Without the auth check, an attacker could repeatedly front-run a victim's withdrawal and DoS it one block at a time. The auth gate closes that off at zero cost.

### What the `onBehalfOf` → `msg.sender` split enables

* **Bundlers with Permit2.** The bundler holds approvals from the end user, pulls tokens from its own balance (funded via permit), and calls `supplyCollateral(marketId, amount, endUser, minShares)`. Shares go to the end user; the bundler simply forwarded the call.
* **Wrapper NFTs.** `SwapPositionWrapper` is custodial: to wrap, the buyer first calls `setAuthorization(wrapperAddress, true)`, then `wrap(swapId)` — the wrapper verifies that pre-authorization and calls `transferSwapPosition(swapId, wrapperAddress, buyer)` as the buyer's delegate, taking over as the swap's `userAddress` and minting an ERC-721 to the buyer. From then on, plain ERC-721 transfers of the NFT never touch SwapCore — core ownership stays with the wrapper for the NFT's whole life. `unwrap(tokenId)` reverses it: the wrapper calls `transferSwapPosition(swapId, nftHolder, wrapperAddress)` via the self-path (it is the current owner). One consequence of the custody hand-off: while wrapped, `isAuthorized` is keyed on the *wrapper's* address, so any operators the original owner granted via `setAuthorization` lose their rights over the position until it is unwrapped.
* **Vault adapters.** Morpho vault adapters use the same trick: the adapter is the on-chain LP, the vault's users get accounting via the vault's own share token.

***

## setAuthorization

```solidity
function setAuthorization(address authorized, bool status) external;
```

**Who calls:** anyone — the caller is authorizing (or revoking) `authorized` to act on **their own** behalf. There is no admin path; every account manages its own authorization list. Like all protocol calls, this is sent to the SwapStore proxy address, where the `isAuthorized` mapping lives.

**What it does:** sets `isAuthorized[msg.sender][authorized] = status` — just a single idempotent storage write, no external calls and no checks.

**Parameters:**

| Parameter    | Meaning                                                    |
| ------------ | ---------------------------------------------------------- |
| `authorized` | The address you're granting or revoking authorization for. |
| `status`     | `true` to grant, `false` to revoke.                        |

**State changes / events:**

* `isAuthorized[msg.sender][authorized] = status`.
* Emits `AuthorizationSet(msg.sender, authorized, status)`.

**Reverts:** none directly. The function does not validate `authorized != address(0)` — authorizing the zero address is a no-op since `msg.sender == address(0)` is unreachable, so it simply produces a dead entry.

**Example — authorizing a bundler once, then letting it act repeatedly:**

```solidity
// Done once from the end-user's EOA:
swapCore.setAuthorization(bundler, true);

// From then on, the bundler can do things like:
swapCore.supplyCollateral(marketId, amount, endUser, minShares);
swapCore.withdrawCollateral(marketId, amount, endUser, bundler, minOut, maxShares);
swapCore.exitSwapEarly(requests, endUser);
```

When the bundler is retired or compromised, revoke:

```solidity
swapCore.setAuthorization(bundler, false);
```

There is no multi-authorization atomic helper; if you want to authorize multiple addresses in one transaction, call `setAuthorization` for each via a multicall wrapper on your side.

**See also:** [`isAuthorized`](#isauthorized).

***

## isAuthorized

```solidity
mapping(address => mapping(address => bool)) public isAuthorized;
```

Solidity auto-generates a getter:

```solidity
function isAuthorized(address owner, address operator) external view returns (bool);
```

**Who calls:** anyone — UIs, indexers, other contracts that want to know whether they're currently permitted to call guarded functions on behalf of `owner`.

**What it returns:** `true` if `owner` has previously called `setAuthorization(operator, true)` and has not since revoked. **Returning `true` from this function is not the same as `_isSenderAuthorized` returning true** — the internal helper also accepts `owner == operator` as an implicit self-authorization, but the public mapping does not set that bit. A contract checking the mapping directly should reproduce the `operator == owner || isAuthorized[owner][operator]` logic if it wants to mirror SwapCore's rule.

**State:** `isAuthorized` is per-address, not per-market and not per-function. Once you authorize an operator, they can act for you on any market and any guarded function.

**See also:** [`setAuthorization`](#setauthorization).


---

# 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://docs.kairosswap.com/protocol/authorization.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.
