ChainSignal Agent Protocol (CSAP) – Wiki CSAP

Reference architecture and API/ABI signatures for a Solana-based AI agent operated by decentralized community nodes. On-chain: coordination and verification. Off-chain: inference and attestation.

Wiki

Top

1. Overview

ChainSignal has two layers:

  • On-chain (Solana program): registers nodes/models/jobs/results, manages stake/slashing/rewards, and provides deterministic state transitions (accounts).
  • Off-chain (community node runtime): executes inference jobs, manages model shards, signs results, and produces attestations.
Design principle: Store coordination and verification on-chain, keep model weights and heavy computation off-chain.

2. Repository Layout

chainsignal/
  programs/
    csap/                 # Solana program (Rust/Anchor)
  node/
    runtime/              # Node daemon (Rust)
    adapters/             # RPC/indexers
  sdk/
    ts/                   # TypeScript SDK
    rust/                 # Rust SDK
  api/
    openapi.yaml          # HTTP gateway (optional)
    proto/                # gRPC definitions
  docs/
    spec.md               # Documentation

3. Core Concepts

3.1 Node

A community node is a worker that posts stake, registers on-chain, executes jobs, and signs results.

3.2 Model

Models are registered as commitments: model_hash (SHA-256 over architecture/weights/tokenizer/config), shard_manifest_hash, version_semver.

3.3 Job

A job defines input (e.g., wallet address, time window, featureset), target model, quorum, and fees/rewards.

3.4 Result

Results contain scores/labels, a node signature, and optionally a Proof-of-Inference (PoI) commitment.

4. System Flow

Client (SDK)
  |  create_job()
  v
Solana Program (CSAP)
  |  job_account created
  v
Nodes subscribe (WS/gRPC)
  |  claim_job()
  v
Nodes compute inference
  |  submit_result()
  v
Program aggregates results
  |  finalize_job()
  v
Rewards / Slashing / Registry updates

5. On-Chain Program – Accounts

5.1 GlobalConfig (PDA)

pub struct GlobalConfig {
  pub admin: Pubkey,
  pub fee_mint: Pubkey,
  pub min_node_stake: u64,
  pub result_ttl_slots: u64,
  pub quorum_default: u8,
  pub slashing_bps: u16,
  pub bump: u8,
}

5.2 NodeRegistry (PDA)

pub struct NodeRecord {
  pub node_pubkey: Pubkey,
  pub operator: Pubkey,
  pub stake_lamports: u64,
  pub endpoint: [u8; 96],        // utf8: "grpc://node1..."
  pub capabilities: u64,         // bitflags: GPU, AVX2, RAM>=32, ...
  pub reputation: i64,           // signed score
  pub last_heartbeat_slot: u64,
  pub status: u8,                // 0=inactive,1=active,2=jailed
}

5.3 ModelRegistry (PDA)

pub struct ModelRecord {
  pub model_id: [u8; 32],        // derived from model_hash
  pub owner: Pubkey,
  pub model_hash: [u8; 32],
  pub shard_manifest_hash: [u8; 32],
  pub version_semver: [u8; 16],
  pub min_quorum: u8,
  pub max_latency_ms: u32,
  pub status: u8,                // 0=deprecated,1=active
}

5.4 InferenceJob (PDA)

pub struct InferenceJob {
  pub job_id: [u8; 32],
  pub requester: Pubkey,
  pub model_id: [u8; 32],
  pub created_slot: u64,
  pub expires_slot: u64,
  pub quorum: u8,
  pub max_results: u8,
  pub input_hash: [u8; 32],      // hash(InputPayload)
  pub input_uri: [u8; 96],       // optional blob ref
  pub status: u8,                // 0=open,1=finalizing,2=final,3=expired
  pub fee_lamports: u64,
}

5.5 InferenceResult (PDA)

pub struct InferenceResult {
  pub job_id: [u8; 32],
  pub node_pubkey: Pubkey,
  pub submitted_slot: u64,
  pub result_hash: [u8; 32],     // hash(ResultPayload)
  pub result_uri: [u8; 96],      // optional blob ref
  pub signature: [u8; 64],       // ed25519(job_id||result_hash)
  pub poi_hash: [u8; 32],        // optional PoI commitment
  pub status: u8,                // 0=submitted,1=accepted,2=rejected
}

6. On-Chain Instructions (ABI)

Instruction Purpose Validation
init_global_config(admin, fee_mint, params) Initialize GlobalConfig Admin authority
register_node(endpoint, capabilities) Register/update node Stake ≥ min_node_stake
heartbeat() Liveness signal Node exists
register_model(model_hash, shard_manifest_hash, version, constraints) Register model commitment Owner signature
create_job(model_id, input_hash, input_uri, quorum, ttl_slots) Create inference job Model active, quorum valid
submit_result(job_id, result_hash, result_uri, signature, poi_hash) Submit result Job open, TTL ok, node active, signature ok
finalize_job(job_id) Aggregate + rewards/slashing Quorum reached, not expired
Aggregation example: Score = median, label = majority vote. Slash outliers when |score - median| > threshold.

7. Payload Formats (Off-Chain)

7.1 InputPayload (CBOR / JSON view)

{
  "schema": "csap.input.v1",
  "kind": "wallet_risk_score",
  "wallet": "9xQeWvG816bUx9EP... (base58)",
  "chain": "solana",
  "window": {"from": 1734300000, "to": 1734386400},
  "featureset": "standard_v2",
  "options": {"include_explanations": true}
}

7.2 ResultPayload (CBOR / JSON view)

