# Cambrian CLI

## Prerequisites

* node.js >= 22.0.0
* docker >= 20.0.0

## Installing Cambrian utility

```
npm i --global @cambrianone/camb-client@latest
```

## Initialization

{% @mermaid/diagram content="flowchart TB
subgraph Offchain
avs\[AVS]

```
    subgraph Operators
        operator1[Operator1]
        operator2[Operator2]
        operator3[Operator3]
    end

end

subgraph Onchain
    poa[Cambrian PoA program]

    oracle[Cambrian Oracle program]

    jito-restaking[Jito Restaking program]

    jito-vault[Jito Vault program]
end

cli["Cambrian CLI utility"]

cli --> |1.1 Scaffolds AVS| avs
cli --> |1.2 Scaffolds Operators| Operators
cli --> |1.3 Initializes PoA and operators onchain| Onchain" %}
```

### Scaffold AVS and initialize PoA onchain

```bash
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 operators
* `Enter AVS HTTP port to bind to` - provide an HTTP port to bind to, it should be reachable from all the operators
* `Enter AVS WS port to bind to` - provide a WebSockers port to bind to, it should be reachable from all the operators
* `Enter admin private key or press enter to generate a new one` - admin private key / keypair as array of `uint8` or as base58-encoded string
* `Enter Solana API URL or press enter to use default` - Solana JSON RPC endpoint
* `Enter Solana API WS URL or press enter to use default` - Solana WebSockets endpoint
* `Enter 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 storage
* `Enter storage space` - common oracle storage space, in bytes
* `Enter consensus threshold` - The minimum number of operators required to approve a proposal before execution
* `Enter stake threshold` - The **minimum stake** required for an operator to participate in CCP

### List installed AVS instances

```bash
camb avs list
```

### Start AVS:

```bash
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.

```bash
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 endpoint
* `Enter 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:

```bash
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

{% @mermaid/diagram content="flowchart TB
subgraph Offchain
avs\[AVS]

```
    subgraph Operators
        operator1[Operator1]
        operator2[Operator2]
        operator3[Operator3]
    end

end

subgraph Onchain
    poa[Cambrian PoA program]

    oracle[Cambrian Oracle program]

    jito-restaking[Jito Restaking program]

    jito-vault[Jito Vault program]
end

cli["Cambrian CLI utility"]


cli --> |2.1 Starts AVS| avs
cli --> |2.2 Starts Operators| Operators
" %}
```

### List installed operator nodes (outputs voter public keys)

```bash
camb operator list -a <AVS public key>
```

### Start operators:

```bash
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

{% @mermaid/diagram content="flowchart TB
subgraph Offchain
avs\[AVS]

```
    subgraph Operators
        operator1[Operator1]
        operator2[Operator2]
        operator3[Operator3]
        payload-container1([Payload Container])
        payload-container2([Payload Container])
        payload-container3([Payload Container])
    end

end

subgraph Onchain
    poa[Cambrian PoA program]

    oracle[Cambrian Oracle program]

    jito-restaking[Jito Restaking program]

    jito-vault[Jito Vault program]
end

cli["Cambrian CLI utility"]


cli --> |3.1 Sends name of payload container image to AVS| avs
avs --> |3.2 Broadcasts name of payload container image to Operators| Operators

operator1 --> |3.3 Starts container| payload-container1
payload-container1 --> |3.4 Returns oracle data and proposal instructions| operator1

operator2 --> |3.3 Starts container| payload-container2
payload-container2 --> |3.4 Returns oracle data and proposal instructions| operator2

operator3 --> |3.3 Starts container| payload-container3
payload-container3 --> |3.4 Returns oracle data and proposal instructions| operator3

Operators --> |3.5 Store data to oracle storage| oracle
Operators --> |3.6 Send proposal instructions| poa" %}
```

### 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:

```typescript
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:

```typescript
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:

```typescript
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:

```json
{
  "$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:

```bash
$ 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

```bash
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

```bash
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 storage
* Operators invoke `handle_proposal` instruction in PoA program with proposal instructions
