# 德州扑克 BIBI 桌 — 部署教程（最终版）

本期只部署 **一套 BIBI 桌（Implementation + ERC-1967 Proxy）**。USDT 不上合约。  
**私钥不要发给任何人（包括开发者）。**

玩家永远只跟 **Proxy 地址**交互。网站 `NEXT_PUBLIC_POKER_BIBI_TABLE` 必须填 Proxy，不是 Implementation。

---

## 功能已写完（对照表）

| 约定 | 合约里怎么实现 | 状态 |
|---|---|---|
| Proxy 可升级 | 与矿工同一份 `ERC1967Proxy` + `upgradeTo` | 已写 |
| 只做 BIBI | 部署脚本只打一张桌；USDT 网站显示「即将开放」 | 已写 |
| 六档买入 | `PokerBuyIn`：500 / 1000 / 1500 / 2000 / 3000 / 5000 亿；`chipScale = 1e18` | 已写 |
| 小盲 / 大盲 / 下注不上链 | 合约里没有这三个字段；服务器 `rooms.ts`：1/2，无前注 | 已写 |
| 链上只有买入、离桌、发牌承诺、结算 | `buyIn(seat,chips)` / `rebuy(chips)` / `cashOut` / `startHand`+`setVrfSeed` / `settleHand` | 已写 |
| 共享账本 | `PokerBank`：选档上桌/重买；账本只补差额；离桌回账本；`withdraw` 提到钱包 | 已写 |
| 底池扣 1%，99% 分给玩家 | `RAKE_BPS = 100`；`won[]` 之和必须等于 `putSum - 1%` | 已写 |
| 被邀请人下注的 0.2% 给邀请人 | `INVITE_REBATE_BPS = 20`；立刻 `transfer` 到收款地址 | 已写 |
| 1% 里剩下的进运营 | `opsReceiver` | 已写 |
| 对接矿工邀请 | 结算先读矿工 Proxy 的 `traderInviter` / `inviterPayout` | 已写 |
| 本期不做 Chainlink VRF | `setVrfSeed` 由 Dealer 写入，以后可换 | 已写 |

**不在合约里的：** 跟注、加注、弃牌、翻牌、手牌保密（只上 commit）、座位规则动画。这些继续服务器控制。

---

## 你要准备的

1. MetaMask，切 **BSC 主网**，用矿工 Owner `0x8b3125faa80de93f217084c750ed2024e1d65364`（确认有 BNB）  
2. Remix：https://remix.ethereum.org  
3. 源文件（下面第 1 步会贴）：
   - `PokerBuyIn.sol`
   - `PokerDeck.sol`
   - `PokerTable.sol`（`import {PokerDeck}` + `import {PokerBuyIn}`）
   - `PokerBank.sol`（`import {PokerBuyIn}`；**不可升级**，每次规则变都要新部署）
   - `ERC1967Proxy.sol`（和矿工那份相同，可直接复用）
4. 钱包与矿工对齐：

| 角色 | 干什么 | 地址 |
|---|---|---|
| Owner | 部署、以后 `upgradeTo` | `0x8b3125faa80de93f217084c750ed2024e1d65364` |
| Dealer | 服务器签 `startHand` / `setVrfSeed` / `settleHand` | `0xfe4b12646340d8547b8ade01aa0e693a6a366b63`（矿工邀请 registry，私钥已在阿里云 `INVITE_REGISTRY_PRIVATE_KEY`） |
| 运营收款 | 1% 抽水扣掉邀请返还后打到这里 | `0x8b3125faa80de93f217084c750ed2024e1d65364`（矿工 `poolClaimer` / `opsRebateReceiver`） |

合约 **没有** `transferOwnership`。Owner 必须填矿工那把。Dealer 不必另开钱包、也不必再存一份私钥：网站会复用 `INVITE_REGISTRY_PRIVATE_KEY`。确认 registry 这把有 BNB 即可。

---

## 初始化参数（必须完全一致）

`initialize(token, owner, dealer, opsReceiver, inviteBook, buyInChips, chipScale)`

| 参数 | 值 |
|---|---|
| `token_` | `0xfe8bf5b8f5e4eb5f9bc2be16303f7dab8cf56aa8`（BIBI） |
| `owner_` | `0x8b3125faa80de93f217084c750ed2024e1d65364`（矿工 Owner） |
| `dealer_` | `0xfe4b12646340d8547b8ade01aa0e693a6a366b63`（矿工邀请 registry / Dealer） |
| `opsReceiver_` | `0x8b3125faa80de93f217084c750ed2024e1d65364`（与矿工三池领取 / 邀请运营相同） |
| `inviteBook_` | `0x12009ae412b98e8ae6e12e11ccaadbe8ba6f58d2`（矿工 Proxy，邀请簿） |
| `buyInChips_` | `100000000000`（默认档 1000 亿；玩家入座另选六档之一） |
| `chipScale_` | `1000000000000000000`（1e18） |

