nautechsystems/nautilus_trader · error
Pool manifest references an unpinned contract
Error message
Pool manifest references an unpinned contract
What it means
This error is thrown while validating a blockchain deployment manifest: every address field of a pool entry (pool address, token0, token1, factory, quote_contract) must correspond to a contract identity already pinned in the manifest's contract_addresses set. The library rejects pools that point at contracts that were not declared and hash-pinned, because an unpinned contract could be swapped for malicious or different bytecode at deploy time.
Source
Thrown at crates/adapters/blockchain/src/rpc/verification.rs:1378
anyhow::ensure!(
!token.name.trim().is_empty()
&& !token.symbol.trim().is_empty()
&& matches!(token.asset_role.as_str(), "base" | "quote" | "both"),
"Token manifest identity or asset role is invalid"
);
}
for pool in &manifest.pools {
for address in [
&pool.address,
&pool.token0,
&pool.token1,
&pool.factory,
&pool.quote_contract,
] {
let address = Address::from_str(address)
.map_err(|_| anyhow::anyhow!("Pool manifest address is invalid"))?;
anyhow::ensure!(
contract_addresses.contains(&address),
"Pool manifest references an unpinned contract"
);
}
anyhow::ensure!(
pool.fee != 0 && pool.fee <= 1_000_000,
"Pool fee is invalid"
);
}
let mut call_edges = HashSet::new();
for edge in &manifest.call_edges {
anyhow::ensure!(
matches!(
edge.purpose.as_str(),
"wrap" | "approve" | "swap_sell" | "swap_buy"
) && matches!(
edge.call_type.as_str(),
"call" | "staticcall" | "delegatecall" | "callcode"View on GitHub (pinned to 18893faf8b)
Solutions
- Add a contract entry (with matching address and pinned code hash) for each address the pool references, especially token0/token1 and the factory
- Fix the pool's address fields to match the exact pinned contract addresses (parsed Address comparison, case-insensitive)
- If the pool came from another network manifest, regenerate the pool entry for this chain's pinned contracts
- Re-run the manifest generator so pools and pinned contracts are produced from one consistent source
Example fix
// before
{"pools": [{"address": "0xabc...", "token0": "0x111...", "token1": "0x222...", "factory": "0x333...", "quote_contract": "0x444..."}]}
// contracts section missing 0x111...
// after
{"contracts": [{"address": "0x111...", "role": "Token", "runtime_code_hash": "0x..."}, ...], "pools": [{"address": "0xabc...", "token0": "0x111...", "token1": "0x222...", "factory": "0x333...", "quote_contract": "0x444..."}]} Defensive patterns
Strategy: validation
Validate before calling
// Rust: pre-validate pool references against pinned contracts
let pinned: HashSet<Address> = manifest.contracts.iter()
.filter_map(|c| Address::from_str(&c.address).ok())
.collect();
for pool in &manifest.pools {
for field in [&pool.address, &pool.token0, &pool.token1, &pool.factory, &pool.quote_contract] {
let addr = Address::from_str(field)
.map_err(|_| format!("pool address invalid: {field}"))?;
anyhow::ensure!(pinned.contains(&addr), "pool references unpinned contract {field}");
}
} Type guard
fn is_pinned(address: &str, pinned: &HashSet<Address>) -> bool {
Address::from_str(address).map(|a| pinned.contains(&a)).unwrap_or(false)
} Try / catch
match validate_deployment_manifest(&manifest) {
Err(e) if e.to_string().contains("unpinned contract") => {
eprintln!("Add the referenced contract to the manifest's contracts section: {e}");
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Always generate pools and pinned contracts from the same generator so references stay consistent
- Checklist: every pool field address must appear in the contracts section
- Never hand-edit manifests; regenerate after contract deployments change
- Diff new manifests against a previously validated one before submitting
When it happens
Trigger: Calling the deployment manifest validation with a pool whose address, token0, token1, factory, or quote_contract is not listed among the manifest's pinned contracts; this fires after the address parses as a valid Ethereum address, so it is purely a membership failure.
Common situations: Hand-editing a manifest and adding a pool without adding its token contracts to the contracts section; copying a pool from another chain/network manifest where the factory or quote contract has a different address; a contract being re-pinned to a new address so the pool's old reference no longer matches.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Pool fee is invalid
- Deployment manifest is missing a swap call graph
- DEX {dex_id} is not registered in the data client
- `router_addresses` must contain at least one router address
- Quote spend limit for {token_in} -> {token_out} is denominat
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/977134f62123aa8e.
Report an issue: GitHub.