Skip to content

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's VAULT_PAYOUT carry, 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 saysDo
Nothing, submitted, attempts climbingThe 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, pendingIt 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 undoneNormal: 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 YA 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 failedFive 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:

  1. Make the new key (ideally in a remote signer). Fund it with BNB.
  2. From the Safe: CTFExchange.addOperator(new), then JuaVault.setOperator(new) (the vault's operator must be the exchange's).
  3. Update EXCHANGE_OPERATOR_KEY (or the signer variables) and run a settings-only deploy. In-flight submitted settlements from the old key still confirm by their receipts.
  4. 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.

ThingHowWhen
The databaseSupabase'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 signersEach 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 vaultAt every signer change
Secrets and variablesAn 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 issuedAt every change
The contracts' addresses and blocksEXCHANGE_*, TON_EXCHANGE_* and the fee vaults, with deployment blocks and logical timesAt deployment
The repositoryGitHub; 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 audit in 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.conf and the firewall (Behind Cloudflare).
  • Each release: bun run verify locally before pushing to prod; read the deploy's log; the post-deploy checks in Monitoring.

Jua Markets · Africa's prediction market.