Skip to content

Commit

Permalink
feat: ceramic cli
Browse files Browse the repository at this point in the history
  • Loading branch information
dvilelaf committed Jun 12, 2024
1 parent f36da23 commit 8354b61
Show file tree
Hide file tree
Showing 6 changed files with 472 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ impact_evaluator_local/
farcaster_test/
!packages/valory/agents/farcaster_test/
keys1.json

ceramic/dids.py
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ py-multicodec = "==0.2.1"
py-eth-sig-utils = "*"
protobuf = "<4.25.0,>=4.21.6"
farcaster = "==0.7.11"
pycryptodome = "==3.18.0"
jwcrypto = "*"

[requires]
python_version = "3.10"
112 changes: 112 additions & 0 deletions ceramic/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import requests
import os
from payloads import (build_commit_payload, build_data_from_commits,
build_genesis_payload)

HTTP_OK = 200

class Ceramic:
"""Ceramic client"""
# HTTP API docs:
# https://developers.ceramic.network/build/http/api/#ceramic-http-api

# Protocol specification:
# https://github.com/ceramicnetwork/ceramic/blob/main/SPECIFICATION.md

CLAY_URL_BASE = "https://ceramic-clay.3boxlabs.com" # read & write, testing
LOCAL_URL_BASE = "https://locahost:7007"
GATEWAY_URL_BASE = "https://gateway-clay.ceramic.network" # gateway node (read only)
DEFAULT_URL_BASE = "https://ceramic-valory.hirenodes.io"

def __init__(self, url_base=None) -> None:
if not url_base:
url_base = os.getenv("CERAMIC_NODE", None)
if not url_base:
url_base = self.DEFAULT_URL_BASE
print(f"CERAMIC_NODE was not set. Using default value: {self.DEFAULT_URL_BASE}")
self.url_base = url_base

def _make_request(self, url: str, request_type: str="get", json_data: dict={}):
"""Handle requests"""
response = None
if request_type not in ("get", "post", "delete"):
raise ValueError(f"Request method '{request_type}' not supported")
if request_type == "get":
response = requests.get(url)
if request_type == "post":
response = requests.post(url, json=json_data)
if request_type == "delete":
response = requests.delete(url)
if not response:
return None, None
headers = response.headers.get("content-type")
data = response.json() if headers and "application/json" in headers else {}
return response.status_code, data

def _request_create_stream(self, genesis_payload: dict):
return self._make_request(f"{self.url_base}/api/v0/streams", "post", json_data=genesis_payload)

def _request_create_commit(self, commit_payload: dict):
return self._make_request(f"{self.url_base}/api/v0/commits", "post", json_data=commit_payload)

def get_stream(self, streamid: str):
return self._make_request(f"{self.url_base}/api/v0/streams/{streamid}", "get")

def pin_stream(self, streamid: str):
return self._make_request(f"{self.url_base}/api/v0/pins/{streamid}", "post")

def unpin_stream(self, streamid: str):
return self._make_request(f"{self.url_base}/api/v0/pins/{streamid}", "delete")

def get_commits(self, streamid: str):
return self._make_request(f"{self.url_base}/api/v0/commits/{streamid}", "get")

def get_data(self, stream_id: str) -> tuple:
code, data = self.get_commits(stream_id)

if code != HTTP_OK or not data:
print(f"Error {code} fetching data from stream {stream_id}")
return None, None, None

# Extract first and last commit info
genesis_cid_str = data["commits"][0]["cid"]
previous_cid_str = data["commits"][-1]["cid"]

# Rebuild the current data
return build_data_from_commits(data["commits"]), genesis_cid_str, previous_cid_str

def create_stream(self, did: str, did_seed: str, data: dict, extra_metadata: dict = {}) -> str:
# Prepare the genesis payload
genesis_payload = build_genesis_payload(did, did_seed, data, extra_metadata)

# Create a new stream
code, data = self._request_create_stream(genesis_payload=genesis_payload)
if code != HTTP_OK or not data:
print(f"Error creating stream: {data}")
return data['streamId']

def update_stream(self, did: str, did_seed: str, stream_id: str, new_data: dict) -> None:
# Get all the commits
data, genesis_cid_str, previous_cid_str = self.get_data(stream_id)
if not data:
print(f"Error updating stream {stream_id}")
return

# Prepare the commit payload
commit_payload = build_commit_payload(
did,
did_seed,
stream_id,
data,
new_data,
genesis_cid_str,
previous_cid_str
)

# Create a new commit
code, data = self._request_create_commit(commit_payload=commit_payload)
if code != HTTP_OK or not data:
print(f"Error while updating stream {stream_id}")



68 changes: 68 additions & 0 deletions ceramic/ceramic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import click
from api import Ceramic
import json
from dids import dids

# A dids.py containin at least some did is required
# dids = {
# "dummy": {
# "did": "did:key:<did>",
# "seed": "<did_seed>"
# }
# }

def write_file(data, path):
"""Write data to file"""
with open(path, "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)

def read_file(path):
"""Read data from file"""
with open(path, "r", encoding="utf-8") as file:
return json.load(file)

@click.group()
def cli():
"""Ceramic cli."""
pass


@cli.command()
@click.argument("stream_id")
@click.option("--file", default=None, help="Path to the output file")
def read(stream_id, file):
"""Read a stream"""
api = Ceramic()
data, _, _ = api.get_data(stream_id)
if file:
write_file(data, file)
click.echo(f"Read stream {stream_id}:\n{json.dumps(data, indent=4)}")


@cli.command()
@click.argument("file")
@click.argument("did_name")
def create(file, did_name):
"""Create a new stream from file"""
api = Ceramic()
data = read_file(file)
did = dids.get(did_name, {})
stream_id = api.create_stream(did.get("did", ""), did.get("seed", ""), data)
click.echo(f"Created stream {stream_id}")


@cli.command()
@click.argument("stream_id")
@click.argument("file")
@click.argument("did_name")
def update(stream_id, file, did_name):
"""Update a stream from file."""
api = Ceramic()
new_data = read_file(file)
did = dids.get(did_name, {})
api.update_stream(did.get("did", ""), did.get("seed", ""), stream_id, new_data)
click.echo("Updated stream")


if __name__ == "__main__":
cli()
Loading

0 comments on commit 8354b61

Please sign in to comment.