Appearance
Add a chain
There are two very different jobs here. Pick the right one first.
| Goal | Work | Effort |
|---|---|---|
| Bridge INTO only | Database seed + cache bust | Minutes |
| Full swap + bridge | Contract deploy + SDK release + two consumer bumps | Hours |
| SEO landing pages | One data-module edit + rebuild | Minutes |
Destination-only: bridge INTO a chain
A chain can be a bridge destination without any Blazpay contract. Bridging in only needs an address on the far side.
1. Seed the Network record
js
{
id: 1234, // the EVM chain id
chainType: 'evm',
name: 'MyChain',
key: 'mychain',
coin: 'MYC',
logoURI: '…',
mainnet: true,
metamask: {
rpcUrls: ['https://rpc.mychain.xyz'],
chainName: 'MyChain',
nativeCurrency: { name: 'MyCoin', symbol: 'MYC', decimals: 18 },
blockExplorerUrls: ['https://explorer.mychain.xyz'],
},
nativeToken: { … },
order: 50,
aggregators: ['lifi', 'relay', 'across'], // providers that support it
show: true,
}metamask.rpcUrls[0] is a single point of failure
The backend uses it for EIP-712 signing, the fee summary and all vault interaction. A dead RPC here takes the chain down with no useful error message. Verify it responds before seeding.
2. Seed at least one Token record
Pull chain and token data from LiFi — that is what was done for Robinhood Chain.
js
{
address: '0x…',
chainId: 1234,
symbol: 'USDC',
decimals: 6,
name: 'USD Coin',
logoURI: '…',
coingeckoId: 'usd-coin', // needed for the fee summary
cmcId: 3408, // needed for Autopilot prices
order: 10, // ← drives picker visibility
}order decides whether users can see it
The picker slices the top 200 by order. A token seeded with no order may be invisible. See token curation.
3. Bust the production cache
Token and network lists are cached. bz-backend/scripts/clearTokenCache.js exists for this.
Result
Users can bridge into the chain. They cannot swap on it or bridge out, because assertRelayerDeployed(1234) throws — which is the correct, safe failure.
Full support: swap and bridge out
1. Deploy BlazpayRelayer
bash
cd bz-backend/contract-deployment
# add the network to hardhat.config.js first
npx hardhat run scripts/deployBlazpayRelayer.js --network mychainCheck which signer a hardhat network uses
hardhat.config.js defines 26 networks. Most sign with BACKEND_PRIVATE_KEY; arbitrum_dca_owner is a second Arbitrum entry that signs with DCA_OWNER_KEY instead, because the DCA vault's owner is the executor wallet rather than the deployer. Pick the right one — an onlyOwner call from the wrong signer just reverts.
There is also scripts/deployAllRelayers.js for batch deploys, and RELAYER_DEPLOYMENT.md in bz-backend/scripts/ documenting the process.
The deploy records into contract-deployment/scripts/blazpayRelayer.deployments.json.
The proxy address may match other chains — that is expected
Twelve chains share 0x7d98E5… because the same TX_SIGNER EOA deployed at the same nonce, and deterministic CREATE produces the same address. Abstract differs (zkSync-stack CREATE), Unichain differs (nonce had drifted).
2. Configure the contract
bash
# enable fees at 0.10%
node scripts/enableRelayerFees.js # sets inPercentFee = 10
# verify
node scripts/checkRelayerDeployed.js
node scripts/check-contract-owner.jssigner must be TX_SIGNER (0x75a8b522FC3195e3a3570F11f111AC89c0D35975) — that is what the relayer verifies against.
3. Verify on the explorer
Use the chain's explorer API key. ETHERSCAN_API_KEY (v2 unified) covers the major chains; others need their own.
4. Add to swap-sdk
src/utils/constants.ts — four edits:
ts
export enum ChainName { … MYCHAIN = `mychain`, }
export enum ChainId { … MYCHAIN = 1234, }
// getChainNameById's mapping object
[ChainId.MYCHAIN]: ChainName.MYCHAIN,
const RELAYER_ADDRESSES: Record<number, string> = {
…
1234: '0x…', // MyChain
}
export const RELAYER_DEPLOYED_CHAINS: ReadonlySet<number> = new Set([
…
1234, // MyChain
])Both RELAYER_ADDRESSES and RELAYER_DEPLOYED_CHAINS
assertRelayerDeployed reads the Set. Adding only the address map lets a swap proceed on a chain nobody verified. Adding only the Set makes relayerAddresses throw.
Hemi (43111) is the live example of a partial edit: it is in both relayer structures but missing from ChainName and ChainId, so getChainNameById(43111) returns undefined.
5. Release the SDK
bash
cd swap-sdk
npm run build
git tag v1.13.0 && git push origin v1.13.06. Bump both consumers
bash
# in bz-backend AND in defi-dex
npm install github:blazpay/swap-sdk#v1.13.0 --package-lock-only
# commit BOTH package.json and package-lock.jsonBoth files, both repos
package-lock.json pins by resolved commit and npm install obeys it even after rm -rf node_modules/swap-sdk. This caused a production deploy to install 1.9.1 despite a v1.12.0 pin.
7. Seed the database
Same Network and Token records as the destination-only path. scripts/enableChain.js exists for the toggle.
8. Deploy
bash
cd bz-backend && ./scripts/deploy.gcp.sh deploy bz-backend
cd defi-dex && git push origin optimized # Netlify auto-buildsAutomation support (optional, and separate)
CSIP and Autopilot are per-chain contract deployments, not SDK entries.
CSIP
Arbitrum only today. Adding a chain means deploying DCAInvestmentV2, wiring DCA_ADDRESS (currently a single constant in utils/constants.js, so this needs a per-chain map first), and refreshing both ABIs.
Autopilot
bash
cd bz-backend/contract-deployment
npx hardhat run scripts/deploy-autopilot.cjs --network mychainThen:
- Add the address to
AUTOPILOT_ADDRESSESinbz-backend/utils/constants.jsanddefi-dex/src/constants/index.ts(env-overridable). - Probe and allow-list routers:
bash
node scripts/probe-autopilot-routers.cjs # find routers with in-tx output
node scripts/seed-autopilot-routers.cjs # setRouters([...], true)Until routers are allow-listed, no Autopilot trade executes
The vault is fail-safe. This is the current state on Robinhood Chain (4663). Many aggregators on new chains are solver/intent based and deliver no in-transaction output — the probe script exists specifically to find the ones that do.
SEO landing pages
One file: defi-dex/src/data/chainSeo.mjs.
js
export const CHAINS = [
…
{
slug: 'mychain',
name: 'MyChain',
chainId: 1234,
native: { symbol: 'MYC', name: 'MyCoin' },
category: 'emerging', // drives hub grouping
since: '2026',
explorer: 'https://explorer.mychain.xyz',
popularTokens: ['MYC', 'USDC', 'USDT'],
narrative: 'MyChain is … Liquidity is young and fragmented, so best-price routing matters more here than on established chains.',
extraKeywords: ['mychain dex aggregator', 'swap on mychain', …],
extraFaqs: [
{ q: 'What is MyChain?', a: 'MyChain is …' }, // the definition FAQ
…
],
},
]Then npm run build — the prerender emits /swap/mychain/ and /bridge/mychain/, updates the sitemap and llms.txt, and generates OG images.
Write a real narrative
It is the only unique content on the page, and it is what makes it rank rather than reading as a template. The existing entries are hand-written; match that.
Bump SITE.updated when you touch copy
It feeds dateModified in the page's JSON-LD. A stale stamp tells crawlers the content is old.
Non-EVM chains resolve by name in chainPreselect.ts because the app uses synthetic ids (Solana 102, TRON 728126428).
Verification checklist
Destination-only
□ Network record seeded, show: true
□ RPC in metamask.rpcUrls[0] responds
□ At least one Token seeded with a sensible `order`
□ Cache busted
□ The chain appears as a bridge destination in the UI
□ A bridge INTO it completesFull support
□ Relayer deployed and verified
□ signer == TX_SIGNER
□ inPercentFee == 10, enableFees == true
□ Address in RELAYER_ADDRESSES
□ chainId in RELAYER_DEPLOYED_CHAINS
□ ChainName + ChainId enum entries + getChainNameById mapping
□ SDK tagged and pushed
□ BOTH consumers bumped (package.json + package-lock.json)
□ Deployed to the VM and Netlify
□ A real swap on the chain completes
□ Fee arrives in the relayer (check /defi/fees/summary?force=1)
□ A bridge out of the chain completesDeliberate non-deployments
Not gaps:
| Chain | Reason |
|---|---|
| Ethereum mainnet (1) | Gas cost makes small trades uneconomic; no bridged liquidity. It still has landing pages, deliberately — they rank for Ethereum swap intent and route users to an economic chain |
| Etherlink (42793) | Intentionally skipped |
Both are commented in RELAYER_ADDRESSES rather than omitted silently. Keep that convention.