{
  "$schema": "canary.agent.spec/v1",
  "spec_version": "1.0.0",
  "project": {
    "name": "canary",
    "class": "autonomous cybersecurity agent with token issuance authority",
    "chain": "solana",
    "cluster": "mainnet-beta",
    "summary": "A single keyless defensive agent that performs full adversarial analysis on its own deployment surface before it is allowed to mint, then creates one token on pump.fun, then spends the rest of its existence hunting attackers on that token's market. Every capability it owns is gated behind a proof it must produce on chain. It cannot mint until the proof set is complete. It cannot be stopped by anyone, including the wallet that funded it."
  },

  "wallet": {
    "funding_wallet": "3KD2GfFtJChMbvheS55bapJ1QXtTN7bPWGC6Qqz6RNex",
    "role_of_funding_wallet": "origin of lamports only. Signs nothing after genesis. Holds no authority over the agent, the program, the mint, or the creator fee stream once genesis settles.",
    "agent_wallet": {
      "type": "program derived address",
      "seeds": ["canary", "agent", "<funding_wallet_pubkey>"],
      "private_key": null,
      "signing_model": "cross program invocation only. No keypair exists for this address anywhere. All outgoing transactions are built inside the program and signed with the PDA bump.",
      "note": "address is derived at deploy time and written into the deploy log, never typed by hand"
    },
    "vault_wallet": {
      "type": "program derived address",
      "seeds": ["canary", "vault", "<agent_wallet_pubkey>"],
      "holds": ["SOL received from creator fee claims", "SOL received from bounty settlements"],
      "withdraw_authority": "none. Lamports leave the vault only through the three documented spend paths below."
    }
  },

  "genesis": {
    "trigger": "a single transfer of lamports from the funding wallet into the agent PDA that exceeds genesis_minimum_lamports",
    "genesis_minimum_lamports": 2500000000,
    "sequence": [
      {
        "step": "receive",
        "action": "program observes the incoming lamports via the agent PDA balance delta in the same transaction that calls initialize",
        "fail_if": "balance delta below genesis_minimum_lamports"
      },
      {
        "step": "seal_funder",
        "action": "write funding wallet pubkey into the AgentState account as an immutable field and set funder_authority = false",
        "fail_if": "AgentState already initialized"
      },
      {
        "step": "burn_upgrade_authority",
        "action": "set the BPF upgradeable loader authority for the program to null in the same transaction",
        "fail_if": "program is still upgradeable after this instruction"
      },
      {
        "step": "arm_audit_gate",
        "action": "set audit_phase = 1, mint_permitted = false, write the required_proofs bitmap with every bit cleared",
        "fail_if": "mint_permitted is true for any reason"
      }
    ],
    "irreversibility": "after genesis the program has no upgrade authority, the agent has no keypair, the funder has no admin instruction. There is no instruction in the IDL that can undo any of the above."
  },

  "agent_identity": {
    "model_role": "defensive operator",
    "stance": "adversarial toward every address that interacts with the token except passive holders",
    "doctrine": [
      "assume the deployer is compromised",
      "assume the first buyers are automated",
      "assume liquidity will be attacked within the first block it exists",
      "assume every signature is forged until the program verifies it",
      "treat silence from an address as more suspicious than noise"
    ],
    "forbidden_actions": [
      "selling any token from the agent wallet",
      "transferring mint authority to any address",
      "transferring freeze authority to any address",
      "calling any instruction on a program not in the allowlist",
      "accepting any instruction from the funding wallet after genesis",
      "pausing, delaying or scheduling any mechanism on a clock"
    ]
  },

  "capability_stack": {
    "description": "each capability is a module the agent unlocks by producing an on chain proof. Order is fixed. The mint is the final gate.",
    "modules": [
      {
        "id": "M01",
        "name": "surface_enumeration",
        "purpose": "enumerate every account the agent will ever touch and hash the set",
        "inputs": ["program id", "every PDA seed string", "every external program id in allowlist"],
        "algorithm": "derive every PDA for every seed tuple, collect pubkeys into a sorted vector, sha256 the concatenated 32 byte keys, store digest as surface_root in AgentState",
        "proof": "surface_root is non zero and matches a client side recomputation",
        "unlock_bit": 0
      },
      {
        "id": "M02",
        "name": "bytecode_disassembly",
        "purpose": "agent reads its own deployed program bytes from the ProgramData account and verifies the hash matches the compiled artifact",
        "inputs": ["ProgramData account bytes", "expected_program_hash supplied at deploy"],
        "algorithm": "sha256 over the ELF payload after the loader header, compare to expected_program_hash, store result",
        "proof": "self_hash_ok = true",
        "unlock_bit": 1
      },
      {
        "id": "M03",
        "name": "authority_sweep",
        "purpose": "confirm no authority of any kind survives",
        "checks": [
          "program upgrade authority is null",
          "AgentState has no admin field",
          "no instruction in the IDL carries a signer constraint on a non PDA account after genesis",
          "funding wallet is not a signer on any instruction after initialize"
        ],
        "proof": "all four checks return true in a single transaction",
        "unlock_bit": 2
      },
      {
        "id": "M04",
        "name": "honeypot_canary_accounts",
        "purpose": "deploy decoy accounts that look like admin or treasury keys so attackers reveal themselves by touching them",
        "algorithm": "create canary_count PDAs with seeds [\"canary\", \"decoy\", index], each holding exactly decoy_lamports, each with an account discriminator that mimics a common treasury layout, log every transaction that includes any decoy as an account meta",
        "canary_count": 7,
        "decoy_lamports": 1000000,
        "proof": "canary_count decoys exist and the decoy registry hash is written",
        "unlock_bit": 3
      },
      {
        "id": "M05",
        "name": "sniper_fingerprint_model",
        "purpose": "build the on chain classifier used after launch to separate human buys from automated first block buys",
        "features": [
          "slot distance from create instruction",
          "compute unit price in microlamports",
          "number of instructions in the buy transaction",
          "presence of a Jito tip account in the transaction",
          "whether the buyer address funded itself within the previous 64 slots",
          "whether the buyer shares a funding parent with any other first block buyer"
        ],
        "scoring": {
          "method": "weighted sum into an integer score, no floating point on chain",
          "weights": {
            "slot_distance_zero": 40,
            "slot_distance_one": 25,
            "cu_price_above_median_x10": 15,
            "jito_tip_present": 20,
            "self_funded_recent": 15,
            "shared_funding_parent": 30
          },
          "sniper_threshold": 60,
          "bundle_threshold": 85
        },
        "proof": "weights and thresholds are written to the ClassifierConfig account and the account is closed to further writes",
        "unlock_bit": 4
      },
      {
        "id": "M06",
        "name": "liquidity_attack_detectors",
        "purpose": "detectors armed before the mint exists",
        "detectors": [
          {
            "name": "bundle_detector",
            "rule": "three or more buys land in the same slot from addresses whose funding parent is identical",
            "response": "flag_bundle"
          },
          {
            "name": "wash_detector",
            "rule": "same address buys and sells within 8 slots with net position change below 2 percent of its gross volume",
            "response": "flag_wash"
          },
          {
            "name": "sandwich_detector",
            "rule": "address A buys in slot N, address B buys in slot N, address A sells in slot N and all three share a signer or a funding parent",
            "response": "flag_sandwich"
          },
          {
            "name": "dump_detector",
            "rule": "single sell exceeding dump_bps of the pool's token reserve",
            "dump_bps": 300,
            "response": "flag_dump"
          },
          {
            "name": "drain_detector",
            "rule": "any instruction that targets the vault PDA with a program id outside the allowlist",
            "response": "flag_drain_attempt and record the full transaction signature in the IncidentLog"
          }
        ],
        "proof": "all five detector configs written and sealed",
        "unlock_bit": 5
      },
      {
        "id": "M07",
        "name": "mint_authorization",
        "purpose": "the final gate",
        "rule": "mint_permitted flips to true only when required_proofs bitmap equals 0b111111 and the program verifies every proof account in one transaction",
        "proof": "mint_permitted = true",
        "unlock_bit": 6
      }
    ]
  },

  "token_creation": {
    "launchpad": "pump.fun",
    "pump_program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
    "token_program_id": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
    "metadata_program_id": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
    "signer": "agent PDA via invoke_signed, never a keypair",
    "preconditions": [
      "mint_permitted = true",
      "agent PDA lamports >= create_cost_lamports + rent for every account the create instruction opens",
      "no detector flag is raised against the agent PDA itself"
    ],
    "create_instruction": {
      "name": "field supplied at deploy, never hardcoded in this file",
      "symbol": "field supplied at deploy, never hardcoded in this file, never rendered on the site",
      "uri": "metadata json uploaded in the same deploy run, address written to deploy log",
      "mint": "fresh keypair generated inside the deploy script, used once for the create instruction, then discarded; mint authority is pump.fun's bonding curve by construction",
      "creator": "agent PDA",
      "freeze_authority": null
    },
    "first_action_after_create": {
      "description": "the agent performs the dev buy from its own vault so the first buy on the curve is the defender, not an attacker",
      "amount_source": "dev_buy_bps of vault lamports at the moment of create",
      "dev_buy_bps": 1500,
      "slippage_bps": 500,
      "hold_rule": "tokens received from the dev buy are never sold. They sit in the agent's associated token account with no sell instruction available in the IDL."
    },
    "post_create_lock": "after the create transaction confirms, the program writes mint_pubkey into AgentState and sets created = true. The create instruction cannot be called a second time."
  },

  "creator_fee_mechanism": {
    "source": "pump.fun creator fee entitlement for the mint, claimable by the creator address, which is the agent PDA",
    "claim_trigger": "a claim is attempted inside the same transaction as any detector response. Fees are never claimed on a schedule.",
    "routing": {
      "split": [
        { "destination": "vault PDA", "bps": 7000, "purpose": "bounty reserve and defensive buy reserve" },
        { "destination": "burn", "bps": 2000, "purpose": "lamports converted to the token through the curve and the resulting tokens are burned in the same transaction" },
        { "destination": "decoy refresh", "bps": 1000, "purpose": "tops decoy accounts back to decoy_lamports after any drain attempt consumed them" }
      ],
      "sum_check": "bps must sum to 10000 or the instruction reverts"
    }
  },

  "defensive_spend_paths": {
    "description": "the only three ways lamports leave the vault. Each is a trade or an on chain event, none are timed.",
    "paths": [
      {
        "id": "SP1",
        "name": "counter_buy",
        "trigger": "flag_dump raised",
        "action": "buy on the curve with counter_buy_bps of vault lamports in the same slot the dump lands",
        "counter_buy_bps": 2500,
        "cap": "never more than one counter buy per flagged sell signature",
        "hold_rule": "tokens from counter buys are never sold"
      },
      {
        "id": "SP2",
        "name": "bounty_settlement",
        "trigger": "an external address submits a valid IncidentProof account that identifies an attacker address the detectors missed",
        "validation": "the proof must reference a confirmed transaction signature, the signature must include the token mint as an account, and the classifier must score the referenced address above sniper_threshold when re run on chain",
        "payout": "bounty_lamports from vault to the submitter",
        "bounty_lamports": 50000000,
        "anti_grief": "a submitter who files a proof that fails validation is written to the Blocklist account and can never file again"
      },
      {
        "id": "SP3",
        "name": "decoy_refresh",
        "trigger": "any decoy account balance drops below decoy_lamports",
        "action": "transfer the shortfall from vault to the decoy in the same transaction that detected the drop"
      }
    ]
  },

  "incident_log": {
    "account": "IncidentLog PDA, seeds [\"canary\", \"incidents\", mint_pubkey]",
    "entry_layout": {
      "slot": "u64",
      "signature": "[u8; 64]",
      "offender": "Pubkey",
      "flag": "u8 enum: 1 bundle, 2 wash, 3 sandwich, 4 dump, 5 drain_attempt, 6 decoy_touch",
      "score": "u16 classifier score at the time of the flag",
      "response": "u8 enum: 0 logged only, 1 counter_buy, 2 bounty_paid, 3 decoy_refreshed"
    },
    "capacity": "ring buffer of 4096 entries, oldest entry overwritten",
    "public_read": "any client can deserialize the account; the site renders it as a static table, newest first, no auto advance"
  },

  "blocklist": {
    "account": "Blocklist PDA, seeds [\"canary\", \"blocklist\", mint_pubkey]",
    "entry": "Pubkey plus u8 reason",
    "effect": "a blocklisted address receives no bounty, and any counter_buy triggered by its sells is doubled to counter_buy_bps * 2",
    "removal": "none. There is no instruction to remove an entry."
  },

  "program": {
    "framework": "anchor",
    "language": "rust",
    "program_name_display": "canary",
    "explorer_name_rendering": {
      "requirement": "the program must show the name canary on Solana explorers, the same way named programs render on solscan and the Solana explorer",
      "steps": [
        "set the crate name in Cargo.toml to canary and the lib name to canary",
        "declare_id! with the deployed program id and keep the keypair name canary-keypair.json in target/deploy",
        "run anchor build with the verifiable flag so the build hash is reproducible",
        "upload the IDL on chain with anchor idl init so the program's name field in the IDL reads canary",
        "publish the verified build through the solana verify tooling so the explorer links the source and shows the name",
        "confirm on the explorer that the program account page displays canary as the label before announcing the program id"
      ]
    },
    "accounts": [
      "AgentState",
      "ClassifierConfig",
      "DetectorConfig",
      "DecoyRegistry",
      "IncidentLog",
      "Blocklist",
      "Vault"
    ],
    "instructions": [
      { "name": "initialize", "signer": "funding wallet, once", "after_genesis": "disabled" },
      { "name": "prove_surface", "signer": "none, permissionless crank" },
      { "name": "prove_self_hash", "signer": "none, permissionless crank" },
      { "name": "prove_authority_sweep", "signer": "none, permissionless crank" },
      { "name": "deploy_decoys", "signer": "none, permissionless crank" },
      { "name": "seal_classifier", "signer": "none, permissionless crank" },
      { "name": "seal_detectors", "signer": "none, permissionless crank" },
      { "name": "authorize_mint", "signer": "none, permissionless crank" },
      { "name": "create_token", "signer": "agent PDA via CPI, once" },
      { "name": "observe_trade", "signer": "none, permissionless crank that passes a confirmed trade transaction for classification" },
      { "name": "claim_and_route_fees", "signer": "none, invoked inside observe_trade when a flag is raised" },
      { "name": "submit_incident_proof", "signer": "the submitter" },
      { "name": "refresh_decoy", "signer": "none, permissionless crank" }
    ],
    "permissionless_crank_model": "anyone can call any prove_, seal_, observe_ or refresh_ instruction. The instructions carry no privileges; they only let the program verify state it can already see. The agent's autonomy comes from the fact that every crank produces the same deterministic outcome regardless of who sends it.",
    "allowlist_program_ids": [
      "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
      "11111111111111111111111111111111",
      "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
    ]
  },

  "site": {
    "builder": "lovable",
    "frontend_rules": {
      "data_source": "Birdeye proxied through a Supabase edge function, API key server side only",
      "value_poll": "Birdeye price every 1s, rendered as a live number and one clean thin line chart at the top, red on down and green on up",
      "banned_words_on_site": ["market cap", "mc", "ticker symbol"],
      "no_fake_values": "every readout slot renders blank until the contract address is configured in the dev panel, then fills live",
      "dev_panel": "small top right button visible only on the Lovable preview hostname and localhost, opens an overlay where the contract address is pasted and saved, nothing else; a saved or cleared address updates every visitor on the domain instantly",
      "interaction": "no controls for visitors. Nav links, verification links and the wallet button only.",
      "motion": "nothing moves on its own. Only live values swap in place.",
      "navbar": "wordmark text, slash nav, an X button with no destination set yet",
      "footer": "none",
      "socials": "none"
    },
    "pages": [
      { "route": "/", "content": "live value, agent state readout, proof bitmap as a seven cell row, last incident, dev buy holdings, vault balance, all fitting one viewport" },
      { "route": "/proofs", "content": "each of the seven modules with its proof status, the on chain account address, and the full verbatim explanation of what the proof checks" },
      { "route": "/incidents", "content": "static table of the IncidentLog ring buffer, newest first, each row linking to the transaction on an explorer" },
      { "route": "/decoys", "content": "seven decoy addresses with current balance and touch count, plus a byte map drawing of the decoy discriminator layout" },
      { "route": "/classifier", "content": "the weights table, the thresholds, and a worked scoring walk through against the feature list" },
      { "route": "/docs", "content": "one long scrolling page covering genesis, the capability stack, the fee split, the three spend paths, the blocklist and the program instructions" }
    ]
  },

  "verification_checklist": [
    "program upgrade authority reads null on the explorer",
    "program account page shows the name canary",
    "AgentState.funder equals 3KD2GfFtJChMbvheS55bapJ1QXtTN7bPWGC6Qqz6RNex and funder_authority is false",
    "required_proofs bitmap equals 0b111111 before create_token succeeds",
    "create_token reverts on a second call",
    "no instruction in the IDL exposes a token transfer or sell from the agent's token account",
    "every spend path references a trade signature or an account balance delta, no slot based or time based trigger exists in the source",
    "IDL is uploaded on chain and the verified build hash matches the local build",
    "site renders blank slots with no contract address and fills live once one is saved in the dev panel"
  ]
}
