Layr8.Client (layr8 v0.2.9)

Copy Markdown View Source

Main entry point for interacting with the Layr8 platform.

Lifecycle

# 1. Start the client
{:ok, client} = Layr8.Client.start_link(%{
  node_url: "wss://node.example.com/plugin_socket/websocket",
  api_key: "my-api-key"
})

# 2. Register message handlers BEFORE connect
:ok = Layr8.Client.handle(client, "https://example.com/proto/1.0/request", fn msg ->
  {:reply, %Layr8.Message{type: "https://example.com/proto/1.0/response", body: %{text: "pong"}}}
end)

# 3. Connect to the cloud-node
:ok = Layr8.Client.connect(client)

# 4. Send messages
:ok = Layr8.Client.send(client, %Layr8.Message{
  type: "https://example.com/proto/1.0/request",
  to: ["did:example:bob"],
  body: %{text: "ping"}
})

# 5. Request/response
{:ok, response} = Layr8.Client.request(client, %Layr8.Message{
  type: "https://example.com/proto/1.0/request",
  to: ["did:example:bob"],
  body: %{text: "ping"}
})

# 6. Shut down
:ok = Layr8.Client.close(client)

Events

Subscribe to lifecycle events by passing callbacks in start_link/1 opts:

  • :on_disconnect(reason :: term() -> any())
  • :on_reconnect(() -> any())

Credentials & Presentations

Credential and presentation operations use the REST API (no WebSocket required):

{:ok, jwt} = Layr8.Client.sign_credential(client, credential, issuer_did: "did:example:alice")

Summary

Functions

Returns a specification to start this module under a supervisor.

Gracefully shuts down the client.

Establishes the WebSocket connection and joins the Phoenix channel.

Returns the agent's DID — either provided in config or assigned by the node on connect.

Retrieves a stored credential by ID.

Registers a handler for a DIDComm message type.

Registers a catch-all handler invoked when no specific handler matches.

Lists stored credentials for a holder. Defaults: holder = did/1.

Sets up MCP (Model Context Protocol) over DIDComm on a protocol base and returns a binding whose Layr8.Mcp.peer/2 yields a caller.

