Cambrian CLI
Prerequisites
node.js >= 22.0.0
docker >= 20.0.0
Installing Cambrian utility
npm i --global @cambrianone/camb-client@latest
Initialization
Scaffold AVS and initialize PoA onchain
camb init -t avs <AVS directory>
Explaining wizard:
Enter AVS IP address to bind to
- provide an IP address to bind to, it should be reachable from all the operatorsEnter AVS HTTP port to bind to
- provide an HTTP port to bind to, it should be reachable from all the operatorsEnter AVS WS port to bind to
- provide a WebSockers port to bind to, it should be reachable from all the operatorsEnter admin private key or press enter to generate a new one
- admin private key / keypair as array ofuint8
or as base58-encoded stringEnter Solana API URL or press enter to use default
- Solana JSON RPC endpointEnter Solana API WS URL or press enter to use default
- Solana WebSockets endpointEnter Cambrian Consensus Program name or press enter to generate a new one
- string identifier (name) of the Cambrian Consensus Program (CCP) instance (aka PoA name)Enter proposal storage key or press enter to generate a new one
- unique string key for the proposal storageEnter storage space
- common oracle storage space, in bytesEnter consensus threshold
- The minimum number of operators required to approve a proposal before executionEnter stake threshold
- The minimum stake required for an operator to participate in CCP
List installed AVS instances
camb avs list
Start AVS:
camb avs run -u <AVS pubkey>
AVS is stated for:
Distributing payload between the operators
Checking vault state and it's updating when needed (once in an epoch)
Scaffold operators and initialize them onchain
Scaffolding operators
Before scaffolding the operators make sure instance of AVS is already running.
camb init -t operator <operator 1 directory>
camb init -t operator <operator 2 directory>
camb init -t operator <operator 3 directory>
Explaining wizard:
Enter AVS HTTP URL
- AVS HTTP endpointEnter AVS WS URL
- AVS WebSockets endpoint
After initialization of AVS and operators you can optionally add an external service to run it alongside with AVS or operator instances:
camb manage add-external -n <service name> -p <path to AVS/operator directory> -i <external service container image> [-e <NAME1=VALUE1...>]
This command will create a boilerplate section for external service in AVS/operator docker-compose file. You can customize it later.
Running components
List installed operator nodes (outputs voter public keys)
camb operator list -a <AVS public key>
Start operators:
camb operator run -u <voter public key>
Each operator waits for the command from the AVS to store data to oracle storage and execute the proposal.
Executing proposal
Running payload
Payload container holds data for oracle storage and execution instructions for the proposal.
Container receives a parameter (in CAMB_MVP
environment variable) serialized as JSON-object.
It's type is:
type TPayloadInput = {
executorPDA?: string;
apiUrl?: string;
extraSigners?: Array<string>;
poaName: string;
proposalStorageKey: string;
}
extraSigners
represents an optional array of serialized private keys used for signing transaction.
Container should write a JSON-stringified object. It's type is:
type TPayloadOutput = {
proposalInstructions: Array<{
accounts: Array<{
address: string;
role: 0 | 1 | 2 | 3
}>,
data: string;
programAddress: string;
}>;
storagePayload:
| { encoding: 'utf-8'; data: string }
| { encoding: 'bytes'; data: number[] }
| { encoding: 'base58'; data: string }
| { encoding: 'base64'; data: string };
}
where proposal instructions data
is base58-serialized data (or array of uint8) buffer and account role
is the following enum:
enum AccountRole {
// Bitflag guide: is signer ⌄⌄ is writable
WRITABLE_SIGNER = /* 3 */ 0b11,
READONLY_SIGNER = /* 2 */ 0b10,
WRITABLE = /* 1 */ 0b01,
READONLY = /* 0 */ 0b00,
}
This type could be represented as JSON-schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TPayloadOutput",
"type": "object",
"properties": {
"proposalInstructions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"accounts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Account address"
},
"role": {
"type": "integer",
"enum": [0, 1, 2, 3],
"description": "Account role as bitflag (is signer | is writable)\n\n0 (0b00): READONLY\n1 (0b01): WRITABLE\n2 (0b10): READONLY_SIGNER\n3 (0b11): WRITABLE_SIGNER"
}
},
"required": ["address", "role"],
"additionalProperties": false
},
"description": "Array of accounts involved in the instruction"
},
"data": {
"type": "string",
"description": "Instruction data as base58 encoded string"
},
"programAddress": {
"type": "string",
"description": "Program address for the instruction"
}
},
"required": ["accounts", "data", "programAddress"],
"additionalProperties": false
},
"description": "Array of proposal instructions"
}
},
"storagePayload": {
"oneOf": [
{
"type": "object",
"properties": {
"encoding": {
"const": "utf-8"
},
"data": {
"type": "string"
}
},
"required": ["encoding", "data"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"encoding": {
"const": "bytes"
},
"data": {
"type": "array",
"items": {
"type": "number"
}
}
},
"required": ["encoding", "data"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"encoding": {
"const": "base58"
},
"data": {
"type": "string"
}
},
"required": ["encoding", "data"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"encoding": {
"const": "base64"
},
"data": {
"type": "string"
}
},
"required": ["encoding", "data"],
"additionalProperties": false
}
]
},
"required": ["proposalInstructions", "storagePayload"],
"additionalProperties": false
}
Example of running payload container:
$ docker run --rm -e CAMB_INPUT='{"poaName": "test", "proposalStorageKey": "test"}' check-oracle | jq .
{
"proposalInstructions": [
{
"programAddress": "ECb6jyKXDTE8NjVjsKgNpjSjcv4h2E7JQ42yKqWihBQE",
"accounts": [
{
"address": "Qrszp1YM5zEW38JgHPMQVedeZ3A14HmaanB3z65d8Ps",
"role": 1
},
{
"address": "ExLXdCbiXhdmRw3MzmNFGPexCgSuoRpC3YTnGQ6dNekP",
"role": 0
},
{
"address": "Sysvar1nstructions1111111111111111111111111",
"role": 0
},
{
"address": "ECb6jyKXDTE8NjVjsKgNpjSjcv4h2E7JQ42yKqWihBQE",
"role": 0
}
],
"data": "EmqjS8fy7HCvpUiTJqJxGB"
}
],
"storagePayload": {
"encoding": "utf-8",
"data": "Local time: 1746009154987"
}
}
Build payload container image
git clone https://github.com/cambrianone/payload-images
cd ./payload-images/check-oracle
docker build -t payload-check-oracle .
Run payload
Send image name of payload container from the previous step (payload-check-oracle
) to the AVS instance
camb payload run-container -a <AVS public key | AVS URL> -p [period in seconds] payload-check-oracle
AVS broadcasts payload container image name to running operators
Operators run payload containers
Payload containers return data to store in oracle storage and proposal instructions
Operators invoke
store_to_storage
instruction in PoA program to store data in oracle storageOperators invoke
handle_proposal
instruction in PoA program with proposal instructions
Last updated