This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pnpm dev # Start Next.js dev server (apps/app)
pnpm build # Build SDK packages first, then the app
pnpm build:sdk # Build @train-protocol/sdk only
pnpm build:packages # Build all packages
pnpm --filter train-app lint # ESLint (next lint on apps/app)
# SDK development
pnpm --filter @train-protocol/sdk dev # Watch mode for SDK
pnpm --filter @train-protocol/sdk check:types # Type-check SDKTests use vitest (pnpm test in each package). Node.js >=20.9.0 required. Package manager: pnpm 10.20.0.
Monorepo (pnpm workspaces):
apps/app— Next.js 15 frontend (Pages Router, not App Router)packages/sdk—@train-protocol/sdk: core HTLC protocol logic, API client, lock verificationpackages/blockchains/— chain-specific HTLC client implementations (evm,solana,starknet,tron,aztec)
What the app does: Cross-chain atomic swaps using HTLC (Hash Time-Locked Contracts). Users lock funds on a source chain, a solver locks on the destination chain, then secrets are revealed to complete the swap. EVM is the primary chain; Solana, Starknet, TON, Aztec support is in progress.
- Zustand stores (
apps/app/stores/):swapStore(main swap state),secretDerivationStore,balanceStore,walletStore,rpcConfigStore, etc. - React Context (
apps/app/context/):atomicContext(HTLC contract interactions),secretDerivationContext,swapAccounts(wallet/account handling),formWizardProvider(multi-step forms),evmConnectorsContext
apps/app/lib/trainApiClient.ts is a thin wrapper delegating to @train-protocol/sdk's TrainApiClient. Uses SSE for streaming:
GET /api/v1/quote/stream— quote streaming (events:quote,done)GET /api/v1/orders/{hashlock}/stream?solverAddress=0x...— order status streamingPOST /api/v1/orders/{hashlock}/reveal-secret?solverAddress=0x...— reveal secret to solverGET /api/v1/networks— network/token metadatasourceNetworkparam must be a CAIP-2 ID (e.g."eip155:11155111")
userLock()— user locks funds with hashlock on source chain (single-step, no separate commit)- Poll
getSolverLock— wait for solver to lock on destination chain - Reveal secret via API (
RevealSecret) — solver claims on destination then source - Swap complete when solver redeems
Key files:
apps/app/lib/abis/atomic/EVM_HTLC.json— unified EVM ABIapps/app/lib/htlc/— HTLC client creationpackages/blockchains/{evm,solana,starknet,tron,aztec}/src/client/— chain-specific HTLC implementations (PublicClient + WalletClient)apps/app/lib/wallets/utils/atomicTypes.ts— chain-specific wallet/atomic interfaces
apps/app/lib/rpc/— RPC resolution:nodeResolver.ts(entry point),evmNodes.ts(static chainlist data fromdata/chainlistRpcs.json),nonEvmNodes.ts(static registry)resolveNodes(caip2Id)returns all available RPCs (existing nodes first, then dynamic/static). Called server-side ingetSettings.tsrpcConfigStoremanages user custom RPC overrides;getEffectiveRpcUrls(network)returns custom URLs ornetwork.nodes- Consensus verification:
getSolverLockDetailsWithConsensus()in SDK queries nodes in batches ofbatchSize(default 3), retries with next batch if quorum (minQuorum, default 2) not met ConsensusOptions:{ minQuorum?: number, batchSize?: number }— configurable per-call or via subclass defaults- Consensus runs once on first solver lock detection (tracked by
consensusVerifiedref inuseSolverLockPolling), then falls back to single-node polling
- Secret derived from:
deriveInitialKey()+deriveSecretFromTimelock(key, nonce) - Nonce =
Date.now()timestamp, stored in URL query params (for page refresh recovery) and on-chain viauserDatabytes field
- EVM: wagmi 2.x + viem 2.x
- Starknet: @starknet-react 5.x + starknet.js
- Solana: @solana/web3.js + wallet-adapter
- TON: @ton/ton + @tonconnect/ui-react
- Code uses legacy "commit" naming (
CommitStatus,commitId) — these refer to locks, not a separate commit step hashlock === commitId— always present from lock creation, never null- Use
statusfield (notclaimed) for lock state:Pending(0),Redeemed(1),Refunded(2)— there is noCompleted(3) solverin swapStore is a solverId string (e.g."plorex"), not a wallet address- For destination chain polling, use
getSolverLock(notgetUserLock) - Contract functions:
userLock/redeemUser/refundUser/getUserLock+solverLock/redeemSolver/getSolverLock
NEXT_PUBLIC_TRAIN_API # Station API base URL
NEXT_PUBLIC_API_VERSION # "sandbox" or "mainnet"
NEXT_PUBLIC_ALCHEMY_KEY # For light client RPC calls
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID # WalletConnect