第五项不要填 `0x000…000`，否则矿工里已经绑过的邀请在扑克结算读不到。

---

## 第 1 步：Remix 放文件

File explorer 里建两个文件，**同一层目录**：

```text
PokerBuyIn.sol
PokerDeck.sol
PokerTable.sol
PokerBank.sol
```

把仓库对应全文粘进去：

- `contracts/poker/PokerBuyIn.sol`
- `contracts/poker/PokerDeck.sol`
- `contracts/poker/PokerTable.sol`
- `contracts/poker/PokerBank.sol`

`PokerTable.sol` 第一行 import 必须是：

```solidity
import {PokerDeck} from "./PokerDeck.sol";
```

再新建 `ERC1967Proxy.sol`，粘贴 `contracts/proxy/ERC1967Proxy.sol` 全文（矿工部署时用过的那份即可）。

Solidity Compiler：

- Compiler：**0.8.24**
- 勾选 **Enable optimization**，runs = **200**
- 先编 `PokerTable.sol`，再编 `ERC1967Proxy.sol`，无红色报错

---

## 第 2 步：部署 Implementation

1. Deploy & Run → Environment：**Injected Provider - MetaMask** → 确认 BSC 主网、矿工 Owner 钱包 `0x8b31…5364`  
2. Contract 下拉选 **`PokerTable`**  
3. **不要填构造参数**，点橙色 **Deploy** → 钱包确认  
4. 复制地址，记作 **`0xIMPL...`**

> **不要**对 Implementation 点 `initialize`（构造时已锁定，会失败）。

---

## 第 3 步：生成 initialize calldata（还不上链）

本机有 Foundry 时（推荐，复制出来最干净）：

```bash
cast calldata "initialize(address,address,address,address,address,uint256,uint256)" \
  0xfe8bf5b8f5e4eb5f9bc2be16303f7dab8cf56aa8 \
  0x8b3125faa80de93f217084c750ed2024e1d65364 \
  0xfe4b12646340d8547b8ade01aa0e693a6a366b63 \
  0x8b3125faa80de93f217084c750ed2024e1d65364 \
  0x12009ae412b98e8ae6e12e11ccaadbe8ba6f58d2 \
  100000000000 \
  1000000000000000000
```

得到一长串 `0x…`，记作 **`0xINIT...`**。

没有 Foundry：在 Remix 已部署的 Implementation 卡片上找到 `initialize`，填上述 7 个参数，点函数旁 **Calldata** 复制。**不要点 transact。**

---

## 第 4 步：部署 Proxy（玩家要用的地址）

1. Contract 下拉选 **`ERC1967Proxy`**  
2. 构造参数：

```text
implementation_ = 0xIMPL...
data_           = 0xINIT...
```

3. Deploy → 钱包确认  
4. 复制 Proxy 地址，记作 **`0xPROXY...`**

这才是网站要填的游戏合约。玩家授权 BIBI、买入、提现都对着这个地址。

Remix 部署完后，用 **Owner** 对 Proxy 再调一次（Foundry 脚本会自动做）：

```text
setInviteRegistry(0xfe4b12646340d8547b8ade01aa0e693a6a366b63)
```

---

## 第 5 步：自检（必须全对再接到网站）

Remix → **At Address** → Contract 选 `PokerTable` → 地址填 **`0xPROXY...`**。只读：

| 调用 | 期望 |
|---|---|
| `version()` | `1.2.0-tier-buyin`（旧 Proxy 升级后 Owner 再调 `setVersion`） |
| `token()` | `0xfe8bf5b8f5e4eb5f9bc2be16303f7dab8cf56aa8` |
| `owner()` | `0x8b3125faa80de93f217084c750ed2024e1d65364` |
| `dealer()` | `0xfe4b12646340d8547b8ade01aa0e693a6a366b63` |
| `opsReceiver()` | `0x8b3125faa80de93f217084c750ed2024e1d65364` |
| `inviteBook()` | `0x12009ae412b98e8ae6e12e11ccaadbe8ba6f58d2` |
| `inviteRegistry()` | `0xfe4b12646340d8547b8ade01aa0e693a6a366b63`（Foundry 部署会自动 set；Remix 部署后 Owner 再调一次 `setInviteRegistry`） |
| `buyInChips()` | `100000000000` |
| `chipScale()` | `1000000000000000000` |
| `tokenForBuyIn()` | `100000000000000000000000000000`（1000 亿 × 1e18） |
| `RAKE_BPS()` | `100` |
| `INVITE_REBATE_BPS()` | `20` |
| `handOpen()` | `false` |

