Theme
Recurring tasks
What someone running the platform does on a schedule: what, when, how, and the exact command or screen.
Sweeping Jua's fee share
What. Every fee reaches a fee vault (JuaVault on BNB Smart Chain, JuaTonVault on TON). Referrers collect their commissions out of it; the rest is Jua's. The owner multisig sweeps Jua's share to the treasury, leaving what referrers can still collect.
When. Monthly, or when a vault's balance grows well past what is owed. Never below what is owed: collections then fail with "Commissions cannot be paid out right now: try again later".
How much. Owed to referrers on a chain = commissions on settled or settling trades on that chain, minus what has been collected or is on its way:
sql
-- Replace 'evm' with 'ton' for the TON vault. Micro-dollars.
with earned as (
select coalesce(sum(ac.amount_micro), 0) as micro
from affiliate_commissions ac
join trades t on t.id = ac.trade_id
join settlements s on s.id = t.settlement_id
where s.venue = 'evm' and s.status in ('confirmed', 'pending', 'submitted')
), paid as (
select coalesce(sum(amount_micro), 0) as micro
from commission_payouts
where venue = 'evm' and status in ('confirmed', 'pending', 'submitted')
)
select earned.micro - paid.micro as owed_micro from earned, paid;Sweepable = the vault's balance − owed − a margin (a day of commissions). Read the balance on the explorer.
How.
- BNB Smart Chain: from the Safe,
JuaVault.sweep(to, amount)(amount in the token's units: 18 decimals for BNB Smart Chain's USDT). The Safe is the vault's owner; the operator cannot sweep. - TON: from the owner multisig, a
Sweep { queryId, amount, to }message to JuaTonVault (amount in USDT units, 6 decimals), with enough TON attached for the jetton transfer (the vault'sVAULT_PAYOUTcarry, 0.15 TON, is a safe guide).
Record the sweep (date, amount, transaction) in the team's finance log; the vault model's crypto.reserveMicro declaration does not apply to the fee vaults.
Topping up operator gas
What. The operators pay the network for every transaction they send; without gas, nothing settles.
When. EVM operator below 0.05 BNB (keep 0.1 or more); TON operator wallet below a few TON above its 1 TON reserve. Check weekly, or automate (Monitoring).
How. Send BNB to the EVM operator's address (EXCHANGE_OPERATOR_ADDRESS, or the address of EXCHANGE_OPERATOR_KEY) from the treasury; send TON to the TON operator wallet's address (the short-of-TON alert names it; or derive it from the seed and wallet kind). Both are plain transfers from any wallet. Also the TON market maker's wallet, if it runs, and the vault model's operator, if the vault still runs.
The TON exchange contract itself also needs TON for the messages it sends; the operator's matches carry it (0.3 TON each, mostly returned), and anyone can top the contract up with a plain TON transfer.
Checking stuck settlements
What. A match that has not reached the chain, or not confirmed, for more than a few minutes.
When. Daily; at once when customers report trades stuck pending.
How. Run the first query in Monitoring. Then, per cause:
last_error says | Do |
|---|---|
Nothing, submitted, attempts climbing | The transaction is not being mined: the operator's BNB (above), the gas price, or an RPC that does not propagate. The worker rebroadcasts by itself; after 10 minutes with the nonce used elsewhere it undoes the match |
A simulation refusal, pending | It waits for an earlier match of the market in flight. If none is, it would have been undone: look at the earlier match |
| waiting for a later match on the chain before it is undone | Normal: the chain decides the later one first |
| A node error (nonce, gas, connection) | The RPCs (Incident runbooks) |
chain_results: CTF holds X, Jua's books Y | A result on chain disagrees with Jua's: find out which is right (the audit log, the market's rules). The chain is final; correct Jua's books by hand |
chain_markets failed | Five prepares failed: the operator's gas or the RPC. Fix it, then set the row back to pending (update chain_markets set status = 'pending', attempts = 0 where market_id = …) |
Checking unconfirmed TON payouts
What. A TON commission payout submitted for over an hour: the vault's payout state is sent but the referrer's wallet neither took it nor returned it (a frozen wallet does that silently).
When. When the alert "A referral commission payout on TON is not confirmed" appears, or daily from the query.
How. Look up the referrer's wallet on a TON explorer. Delivered: update commission_payouts set status = 'confirmed', settled_at = now() where id = …. Not delivered and never will be: the USDT is back in the vault's jetton wallet; set the row failed with a note, and the referrer can collect again. In doubt, leave it: the worker keeps checking.
Rotating keys
What. Replace a hot key before it is compromised, and at once if it is.
When. On a schedule (yearly), whenever someone with access leaves, and in an incident (A stolen operator key).
How, the EVM operator:
- Make the new key (ideally in a remote signer). Fund it with BNB.
- From the Safe:
CTFExchange.addOperator(new), thenJuaVault.setOperator(new)(the vault's operator must be the exchange's). - Update
EXCHANGE_OPERATOR_KEY(or the signer variables) and run a settings-only deploy. In-flightsubmittedsettlements from the old key still confirm by their receipts. - From the Safe:
CTFExchange.removeOperator(old).
The TON operator: make a new wallet and fund it; from the owner multisig SetOperator on JuaTonExchange and on JuaTonVault; update TON_EXCHANGE_OPERATOR_KEY and TON_EXCHANGE_OPERATOR_WALLET; settings-only deploy.
The resolvers: a new EVM resolver can report only conditions prepared with it as oracle (the oracle is fixed per condition), so markets already open keep the old resolver until they resolve: keep the old key available until then, or report their results from a resolver Safe. On TON, SetResolver from the owner applies to every market.
Everything else (database password, service-role key, RPC keys, indexer keys, CRON_SECRET, webhook secrets, the Telegram token): Keys and roles.
Backups
What. The database is the books; the keys are the money; the configuration is the deployment.
| Thing | How | When |
|---|---|---|
| The database | Supabase's daily backups, and point-in-time recovery switched on for real money. Test a restore into a scratch project (bun run db:status against it) | PITR always; a restore test quarterly |
| The Safe and multisig signers | Each signer's hardware wallet seed in their own custody; the list of signers and thresholds, and the deployer keys with their nonces, in the team's vault | At every signer change |
| Secrets and variables | An inventory of every GitHub Actions secret and variable (names and where each value comes from, never the values) in the team's vault; the values themselves live where they were issued | At every change |
| The contracts' addresses and blocks | EXCHANGE_*, TON_EXCHANGE_* and the fee vaults, with deployment blocks and logical times | At deployment |
| The repository | GitHub; a mirror if the team wants one |
The server itself needs no backup: everything on it comes from the deploy (/opt/jua), and nothing is edited by hand.
Other periodic checks
- Weekly: read the Audit log and the Analytics page; look at the dashboard's queues; make sure the dead man's switch is still armed.
- Monthly:
certbot renew --dry-run;bun auditin a checkout; the RPC and indexer plans' usage; Telegram's guidelines for Mini Apps (they change); Cloudflare's address ranges against/etc/nginx/cloudflare-real-ip.confand the firewall (Behind Cloudflare). - Each release:
bun run verifylocally before pushing toprod; read the deploy's log; the post-deploy checks in Monitoring.