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

# Contracts

Solidity 0.8.28, built with Foundry. The vault layer is immutable and has no owner. The registry is owned by a Safe. Every token-layer contract that needs wiring is wired once by the deployer and then has no admin path.

## DealVault

The core. Every deal, every escrowed asset, every internal balance.

```solidity
// Deals
function list(Collateral calldata c, uint128 cap, uint32 term, uint40 listingExpiry, uint128 minPrice)
    external returns (uint256 dealId);
function cancel(uint256 dealId) external;
function fund(uint256 dealId, address lender) external returns (uint256 bidId);   // pulls exactly minPrice, settles at once
function reclaim(uint256 dealId) external;                                          // borrower pays exactly cap
function claim(uint256 dealId) external;                                            // lender, once now >= expiry + GRACE

// Offers (dormant in v1)
function bid(uint256 dealId, uint128 price, uint40 bidExpiry, address lender) external returns (uint256 bidId);
function withdrawBid(uint256 bidId) external;
function accept(uint256 dealId, uint256 bidId) external;

// Balances: pull, never push
function withdrawUSDG() external;
function withdrawERC20(address token) external;
function withdrawPosition(uint256 tokenId) external;
function withdrawCollateral(uint256 dealId) external;   // dispatcher for the deal page

// Views
function getDeal(uint256 dealId) external view returns (Deal memory);
function getBid(uint256 bidId) external view returns (Bid memory);
function claimableAt(uint256 dealId) external view returns (uint40);
function balanceUSDG(address account) external view returns (uint256);
function balanceERC20(address account, address token) external view returns (uint256);
function owedNFT(address account, uint256 tokenId) external view returns (bool);
function openRaw(address token) external view returns (uint256);
function dealCount() external view returns (uint256);
function bidCount() external view returns (uint256);
function GRACE() external view returns (uint48);
```

| Constant             | Value    |
| -------------------- | -------- |
| `MIN_GRACE`          | 24 hours |
| `MAX_LISTING_EXPIRY` | 7 days   |
| `MAX_FEE_BPS`        | 200      |

Immutables: `USDG`, `REGISTRY`, `FEE_SINK`, `POSITION_MANAGER`, `GRACE`.

**Events.** `Listed`, `BidPlaced`, `BidWithdrawn`, `Funded(dealId, bidId, lender, price, fee, fundedAt, expiry)`, `Reclaimed`, `Claimed`, `Cancelled`, `Withdrawn(account, asset, amount)`. Every table the indexer holds is rebuilt from these alone. `fund` emits `BidPlaced` then `Funded` for a synthetic accepted offer, so one accounting path serves both flows.

**Rules.** Custom errors, never revert strings. A `lender` other than the caller is accepted only from a registry-allowlisted router. The ERC-721 receiver accepts exactly the token an in-flight `list` expects, from the PositionManager only, and consumes the expectation. Timestamps only; deadlines are exclusive.

## CollateralRegistry

The one owned contract. Owner: a 2-of-3 Safe, two-step transfer.

```solidity
function setERC20Allowed(address token, bool ok, Lane lane, uint128 minAmount, uint128 maxDealRaw, uint128 maxOpenRaw) external;
function setPoolAllowed(bytes32 poolId, bool ok, uint128 minLiquidity) external;
function setTerms(uint32[] calldata terms) external;        // at most MAX_TERMS, each MIN_TERM..MAX_TERM
function setFee(uint16 bps) external;                       // <= MAX_FEE_BPS
function setInRangeRequired(bool required) external;
function pauseNewDeals(bool paused) external;               // list, bid, accept, fund only
function setRouter(address router, bool ok) external;
function setMemePairs(uint8 mask) external;                 // STOCK = 1, USDG = 2, ETH = 4

function getERC20Config(address token) external view returns (ERC20Config memory);
function getPoolConfig(bytes32 poolId) external view returns (PoolConfig memory);
function isTermAllowed(uint32 term) external view returns (bool);
function allowedTerms() external view returns (uint32[] memory);
function feeBps() external view returns (uint16);
function inRangeRequired() external view returns (bool);
function newDealsPaused() external view returns (bool);
function isRouter(address account) external view returns (bool);
function memePairMask() external view returns (uint8);
```

| Constant                | Value           |
| ----------------------- | --------------- |
| `MAX_FEE_BPS`           | 200             |
| `MIN_TERM` / `MAX_TERM` | 1 day / 30 days |
| `MAX_TERMS`             | 8               |

`Lane` is STOCK, ETH or MEME and is emitted in `ERC20Set` for the indexer. The vault never reads it.

## FeeSink and Buyback

