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

# NFTXGacha

NFTXGacha — the spin half of the gacha: emergent pricing, Chainlink VRF commit/reveal, and the claim pipeline.

`NFTXGacha` is the **spin half** of a gacha machine. It prices a pull from the machine's live inventory, requests randomness from Chainlink VRF, and runs the commit → reveal → claim pipeline that settles a spin. It holds no NFTs of its own: inventory lives in the companion [NFTXGachaVault](/contracts/gacha-vault.md), which this contract draws from through a gated seam. Concepts are covered in the [Gacha](/gacha/gacha.md) section; this page is the function reference.

Function signatures below come from the `IGacha` / `IGachaVRF` interfaces. The contract is `Ownable`, `Pausable`, `ReentrancyGuard`, and inherits `TokenEscrow` for pull-payment refunds and rewards.

{% hint style="warning" %}
Gacha is **pre-mainnet**. `NFTXGacha` is deployed only on **Ethereum Sepolia** (`11155111`) at `0x8Ecd49F762C82B48db927cB80b5629Fb3F4C9294`, as a rehearsal. There is no mainnet deployment. Always confirm against the on-chain record before integrating.
{% endhint %}

## Pricing

### `quote`

Returns the current per-pull price for a batch and the snapshot hash a spin at this block would commit. This is a live read — the price moves with inventory — so use the returned hash as the one a subsequent `claim` must satisfy.

```solidity
function quote(uint _machineId, uint8 _pulls) external view returns (uint pricePerPull_, bytes32 snapshotHash_);
```

| Parameter    | Type    | Description                      |
| ------------ | ------- | -------------------------------- |
| `_machineId` | `uint`  | The machine to quote             |
| `_pulls`     | `uint8` | The number of pulls in the batch |

| Return          | Type      | Description                                      |
| --------------- | --------- | ------------------------------------------------ |
| `pricePerPull_` | `uint`    | The price per pull, in wei, at the current block |
| `snapshotHash_` | `bytes32` | The commitment a spin at this block would store  |

### `machinePricing`

Returns a machine's economics and guard rails — the pricing half of its configuration. The inventory half (collections, unit cap, active flag) lives on the [vault](/contracts/gacha-vault.md).

```solidity
function machinePricing(uint _machineId) external view returns (MachinePricing memory pricing_);
```

The `MachinePricing` struct:

| Field                     | Type     | Description                                      |
| ------------------------- | -------- | ------------------------------------------------ |
| `vigBps`                  | `uint16` | House edge in basis points — the depositor yield |
| `protocolFeeBps`          | `uint16` | Skimmed off spin revenue at claim                |
| `maxBucketWeightShareBps` | `uint16` | Cap on any one bucket's share of the draw        |
| `twapWindow`              | `uint32` | The oracle window every bucket prices over       |
| `minMachineEV`            | `uint96` | Fail-closed EV tripwire, in wei                  |

### `setMachinePricing`

Sets a machine's pricing and guards. Applies from the **next snapshot only** — it never reprices a pending spin.

```solidity
function setMachinePricing(uint _machineId, MachinePricing calldata _pricing) external;
```

**Access:** `onlyOwner`. Reverts (`InvalidMachinePricing`) if a parameter is out of bounds, or (`MachineDoesNotExist`) if the machine was never created.

| Parameter    | Type             | Description                   |
| ------------ | ---------------- | ----------------------------- |
| `_machineId` | `uint`           | The machine to configure      |
| `_pricing`   | `MachinePricing` | The economics and guard rails |

**Returns:** None.

## Spinner lifecycle

### `spin`

Pays for a batch of pulls, freezes the machine's inventory into a hashed snapshot, and requests a random word from Chainlink VRF. The request enters the `PENDING` state; the outcome resolves in a later block.

```solidity
function spin(uint _machineId, uint8 _pulls, uint _maxPricePaid) external payable returns (uint requestId_);
```

