Skip to content

Add a chain

There are two very different jobs here. Pick the right one first.

GoalWorkEffort
Bridge INTO onlyDatabase seed + cache bustMinutes
Full swap + bridgeContract deploy + SDK release + two consumer bumpsHours
SEO landing pagesOne data-module edit + rebuildMinutes

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 mychain

Check 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.js

signer 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.tsfour 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.0

6. 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.json

Both 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-builds

Automation 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 mychain

Then:

  1. Add the address to AUTOPILOT_ADDRESSES in bz-backend/utils/constants.js and defi-dex/src/constants/index.ts (env-overridable).
  2. 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 completes

Full 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 completes

Deliberate non-deployments

Not gaps:

ChainReason
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.

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.