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