**Access:** `payable`, `nonReentrant`, `whenNotPaused`. Reverts on, among others, an inactive or unpriced machine, a pull count of zero or above `MAX_PULLS` (20), inventory below the floor `max(10, 5 × pulls)`, a quoted price above `_maxPricePaid` (`SlippageExceeded`), or attached ETH below price plus the RNG surcharge (`InsufficientPayment`).

| Parameter       | Type    | Description                                                  |
| --------------- | ------- | ------------------------------------------------------------ |
| `_machineId`    | `uint`  | The machine to spin                                          |
| `_pulls`        | `uint8` | The number of pulls to buy (1–20)                            |
| `_maxPricePaid` | `uint`  | The most the caller will pay per pull, in wei (slippage cap) |

| Return       | Type   | Description         |
| ------------ | ------ | ------------------- |
| `requestId_` | `uint` | The spin request id |

### `claim` / `claimFor`

Settle a resolved spin. The caller supplies the snapshot blob (its keccak256 must match the stored commitment) and a count of pulls to settle. Claims are **resumable**: a large batch can be settled across several calls via a cursor, so it survives a gas spike. `claimFor` is the permissionless form — anyone may settle another spinner's request for the configured tip, which a house bot uses to close the reveal-to-claim window promptly.

```solidity
function claim(uint _requestId, bytes calldata _snapshotBlob, uint8 _count) external;
function claimFor(uint _requestId, bytes calldata _snapshotBlob, uint8 _count) external;
```

**Access:** `nonReentrant`. Reverts if the request is not `RESOLVED` (`RequestNotResolved`), the blob doesn't hash to the commitment (`SnapshotMismatch`), or the count is zero (`NothingToClaim`).

| Parameter       | Type    | Description                           |
| --------------- | ------- | ------------------------------------- |
| `_requestId`    | `uint`  | The request to settle                 |
| `_snapshotBlob` | `bytes` | The abi-encoded request-time snapshot |
| `_count`        | `uint8` | How many pulls to settle in this call |

**Returns:** None. Prizes transfer to the spinner; the protocol fee is skimmed, the remainder distributed to depositors, and change for pulls drawn against a drained machine is refunded.

### `rerequest`

Re-randomises a request that has sat `PENDING` past `RETRY_TIMEOUT` (1h): a new VRF request against the **same** snapshot and the **same** price. Expected-value neutral, so it carries no option value; the caller pays a fresh RNG surcharge, so it isn't free to spam.

```solidity
function rerequest(uint _requestId) external payable;
```

**Access:** `payable`, `nonReentrant`. Reverts if the request isn't `PENDING` (`RequestNotPending`) or `RETRY_TIMEOUT` hasn't elapsed (`RetryTooEarly`).

| Parameter    | Type   | Description                         |
| ------------ | ------ | ----------------------------------- |
| `_requestId` | `uint` | The pending request to re-randomise |

**Returns:** None.

### `startEscape` / `finalizeEscape`

The last-resort refund path for a request that never resolved. `startEscape` arms the escape after `ESCAPE_TIMEOUT` (7 days) measured from the **original** request time; `finalizeEscape` completes it after a further `ESCAPE_DELAY` (256 blocks) and refunds the full price from escrow. Any VRF fulfillment arriving before `finalizeEscape` flips the request to `RESOLVED` and voids the escape — so a refund only ever happens when no outcome existed to game.

```solidity
function startEscape(uint _requestId) external;
function finalizeEscape(uint _requestId) external;
```

**Access:** `nonReentrant`, spinner only (`OnlySpinner`). `startEscape` reverts before `ESCAPE_TIMEOUT` (`EscapeTooEarly`) or if already armed (`EscapeAlreadyArmed`); `finalizeEscape` reverts if unarmed (`EscapeNotArmed`) or before the block delay elapses (`EscapeDelayNotElapsed`).

| Parameter    | Type   | Description                 |
| ------------ | ------ | --------------------------- |
| `_requestId` | `uint` | The stuck request to escape |

**Returns:** None.

## Spin state and views

