Skip to main content

Webapp Configuration

The Specter webapp is configured through a central config.js file that defines chain parameters, contract addresses, token registrations, relayer endpoints, bridge settings, and ABI definitions. This page documents every section of the configuration.

File Structure

config.js
├── chainConfig — Network and RPC settings
├── contractAddresses — Deployed contract addresses
├── tokenConfigs — Token metadata and tokenIdHash values
├── relayerConfig — Relayer API endpoints
├── bridgeConfig — Hyperlane bridge settings
└── abis — Contract ABI definitions

Chain Configuration

export const chainConfig = {
chainId: 5446,
chainIdHex: "0x1546",
chainName: "Specter Testnet",
nativeCurrency: {
name: "GHOST",
symbol: "GHOST",
decimals: 18,
},
rpcUrls: [
"https://testnet.specterchain.com",
"https://testnet.umbraline.com", // Legacy alias — both resolve to the same node
],
blockExplorerUrls: [],
};
FieldValueNotes
chainId5446Decimal. Must match MetaMask configuration.
chainIdHex"0x1546"Hex encoding of 5446. Used by wallet_addEthereumChain.
rpcUrlstestnet.specterchain.comPrimary RPC. The umbraline.com alias also works.
nativeCurrency.decimals181 GHOST = 10^18 aghost.
caution

Do not use Chain ID 47474. That was the old Avalanche L1 identifier and is no longer valid. Using it will cause balance queries to return 0 and transactions to fail.

Contract Addresses

export const contractAddresses = {
// Core
COMMIT_REVEAL_VAULT_ADDRESS: "0x908aA11Dc9F2e2C3F69892acaDE112e831c0a14a",
COMMITMENT_TREE_ADDRESS: "0xE29DD14998f6FE8e7862571c883090d14FE29475",
GHOST_REDEMPTION_VERIFIER_ADDRESS: "0xc0A9BcF60A6E4Aabf5Dd3e195b99DE2b9fac3Dee",
NULLIFIER_REGISTRY_ADDRESS: "0xaadb9c3394835B450023daA91Ad5a46beA6e43a1",
NATIVE_ASSET_HANDLER_ADDRESS: "0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3",
ASSET_GUARD_ADDRESS: "0x12d5a4d9Db0607312Fc8F8eE51FDf18D40794aD1",
GHOST_ERC20_FACTORY_ADDRESS: "0xE842ffe639a770a162e0b7EB9f274E49aCA8Fb95",

// Policies
POLICY_REGISTRY_ADDRESS: "0x2DC1641d5A32D6788264690D42710edC843Cb1db",
TIMELOCK_EXPIRY_ADDRESS: "0xd84D534E94f1eacE9BC5e9Bd90338d574d02B95c",
DESTINATION_RESTRICTION_ADDRESS: "0x584F2c7F6da6f25a7bF6A1F3D7F422683Ac52Ef1",
THRESHOLD_WITNESS_ADDRESS: "0x5814e4755C0D98218ddb752D26dD03feba428c80",

// Cryptographic primitives
POSEIDON_T3_ADDRESS: "0xacaef99b13d5846e3309017586de9f777da41548",

// Other
DMS_REGISTRY_ADDRESS: "0x14d5629136edAc7ef2b2E5956838b9Bb0211eB9d",
PERSISTENT_KEY_VAULT_ADDRESS: "0x338B0e3c722702E705357C291E151D76B8Fd9F61",
ACCESS_PROOF_VERIFIER_ADDRESS: "0x508d326D68e5da728f8A74CB4ADB7552f7768B66",
};

Key Addresses

  • COMMIT_REVEAL_VAULT_ADDRESS — The central vault contract. All commit and reveal transactions go through this address.
  • COMMITMENT_TREE_ADDRESS — The append-only Merkle tree that stores commitments. The webapp queries this for the current root and tree size.
  • GHOST_REDEMPTION_VERIFIER_ADDRESS — The Groth16 verifier contract. Called by the vault during reveals to verify the ZK proof.
  • NULLIFIER_REGISTRY_ADDRESS — Tracks spent nullifiers to prevent double-reveals.
  • NATIVE_ASSET_HANDLER_ADDRESS — The sole contract authorized to mint/burn native GHOST via the ghostmint precompile.

Token Configurations

Each token that the webapp supports is defined with its address, metadata, and pre-computed tokenIdHash:

export const tokenConfigs = {
GHOST: {
address: "0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3", // NativeAssetHandler
symbol: "GHOST",
name: "GHOST",
decimals: 18,
isNative: true,
tokenIdHash: "<Poseidon2(0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3, 0)>",
},
gLABS: {
address: "0x062f8a68f6386c1b448b3379abd369825bec9aa2",
symbol: "gLABS",
name: "Ghost LABS",
decimals: 18,
isNative: false,
tokenIdHash: "<Poseidon2(0x062f8a68f6386c1b448b3379abd369825bec9aa2, 0)>",
},
gUSDC: {
address: "0x65c9091a6A45Db302a343AF460657C298FAA222D",
symbol: "gUSDC",
name: "Ghost USDC",
decimals: 6,
isNative: false,
tokenIdHash: "<Poseidon2(0x65c9091a6A45Db302a343AF460657C298FAA222D, 0)>",
},
gWETH: {
address: "0x923295a3e3bE5eDe29Fc408A507dA057ee044E81",
symbol: "gWETH",
name: "Ghost WETH",
decimals: 18,
isNative: false,
tokenIdHash: "<Poseidon2(0x923295a3e3bE5eDe29Fc408A507dA057ee044E81, 0)>",
},
gVIRTUAL: {
address: "0xaF12d2f962179274f243986604F97b961a4f4Cfc",
symbol: "gVIRTUAL",
name: "Ghost VIRTUAL",
decimals: 18,
isNative: false,
tokenIdHash: "<Poseidon2(0xaF12d2f962179274f243986604F97b961a4f4Cfc, 0)>",
},
};

