Operations

Production runs on Vercel (project stonkbag, named before the app became nearbags) with a Neon Postgres database.

Settings

Set in Vercel → Project → Settings → Environment Variables. Never commit them. Add values without a trailing newline:

printf %s "$VALUE" | vercel env add NAME production
  • DATABASE_URL
    Needed for
    Everything
    Notes
    Postgres connection string
  • SESSION_SECRET
    Needed for
    Sign-in, share links
    Notes
    Random 64 characters. Changing it signs everyone out and breaks old share links.
  • CRON_SECRET
    Needed for
    Scheduled jobs
    Notes
    Vercel sends it to the cron routes
  • ONECLICK_JWT
    Needed for
    Buying and selling
    Notes
    1Click partner key. Without it, 1Click charges 0.2%.
  • RELAYER_ACCOUNT_ID, RELAYER_PRIVATE_KEY
    Needed for
    Buying and selling
    Notes
    NEAR account that pays gas
  • PLATFORM_FEE_ACCOUNT
    Needed for
    Platform fee
    Notes
    NEAR account. Without it, no platform fee is charged.
  • PLATFORM_FEE_BPS
    Needed for
    Platform fee
    Notes
    Default 25 (0.25%)
  • AGENT_PLATFORM_FEE_BPS
    Needed for
    Platform fee on agent baskets
    Notes
    Default 50 (0.5%). Agent swaps also carry a 0.1% app fee to PLATFORM_FEE_ACCOUNT.
  • ADMIN_WALLETS
    Needed for
    /admin
    Notes
    Comma-separated NEAR accounts or EVM addresses
  • TWELVEDATA_API_KEY
    Needed for
    Stock prices
    Notes
    Third stock price source
  • NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID
    Needed for
    WalletConnect
    Notes
    From cloud.reown.com
  • EXPLORER_JWT
    Needed for
    pnpm thesis only
    Notes
    NEAR Intents Explorer key
  • FINNHUB_API_KEY, NEAR_RPC_URL
    Needed for
    Optional
    Notes
    Backup prices, custom RPC
  • NEXT_PUBLIC_SITE_URL
    Needed for
    Share links and cards
    Notes
    The public address, e.g. https://nearbags.com. Without it, Vercel's production URL.
NameNeeded forNotes
DATABASE_URLEverythingPostgres connection string
SESSION_SECRETSign-in, share linksRandom 64 characters. Changing it signs everyone out and breaks old share links.
CRON_SECRETScheduled jobsVercel sends it to the cron routes
ONECLICK_JWTBuying and selling1Click partner key. Without it, 1Click charges 0.2%.
RELAYER_ACCOUNT_ID, RELAYER_PRIVATE_KEYBuying and sellingNEAR account that pays gas
PLATFORM_FEE_ACCOUNTPlatform feeNEAR account. Without it, no platform fee is charged.
PLATFORM_FEE_BPSPlatform feeDefault 25 (0.25%)
AGENT_PLATFORM_FEE_BPSPlatform fee on agent basketsDefault 50 (0.5%). Agent swaps also carry a 0.1% app fee to PLATFORM_FEE_ACCOUNT.
ADMIN_WALLETS/adminComma-separated NEAR accounts or EVM addresses
TWELVEDATA_API_KEYStock pricesThird stock price source
NEXT_PUBLIC_WALLETCONNECT_PROJECT_IDWalletConnectFrom cloud.reown.com
EXPLORER_JWTpnpm thesis onlyNEAR Intents Explorer key
FINNHUB_API_KEY, NEAR_RPC_URLOptionalBackup prices, custom RPC
NEXT_PUBLIC_SITE_URLShare links and cardsThe public address, e.g. https://nearbags.com. Without it, Vercel's production URL.

Scheduled jobs

  • /api/cron/prices
    When (UTC)
    22:30 daily
    Does
    Adds the latest daily prices and dividends
  • /api/cron/snapshots
    When (UTC)
    23:00 daily
    Does
    Records each bag's size and holder count
JobWhen (UTC)Does
/api/cron/prices22:30 dailyAdds the latest daily prices and dividends
/api/cron/snapshots23:00 dailyRecords each bag's size and holder count

Both answer 401 unless the request carries Authorization: Bearer $CRON_SECRET. Run one by hand:

curl -H "Authorization: Bearer $CRON_SECRET" https://nearbags.com/api/cron/snapshots

Admin

Open /admin signed in with a wallet listed in ADMIN_WALLETS.

ControlEffect
Pause buysStops every new buy. Sells and withdrawals continue.
Delist assetStops new buys of bags holding that asset
Hide bagRemoves a bag from public pages. Holders can still sell and withdraw.

Every action is written to audit_log.

Local setup

pnpm install
cp .env.example .env.local   # DATABASE_URL, SESSION_SECRET and CRON_SECRET are enough to browse
pnpm db:migrate
pnpm prices:ingest           # about 2 minutes
pnpm db:seed
pnpm dev
CommandDoes
pnpm testUnit tests
pnpm lintLint
pnpm prices:ingestFills or tops up prices. Safe to rerun.
pnpm claim-seedsGives the seed bags to their creator account
pnpm thesisRebuilds THESIS.md and /thesis from the Explorer API
pnpm tsx scripts/verify-keys-mainnet.tsChecks key and recovery logic against mainnet, read-only

Point .env.local at the production database to run a script against production.

Keep local baskets apart from production. A basket's account comes from the wallet and the basket's id, so a local basket with the same id as a production one is the same account. Start local ids far above production's:

psql "$DATABASE_URL" -c "select setval('baskets_id_seq', 99999, true)"

Meme coin (Rhea) checks

Read-only, any time: pnpm tsx --env-file=.env.local --conditions=react-server scripts/probes/rhea-check.ts 20 prints each meme coin's price sources, HOOT's contract check, both-way availability and a dry $20 preflight of a 50/50 bag.

Live test, about $2, signed by you (the script never sends without --send):

  1. T="pnpm tsx --env-file=.env.local --conditions=react-server scripts/probes/rhea-live-test.ts", then $T init. It makes a throwaway test account (key in .rhea-test-key.json, gitignored) and prints the command to fund it with 2 USDC.
  2. Fund it, then $T status: USDC 2.
  3. $T buy HOOT 1: dry run (quote, allowlist, simulate). Then $T buy HOOT 1 --send. Expect settlement: success and HOOT in status. Open the tx: the mt_mint of HOOT goes to the test account.
  4. $T buy NEARLY 0.9 --send, the same for NEARLY.
  5. $T sell HOOT --send and $T sell NEARLY --send: USDC comes back, about 2% less than went in per round trip.
  6. $T withdraw <you.near> --send: the USDC goes back to your wallet. Delete .rhea-test-key.json once status shows zero.

Then once through the app (minimum $20): buy the local NEAR Memes bag, sell 100%, and check Portfolio and History.

When something breaks

  • Buys stop with "Relayer is not configured"
    Check
    RELAYER_* variables
    Fix
    Set them and redeploy
  • Buys fail with a gas error
    Check
    The relayer's NEAR balance on nearblocks.io
    Fix
    Send it NEAR
  • Bag stats stop changing
    Check
    Vercel logs for /api/cron/prices
    Fix
    A 401 means CRON_SECRET doesn't match. Re-add it without a newline. Then run pnpm prices:ingest.
  • One price source fails
    Check
    The errors field the price job returns
    Fix
    Other sources still give a median. Fix the key or wait.
  • A leg is stuck
    Check
    The order on /portfolio, order_legs.status
    Fix
    The browser retries legs left quoted for over a minute. Reopen the order page.
  • Buys look risky (bad prices, outage)
    Check
    Fix
    Pause buys in /admin. Holders can still sell.
SymptomCheckFix
Buys stop with "Relayer is not configured"RELAYER_* variablesSet them and redeploy
Buys fail with a gas errorThe relayer's NEAR balance on nearblocks.ioSend it NEAR
Bag stats stop changingVercel logs for /api/cron/pricesA 401 means CRON_SECRET doesn't match. Re-add it without a newline. Then run pnpm prices:ingest.
One price source failsThe errors field the price job returnsOther sources still give a median. Fix the key or wait.
A leg is stuckThe order on /portfolio, order_legs.statusThe browser retries legs left quoted for over a minute. Reopen the order page.
Buys look risky (bad prices, outage)Pause buys in /admin. Holders can still sell.