> For the complete documentation index, see [llms.txt](https://docs.strike.fun/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.strike.fun/guides/deployment-guide.md).

# Deployment Guide

## Prerequisites

* [Foundry](https://book.getfoundry.sh/) installed (`forge`, `anvil`, `cast`)
* BNB for gas fees
* Deployer EOA private key
* BscScan API key (for contract verification)

## Environment Variables

```bash
export PRIVATE_KEY=0x...                # Deployer private key
export RPC_URL=https://...              # BSC RPC endpoint
export PYTH_ADDRESS=0x...              # Pyth Core contract address (see table below)
export ETHERSCAN_API_KEY=...           # BscScan API key for verification
```

### Pyth Oracle Addresses

| Network     | Chain ID | Pyth Core Address                            |
| ----------- | -------- | -------------------------------------------- |
| BSC Testnet | 97       | `0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb` |
| BSC Mainnet | 56       | `0x4D7E825f80bDf85e913E0DD2A2D54927e9dE1594` |

## Deployment Order

The canonical BSC deploy entrypoint is `script/DeployTestnet.s.sol:DeployTestnetScript`. Despite the name, it is chain-aware and is the script currently used for both BSC testnet (`chainid=97`) and BSC mainnet (`chainid=56`). It also deploys and wires `AIResolver`.

Contracts must be deployed in this exact order because of immutable constructor references:

```
1. Vault              -- no dependencies
2. OutcomeToken        -- no dependencies
3. FeeModel            -- no dependencies
4. OrderBook           -- needs Vault
5. BatchAuction        -- needs OrderBook, Vault, FeeModel, OutcomeToken
6. MarketFactory       -- needs OrderBook, OutcomeToken
7. PythResolver        -- needs Pyth oracle address, MarketFactory
8. Redemption          -- needs MarketFactory, OutcomeToken, Vault
```

### Constructor Arguments

| Contract                                              | Constructor Args                                                |
| ----------------------------------------------------- | --------------------------------------------------------------- |
| `Vault(admin, collateralToken)`                       | deployer address, USDT address                                  |
| `OutcomeToken(admin)`                                 | deployer address                                                |
| `FeeModel(admin, feeBps, protocolFeeCollector)`       | deployer, 20, deployer                                          |
| `OrderBook(admin, vault, feeModel, outcomeToken)`     | deployer, Vault address, FeeModel address, OutcomeToken address |
| `BatchAuction(admin, orderBook, vault, outcomeToken)` | deployer, OrderBook, Vault, OutcomeToken                        |
| `MarketFactory(admin, orderBook, outcomeToken)`       | deployer, OrderBook, OutcomeToken                               |
| `PythResolver(pyth, factory)`                         | PYTH\_ADDRESS, MarketFactory                                    |
| `AIResolver(factory, treasury)`                       | MarketFactory, deployer or configured treasury                  |
| `Redemption(factory, outcomeToken, vault)`            | MarketFactory, OutcomeToken, Vault                              |

## Role Wiring

After all contracts are deployed, grant the following roles. The deployer holds `DEFAULT_ADMIN_ROLE` on every AccessControl contract and can call `grantRole`.

### OrderBook.OPERATOR\_ROLE

```solidity
orderBook.grantRole(OPERATOR_ROLE, address(batchAuction));
orderBook.grantRole(OPERATOR_ROLE, address(marketFactory));
```

BatchAuction needs OPERATOR\_ROLE to call `reduceOrderLots`, `updateTreeVolume`, and `advanceBatch`. MarketFactory needs it to call `registerMarket` and `deactivateMarket`. In production, the keeper is also granted `OPERATOR_ROLE` so it can drive batch clearing.

### Vault.PROTOCOL\_ROLE

```solidity
vault.grantRole(PROTOCOL_ROLE, address(orderBook));
vault.grantRole(PROTOCOL_ROLE, address(batchAuction));
vault.grantRole(PROTOCOL_ROLE, address(redemption));
```

OrderBook calls `lock` and `unlock`. BatchAuction calls `settleFill` and `unlock`. Redemption calls `redeemFromPool`.

### OutcomeToken.MINTER\_ROLE

```solidity
outcomeToken.grantRole(MINTER_ROLE, address(batchAuction));
outcomeToken.grantRole(MINTER_ROLE, address(redemption));
```

BatchAuction calls `mintSingle` during inline settlement. Redemption calls `redeem` (burns winning tokens).

### MarketFactory.ADMIN\_ROLE

```solidity
factory.grantRole(ADMIN_ROLE, address(pythResolver));
factory.grantRole(ADMIN_ROLE, address(aiResolver));
factory.grantRole(ADMIN_ROLE, keeper);
factory.grantRole(ADMIN_ROLE, resolutionKeeper);
factory.grantRole(MARKET_CREATOR_ROLE, deployer);
factory.grantRole(MARKET_CREATOR_ROLE, keeper);
factory.setAIResolver(address(aiResolver));
```

PythResolver and AIResolver call resolution functions on MarketFactory. Production deployments also grant keeper and resolution-keeper admin access on the factory, plus market-creation rights for the deployer and keeper.

### AIResolver Keeper Wiring

```solidity
aiResolver.grantRole(KEEPER_ROLE, keeper);
aiResolver.grantRole(KEEPER_ROLE, resolutionKeeper);
```

### PythResolver Admin

PythResolver uses simple ownership (not AccessControl). The admin is set to `msg.sender` in the constructor (the deployer). Transfer via two-step process:

```solidity
pythResolver.setPendingAdmin(newAdmin);   // called by current admin
pythResolver.acceptAdmin();               // called by newAdmin
```

## Deploy Commands

### Local Devnet (Anvil)

For local development and testing:

```bash
anvil --chain-id 31337

forge script script/Deploy.s.sol \
  --rpc-url http://localhost:8545 \
  --broadcast
```

The local-only `Deploy.s.sol` script deploys a `MockPyth` instance, wires all roles, and creates a test market automatically.

### BSC Testnet / Mainnet

```bash
forge script script/DeployTestnet.s.sol:DeployTestnetScript \
  --rpc-url $RPC_URL \
  --broadcast \
  --verify \
  --etherscan-api-key $ETHERSCAN_API_KEY
```

`DeployTestnetScript` auto-detects `block.chainid`, chooses the correct real Pyth address for BSC testnet or mainnet, deploys `AIResolver`, and wires the production role set.

Note: `script/DeployMainnet.s.sol` still exists in the repo, but the canonical production path is the chain-aware `DeployTestnet.s.sol:DeployTestnetScript` flow above.

## Parimutuel Contract Deployment

The parimutuel stack is intentionally isolated from the binary CLOB contracts and is deployed with `script/DeployParimutuel.s.sol:DeployParimutuelScript`.

It deploys and wires:

1. `ParimutuelFactory`
2. `ParimutuelVault`
3. `ParimutuelPoolManager`
4. `ParimutuelRedemption`
5. `ParimutuelAIResolver`
6. `ParimutuelPythResolver`

Required env:

```bash
export PRIVATE_KEY=0x...          # or DEPLOYER_PRIVATE_KEY
export USDT_ADDRESS=0x...         # BSC mainnet USDT or testnet mock USDT
export PYTH_ADDRESS=0x...         # Pyth Core for the target chain
```

Recommended mainnet role env:

```bash
export PARIMUTUEL_FINAL_ADMIN=0xDA680a19C8E5a43a5B6280d2a1e1A5E2E8ACD874
export PARIMUTUEL_FEE_RECIPIENT=$PARIMUTUEL_FINAL_ADMIN
export PARIMUTUEL_MARKET_CREATOR=0x73f173D43Fa5284e85d8c4F20453E9bA2629Dd9A
export PARIMUTUEL_KEEPER=0x439d5804Bf14e97f1DA3449304f4167621ff9Cfe
```

`PARIMUTUEL_MARKET_CREATOR` should be the keeper/admin-server signer so production admin tooling can create markets. `PARIMUTUEL_KEEPER` should be the resolution keeper because the infra lifecycle task drives AI/Pyth parimutuel resolution with the resolution sender.

Deploy:

```bash
forge script script/DeployParimutuel.s.sol:DeployParimutuelScript \
  --rpc-url $RPC_URL \
  --broadcast \
  --verify \
  --etherscan-api-key $ETHERSCAN_API_KEY
```

After deployment, update the address registry, `strike-infra` Ansible group vars, and `strike-frontend/src/lib/contracts.ts` before enabling mainnet UI/keeper flows.

## Contract Verification

Deploy scripts pass `--verify` to auto-verify on BscScan. To verify a contract manually:

```bash
forge verify-contract <CONTRACT_ADDRESS> src/MarketFactory.sol:MarketFactory \
  --chain-id 56 \
  --etherscan-api-key $ETHERSCAN_API_KEY \
  --constructor-args $(cast abi-encode \
    "constructor(address,address,address,address)" \
    $DEPLOYER $ORDERBOOK $OUTCOMETOKEN $FEECOLLECTOR)
```

For contracts with complex constructor args, use `cast abi-encode` to produce the correct encoding.

## Post-Deploy Validation Checklist

1. **Contract verification** -- all contracts verified on BscScan.
2. **Role wiring** -- confirm every role grant:

   ```bash
   cast call $ORDERBOOK "hasRole(bytes32,address)(bool)" \
     $(cast keccak "OPERATOR_ROLE") $BATCH_AUCTION
   # Repeat for every grant listed above
   ```
3. **Role checks:**
   * `OrderBook.hasRole(OPERATOR_ROLE, BatchAuction)` -- true
   * `OrderBook.hasRole(OPERATOR_ROLE, MarketFactory)` -- true
   * `Vault.hasRole(PROTOCOL_ROLE, OrderBook)` -- true
   * `Vault.hasRole(PROTOCOL_ROLE, BatchAuction)` -- true
   * `Vault.hasRole(PROTOCOL_ROLE, Redemption)` -- true
   * `OutcomeToken.hasRole(MINTER_ROLE, BatchAuction)` -- true
   * `OutcomeToken.hasRole(MINTER_ROLE, Redemption)` -- true
   * `MarketFactory.hasRole(ADMIN_ROLE, PythResolver)` -- true
4. **Test market creation** -- call `MarketFactory.createMarket(...)` with a known Pyth price ID (requires MARKET\_CREATOR\_ROLE) and verify the market appears in `activeMarkets`.
5. **Test place/cancel cycle** -- approve Vault for USDT, call `OrderBook.placeOrder(...)` and verify USDT collateral is escrowed in Vault. Cancel the order and verify USDT is returned to wallet.
6. **Keepers configured** -- batch-keeper, market-keeper, and resolution-keeper (in strike-infra) pointing at correct contract addresses.
7. **Indexer configured** -- indexer (in strike-infra) pointing at correct RPC and contract addresses, listening for all relevant events.
8. **SDK / docs updated** -- update SDK defaults and published deployment docs/README so integrators pick up the new mainnet and testnet addresses.