tokenIdHash

The tokenIdHash is a Poseidon2 hash of the token's contract address and 0. It is used inside the commitment to identify the token without revealing the address in the ZK circuit's public inputs. The value must be pre-computed using circomlibjs:

import { buildPoseidon } from "circomlibjs";
const poseidon = await buildPoseidon();
const tokenIdHash = poseidon.F.toObject(
poseidon([BigInt(tokenAddress), 0n])
);

Each token's tokenIdHash in the config must match the on-chain computation exactly, or reveals will fail with a proof verification error.

Relayer Configuration

The relayer provides off-chain services including Merkle proof generation and transaction relay:

export const relayerConfig = {
baseUrl: "https://relayer.specterchain.com",
endpoints: {
merkleProof: "/api/merkle-proof",
submitReveal: "/api/submit-reveal",
treeStatus: "/api/tree-status",
indexer: "/api/indexer",
health: "/api/health",
},
timeout: 30000, // 30 second timeout
};

Endpoints

EndpointMethodDescription
/api/merkle-proofGETReturns the Merkle proof for a given leafIndex. Query param: ?leafIndex=N.
/api/submit-revealPOSTSubmits a reveal transaction via the relayer (for gasless reveals). Body: proof + public inputs.
/api/tree-statusGETReturns the current tree root, size, and latest block.
/api/indexerGETQueries indexed commitment and reveal events. Supports filtering by address, token, and block range.
/api/healthGETHealth check. Returns 200 if the relayer is operational.

Bridge Configuration

The bridge uses Hyperlane for cross-chain token transfers:

export const bridgeConfig = {
hyperlane: {
specterDomainId: 5446,
warpRoutes: {
gLABS: {
specterToken: "0x062f8a68f6386c1b448b3379abd369825bec9aa2",
syntheticToken: "0xa3239B0FDEE28De133e545424F644503527E508A", // HypGhostERC20Synthetic
},
},
remoteDomains: {
// Add supported remote chains here with their domain IDs
},
},
};

Hyperlane Domain IDs

Hyperlane uses domain IDs to identify chains. The Specter domain ID is 5446 (matching the EVM chain ID). Remote chain domain IDs are configured per deployment.

Warp Routes

Warp routes define the token mapping between Specter and remote chains. Each route specifies:

  • specterToken — the GhostERC20 address on Specter.
  • syntheticToken — the HypGhostERC20Synthetic contract on Specter that handles cross-chain minting/burning.

ABI Definitions

The config file includes ABI fragments for all contracts the webapp interacts with. These are minimal ABIs containing only the functions and events the webapp uses:

export const abis = {
commitRevealVault: [
"function commit(bytes32 commitment, address token, uint256 amount) external payable",
"function commitWithPolicy(bytes32 commitment, address token, uint256 amount, address policyId, bytes policyParams) external payable",
"function reveal(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] publicInputs, address token, uint256 amount, address recipient, address policyId, bytes policyParams) external",
"event CommitmentInserted(bytes32 indexed commitment, uint256 leafIndex, address indexed token, uint256 amount)",
"event Revealed(bytes32 indexed nullifier, address indexed recipient, address token, uint256 amount)",
],
commitmentTree: [
"function getRoot() view returns (bytes32)",
"function getTreeSize() view returns (uint256)",
"function getLeaf(uint256 index) view returns (bytes32)",
],
ghostERC20: [
"function approve(address spender, uint256 amount) returns (bool)",
"function balanceOf(address account) view returns (uint256)",
"function enableGhost() external",
"function ghostEnabled() view returns (bool)",
],
nullifierRegistry: [
"function isSpent(bytes32 nullifier) view returns (bool)",
],
assetGuard: [
"function isRegistered(address token) view returns (bool)",
],
};

Adding a New Token

To add a custom GhostERC20 token to the webapp:

  1. Deploy the token via GhostERC20Factory and call enableGhost() (see Deploy a Privacy Token).
  2. Compute the token's tokenIdHash using Poseidon2.
  3. Add an entry to tokenConfigs in config.js:
myToken: {
address: "0xYourTokenAddress",
symbol: "gMYTOKEN",
name: "Ghost MyToken",
decimals: 18,
isNative: false,
tokenIdHash: "<computed Poseidon2 hash>",
},
  1. Rebuild and redeploy the webapp.

Common Misconfigurations

ProblemCauseFix
Balance shows 0Chain ID set to 47474 instead of 5446Update chainConfig.chainId to 5446
Reveal proof failstokenIdHash mismatchRecompute tokenIdHash with Poseidon2(address, 0)
RPC timeoutsUsing stale RPC URLUse testnet.specterchain.com as primary
Wallet won't connectMissing chain in RainbowKit configEnsure Specter chain is defined in wagmi config