没有 `smallBlind` / `bigBlind` 是正常的。任一项不对 → **不要**接到网站。

BscScan 打开 Proxy 后，用 Implementation ABI 也能读同样的值。

---

## 第 6 步：写进网站 `.env`

本地和阿里云 `/var/www/fomobox/.env`：

```bash
NEXT_PUBLIC_POKER_CHAIN_ID=56
NEXT_PUBLIC_POKER_BIBI_TOKEN=0xfe8bf5b8f5e4eb5f9bc2be16303f7dab8cf56aa8
NEXT_PUBLIC_POKER_BIBI_TABLE=0xPROXY地址
NEXT_PUBLIC_POKER_DEALER=0xfe4b12646340d8547b8ade01aa0e693a6a366b63
# Owner 部署 PokerBank Proxy 并对桌调 setBank 后再填（填 Proxy，不是 Implementation）
# NEXT_PUBLIC_POKER_BANK=0xBANK_PROXY
# 可不填：发牌员会复用已有的 INVITE_REGISTRY_PRIVATE_KEY
# POKER_DEALER_PRIVATE_KEY=
```

**不要填** `NEXT_PUBLIC_POKER_USDT_TABLE`。  
邀请映射继续用现有的 `INVITE_REGISTRY_PRIVATE_KEY`（写矿工 Proxy 即可，扑克结算会读那本簿）。

```bash
pm2 restart fomobox --update-env
```

检查：

- `/api/poker/onchain`：`bibiReady` 为 true，`usdtReady` 为 false  
- `/api/poker/dealer`：`configured` 和 `matches` 都为 true  

---

## 第 7 步：Dealer 用矿工邀请 registry 那把钱包

Dealer = `0xfe4b12646340d8547b8ade01aa0e693a6a366b63`，就是做返佣时 `setInviteRegistry` 的地址。私钥已经在阿里云 `INVITE_REGISTRY_PRIVATE_KEY`，网站发牌会直接用它，不必再抄一份。确认这把钱包有 BNB（写邀请映射时已经在用）。  
每手大约 3 笔：`startHand` → `setVrfSeed` → `settleHand`。余额不够只影响发牌/结算，玩家买入不受影响。

---

## Foundry 一键部署（可选）

```bash
cd contracts
forge test --match-contract PokerTableTest

export POKER_BIBI_TOKEN=0xfe8bf5b8f5e4eb5f9bc2be16303f7dab8cf56aa8
# 不设则：Owner/运营=矿工 0x8b3125…5364，Dealer/registry=0xfe4b…6b63
export PRIVATE_KEY=0x矿工Owner私钥

forge script script/DeployPoker.s.sol:DeployPoker \
  --rpc-url https://bsc-dataseed.binance.org/ \
  --broadcast --private-key $PRIVATE_KEY
```

`POKER_INVITE_BOOK` 默认就是矿工 Proxy，可省略。  
终端里的 `proxy` 就是 `NEXT_PUBLIC_POKER_BIBI_TABLE`。

新账本（现网升级必做，打成 Proxy）：

```bash
forge script script/DeployPokerBank.s.sol:DeployPokerBank \
  --rpc-url https://bsc-dataseed.binance.org/ \
  --broadcast --private-key $PRIVATE_KEY
```

终端里的 `proxy` 填 `NEXT_PUBLIC_POKER_BANK`，并对每张桌 Proxy 调 `setBank`。以后再改账本规则：新 Implementation → Owner 对 Bank Proxy `upgradeTo`。

---

## 共享账本 PokerBank（必须和新桌 Implementation 一起上）

旧 Bank 当时没套 Proxy，不能 `upgradeTo`。新 Bank **做成和矿工/桌一样的 ERC-1967 Proxy**，以后再改规则只换 Implementation，账本地址不变。入座和提取都只走新 Bank。