### `spinRequests`

The public mapping of request id to its full `SpinRequest` — spinner, machine, pulls bought and claimed, lifecycle state, escrowed price, snapshot commitment, and (once resolved) the random word.

```solidity
function spinRequests(uint _requestId) external view returns (SpinRequest memory);
function spinCount() external view returns (uint);
```

`spinCount` is the number of spin requests ever made. The `RequestState` enum is `NONE → PENDING → RESOLVED → CLAIMED / ESCAPED`.

### Wiring and configuration getters

Public getters for the contract's wiring and tunables:

```solidity
function vault() external view returns (IGachaVault);
function hook() external view returns (INFTXV4Hook);
function zap() external view returns (address);
function protocolFeeReceiver() external view returns (address);
function minLiquidity(address _collection) external view returns (uint128);
function rngSurcharge() external view returns (uint96);
function claimTip() external view returns (uint96);
```

VRF configuration is exposed the same way — `vrfCoordinator`, `vrfKeyHash`, `vrfSubscriptionId`, `vrfRequestConfirmations`, and `vrfCallbackGasLimit`.

## Randomness callback

### `rawFulfillRandomWords`

The Chainlink VRF entry point. Writes the random word and flips the matching request to `RESOLVED`. It does exactly one storage write and **must never revert** past its caller check — the coordinator does not retry a reverted callback — so a duplicate, late, stale, or unknown fulfillment is a logged no-op, not a revert.

```solidity
function rawFulfillRandomWords(uint _vrfRequestId, uint[] calldata _randomWords) external;
```

**Access:** the configured VRF coordinator only (`OnlyCoordinator`).

| Parameter       | Type     | Description                              |
| --------------- | -------- | ---------------------------------------- |
| `_vrfRequestId` | `uint`   | The coordinator's request id             |
| `_randomWords`  | `uint[]` | The fulfilled random words (one is used) |

**Returns:** None.

## Administration

All of the following are `onlyOwner`. Because this contract custodies the pricing and randomness wiring for machines that hold user NFTs, it can't be ownerless.

### Wiring setters

The hook, vault, zap, and VRF coordinator are settable — not immutable — so an upstream redeploy never forces a full gacha migration with custodied NFTs.

```solidity
function setHook(INFTXV4Hook _hook) external;
function setVault(IGachaVault _vault) external;
function setZap(address _zap) external;
function setProtocolFeeReceiver(address _protocolFeeReceiver) external;
function setVRFConfig(
    IVRFCoordinatorV2Plus _coordinator,
    bytes32 _keyHash,
    uint _subscriptionId,
    uint16 _requestConfirmations,
    uint32 _callbackGasLimit
) external;
```

Each reverts on a zero address where one would break the contract.

### Guard and economics setters

```solidity
function setMinLiquidity(address _collection, uint128 _minLiquidity) external;
function setMinCardinality(uint16 _minCardinality) external;
function setMinSpinGasUnits(uint64 _minSpinGasUnits) external;
function setRngSurcharge(uint96 _rngSurcharge) external;
function setClaimTip(uint96 _claimTip) external;
```

| Function             | Parameter                      | Description                                                    |
| -------------------- | ------------------------------ | -------------------------------------------------------------- |
| `setMinLiquidity`    | `_collection`, `_minLiquidity` | Minimum pool liquidity a bucket must have to be priced against |
| `setMinCardinality`  | `_minCardinality`              | Minimum oracle ring size (151 for the 1800s window on mainnet) |
| `setMinSpinGasUnits` | `_minSpinGasUnits`             | The basefee multiplier behind `MIN_SPIN_PRICE`                 |
| `setRngSurcharge`    | `_rngSurcharge`                | The per-spin RNG surcharge estimate                            |
| `setClaimTip`        | `_claimTip`                    | The tip paid to a `claimFor` caller                            |

Pausing (`whenNotPaused`) blocks new spins; pending spins still resolve, claim, re-request, and escape — nobody in flight is ever trapped.