{
  "schema": "csap.result.v1",
  "job_id": "0x…",
  "model_id": "0x…",
  "score": 72,
  "labels": ["high_velocity", "new_counterparties"],
  "explanations": [
    {"feature":"tx_burst_15m", "impact": 0.31},
    {"feature":"counterparty_entropy", "impact": 0.22}
  ],
  "stats": {"latency_ms": 184, "confidence": 0.78}
}

7.3 Hashing & Signatures

input_hash  = sha256(cbor(InputPayload))
result_hash = sha256(cbor(ResultPayload))

sig = ed25519_sign(node_key, job_id || result_hash)

8. Node Runtime

8.1 Components

  • Watcher: Solana WS/RPC → new jobs
  • Scheduler: claiming, rate-limits, backpressure
  • ModelManager: shards, cache, versions
  • InferenceEngine: CPU/GPU backend
  • Attestor: PoI commitments + signatures
  • Submitter: submits results on-chain

8.2 Node Config (node.toml)

[node]
operator_keypair_path = "./keys/operator.json"
node_keypair_path     = "./keys/node.json"
endpoint              = "grpc://127.0.0.1:9090"
capabilities          = 0b0000000000000011  # bit0=CPU_AVX2, bit1=GPU

[solana]
cluster   = "mainnet-beta"
rpc_url   = "https://api.mainnet-beta.solana.com"
ws_url    = "wss://api.mainnet-beta.solana.com"
program_id = "CSAP111111111111111111111111111111111111"

[models]
cache_dir = "./model_cache"
max_gb    = 60
allowlist = ["model_id_hex_1", "model_id_hex_2"]

[scheduler]
max_concurrent_jobs = 4
job_poll_ms         = 250
result_timeout_ms   = 2500

8.3 Startup (CLI example)

csap-node init-keys
csap-node stake --lamports 5000000000
csap-node register --endpoint grpc://node.example:9090 --capabilities GPU,AVX2
csap-node run

9. gRPC API (Node ↔ Client/Gateway)

9.1 Proto sketch

service NodeService {
  rpc Health(HealthRequest) returns (HealthReply);
  rpc Describe(DescribeRequest) returns (DescribeReply);
  rpc SubmitLocalJob(LocalJobRequest) returns (LocalJobReply);
  rpc StreamJobs(StreamJobsRequest) returns (stream JobEvent);
}

9.2 Health

Returns version, uptime, loaded models, and queue depth.

10. TypeScript SDK

10.1 Install

pnpm add @chainsignal/csap-sdk

10.2 Create a job + read final result

import { CSAPClient } from "@chainsignal/csap-sdk";

const csap = new CSAPClient({
  rpcUrl: "https://api.mainnet-beta.solana.com",
  programId: "CSAP111111111111111111111111111111111111",
});

const input = {
  schema: "csap.input.v1",
  kind: "wallet_risk_score",
  wallet: "9xQeWvG816bUx9EP...",
  chain: "solana",
  window: { from: 1734300000, to: 1734386400 },
  featureset: "standard_v2",
  options: { include_explanations: true },
};

const { jobId } = await csap.jobs.create({
  modelId: "b3f1…",
  input,
  quorum: 7,
  ttlSlots: 900,
});

const final = await csap.jobs.waitFinal(jobId, { timeoutMs: 15000 });
console.log(final.score, final.labels);

11. Error Codes

11.1 On-chain ProgramError

Code Name Meaning
1001NodeStakeTooLowStake below minimum
1002NodeNotActiveNode inactive/jailed
1101ModelNotActiveModel deprecated/inactive
1201JobExpiredTTL exceeded
1202QuorumInvalidQuorum out of bounds
1301DuplicateResultNode already submitted
1302InvalidSignatureSignature verification failed
1401FinalizeNotReadyQuorum not reached

11.2 Node runtime

Code Meaning
N200ModelShardMissing
N240InferenceTimeout
N260RPCBackoff

12. Security & Integrity

  • Signed results: ed25519 over job_id||result_hash.
  • Quorum: reduces single-node manipulation.
  • Reputation + slashing: penalizes systematic outliers.
  • PoI: commitment over ModelCommitment + InputHash + Nonce + TraceHash.
  • Privacy: inputs can be referenced by hash + optional blob URI.

13. Governance & Upgrades

  • GlobalConfig.admin can update parameters via set_params().
  • Model owners can deprecate models.
  • An upgrade authority can deploy program upgrades (Upgradeable Loader).
  • Optional: migrate to DAO governance via timelock + multisig.

14. Minimal Anchor IDL Excerpt

{
  "name": "csap",
  "instructions": [
    {"name":"registerNode","accounts":[...],
     "args":[{"name":"endpoint","type":"bytes"},{"name":"capabilities","type":"u64"}]},
    {"name":"registerModel","accounts":[...],
     "args":[{"name":"modelHash","type":"bytes"},{"name":"manifestHash","type":"bytes"},{"name":"version","type":"bytes"}]},
    {"name":"createJob","accounts":[...],
     "args":[{"name":"modelId","type":"bytes"},{"name":"inputHash","type":"bytes"},{"name":"inputUri","type":"bytes"},{"name":"quorum","type":"u8"},{"name":"ttlSlots","type":"u64"}]},
    {"name":"submitResult","accounts":[...],
     "args":[{"name":"jobId","type":"bytes"},{"name":"resultHash","type":"bytes"},{"name":"resultUri","type":"bytes"},
             {"name":"signature","type":"bytes"},{"name":"poiHash","type":"bytes"}]},
    {"name":"finalizeJob","accounts":[...],
     "args":[{"name":"jobId","type":"bytes"}]}
  ]
}