FeeSink pulls fees credited to it by the vault with `collect()`, never pushed, and holds a route switch: TREASURY or BUYBACK. Buyback: `buyback(clipUSDG)` is permissionless above `threshold()`, swaps USDG → ETH → GAGE → sGAGE with each leg bounded by `MAX_IMPACT_BPS` (1%), burns the sGAGE, and pays the caller `bountyBps()`.

## EntryRouter

Stateless. `fundWithETH(dealId, commands, inputs, deadline)` swaps ETH to USDG through the Universal Router, then funds the deal with the caller as lender. Surplus USDG is credited to the caller's vault balance. `bidWithETH` is the dormant offer variant.

## sGAGE

ERC-20 with `burn(amount)`. The constructor mints `TOTAL_SUPPLY_AT_MINT` (5,000,000,000) once: 4.0B to Emissions, 1.0B for the seed. No mint function exists afterwards.

## Drip

Locked balances keyed by a drip id. `claim(dripId)` and `claimMany(dripIds)` transfer what has unlocked since the last claim. Unlocked at time `t` is `total × e² ÷ L²` with `e = min(t − start, L)`. Grantors are DealRewards and LPRewards only, set once. `totalLocked()` is the sum outstanding.

## Emissions

Holds the 4.0B reserve and the 52-week table. `WEEKS()`, `EPOCH()`, `RESERVE()`, `weekly(epoch)`, `prefixSum(epoch)`, `launchAt()`, `currentEpoch()`, `epochStart(epoch)`, `epochOf(timestamp)`, `dealShareBps(epoch)`, `term21ShareBps(epoch)`, `liquidityBudget(epoch)`, `dealBudget(epoch, term)`. `release(epoch)` and `rollover(epoch)` are permissionless; `finalize()` burns the remainder after week 52. Cumulative transfers out can never exceed the table's prefix sums.

## DealRewards

`register(dealId)` is permissionless: reads the funded deal from the vault, derives the epoch from `fundedAt`, computes `fee × rate[term]` capped at `MAX_REWARD_SHARE_BPS` (80%) of the fee at the posted price, reserves it from the term's budget, and grants two drips of length `term` starting at `fundedAt`. `quote(term, fee)` previews a reward. `epochRates(epoch)` and `effectiveRates(epoch)` expose the posted and carried-forward rates. `setEpochRates` is the Safe's, for the current or a future epoch only.

## LPHook

A v4 hook on the GAGE/sGAGE pool with `afterAddLiquidity` and `afterRemoveLiquidity` permissions and no others. When the sender is the PositionManager it calls `LPRewards.onLiquidityChange(tokenId)` inside a `try` with a fixed gas stipend and emits `RecordFailed` on any failure. It never reverts and never returns a delta.

## LPRewards

`checkpoint(tokenId)` and `checkpointMany` are permissionless. `collect(tokenId)` by the position's owner moves earned emissions into a 7-day drip. Views: `earned(tokenId)`, `positionState(tokenId)`, `valueInGAGE(tokenId)`, `totalWeight()`, `poolKey()`, `seedTokenId()`. `notifyEmissions` and `notifyLump` are called by Emissions. The seed and the floor bands carry zero weight.

## CreatorFeeSplitter

The Pons creator wallet. `claim()` pulls the creator share. `split()` sends `OPS_SHARE_BPS` (50%) to operations less the caller's bounty and parks the rest as GAGE-only liquidity in the band `BAND_TICKS` (480) under the market. `sweep(tokenId)` burns the sGAGE a band bought once the market has fallen through it. `bandNow()`, `floorCount()`, `floorTokenIds(i)`, `bands(tokenId)` and `floorBacking()` describe the ladder. Bands can never be withdrawn.

## ReinvestRouter

Stateless. `reinvestMatch(...)` and `reinvestZap(...)` mint a new GAGE/sGAGE position to the caller; `increaseMatch(...)` and `increaseZap(...)` add to one the caller owns, which needs the caller's NFT approval to the router. Pulls sGAGE with a plain approval. Ends every transaction holding nothing.

## SeedTimelock

Holds the seed position for `LOCK_LENGTH` (365 days from `lock()`). Anyone can call `collectFees()` to send accrued GAGE and sGAGE to the fixed treasury (the deployment wallet), without removing liquidity. `release()` sends the NFT to that wallet at or after `releaseAt()`.

## Source and ABIs

The source will be verified on the explorer at each deployment. ABIs are the Foundry build outputs, one per contract and interface. The chain's Universal Router is a modified fork; anything that builds swap calldata for the entry router must target its ABI, which carries an extra `minHopPriceX36` field in the v4 swap struct.


---

# 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.gage.cash/developers/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.