1. Remix 部署 `PokerBank` Implementation（无构造参数，**不要** `initialize`）  
2. 部署 `ERC1967Proxy(impl, initialize calldata)`：`initialize(token, owner, chipScale=1e18, autoLeaveFeeChips)`  
3. 部署新的 `PokerTable` Implementation → Owner 对现有每张桌 Proxy `upgradeTo`，再 `setVersion("1.2.0-tier-buyin")`  
4. Owner 或 Dealer 对每张桌 Proxy 调 `setBank(新Bank的Proxy)`  
5. 网站 `.env` 写 `NEXT_PUBLIC_POKER_BANK=新Bank Proxy`  

未 `setBank` 时走旧路径：买入锁在桌合约，`cashOut` 直接回钱包。  
接上账本后：入座/重买选六档之一；账本只补差额；混档同桌，全下只用自己筹码；`cashOut` 回新账本（入座不可提取）；大厅「提取」提到钱包。自动离桌由 Dealer `forceStand`。

**顺序：先合约（新 Bank Proxy + 桌 upgradeTo + setBank），再发网站。** 新网站会调 `buyIn(seat,chips)` / `rebuy(chips)`，旧桌 Implementation 没有这两档重载。

起身（占座旁观）在服务器：当手打完再生效；再坐下也是下一局；连续 5 局未坐下则自动离桌；参与中只剩两人不能起身，只能离桌；只剩一人等待时，已起身的人自动离桌。

## 玩家怎么买入

1. 连接钱包，切 BSC  
2. 账本 + 钱包合计至少够所选档（500–5000 亿）+ 一点 BNB  
3. 点入座：弹出六档，先授权 **Bank**（只批差额），再 `buyIn(seat, chips)`  
4. 一手进行中可以买入并立刻占座，这手旁观，下一局发牌才参与；离桌 / 重买仍要等这手结束  
5. 离桌后筹码回账本，再点提取到钱包  

盲注（1/2，无前注）只显示在牌桌上，不会单独打一笔链上交易。进房间本身不弹钱包，点入座才弹。

---

## 结算与返佣（部署后自动生效）

| 去向 | 怎么算 | 到账 |
|---|---|---|
| 玩家 | 底池 **99%**，按 `won[]` | 留在 `stack`，离桌回账本 |
| 邀请人 | 被邀请人本手累计下注 × **0.2%** | 立刻转到 `inviterPayout[锚点]` |
| 运营 | 1% 里去掉邀请返还后的剩余 | 立刻转到 `opsReceiver` |

查找顺序：矿工 Proxy 邀请簿 → 本桌本地映射。矿工里绑过的钱包不用再抄一份。

可选：Owner 对扑克 Proxy 调 `setInviteRegistry(0xRegistry钱包)` 后，新绑定会双写本桌。不调也能用矿工簿。

---

## 以后改什么、要不要升级

| 想改什么 | 怎么做 |
|---|---|
| 小盲 / 大盲 / 下注时限 | 改服务器 `rooms.ts`，不用动合约。无前注。 |
| 买入金额（桌子必须空着） | Owner 调 `setBuyIn(chips, scale)` |
| 发牌员钱包 | Owner 调 `setDealer` |
| 运营收款地址 | Owner 调 `setOpsReceiver` |
| 邀请簿 | Owner 调 `setInviteBook` |
| 结算逻辑、座位规则、VRF | 新 Implementation → Owner 对 Proxy 调 `upgradeTo` |
| USDT 桌 | 以后再部署一套同样的 Proxy |

升级：再部署一份 `PokerTable` → Owner 对 Proxy 调 `upgradeTo(新IMPL)`。座位和筹码在 Proxy 存储里，不会丢。

### 手牌进行中买入（1.3.0-midhand-buyin）

现网每张桌都是 ERC-1967 Proxy。换逻辑只部署新 Implementation，再由 **Owner** 对每张桌 Proxy 调 `upgradeToAndCall(新IMPL, setVersion("1.3.0-midhand-buyin"))`。

1. 确认桌上没人（`occupants` 全空、`handOpen == false`）
2. 部署 `PokerTable` Implementation（无构造参数，**不要** `initialize`）
3. 网站 `.env` 写 `POKER_TABLE_IMPL=新IMPL`
4. Owner 连钱包后在大厅点「升级全部桌到可手牌中买入」，或对每张 Proxy 调上面的 `upgradeToAndCall`
5. 读 `implementation()` 和 `version()` 核对

升级后 `buyIn(seat, chips)` 在手牌进行中会立刻占座；网站把新人标成「待入座」，这手结束后才发牌。`cashOut` / `rebuy` / `startHand` 仍要等这手结束。
