{"record":{"id":"977134f62123aa8e","repo":"nautechsystems/nautilus_trader","slug":"pool-manifest-references-an-unpinned-contract","errorCode":null,"errorMessage":"Pool manifest references an unpinned contract","messagePattern":"Pool manifest references an unpinned contract","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/rpc/verification.rs","lineNumber":1378,"sourceCode":"        anyhow::ensure!(\n            !token.name.trim().is_empty()\n                && !token.symbol.trim().is_empty()\n                && matches!(token.asset_role.as_str(), \"base\" | \"quote\" | \"both\"),\n            \"Token manifest identity or asset role is invalid\"\n        );\n    }\n\n    for pool in &manifest.pools {\n        for address in [\n            &pool.address,\n            &pool.token0,\n            &pool.token1,\n            &pool.factory,\n            &pool.quote_contract,\n        ] {\n            let address = Address::from_str(address)\n                .map_err(|_| anyhow::anyhow!(\"Pool manifest address is invalid\"))?;\n            anyhow::ensure!(\n                contract_addresses.contains(&address),\n                \"Pool manifest references an unpinned contract\"\n            );\n        }\n        anyhow::ensure!(\n            pool.fee != 0 && pool.fee <= 1_000_000,\n            \"Pool fee is invalid\"\n        );\n    }\n    let mut call_edges = HashSet::new();\n    for edge in &manifest.call_edges {\n        anyhow::ensure!(\n            matches!(\n                edge.purpose.as_str(),\n                \"wrap\" | \"approve\" | \"swap_sell\" | \"swap_buy\"\n            ) && matches!(\n                edge.call_type.as_str(),\n                \"call\" | \"staticcall\" | \"delegatecall\" | \"callcode\"","sourceCodeStart":1360,"sourceCodeEnd":1396,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/rpc/verification.rs#L1360-L1396","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n{\"pools\": [{\"address\": \"0xabc...\", \"token0\": \"0x111...\", \"token1\": \"0x222...\", \"factory\": \"0x333...\", \"quote_contract\": \"0x444...\"}]}\n// contracts section missing 0x111...\n// after\n{\"contracts\": [{\"address\": \"0x111...\", \"role\": \"Token\", \"runtime_code_hash\": \"0x...\"}, ...], \"pools\": [{\"address\": \"0xabc...\", \"token0\": \"0x111...\", \"token1\": \"0x222...\", \"factory\": \"0x333...\", \"quote_contract\": \"0x444...\"}]}","handlingStrategy":"validation","validationCode":"// Rust: pre-validate pool references against pinned contracts\nlet pinned: HashSet<Address> = manifest.contracts.iter()\n    .filter_map(|c| Address::from_str(&c.address).ok())\n    .collect();\nfor pool in &manifest.pools {\n    for field in [&pool.address, &pool.token0, &pool.token1, &pool.factory, &pool.quote_contract] {\n        let addr = Address::from_str(field)\n            .map_err(|_| format!(\"pool address invalid: {field}\"))?;\n        anyhow::ensure!(pinned.contains(&addr), \"pool references unpinned contract {field}\");\n    }\n}","typeGuard":"fn is_pinned(address: &str, pinned: &HashSet<Address>) -> bool {\n    Address::from_str(address).map(|a| pinned.contains(&a)).unwrap_or(false)\n}","tryCatchPattern":"match validate_deployment_manifest(&manifest) {\n    Err(e) if e.to_string().contains(\"unpinned contract\") => {\n        eprintln!(\"Add the referenced contract to the manifest's contracts section: {e}\");\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["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"],"tags":["blockchain","manifest-validation","config"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}