Forgets the cached Verifiable Grants for did (default: this agent's), so the next message re-reads them.

Sends a DIDComm message and waits for a correlated response (matched by thread ID).

request/3 without the raises: returns {:ok, Message.t()} or {:error, reason}:timeout, :not_connected, or {:problem_report, code, comment}.

Sends a DIDComm message.

Signs a W3C Verifiable Credential. Defaults: issuer = did/1, format = "compact_jwt".

Signs a W3C Verifiable Presentation. Defaults: holder = did/1, format = "compact_jwt".

Starts the Layr8 Client GenServer.

Stores a signed credential JWT for a holder. Defaults: holder = did/1.

Verifies a signed credential. Defaults: verifier = did/1.

Verifies a signed presentation. Defaults: verifier = did/1.

Types

start_opts()

@type start_opts() :: %{
  optional(:node_url) => String.t(),
  optional(:api_key) => String.t(),
  optional(:agent_did) => String.t(),
  optional(:on_disconnect) => (term() -> any()),
  optional(:on_reconnect) => (-> any()),
  optional(:on_grant_miss) => (map() -> any()),
  optional(:did_spec) => map()
}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

close(pid)

@spec close(pid()) :: :ok

Gracefully shuts down the client.

connect(pid)

@spec connect(pid()) :: :ok

Establishes the WebSocket connection and joins the Phoenix channel.

Blocks until the join is acknowledged. Returns :ok on success or raises on error.

did(pid)

@spec did(pid()) :: String.t()

Returns the agent's DID — either provided in config or assigned by the node on connect.

get_credential(pid, id)

@spec get_credential(pid(), String.t()) :: {:ok, map()} | {:error, term()}

Retrieves a stored credential by ID.

handle(pid, msg_type, fun)

@spec handle(pid(), String.t(), Layr8.Handler.handler_fn()) :: :ok

Registers a handler for a DIDComm message type.

Must be called before connect/1. Raises Layr8.AlreadyConnectedError if called after.

Handler Signature

fn msg -> {:reply, %Layr8.Message{...}} | :noreply | :pass | {:error, reason} end

handle_all(pid, fun)

@spec handle_all(pid(), Layr8.Handler.handler_fn()) :: :ok

Registers a catch-all handler invoked when no specific handler matches.

Must be called before connect/1. Raises Layr8.AlreadyConnectedError if called after.

Handler Signature

fn msg -> {:reply, %Layr8.Message{...}} | :noreply | :pass | {:error, reason} end

list_credentials(pid, opts \\ [])

@spec list_credentials(
  pid(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}

Lists stored credentials for a holder. Defaults: holder = did/1.

mcp(pid, base \\ nil)

@spec mcp(pid(), String.t()) :: {:ok, Layr8.Mcp.Binding.t()} | {:error, term()}

Sets up MCP (Model Context Protocol) over DIDComm on a protocol base and returns a binding whose Layr8.Mcp.peer/2 yields a caller.

Must be called before connect/1 (like handle/3): it registers the protocol subscription the cloud-node needs in order to deliver #{base}/* replies. Idempotent per base — calling it twice returns a second binding over the same subscription rather than raising the way a duplicate handle/3 would. Compose freely with your own handle/3 registrations.

See Layr8.Mcp for the call surface.

refresh_grants(pid, did \\ nil)

@spec refresh_grants(pid(), String.t() | nil) :: :ok

Forgets the cached Verifiable Grants for did (default: this agent's), so the next message re-reads them.

The cache TTL is the whole freshness story: a grant minted seconds ago is invisible until it lapses. An agent that has just been TOLD it was granted something — by a request/approve flow, or by a person on the other end of a chat — should not have to wait out a timer it cannot see.

request(pid, msg, opts \\ [])

@spec request(pid(), Layr8.Message.t() | map(), keyword()) :: {:ok, Layr8.Message.t()}

Sends a DIDComm message and waits for a correlated response (matched by thread ID).

Returns {:ok, Layr8.Message.t()} or raises Layr8.ProblemReportError / Layr8.NotConnectedError.

Options

  • :timeout — milliseconds to wait for a reply (default 30s)
  • :parent_thread — set pthid for nested thread correlation

request_result(pid, msg, opts \\ [])

@spec request_result(pid(), Layr8.Message.t() | map(), keyword()) ::
  {:ok, Layr8.Message.t()} | {:error, term()}

request/3 without the raises: returns {:ok, Message.t()} or {:error, reason}:timeout, :not_connected, or {:problem_report, code, comment}.

Same request path, same options. This exists because a remote call failing is an ordinary outcome for some callers (Layr8.Mcp routes on it rather than rescuing), while for others an unanswered request is exceptional — the SDK should not force either one to convert.

send(pid, msg, opts \\ [])

@spec send(pid(), Layr8.Message.t() | map(), keyword()) :: :ok

Sends a DIDComm message.

By default waits for server acknowledgment. Pass fire_and_forget: true to skip.

Returns :ok or raises Layr8.NotConnectedError.

sign_credential(pid, credential, opts \\ [])

@spec sign_credential(pid(), map(), keyword()) :: {:ok, String.t()} | {:error, term()}

Signs a W3C Verifiable Credential. Defaults: issuer = did/1, format = "compact_jwt".

sign_presentation(pid, credentials, opts \\ [])

@spec sign_presentation(pid(), [String.t()], keyword()) ::
  {:ok, String.t()} | {:error, term()}

Signs a W3C Verifiable Presentation. Defaults: holder = did/1, format = "compact_jwt".

start_link(opts \\ %{})

@spec start_link(start_opts()) :: GenServer.on_start()

Starts the Layr8 Client GenServer.

Accepts the same keys as Layr8.Config.resolve!/1 plus:

  • :on_disconnect — called with the disconnect reason
  • :on_reconnect — called after successful reconnection
  • :on_grant_miss — called when a message went out with NO covering Verifiable Grant and the node then denied it, when the covering set had to be capped, or when the grants could not be read at all. See Layr8.Wallet: the sender is the only party that knows nothing was attached, and the node's denial names the grant it could not find, which sends people to check a grant that is fine.

Raises Layr8.Error if required config fields are missing.

store_credential(pid, jwt, opts \\ [])

@spec store_credential(pid(), String.t(), keyword()) ::
  {:ok, map()} | {:error, term()}

Stores a signed credential JWT for a holder. Defaults: holder = did/1.

verify_credential(pid, signed, opts \\ [])

@spec verify_credential(pid(), String.t(), keyword()) ::
  {:ok, map()} | {:error, term()}

Verifies a signed credential. Defaults: verifier = did/1.

verify_presentation(pid, signed, opts \\ [])

@spec verify_presentation(pid(), String.t(), keyword()) ::
  {:ok, map()} | {:error, term()}

Verifies a signed presentation. Defaults: verifier = did/1.