尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Floor: spot-only CPPI protection vault for tokenized stocks on BNB Chain
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Set a floor under your tokenized stocks. Spot trades only.
• Web: (Vercel fallback https://floorbnb.vercel.app, per render.yaml CORS list). API and MCP: https://floor-server-wb4i.onrender.com (/healthz, /v1/*, /mcp), from apps/web/lib/app-config.ts. It runs on a Render free instance that sleeps after 15 minutes idle; the first request can take 30 to 60 seconds (docs/DEPLOY_RENDER.md).
• Three-minute tour
1. /try: pick a basket and a floor and run the backtest simulator. No wallet needed (apps/web/app/try/page.tsx).
2.檢視完整 README ↗Set a floor under your tokenized stocks. Spot trades only.
Live site · For judges · Binance integration · Contracts on BscScan · How it works · Trust model · Status and limits
https://floorbnb.vercel.app, per render.yaml CORS list). API and MCP: https://floor-server-tfc6.onrender.com (/healthz, /v1/*, /mcp), from apps/web/lib/app-config.ts. It runs on a Render free instance that sleeps after 15 minutes idle; the first request can take 30 to 60 seconds (docs/DEPLOY_RENDER.md)./try: pick a basket and a floor and run the backtest simulator. No wallet needed (apps/web/app/try/page.tsx)./app/position?v=<vault> (reached from /app/positions): on a position, look at the Binance price check, company card and candles. They read GET /v1/market, /v1/market/:symbol/candles (apps/api/src/app.ts)./docs/binance: live table of every Binance Web3 module, with call counts from GET /v1/binance/status./docs/agents: how an agent uses the MCP server, the skill and the keeper.https://floor-server-tfc6.onrender.com/mcp. Check it with curl -s -X POST <url> -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'. Tools return unsigned transactions only (tool list).Floor runs on BNB Smart Chain mainnet (chain id 56), deployed at block 125815981. All three contracts are source-verified on BscScan. Addresses come from packages/contracts/deployments/56.json.
| Contract | Role | Address |
|---|---|---|
| FloorFactory | Creates one vault per position. Holds roles, caps, defaults, asset and router lists. | 0x1147d482fD08DDd7F377838efb610B606B3Ad765 |
| FloorLens | Read-only view helper for positions and keeper scans. | 0x63Ae440B9D309959442eaD3E08cBC3A025C67780 |
| FloorVault (implementation) | The code every position clone copies. | 0xEA0603a83BCf28a1D57d8534971149eACD874055 |
| Fact | Value |
|---|---|
| Owner and guardian | The same address, 0x762c9626711BCc882050cBf06Edd610fE8b91F1A. Disclosed. |
| Keeper (also the deployer) | 0x46FD797AeBD0250A2E768022AD992DF21F12e58a |
| Launch caps | 1,000 USDT per position, 5,000 USDT in total across all users. The owner can change both with setLimits. |
| Product minimum | 5 USDT per position (web and API guard; the factory's own hard floor is 1 USDT) |
| Router | Direct PancakeSwap v3 router only: 0x1b81D678ffb9C0263b24A97847620C99d213eB14 |
| Assets | NVDAB, SPCXB, QQQB (BSC bStocks). Deposits are USDT only. |
Check it yourself. These are free read calls:
export RPC=https://bsc-dataseed.bnbchain.org
export FACTORY=0x1147d482fD08DDd7F377838efb610B606B3Ad765
cast call $FACTORY "owner()(address)" --rpc-url $RPC
cast call $FACTORY "guardian()(address)" --rpc-url $RPC
cast call $FACTORY "maxDeposit()(uint256)" --rpc-url $RPC # 1000000000000000000000 = 1,000 USDT
cast call $FACTORY "maxTotalTvl()(uint256)" --rpc-url $RPC # 5000000000000000000000 = 5,000 USDT
Floor is a protection vault for tokenized stocks on BNB Chain. You deposit USDT into your own vault, pick a floor (the lowest value you want to hold) and a term. The vault holds a mix of bStocks and USDT. It sells stock as prices fall and buys it back as they rise, using the CPPI rule (constant proportion portfolio insurance). Every trade is a plain spot swap on PancakeSwap v3. There is no leverage, no borrowing, no derivatives and no protocol fee in v1.
Floor reduces downside. It does not remove it. If a price gaps by more than about 24% before the vault can rebalance, the value can end below the floor. See Risks.
Tokenized stocks put US equities on a public chain. They also put their drawdowns on a public chain. Holders on BNB Chain have lending (Venus, Lista DAO), baskets and perps, but we found no downside-protection product for tokenized stocks there (CONTEXT.md, competitive landscape). In traditional finance this protection is sold as a principal-protected note: fees, minimums, bank counterparties.
Floor rebuilds the same rule as a public contract with spot swaps only. The price is not a fee. It is upside you give up, and the docs measure how much.
The vault holds stock equal to four times your cushion, capped at your whole value. The rest stays in USDT.
F = deposit x floor% the floor
C = max(V - F, 0) the cushion (V is current vault value)
E* = min(m x C, V) target stock exposure, m = 4
When the cushion shrinks the vault sells stock. When it grows the vault buys stock back. A gap of 1/m = 25% in the stock leg uses the whole cushion, and costs plus the sell band take about a point of that, so the gap limit is about 24%.
Deposit 10,000 USDT, floor 90%. So F = 9,000, C = 1,000, E* = min(4 x 1,000, 10,000) = 4,000 in stock and 6,000 in USDT. Bands and trading costs are ignored here (the contract applies both).
| Step | Event | Stock | USDT | Value V | Cushion C | Target E* | Trade |
|---|---|---|---|---|---|---|---|
| 0 | Deposit | 4,000 | 6,000 | 10,000 | 1,000 | 4,000 | none |
| 1 | Stock falls 10% | 3,600 | 6,000 | 9,600 | 600 | 2,400 | sell 1,200 |
| 2 | Falls another 10% | 2,160 | 7,200 | 9,360 | 360 | 1,440 | sell 720 |
| 3 | From step 0, stock rises 10% | 4,400 | 6,000 | 10,400 | 1,400 | 5,600 | buy 1,200 |
| 4 | From step 0, instant 25% gap | 3,000 | 6,000 | 9,000 | 0 | 0 | sell 3,000 (V = F exactly) |
| 5 | From step 0, instant 26% gap | 2,960 | 6,000 | 8,960 | 0 | 0 | below the floor by 40 |
Rows 1 and 2 are sequential. Rows 3 to 5 each start from step 0. Step 5 shows the limit: a gap bigger than 1/m falls through the floor. Once V reaches F the vault holds only USDT until the term ends (the "cash lock"). The same table is on /docs/how-it-works.
Protection is paid for in upside. The share of a gain you keep is about 4 x (100 - floor)%. Measured at m = 4 on pooled equities, 6 bps one way, daily rebalance (source: apps/web/app/docs/evidence/page.tsx, from research/m_study/REPORT.md):
| Floor | Share of gain kept in up years | Clear-breach rate |
|---|---|---|
| 95% | about 23% | about 0.3% |
| 90% | about 42% | about 0.4% |
| 85% | about 59% | about 0.5% |
| 80% | about 72% | about 0.5% |
The 42% figure is a pooled mean over up years at a 90% floor, not a typical year. Across 1,581 one-year windows the vault's median year was +1.6% against +13.7% for holding. In a normal year the vault gives most of the return away. That is the price of the floor.
flowchart LR
U[User wallet] -->|approve USDT, createPosition| F[FloorFactory]
F -->|clone| V[FloorVault<br/>one per position]
F -. roles, caps, asset and router lists,<br/>holiday table, halt flag .-> V
K[Keeper EOA] -->|rebalance: one swap per call| V
P[Anyone] -->|rebalancePublic after publicDelay| V
V -->|exactInputSingle, direct router only| R[PancakeSwap v3 router]
R --> L[(USDT / bStock pools)]
V -->|10-minute TWAP read| L
L2[FloorLens] -->|status, scan| V
A[Agents: MCP, API, skill] -->|read, build unsigned txs| L2
A -->|unsigned tx, user signs| U
U -->|requestClose, closeToUSDT, exitInKind| V
Key design points (full design in docs/CONTRACTS.md, off-chain in docs/ARCHITECTURE.md, reconciliation in docs/DECISIONS.md):
Chain events are permanent, so Floor reads them once and keeps them. The server (apps/onebox) runs a Postgres-backed indexer next to the keeper:
packages/db holds three tables: chain_logs (raw factory and vault logs, keyed by tx hash and log index), index_cursor (the last synced block per address) and block_times. Migrations apply on boot.INDEXER_INTERVAL_SEC (default 60 s) the indexer brings the factory and every vault up to the chain head. Each pass reads only the new blocks since the cursor (plus a 12-block reorg margin) through wide-range log RPCs./v1/positions/:vault/activity, /v1/keeper/runs, /v1/positions?owner= and /v1/floor are served from the database. A cold start answers in about a second instead of rescanning history.POST /v1/positions/:vault/refresh and POST /v1/index/refresh sync to head on demand and return { syncedToBlock, newLogs }. The position and keeper pages have a Refresh button that calls them and reloads.eth_getLogs calls./healthz reports indexer: { enabled, lastSyncAt, headLag, lastError }.sequenceDiagram
actor U as User
participant W as Web app or agent
participant F as FloorFactory
participant V as FloorVault clone
participant K as Keeper
U->>W: choose basket, floor 80 to 95%, term 30 to 365 days
W-->>U: floor in USDT, starting split, gap limit, disclosure
U->>F: approve USDT, createPosition
F->>V: deploy clone, move USDT in
loop Mon to Fri, 15:30 to 19:30 UTC
K->>V: rebalance (one swap, re-validated on chain)
end
alt exit any time
U->>V: exitInKind(to)
else close to USDT
U->>V: requestClose
K->>V: rebalance (sell to dust)
U->>V: closeToUSDT
end
sequenceDiagram
participant A as Agent
participant M as Floor MCP server
participant X as b402
participant C as BNB Chain
A->>M: get_floor_info (free)
M-->>A: addresses, bounds, caps, disclosure
A->>M: quote_protection (paid, no payment)
M-->>A: 402 payment required (price, asset, payTo)
A->>A: user's wallet signs the payment
A->>M: retry with payment in _meta
M->>X: verify, then settle
M-->>A: quote and payment response
A->>M: build_create_position_tx (free)
M-->>A: UNSIGNED approve and createPosition
A->>C: user's wallet signs and sends
Solidity 0.8.28, Foundry, EVM cancun, optimizer 200 runs (packages/contracts/foundry.toml). Sources in packages/contracts/src.
| Contract | What it does | Guards |
|---|---|---|
FloorFactory | createPosition clones a vault. Owns roles, asset and router lists, defaults, launch caps (setLimits), pause and halt flags, the NYSE holiday table. | Floor 50 to 98%, term 7 to 400 days (contract bounds). Caps per position and total. Routers added later wait 24 hours (ROUTER_DELAY). Two-step ownership transfer. |
FloorVault (clone) | Holds USDT and bStocks for one position. rebalance (keeper), rebalancePublic (anyone), requestClose, closeToUSDT, exitInKind, rescue (owner of the position). | TWAP price, SwapGuard, trade window, per-asset trade cap, minInterval. exitInKind is always allowed. |
FloorLens | status(vault) and scan(from, to) views. | Read-only. |
CPPIMath | Floor, cushion, target exposure, sell and buy amounts, minOut. | Pure functions, fixed point (WAD). |
TwapOracle | 10-minute Pancake v3 TWAP and spot checks. | Tick deviation bound (maxTickDev 300), history and liquidity checks. |
SwapGuard | Runs the router call and checks balance deltas. | Vault is the only receiver. Spend and receive are checked, minOut is enforced. |
MarketHours | Mon to Fri, 15:30 to 19:30 UTC window plus holiday table. | Guardian-set non-trading days. Holiday table runs through 2028-12-31. |
DefaultsCheck | One set of bounds for the defaults struct. | Used by constructor, setDefaults and deploy preflight. |
Defaults at deploy (packages/contracts/script/params/56.json): sell band 100 bps, buy band 200 bps, minInterval 900 s, publicDelay 3600 s, twapWindow 600 s, maxTickDev 300, tolerance 30 bps (aggregator) and 100 bps (direct), minTrade 20 USDT, dust 1 USDT. Per-asset trade caps: NVDAB 25,000, SPCXB 10,000, QQQB 5,000 USDT.
Exit paths:
exitInKind(to): owner only, any time, no keeper or market hours needed. Sends all USDT first, then each held bStock, skipping a token that fails (for example a paused token). It is never blocked by the guardian or a pause.requestClose, then closeToUSDT: sells to USDT through the normal path, then pays out.rebalancePublic(assetIdx): anyone, no calldata, direct pool only, after publicDelay of open-market time, at 2x the normal band.The keeper (apps/keeper) scans positions through FloorLens and sends one-swap rebalance calls inside the trading window. The vault re-validates everything the keeper chooses: direction, size, router allowlist, minOut, window and minInterval. A keeper cannot withdraw, set prices or pick a recipient. An EOA keeper is primary. A Binance Agentic Wallet as an optional second keeper is not set up.
When the keeper has Binance Web3 keys (BW3_API_KEY, BW3_API_SECRET, the same ones the API uses), it adds three Binance checks (apps/keeper/src/bw3.ts). They can never block a sell, and any Binance failure fails open (the rebalance proceeds as before):
KEEPER_BW3_PRICE_GUARD_BPS, default 500, 0 disables): a rebalance that BUYS a bStock is skipped when the RWA Data API token price and the underlying reference price differ by more than this many bps. Sells are never skipped.KEEPER_BW3_GAS, default on): the Transaction API gas-price is used only when it is within 0.5x to 3x of the RPC's own price; otherwise the RPC price is used.KEEPER_BW3_SHADOW_SIM, default on): the Transaction API simulate runs as a second opinion and is only logged; it does not change what is sent.The keeper still routes through direct PancakeSwap v3 (KEEPER_ROUTE=direct).
pnpm --filter @floor/keeper start once --dry-run # one scan, no transaction sent
One row per Binance module. Status follows the inProduction flag in apps/api/src/binanceStatus.ts, which also feeds GET /v1/binance/status and the /docs/binance page. Client: packages/bw3 (HMAC-signed, schema-checked, per-path call stats in src/stats.ts). Live responses recorded on 2026-10-10 are in packages/bw3/fixtures/live-2026-10-10/.
| Module | What Floor uses it for | Files | Notes |
|---|---|---|---|
RWA Data API rwa/price | Token price against the underlying reference price: shown on /v1/market and the position page, and used by the keeper price guard | packages/bw3/src/client.ts, apps/api/src/app.ts, apps/keeper/src/bw3.ts, apps/web/components/binance/price-check.tsx | binanceStatus.ts market.rwa.price. 2026-10-10: NVDAB token price 230.62 vs reference 230.44 |
RWA Data API rwa/underlying-market | Underlying quote fields on /v1/market. bStocks return null marketStatus and price fields; Floor passes the null through | packages/bw3/src/client.ts, apps/api/src/app.ts | binanceStatus.ts market.rwa.underlying-market; fixtures underlying-market-*.json |
RWA Data API rwa/underlying-profile | Company card on the position page | packages/bw3/src/client.ts, apps/api/src/app.ts, apps/web/components/binance/company-card.tsx | binanceStatus.ts market.rwa.underlying-profile; fixtures underlying-profile-*.json |
RWA Data API rwa/tokens, rwa/search, rwa/platforms | Client methods and recorded fixtures, used by the probe script packages/bw3/scripts/probe-rwa.ts | packages/bw3/src/client.ts | Fixtures tokens-bstock.json, search-*.json, platforms.json |
Market API candles | Price chart on the position page, GET /v1/market/:symbol/candles. The query parameter is bar (not interval), limit max 300 | packages/bw3/src/client.ts, apps/api/src/app.ts, apps/web/components/binance/candles.tsx | binanceStatus.ts market.candles (history on BSC is short, about 120 daily candles at last probe); fixtures candles-*.json |
Transaction API gas-price |
Floor is built to be used by agents as well as people. State-changing tools only return unsigned transactions. Floor never signs and never holds keys.
| Surface | Where | Notes |
|---|---|---|
| MCP server | apps/mcp, POST /mcp (Streamable HTTP, stateless), default port 8788. Hosted: https://floor-server-tfc6.onrender.com/mcp | 9 tools, below |
| REST API | apps/api, default port 8787 | Reads, unsigned tx builders, one paid route |
| Agent skill | skills/floor/SKILL.md | Confirm-then-sign workflow and honest-claim rules |
| Payments | packages/x402 | b402 on eip155:56 (x402 v2 wire format). |
MCP tools (source: apps/mcp/src/tools.ts):
| Tool | Cost | Purpose |
|---|---|---|
get_floor_info | free | Factory, lens, USDT, enabled assets, bounds, caps, state, disclosure |
list_assets | free | Enabled bStocks with pool, fee, price and market status when available |
get_status | free | One position (vault) or all of an owner's (owner): value, floor, cushion, state |
get_rebalance_history | free | Recent keeper runs, optional vault, limit 1 to 100 |
build_create_position_tx | free | Unsigned USDT approve and createPosition |
build_exit_tx | free | One unsigned exit: requestClose, closeToUSDT or exitInKind |
quote_protection | paid | Starting split, floor value, gap tolerance |
backtest | paid | Stored historical results for a basket and mode (no live computation) |
simulate_gap | paid | What an instant fall of X bps does to a new position |
quote_protection, backtest and simulate_gap each cost 0.01 USD per call (default; PRICE_QUOTE_PROTECTION_USD, PRICE_BACKTEST_USD, PRICE_SIMULATE_GAP_USD override it), paid in USD1 or U on BSC. Read the live price from get_floor_info (tools.paid[].priceUsd).
API routes (apps/api/src/app.ts): GET /healthz, /v1/floor, /v1/assets, /v1/market, /v1/positions, /v1/positions/:vault, /v1/keeper/runs, /v1/keeper/runs/:id; POST /v1/tx/create-position, /v1/tx/exit, and the paid POST /v1/paid/quote.
Copy-paste example against a local MCP server (see Quick start):
curl -s http://127.0.0.1:8788/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_floor_info","arguments":{}}}'
# Paid tool: the first call returns a payment-required result. Pay, then retry
# with params._meta["x402/payment"]. depositUsdt is a decimal string.
curl -s http://127.0.0.1:8788/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"simulate_gap","arguments":{"depositUsdt":"1000","floorBps":9000,"termSeconds":31536000,"gapBps":2500}}}'
Paid MCP tools settle over b402, Binance's x402-compatible payment rail on BSC. The b402 client (packages/x402/src/b402.ts) calls Binance's POST https://web3.binance.com/build/api/v2/b402/{supported,verify,settle} with a Binance Web3 API key that has the B402 Payments permission (X402_FACILITATOR=b402). The payer signs an EIP-3009 authorization and Binance sponsors the gas. The payee for paid calls is 0xF5f349ABe9647278AC3450058bc054886DaF816B.
Floor is non-custodial in the sense that no role can move a depositor's funds. It is not trustless. These roles exist, and at launch two of them are one address.
| Role | Address | Can | Cannot |
|---|---|---|---|
| Owner | 0x762c9626711BCc882050cBf06Edd610fE8b91F1A | List assets, add routers (active after 24 hours), set keepers and the guardian, set defaults for new positions, set the launch caps with setLimits (no redeploy, existing positions unaffected), pause, transfer ownership (two-step). | Touch an existing position, move user funds, change a position's parameters. |
| Guardian | Same address as the owner | Pause and halt trading, set non-trading days, remove a router, disable an asset, approve a new bStock implementation. | Move funds. Add a router. |
| Keeper | 0x46FD797AeBD0250A2E768022AD992DF21F12e58a | Trigger rule-valid rebalances, one swap per call. | Withdraw, set prices, choose recipients, use a non-allowlisted router, trade outside the window or faster than minInterval. |
| Position owner (you) | Your wallet | requestClose, closeToUSDT, exitInKind, rescue. | Change floor, term or weights after creation. |
| Anyone | n/a | rebalancePublic, createPosition for self. | n/a |
What this means in practice:
minInterval (design: docs/CONTRACTS.md section 7).exitInKind.exitInKind skips a token that cannot be moved.Layers between a price move and a user's value:
minOut from the TWAP minus tolerance.minInterval, minTrade, per-asset maxTradeValue.exitInKind always callable by the position owner.Reviews. There has been no formal human audit. The contracts were checked with AI-assisted security reviews using the open-source Pashov Audit Group skills. Pashov Audit Group did not review Floor and this is not an endorsement. Process and claim wording: docs/AUDIT.md. Reports and triage: reviews/. Accepted findings and one proposed, unsigned acceptance: reviews/acceptances.md. Tests live in packages/contracts/test (unit, fuzz, invariant, fork).
stateDiagram-v2
[*] --> Active: createPosition
Active --> Active: rebalance (keeper or public)
Active --> CashLocked: value reaches floor, all guards pass
CashLocked --> Closed: exitInKind or closeToUSDT
Active --> Closing: requestClose
Closing --> Closed: closeToUSDT after stock is at dust
Active --> Closed: exitInKind
Closing --> Closed: exitInKind
Closed --> [*]
All numbers below come from the repo. They are past data and do not predict the future. Daily closes only, survivorship bias, no intraday or halt gaps.
Long-history study: 1,581 non-overlapping one-year windows over 38 series, 1928 to 2026, 90% floor, m = 4, 6 bps one way, full overnight and weekend gaps, 0% on stablecoins (research/m_study/REPORT.md, research/m_study2/REPORT.md, table T1).
"Clear breach" means the vault ended more than 1 point below the floor, at a 90% floor:
| Term | Clear-breach rate (95% CI) |
|---|---|
| 1 week | 0.02% (0.01 to 0.04) |
| 2 weeks | 0.04% (0.02 to 0.06) |
| 1 month | 0.09% (0.04 to 0.15) |
| 3 months | 0.20% (0.08 to 0.36) |
| 6 months | 0.25% (0.06 to 0.50) |
| 1 year | 0.44% (0.07 to 0.93) |
Reading the numbers:
apps/web/app/docs/evidence/page.tsx).CONTEXT.md). Never read the floor as "max loss 10%".docs/data/).ops/spikes/RESULTS-taker.md, reviews/claims-01.md). Aggregator quotes on a $10k round trip (QQQB 0.7 bps, NVDAB 5.9, SPCXB 6.0) are a best case, not the live route, because the keeper does not use the aggregator (docs/RESEARCH_RESULTS.md). Weekend cost is not measured.floor/
├── apps/
│ ├── web/ Next.js site, docs pages, app (locked by default, see docs/BRANCHING.md)
│ ├── api/ Hono REST API: reads, unsigned tx builders, paid route
│ ├── mcp/ MCP server: 6 free and 3 paid tools, b402 payment gate
│ ├── keeper/ Rebalance keeper (CLI: once, run)
│ └── onebox/ API, MCP and an in-process keeper loop in one process
├── packages/
│ ├── contracts/ Foundry project: src, script, test, deployments, holidays
│ ├── sdk/ CPPI maths, constants, disclosure strings
│ ├── x402/ x402 gate, self facilitator, b402 client
│ ├── bw3/ Binance Web3 API client
│ └── db/ x402 payment receipt storage
├── local/ One-command local stack on an Anvil fork of BSC
├── skills/floor/ Agent skill
├── docs/ CONTRACTS, ARCHITECTURE, DECISIONS, AUDIT, BRANCHING, backtest data
├── reviews/ AI-assisted review reports, triage, acceptances
├── research/ Backtest studies (m_study, m_study2)
└── ops/ Deploy runbook, ERC-8004 prep, README image briefs
Needs Node 20+, pnpm 10 (packageManager is [email protected]), and Foundry for contracts and the local stack.
pnpm install
pnpm build # pnpm -r build
pnpm typecheck
pnpm test # pnpm -r test (vitest)
Test counts (run 2026-10-10 on dev): TypeScript 686 vitest tests passed, 9 skipped (fork or RPC needed), 0 failed (web 164, sdk 310, keeper 59, api 42, x402 32, bw3 32, mcp 27, onebox 11, local 8, db 1). Contracts: forge test --offline 270 passed, 0 failed, 2 skipped. An earlier run on 2026-10-06 had one invariant failure (invariant_keeperHandlerNoViolations) that did not reproduce on 2026-10-10. Fork tests need a BSC RPC.
Contracts:
cd packages/contracts
forge build
forge test # fork tests need a BSC RPC; see docs/CONTRACTS.md section 12
Run everything locally on an Anvil fork of BSC mainnet (contracts, API, keeper, MCP, web). Nothing touches a real network. Details in local/README.md:
pnpm local:up # about 2 minutes; prints URLs, addresses and flows
pnpm local:status
pnpm local:down
Run single pieces:
NEXT_PUBLIC_APP_LOCKED=0 pnpm --filter web dev # web app; locked without the flag
pnpm --filter @floor/api dev # API on :8787
pnpm --filter @floor/mcp dev # MCP on :8788
Dependencies are locked by pnpm-lock.yaml. Commit it with any dependency change.
Branching: main is what production serves and is locked by default. dev is the integration branch. The app routes are gated by NEXT_PUBLIC_APP_LOCKED; docs stay public. Details in docs/BRANCHING.md.
The mainnet deploy runbook is ops/deploy/runbook.md: pre-flight checklist, one-script deploy, owner post-deploy steps, BscScan verification (with a Sourcify fallback), keepers, rollback. Parameters are in packages/contracts/script/params/56.json. The runbook's AUDITED_COMMIT placeholder is still unfilled because no reviews/audit-pashov-final.md exists. Hosted-service notes: docs/DEPLOY_RENDER.md. ERC-8004 registration is prepared in ops/erc8004/ and has not been broadcast.
Names only. Values are secrets or deployment-specific and never belong in git. The full list with defaults is in ops/deploy/render.env.template.
| Area | Variables |
|---|---|
| Indexer | DATABASE_URL, INDEXER_INTERVAL_SEC |
| Chain | FLOOR_CHAIN_ID, FLOOR_RPC_URL, BSC_RPC_URL, FLOOR_FACTORY, FLOOR_LENS, FLOOR_RUNS_FROM_BLOCK, FLOOR_ASSETS |
| Keeper | KEEPER_ADDRESS, KEEPER_PRIVATE_KEY, KEEPER_ROUTE, KEEPER_INTERVAL_SEC, KEEPER_HEARTBEAT_FILE, ALERT_WEBHOOK_URL, KEEPER_BW3_PRICE_GUARD_BPS, KEEPER_BW3_GAS, KEEPER_BW3_SHADOW_SIM |
| b402 | X402_FACILITATOR (b402), X402_NETWORK, X402_PAYTO, X402_ASSET, X402_ASSET_SYMBOL, X402_ASSET_DECIMALS, SELF_FACILITATOR_KEY |
| Binance Web3 (API, MCP, keeper) | BW3_API_KEY, BW3_API_SECRET (b402 needs a key with the B402 Payments permission) |
| Prices | PRICE_QUOTE_PROTECTION_USD, PRICE_BACKTEST_USD, PRICE_SIMULATE_GAP_USD |
| Servers | MCP_PORT, MCP_PUBLIC_URL, MCP_ALLOWED_ORIGINS, API_BASE_URL, WEB_ORIGIN, TRUST_PROXY |
| Web | NEXT_PUBLIC_APP_LOCKED, APP_LOCKED, NEXT_PUBLIC_SITE_URL |
| Local stack | BSC_FORK_RPC_URL, LOCAL_PORT_OFFSET |
docs/AUDIT.md and reviews/. Launch caps are small for that reason.Full list: apps/web/app/docs/risks/page.tsx.
Only items that appear in the docs:
settle live (ops/b402/settle-once.ts, human-run, not executed).CONTEXT.md).Built for BNB Hack: Tokenized Stocks Edition. Tracks and fit, from CONTEXT.md: bStocks (NVDAB, SPCXB, QQQB) are central, spot only, BSC mainnet deployment, execution on PancakeSwap v3.
What we did not do:
settle run live.Work on dev, promote to main by fast-forward after the gates in docs/BRANCHING.md: pnpm -r typecheck, pnpm -r lint, pnpm -r test, pnpm --filter web build. Any change under packages/contracts/src or packages/contracts/script after an audit run voids that run (docs/AUDIT.md). Never commit secrets or .env files.
No LICENSE file exists in this repository, so no license is granted. Individual Solidity files carry SPDX-License-Identifier: MIT headers. The owner needs to decide and add a license file.
Floor is experimental software. It is not financial advice, not insurance and not a guarantee of any outcome. Backtests use past prices and do not predict the future. You can lose money, including below your chosen floor. Tokenized stocks carry issuer, regulatory, liquidity and smart-contract risk. Read apps/web/app/docs/risks/page.tsx and the contracts before depositing, and only deposit what you can afford to lose.
dx/human/ (written by the team).dev, run by the team): bw3 32, api 42, web 164, keeper 59 (+5 fork tests skipped), onebox 11, mcp 27 (+2 skipped), sdk 310, x402 32 (+2 skipped); forge test --offline 270 passed, 0 failed, 2 skipped.| Keeper gas price, with a 0.5x to 3x sanity band against the RPC price and an RPC fallback |
apps/keeper/src/bw3.ts |
binanceStatus.ts tx.gas-price; keeper tests. The API process cannot see keeper call counts |
Transaction API simulate | Log-only shadow check of each rebalance transaction | apps/keeper/src/bw3.ts | binanceStatus.ts tx.simulate |
Trading API quote / swap | Aggregator quotes in the builder; the swap route is available to the keeper | apps/keeper/src/agg.ts, packages/bw3/src/client.ts | binanceStatus.ts trading.quote. Rebalances run on PancakeSwap v3 (KEEPER_ROUTE=direct) |
| b402 Payments | Payment rail for paid MCP tools (supported, verify, settle). Paid MCP tools settle over b402 | packages/x402/src/b402.ts, ops/b402/settle-once.ts | binanceStatus.ts b402: X402_FACILITATOR=b402 |
| Agentic Wallet / Wallet Skills | Floor supports signing with Binance Agentic Wallet: Developer Mode contract calls plus MCP bawCommands in build_create_position_tx and build_exit_tx | skills/floor/references/agentic-wallet.md, apps/mcp/src/tools.ts, apps/web/components/app/agent-run.tsx | Runs on the user's or agent's own wallet; Floor holds no Agentic Wallet |
| BNB Agent Studio | No listing; the Agent Studio side prize is skipped (docs/DECISIONS.md decision 7) | none | ERC-8004 registration JSON is "active": false (apps/web/public/.well-known/agent-registration.json), prepared, not broadcast |