From 562b2e9fabdfedca9384d068b1bff97cd06988e7 Mon Sep 17 00:00:00 2001 From: devops-talus Date: Mon, 3 Nov 2025 10:55:22 +0100 Subject: [PATCH 1/8] Docs update from nexus (#151) * Update docs from nexus (commit: c6f0df3a1eca5b8e5299a2e12e003bf2299038a2) * Update SUMMARY.md with new files under 'Looking for a home' --------- Co-authored-by: kouks <3684840+kouks@users.noreply.github.com> --- SUMMARY.md | 33 + nexus/TAP/agent-development.md | 36 + nexus/TAP/default-tap.md | 369 +++ nexus/conventions/sui-move.md | 8 + nexus/crates/leader.md | 168 ++ nexus/crates/sui-tx.md | 9 + .../branching_and_conditionals.md | 91 + nexus/flow-controls/looping.md | 137 ++ nexus/glossary.md | 28 + nexus/images/dag-rules/dag.1.png | Bin 0 -> 7531 bytes nexus/images/dag-rules/dag.2.png | Bin 0 -> 7696 bytes nexus/images/dag-rules/dag.3.png | Bin 0 -> 7239 bytes nexus/images/dag-rules/dag.4.png | Bin 0 -> 7977 bytes nexus/images/dag-rules/dag.5.png | Bin 0 -> 11642 bytes nexus/images/dag-rules/dag.6.png | Bin 0 -> 11566 bytes nexus/images/dag-rules/dag.7.png | Bin 0 -> 8573 bytes nexus/images/dag-rules/dag.8.png | Bin 0 -> 9638 bytes nexus/images/dag-rules/dag.legend.png | Bin 0 -> 24353 bytes nexus/images/nexus-interface-v1.png | Bin 0 -> 266153 bytes nexus/index.md | 103 + nexus/packages/nexus-interface.md | 53 + nexus/packages/primitives.md | 214 ++ .../packages/reference/nexus_interface/v1.md | 87 + .../reference/nexus_interface/version.md | 130 + .../reference/nexus_primitives/data.md | 169 ++ .../reference/nexus_primitives/event.md | 65 + .../reference/nexus_primitives/owner_cap.md | 478 ++++ .../nexus_primitives/proof_of_uid.md | 347 +++ .../nexus_primitives/proven_value.md | 259 ++ .../packages/reference/nexus_workflow/dag.md | 2084 +++++++++++++++++ .../nexus_workflow/default_gas_extension.md | 158 ++ .../reference/nexus_workflow/default_tap.md | 236 ++ .../packages/reference/nexus_workflow/gas.md | 868 +++++++ .../reference/nexus_workflow/leader_cap.md | 110 + .../packages/reference/nexus_workflow/main.md | 84 + .../reference/nexus_workflow/tool_registry.md | 745 ++++++ nexus/packages/workflow.md | 244 ++ nexus/tokenomics/default-gas-extension.md | 133 ++ nexus/tokenomics/gas-service.md | 359 +++ nexus/tokenomics/tokenomics.md | 66 + nexus/tool.md | 77 + 41 files changed, 7948 insertions(+) create mode 100644 nexus/TAP/agent-development.md create mode 100644 nexus/TAP/default-tap.md create mode 100644 nexus/conventions/sui-move.md create mode 100644 nexus/crates/leader.md create mode 100644 nexus/crates/sui-tx.md create mode 100644 nexus/flow-controls/branching_and_conditionals.md create mode 100644 nexus/flow-controls/looping.md create mode 100644 nexus/glossary.md create mode 100644 nexus/images/dag-rules/dag.1.png create mode 100644 nexus/images/dag-rules/dag.2.png create mode 100644 nexus/images/dag-rules/dag.3.png create mode 100644 nexus/images/dag-rules/dag.4.png create mode 100644 nexus/images/dag-rules/dag.5.png create mode 100644 nexus/images/dag-rules/dag.6.png create mode 100644 nexus/images/dag-rules/dag.7.png create mode 100644 nexus/images/dag-rules/dag.8.png create mode 100644 nexus/images/dag-rules/dag.legend.png create mode 100644 nexus/images/nexus-interface-v1.png create mode 100644 nexus/index.md create mode 100644 nexus/packages/nexus-interface.md create mode 100644 nexus/packages/primitives.md create mode 100644 nexus/packages/reference/nexus_interface/v1.md create mode 100644 nexus/packages/reference/nexus_interface/version.md create mode 100644 nexus/packages/reference/nexus_primitives/data.md create mode 100644 nexus/packages/reference/nexus_primitives/event.md create mode 100644 nexus/packages/reference/nexus_primitives/owner_cap.md create mode 100644 nexus/packages/reference/nexus_primitives/proof_of_uid.md create mode 100644 nexus/packages/reference/nexus_primitives/proven_value.md create mode 100644 nexus/packages/reference/nexus_workflow/dag.md create mode 100644 nexus/packages/reference/nexus_workflow/default_gas_extension.md create mode 100644 nexus/packages/reference/nexus_workflow/default_tap.md create mode 100644 nexus/packages/reference/nexus_workflow/gas.md create mode 100644 nexus/packages/reference/nexus_workflow/leader_cap.md create mode 100644 nexus/packages/reference/nexus_workflow/main.md create mode 100644 nexus/packages/reference/nexus_workflow/tool_registry.md create mode 100644 nexus/packages/workflow.md create mode 100644 nexus/tokenomics/default-gas-extension.md create mode 100644 nexus/tokenomics/gas-service.md create mode 100644 nexus/tokenomics/tokenomics.md create mode 100644 nexus/tool.md diff --git a/SUMMARY.md b/SUMMARY.md index a9141a9..08bd815 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -72,3 +72,36 @@ - [LLM: OpenAI Chat Completion](tools/llm-openai-chat-completion/README.md) - [Social : X](tools/social-twitter/README.md) - [Storage: Walrus](tools/storage-walrus/README.md) + +## Looking for a home + +* [nexus/TAP/agent-development.md](nexus/TAP/agent-development.md) +* [nexus/TAP/default-tap.md](nexus/TAP/default-tap.md) +* [nexus/conventions/sui-move.md](nexus/conventions/sui-move.md) +* [nexus/crates/leader.md](nexus/crates/leader.md) +* [nexus/crates/sui-tx.md](nexus/crates/sui-tx.md) +* [nexus/flow-controls/branching_and_conditionals.md](nexus/flow-controls/branching_and_conditionals.md) +* [nexus/flow-controls/looping.md](nexus/flow-controls/looping.md) +* [nexus/glossary.md](nexus/glossary.md) +* [nexus/index.md](nexus/index.md) +* [nexus/packages/nexus-interface.md](nexus/packages/nexus-interface.md) +* [nexus/packages/primitives.md](nexus/packages/primitives.md) +* [nexus/packages/reference/nexus_interface/v1.md](nexus/packages/reference/nexus_interface/v1.md) +* [nexus/packages/reference/nexus_interface/version.md](nexus/packages/reference/nexus_interface/version.md) +* [nexus/packages/reference/nexus_primitives/data.md](nexus/packages/reference/nexus_primitives/data.md) +* [nexus/packages/reference/nexus_primitives/event.md](nexus/packages/reference/nexus_primitives/event.md) +* [nexus/packages/reference/nexus_primitives/owner_cap.md](nexus/packages/reference/nexus_primitives/owner_cap.md) +* [nexus/packages/reference/nexus_primitives/proof_of_uid.md](nexus/packages/reference/nexus_primitives/proof_of_uid.md) +* [nexus/packages/reference/nexus_primitives/proven_value.md](nexus/packages/reference/nexus_primitives/proven_value.md) +* [nexus/packages/reference/nexus_workflow/dag.md](nexus/packages/reference/nexus_workflow/dag.md) +* [nexus/packages/reference/nexus_workflow/default_gas_extension.md](nexus/packages/reference/nexus_workflow/default_gas_extension.md) +* [nexus/packages/reference/nexus_workflow/default_tap.md](nexus/packages/reference/nexus_workflow/default_tap.md) +* [nexus/packages/reference/nexus_workflow/gas.md](nexus/packages/reference/nexus_workflow/gas.md) +* [nexus/packages/reference/nexus_workflow/leader_cap.md](nexus/packages/reference/nexus_workflow/leader_cap.md) +* [nexus/packages/reference/nexus_workflow/main.md](nexus/packages/reference/nexus_workflow/main.md) +* [nexus/packages/reference/nexus_workflow/tool_registry.md](nexus/packages/reference/nexus_workflow/tool_registry.md) +* [nexus/packages/workflow.md](nexus/packages/workflow.md) +* [nexus/tokenomics/default-gas-extension.md](nexus/tokenomics/default-gas-extension.md) +* [nexus/tokenomics/gas-service.md](nexus/tokenomics/gas-service.md) +* [nexus/tokenomics/tokenomics.md](nexus/tokenomics/tokenomics.md) +* [nexus/tool.md](nexus/tool.md) diff --git a/nexus/TAP/agent-development.md b/nexus/TAP/agent-development.md new file mode 100644 index 0000000..f990a51 --- /dev/null +++ b/nexus/TAP/agent-development.md @@ -0,0 +1,36 @@ +# Agent Development + +Nexus, as an agentic framework, provides a useful set of abstractions and, both onchain and offchain components that orchestrate the development, registration and discovery of tools, the composition of those tools in workflows and finally the workflow execution. Find the definitions of these terms in the [glossary](../glossary.md) in case they are unfamiliar. + +As a result, Nexus greatly facilitates building Talus Agents for [agent developers](../index.md#actors). + +{% hint style="info" %} +A Talus agent is an onchain identity associated with one or more onchain assets and workflow services +{% endhint %} + +## Development Requirements + +There's a number of ways an agent developer could build their agents, with varying levels of complexity and customization: + +* **Custom TAP + custom workflow (hard)**: Construct the workflow(s) (DAGs) AND write a Sui Move "Talus Agent Package" (TAP) that holds the workflows(s) as well as some custom business logic related to it. This is the most complex as it requires the agent developer to develop a custom TAP package and define the DAG. In return you get the highest amount of customization. + +* **Custom TAP + existing workflow (medium)**: Write a Sui Move TAP that uses existing workflows in a _plug-and-play_ manner. There could be additional business logic added to the TAP. This is an intermediate level of complexity that gives a sufficient amount of customization provided there are workflows available that satisfy requirements. + +* **Default TAP + custom workflow (easy)**: Only construct the workflow(s) (DAGs) and use the [_default TAP_](default-tap.md) to execute it and call the workflow engine. The default TAP provides a template TAP implementation and reduces the development work to the configuration of the workflow DAG(s). This simplifies development considerably at the cost of flexibility in adding business logic to trigger DAG execution. + +{% hint style="success" %} +How to choose the way to build? + +The choice of how to build the agent will be determined by weighing: + +* familiarity with Sui Move, +* the need for custom logic to govern workflow execution, payment etc. +* the availability of existing workflows that meet the application's needs +{% endhint %} + +## Explore further + +Continue learning about agent development with the following sections: + +* the [Nexus interface for TAPs](../packages/nexus-interface.md) section, outlining what interface the TAP must comply with +* the reference [default TAP](default-tap.md) implementation, that serves as an example for a bare-bones TAP \ No newline at end of file diff --git a/nexus/TAP/default-tap.md b/nexus/TAP/default-tap.md new file mode 100644 index 0000000..dfedffc --- /dev/null +++ b/nexus/TAP/default-tap.md @@ -0,0 +1,369 @@ +# Default Talus Agent Package (TAP) + +## Overview + +The Default TAP is a useful helper component for Nexus agent developers. It serves as a template or base implementation of a Talus Agent Package (TAP) that can be used for examples, tests, or to run JSON-defined DAGs from the CLI. + +## Interface Compliance + +The Default TAP implements the [Nexus Interface V1][nexus-interface-v1] specification, which defines the required functionality for any Talus Agent Package to integrate with the Nexus workflow engine. Key interface requirements include: + +1. **Version Management** + + - Must declare and maintain interface version compatibility. + - Must support version checking for backward compatibility. + +1. **Workflow Management** + + - Must handle worksheet management and state tracking. + - Must support tool evaluation confirmation. + +1. **Authorization** + - Must implement witness-based authorization. + - Must support package upgrade mechanisms. + +For detailed interface requirements, see the [Nexus Interface Documentation][nexus-interface]. + + + +{% hint style="info" %} +In the code snippets below, we reference some Sui Move patterns (e.g. hot potato), please refer to the [primitives package doc][primitives] for more information on the approach taken here. +{% endhint %} + +### DefaultTAP Structure + +The agent will be represented by the `DefaultTAP` struct, which is a shared object that contains: + +- `id`: A unique identifier for the TAP instance. +- `witness`: A Bag containing authorization tokens for package identification and upgrade management. +- `iv`: The interface version (currently v1) that clients can use to determine compatibility. + +```rust +use nexus_interface::version::InterfaceVersion; +use sui::bag::{Self, Bag}; +use sui::object::UID; + +/// Shared object implementing the Nexus Interface v1 specification. +public struct DefaultTAP has key { + id: UID, + /// On package upgrade, we'll want to replace the previous witness with a + /// new one that identifies the new package. + /// + /// This is because as of now Sui doesn't give us access to the package ID + /// so we need to always create a new type and query it from the type via + /// its type name. + witness: Bag, + /// Clients can search the talus agent shared object for this type to + /// determine what interface to expect. + iv: InterfaceVersion, +} +``` + +### Constructor and Leader Registration + +The DefaultTAP is created using the `new()` constructor function, which: + +1. Creates a new DefaultTAP instance with a unique ID. +1. Initializes a witness token for package identification. +1. Sets the Nexus interface version to v1. +1. Registers the TAP with the Nexus leader. + +```rust +use nexus_interface::version::{Self, InterfaceVersion}; +use sui::bag::{Self, Bag}; +use sui::object::UID; +use sui::transfer::share_object; + +public(package) fun new(ctx: &mut TxContext) { + let default_tap = DefaultTAP { + id: object::new(ctx), + witness: { + let mut b = bag::new(ctx); + b.add(b"witness", DefaultTAPV1Witness { id: object::new(ctx) }); + b + }, + iv: version::v(1), + }; + + announce_interface_package( + default_tap.get_witness(), + vector[object::id(&default_tap)], + ); + + share_object(default_tap); +} + +/// with... +public struct DefaultTAPV1Witness has key, store { + id: UID, +} +``` + +### Worksheet + +The worksheet function is a core requirement of the Nexus Interface V1 specification. It creates a proof of UID (Unique Identifier) that serves as a "stamp collector" for tracking workflow execution state. This proof: + +1. Acts as a hot-potato object that collects execution confirmations from Nexus components like the DAG. +1. Must be constructed with a type defined in the same package and module that implements the interface. +1. Is used to verify that required operations have been performed in the correct sequence. + +```rust +use nexus_interface::version::InterfaceVersion; +use nexus_primitives::proof_of_uid::{Self, ProofOfUID}; + +public fun worksheet(self: &DefaultTAP): ProofOfUID { + self.iv.expect_v(1); + proof_of_uid::new_with_type(&self.get_witness().id, self.get_witness()) +} +``` + +### Tool Evaluation Confirmation + +The `confirm_tool_eval_for_walk` function is another core requirement of the Nexus Interface V1 specification. It is invoked by the Nexus Leader after a workflow contract has advanced the DAG to: + +1. Consume the worksheet hot-potato. +1. Verify that all required confirmations have been collected. +1. Complete the tool evaluation cycle for a specific walk in the workflow. + +```rust +use nexus_interface::version::InterfaceVersion + +public fun confirm_tool_eval_for_walk( + self: &mut DefaultTAP, + worksheet: ProofOfUID, +) { + self.iv.expect_v(1); + worksheet.consume(&self.get_witness().id); +} + +/// with... +fun get_witness(self: &DefaultTAP): &DefaultTAPV1Witness { + self.witness.borrow(b"witness") +} +``` + +## DAG Execution + +The Default TAP works in conjunction with the Nexus workflow engine, which provides: + +1. **DAG Implementation** + + - Directed Acyclic Graph data structure for modeling complex workflows. + - Support for vertices, edges, and input/output ports. + - Entry group management for workflow initiation. + +1. **Tool Registry** + + - Registration and management of available tools. + +1. **Tool Invocation** + - Support for tool execution invocation, onchain or offchain. + - Leader capability management. + +Where the above steps effectively ensure interface compliance, the (default) TAP also needs to enable DAG execution. + +### Begin DAG Execution + +The DAG execution is initiated through the `begin_dag_execution` function. + +This function: + +1. Takes a DAG and entry vertices with their input data. +1. Creates a worksheet to track execution state. +1. Begins execution of the entry group through the DAG. +1. Requests the network to execute walks through the DAG. +1. Shares the execution object for tracking progress. + +```rust +use nexus_primitives::data::NexusData; +use nexus_primitives::proof_of_uid::{Self, ProofOfUID}; +use nexus_workflow::dag::{DAG, Vertex, InputPort, EntryGroup}; + +/// Invokes the provided entry vertex on a DAG with the provided input data for +/// each input port. +public fun begin_dag_execution( + self: &mut DefaultTAP, + dag: &DAG, + network: ID, + entry_group: EntryGroup, + mut with_vertex_inputs: VecMap>, + ctx: &mut TxContext, +) { + self.iv.expect_v(1); + + let mut entry_vertices = vector[]; + let mut with_vertices_inputs = vector[]; + while (!with_vertex_inputs.is_empty()) { + let (vertex, inputs) = with_vertex_inputs.pop(); + entry_vertices.push_back(vertex); + with_vertices_inputs.push_back(inputs); + }; + + let mut worksheet = self.worksheet(); + + let (mut execution, ticket) = dag.begin_execution_of_entry_group( + &mut worksheet, + network, + entry_group, + entry_vertices, + with_vertices_inputs, + ctx + ); + + worksheet.consume(&self.get_witness().id); + + dag.request_network_to_execute_walks(&mut execution, ticket, ctx); + + public_share_object(execution); +} +``` + +## Security Considerations + +- The TAP uses witness tokens for authorization as required by the [Nexus Interface][nexus-interface]. +- Interface version checking ensures compatibility. +- Worksheet proofs ensure state integrity. +- Tool execution is properly isolated. + +For a broader security analysis of Nexus, refer to the [whitepaper section 4.4][whitepaper]. + +## Full Module Code + +Toggle to see the full module code for the default TAP: + +
+ +Toggle code + +```rust +module nexus_workflow::default_tap; + +//! Module defining a default talus agent package with no added logic but +//! executing the provided DAG. This can be used for examples, tests or to run +//! JSON-defined DAGs from the CLI or via other means. + +use nexus_interface::version::InterfaceVersion; +use nexus_primitives::data::NexusData; +use nexus_primitives::proof_of_uid::{Self, ProofOfUID}; +use nexus_workflow::dag::{DAG, Vertex, InputPort, EntryGroup}; + +use sui::bag::{Self, Bag}; +use sui::transfer::{share_object, public_share_object}; +use sui::vec_map::{VecMap}; + +/// Shared object. +public struct DefaultTAP has key { + id: UID, + /// On package upgrade, we'll want to replace the previous witness with a + /// new one that identifies the new package. + /// + /// This is because as of now Sui doesn't give us access to the package ID + /// so we need to always create a new type and query it from the type via + /// its type name. + witness: Bag, + /// Clients can search the talus agent shared object for this type to + /// determine what interface to expect. + iv: InterfaceVersion, +} + +/// Used to identify the package::module deployment. +/// +/// Will also serve as authorization token to change list of shared objects for +/// nexus interface v1. +public struct DefaultTAPV1Witness has key, store { + id: UID, +} + +public(package) fun new(ctx: &mut TxContext) { + let default_tap = DefaultTAP { + id: object::new(ctx), + witness: { + let mut b = bag::new(ctx); + b.add(b"witness", DefaultTAPV1Witness { id: object::new(ctx) }); + b + }, + iv: nexus_interface::version::v(1), + }; + + nexus_interface::v1::announce_interface_package( + default_tap.get_witness(), + vector[object::id(&default_tap)], + ); + + share_object(default_tap); +} + +// === Client entry === + +/// Invokes the provided entry vertex on a DAG with the provided input data for +/// each input port. +#[allow(lint(share_owned))] +public fun begin_dag_execution( + self: &mut DefaultTAP, + dag: &DAG, + network: ID, + entry_group: EntryGroup, + mut with_vertex_inputs: VecMap>, + ctx: &mut TxContext, +) { + self.iv.expect_v(1); + + let mut entry_vertices = vector[]; + let mut with_vertices_inputs = vector[]; + while (!with_vertex_inputs.is_empty()) { + let (vertex, inputs) = with_vertex_inputs.pop(); + entry_vertices.push_back(vertex); + with_vertices_inputs.push_back(inputs); + }; + + let mut worksheet = self.worksheet(); + + let (mut execution, ticket) = dag.begin_execution_of_entry_group( + &mut worksheet, + network, + entry_group, + entry_vertices, + with_vertices_inputs, + ctx + ); + + worksheet.consume(&self.get_witness().id); + + dag.request_network_to_execute_walks(&mut execution, ticket, ctx); + + public_share_object(execution); +} + +// === Nexus interface v1 === + +/// Nexus resources can prove that they did a thing. +public fun worksheet(self: &DefaultTAP): ProofOfUID { + self.iv.expect_v(1); + + proof_of_uid::new_with_type(&self.get_witness().id, self.get_witness()) +} + +/// One calls this after workflow contract is done with advancing the DAG. +public fun confirm_tool_eval_for_walk( + self: &mut DefaultTAP, + worksheet: ProofOfUID, +) { + self.iv.expect_v(1); + + worksheet.consume(&self.get_witness().id); +} + +fun get_witness(self: &DefaultTAP): &DefaultTAPV1Witness { + self.witness.borrow(b"witness") +} + +``` + +
+ + + +[whitepaper]: https://talus.network/nexus/whitepaper.pdf +[nexus-interface-v1]: ../packages/nexus-interface.md#v1 +[nexus-interface]: ../packages/nexus-interface.md +[primitives]: ../packages/primitives.md diff --git a/nexus/conventions/sui-move.md b/nexus/conventions/sui-move.md new file mode 100644 index 0000000..6717102 --- /dev/null +++ b/nexus/conventions/sui-move.md @@ -0,0 +1,8 @@ +# Sui Move Conventions + +In Nexus and related packages we (to the best of our ability) follow these conventions. + +## Enum's `_variant_name` + +Each enumeration variant contains a property `_variant_name: AsciiString`, which will always be a static string representing the variant name. +This helps clients to identify the variant because at the moment RPCs do not include the variant name in the response. diff --git a/nexus/crates/leader.md b/nexus/crates/leader.md new file mode 100644 index 0000000..30c9245 --- /dev/null +++ b/nexus/crates/leader.md @@ -0,0 +1,168 @@ +# Leader + +## Sequence diagram + +High-level sequence diagram describing the duties of the Leader. + +```mermaid +sequenceDiagram + participant RG as [On-chain] Registry + participant WF as [On-chain] Workflow + participant LD as [Off-chain] Leader + participant DB as [Off-chain] Indexer + participant TL as [Either] Tool + + critical initialize service + LD-->>LD: load env, global objects, secret mnemonics + LD->>WF: request leader caps and gas coins + LD->>DB: establish a connection to indexer + end + + loop listen for RequestWalkExecutionEvent + WF->>LD: event incoming + LD->>DB: save event in indexer + Note over LD,DB: id, digest, json, retried, error, status: Queued + end + + loop iterate over `status: Queued` events + LD->>DB: update to `status: Active` + LD->>RG: load tool definition from event payload + Note over LD,RG: This definition should be cached + LD->>WF: request input data for tool + LD-->>LD: verify input data based on schema from tool def + LD->>+TL: invoke tool + TL->>-LD: tool finished and returned output data + LD-->>LD: verify output data based on schema from tool def + LD->>LD: wait for open channel + end + + loop channel listening + LD-->>LD: determine execution outcome + + alt successful + LD->>WF: submit execution result TX + LD->>DB: update `status: Successful` + else failed + alt retriable + LD->>DB: update `status: Queued, retried++` + else fatal + LD->>WF: submit execution result TX + LD->>DB: update `status: Dead` + end + end + end + + loop iterate over `status: Active, updated_at < N` events + LD->>WF: submit execution result TX over a channel + LD->>DB: update `status: Dead` + Note over LD, DB: We should aim to avoid this alotgether + end +``` + +## Processes + +There are multiple processes running in parallel in the leader node. These are as follows: + +1. **`RequestWalkExecutionEvent` listener** + +- This process repeatedly queries Sui RPC for new events coming from the Workflow. It then saves these events on the Indexer. +- It persists the last visited cursor, a pointer to the event it has written to our Indexer last. This way we ensure no events are lost in case the Leader goes down. + +2. **`Queued` events execution engine** + +- This part of the Leader queries `status: Queued` events in order to invoke Tools specified on the event. +- Communicates with the Workflow to fetch input data for Tools. +- Communicates with the Tool Registry to fetch Tool schemas in order to validate its input data. +- Invokes Tools with the verified input data. +- Verifies output data from Tools based on its output schemas. +- Finally, it halts until a channel is open to send a TX back to Workflow (this can happen at any point during the execution if it errors). + +3. **Workflow communication channel** + +- N channels can run in parallel where N is the number of `leader_cap`s the Leader has available. +- Receives messages from the execution engine and evaluates the result. Depending on this, multiple outcomes can happen: + - (a) `Successful` - TX to Workflow is sent and status is updated in Indexer to `Successful`. If TX fails, we have to block the channel for 24h. + - (b.i) `Failed.Retriable` - Status is updated back to `Queued` and retires are incremented in Indexer. + - (b.ii) `Failed.Fatal` - TX to Workflow is sent and status is updated in Indexer to `Dead`. If TX fails, we have to block the channel for 24h. + +4. **Stale `Active` events disposal** + +- Events that are still `Active` after a configured time has passed should be disposed of and marked as `Dead`. +- This should also notify Workflow via a TX. + +## High integrity channel + +Some parts of the Leader service use a custom channel implementation that handles indexing of messages sent over this channel, as well as retries and sweeps of stale messages. + +Notably, the event listener<>event executor and the event executor<>merchant processes communicate via this channel. + +The following state diagrams should highlight the functionality of the channel. + +{% hint style="info" %} +Note that this channel "assumes" it has a stable Redis connection. There are edge cases, where dropping events is very unlikely, but possible. One such edge case is if sending a message over this channel fails due to Redis being unavailable but the Sui event listener successfully saves the next page cursor to Redis. This can in the future be improved by handling Redis errors within the channel differently (by for example, halting). +{% endhint %} + +### High integrity channel receiver and sender + +```mermaid +stateDiagram-v2 + state HighIntegrityChannel { + state if_successful <> + [*] --> Receiver: Start + Receiver --> ReceiveMessage: recv + ReceiveMessage --> MoveToActiveSet + MoveToActiveSet --> ProcessMessage + ProcessMessage --> if_successful + if_successful --> NackMessage: Err + if_successful --> AckMessage: Ok + NackMessage --> MoveBackToQueued + AckMessage --> RemoveMessage + MoveBackToQueued --> [*] + RemoveMessage --> [*] + + -- + + [*] --> Sender: Start + state if_exists <> + state if_channel_capacity <> + Sender --> CheckMessageExists + CheckMessageExists --> if_exists + if_exists --> [*]: Exists + if_exists --> SaveMessageData: NotExists + SaveMessageData --> CheckChannelCapacity + CheckChannelCapacity --> if_channel_capacity + if_channel_capacity --> [*]: Full + if_channel_capacity --> SendMessage: NotFull + SendMessage --> [*]: send + } +``` + +### Retrier and sweeper processes + +```mermaid +stateDiagram-v2 + state RetrierAndSweeper { + state if_retry_count <> + state if_message_exists <> + [*] --> Retrier: Start + Retrier --> FetchRandomQueuedMessage + FetchRandomQueuedMessage --> if_message_exists + if_message_exists --> CheckRetryCount: MessageExists + if_message_exists --> [*]: NoMessage + CheckRetryCount --> if_retry_count + if_retry_count --> DropMessage: MaxRetriesReached + if_retry_count --> ResendMessage: RetriesRemaining + ResendMessage --> [*]: resend + DropMessage --> [*] + + -- + + [*] --> Sweeper: Start + state if_stale <> + Sweeper --> CheckStaleMessages + CheckStaleMessages --> if_stale + if_stale --> MoveBackToQueuedSet: Stale + if_stale --> [*]: NotStale + MoveBackToQueuedSet --> [*] + } +``` diff --git a/nexus/crates/sui-tx.md b/nexus/crates/sui-tx.md new file mode 100644 index 0000000..523d14e --- /dev/null +++ b/nexus/crates/sui-tx.md @@ -0,0 +1,9 @@ +# Leader SUI Transactions + +The Leader will be assigned a wallet via the `SUI_SECRET_MNEMONIC` environment variable. This wallet will hold N `leader_cap`s. These are owned objects that are used to verify that the transaction (TX) submission was performed by the Leader and are created and transferred to the leader upon deploying the Nexus onchain packages. On Sui, to avoid [equivocation](https://docs.sui.io/guides/developer/sui-101/avoid-equivocation), only one parallel TX can be sent per-`leader_cap`. The wallet is assigned N `Coin` objects as gas (on mainnet) or Coin objects are poured from a faucet (on localnet and testnet). + +{% hint style="warning" %} Important thing to note is that, upon a failed transaction, Sui will lock all objects associated with the transaction until the next epoch, which is 24 hours. That means that we need to "damage" a `leader_cap` if a transaction fails and not use it for the next 24 hours. {% endhint %} + +Initially, there will only be one transaction type from Leader back to Workflow. This transaction notifies the Workflow of the Tool invocation result. More details are available in the [Leader](leader.md) documentation. + +Once onchain Tools are defined, the Leader will also have to send transactions to invoke these Tools. These are currently still in the design phase however. diff --git a/nexus/flow-controls/branching_and_conditionals.md b/nexus/flow-controls/branching_and_conditionals.md new file mode 100644 index 0000000..e19edce --- /dev/null +++ b/nexus/flow-controls/branching_and_conditionals.md @@ -0,0 +1,91 @@ +# Conditionals and Branching + +Branching in the Nexus workflow DAG is powered by **output variants**. Each `Vertex` (Tool) evaluates to **exactly one output variant** at runtime, and these variants are **mutually exclusive**. This exclusivity ensures that branching can be modeled without introducing race conditions. + +{% hint style="info" %} +Think of branching in the DAG as a `switch` or `match` expression in a programming language: +a tool executes, evaluates into one variant, and only the branch connected to that variant is taken. +{% endhint %} + +--- + +## How conditionals work + +1. A `Vertex` is executed once its input ports are satisfied. +1. Execution of the tool produces **exactly one output variant**. +1. Each variant can have multiple outgoing edges, leading to new vertices. +1. Because variants are mutually exclusive, only **one branch path** is ever taken from a given vertex at runtime. + +This makes conditionals in the workflow DAG explicit and safe: they cannot "split" into multiple variants simultaneously. + +--- + +## Branching vs. Concurrency + +- **Branching**: Multiple **output variants** define mutually exclusive branches. Only one is taken at runtime. +- **Concurrency**: A single output variant may connect to multiple edges. In that case, the walk **forks** into multiple concurrent executions. + +{% hint style="success" %} +Conditional branching (via output variants) and parallelism (via multiple outgoing edges from the same variant) can coexist in the same DAG. This enables both **decision-making** and **fan-out** within workflows. +{% endhint %} + +--- + +## Valid and invalid patterns + +### ✅ Concurrent branching + +```mermaid +flowchart TD + A[Vertex A] -->|Variant X, Port 1| B[Vertex B] + A -->|Variant X, Port 2| C[Vertex C] +``` + +Once vertex `A` completes, its output variant spawns **two concurrent walks** → `B` and `C` execute in parallel. + +--- + +### ✅ Conditional branching + +```mermaid +flowchart TD + A[Vertex A] -->|Variant ok| B[Vertex B] + A -->|Variant err| C[Vertex C] +``` + +Variants are **mutually exclusive**, so only **one branch** (`ok` or `err`) can be taken. +No race conditions are possible. + +--- + +### ❌ Invalid race condition + +```mermaid +flowchart TD + A[Vertex A] -->|Variant x| B[Vertex B] + C[Vertex C] -->|Variant y| B +``` + +Here, two different vertices send edges into the same input port of `B`. +This creates **two sources of data** → a **race condition**, which is **not allowed**. + +--- + +### ✅ Variant-based conditional merge + +```mermaid +flowchart TD + A[Vertex A] -->|Variant x| D[Vertex D] + A -->|Variant y| D +``` + +Both edges lead to the same downstream input port of `D`. +Because only **one variant** of `A` is active at runtime, no race condition occurs. + +--- + +## Design principles + +- **Determinism**: Tools always evaluate into exactly one variant, ensuring only one conditional branch is taken. +- **Safety**: Static analysis validates that multiple concurrent walks cannot introduce race conditions. +- **Expressiveness**: Workflows can model decision trees, parallel execution, and conditional merges, all within the DAG semantics. diff --git a/nexus/flow-controls/looping.md b/nexus/flow-controls/looping.md new file mode 100644 index 0000000..2c6bd26 --- /dev/null +++ b/nexus/flow-controls/looping.md @@ -0,0 +1,137 @@ +# Looping + +The Nexus workflow DAG supports **looping constructs** via special **edge types**. These edges extend the basic semantics of the DAG to enable iteration, repetition, and array transformations while preserving the guarantees of determinism and safety. + +Loops in the DAG are _not implicit cycles_ (which are otherwise forbidden). Instead, they are expressed explicitly through these loop edge types: + +- `for_each` +- `collect` +- `do_while` +- `break` + +{% hint style="warning" %} +Caveat: Loops **cannot nest**. Static analysis enforces this to avoid unbounded recursion or overly complex execution semantics. +{% endhint %} + +{% hint style="warning" %} +Caveat: Intermediate vertices between `for_each` and `collect` **must not evaluate to array types**. This is because `NexusData` is represented as a two-dimensional vector (`vector[][]`), and nested arrays would require recursive serialization which is not currently supported. Moreover, this **cannot be enforced statically** as the return types of intermediate vertices may not be known until runtime. +{% endhint %} + +--- + +## For-each and Collect + +The **`for_each`** / **`collect`** pair enables iteration over arrays. + +### For-each + +- A `for_each` edge must originate from a vertex whose output port evaluates to an **array type**. +- The downstream vertex is evaluated **N times** (once for each element of the array). +- Each iteration executes **in parallel**, with no guarantee of order. +- Each iteration receives a single array element as its input data. + +### Collect + +- A `collect` edge must follow a `for_each` branch. There can, however, be any number of vertices between the `for_each` and `collect` edges. +- It **recombines** the results of the looped vertex into a new array. +- The order of elements in the resulting array is preserved, even though the loop body executes in parallel. + +{% hint style="success" %} +Think of `for_each` as a **map** operation, and `collect` as the **reduce back to array**. +{% endhint %} + +--- + +### Example: Array transformation + +Suppose Tool `A` outputs an array `[1, 2, 3]`. + +1. A `for_each` edge connects `A` → `B`. + + - Tool `B` runs three times with inputs `1`, `2`, and `3`. + - Each run produces an incremented number, outputting `2`, `3`, and `4`. + +1. A `collect` edge connects `B` → `C`. + + - Tool `C` receives `[2, 3, 4]`. + +This creates a parallel map-like computation: + +```mermaid +flowchart TD + A[Tool A
Outputs array 1,2,3] -->|for_each| B[Tool B
Runs 3x
Inputs: 1, 2 and 3] + B -->|collect| C[Tool C
Receives array 2,3,4] +``` + +{% hint style="info" %} +It is perfectly valid to add another vertex (or any number of them) between `B` and `C` that also processes each element before collecting. +{% endhint %} + +--- + +## Do-while and Break + +The **`do_while`** / **`break`** edge pair enables conditional loops. + +### Rules + +- Both `do_while` and `break` edges must originate from **two distinct output variants** of the **same vertex**. +- Both edges **must** be present for the loop to be valid. +- On each iteration: + + 1. The vertex produces an output. + 1. If the **`do_while` edge** is taken, execution loops back, overwriting the input data with the new outputs. + 1. If the **`break` edge** is taken, execution exits the loop and continues forward. + +### Example: Increment until threshold + +1. Tool `A` adds `+1` to a number. +1. Tool `B` checks whether the number is `< 3`. + + - If **true**, the `do_while` edge loops back to `A`. + - If **false**, the `break` edge continues the walk. + +At runtime: + +- Start with `0`. +- Loop iterations: `1 → 2 → 3`. +- Once `3` is reached, the loop exits via `break`. + +```mermaid +flowchart TD + Start[Input: 0] --> A[Tool A
+1] + A --> B[Tool B
Check < 3?] + B -->|do_while| A + B -->|break| End[Continue workflow] +``` + +--- + +## Loop Execution Limits + +- Loops are **bounded** with an iteration cap of `0xff` (255 iterations). +- This applies to both `for_each` iterations and `do_while` loops. +- If the limit is reached, the walk halts as failed. + +--- + +## Design principles + +- **Explicitness**: Loops are modeled with special edge types, never with raw cycles in the DAG. +- **Parallelism**: For-each executions run in parallel, but results are deterministically ordered at collect time. +- **Safety**: No nested loops are allowed. Iteration count is capped. Static validation enforces correct loop structure. + +--- + +## Data Representation + +To support loop semantics, `NexusData` is serialized as a **two-dimensional vector** (`vector[][]`). + +- Outer vector: represents array elements or iterations. +- Inner vector: holds the serialized JSON data for each element. + +For example: + +- A tool `social.twitter.get-tweets` outputs `Tweet[]`. +- Each tweet is serialized into its own inner `vector[]`. +- This ensures `for_each` iteration and `collect` recombination work seamlessly at runtime. diff --git a/nexus/glossary.md b/nexus/glossary.md new file mode 100644 index 0000000..f8917ef --- /dev/null +++ b/nexus/glossary.md @@ -0,0 +1,28 @@ +# Glossary + +- **`Tool Registry`** - Onchain object that holds Tool definitions so that the Leader knows where and how to invoke them. +- **`Tool`** - HTTP service or a smart contract with a predefined interface, executing a specific task. It is a Vertex in the Nexus DAG. +- **`DAG`** - Directed acyclic graph describes how outputs from Tools flow into inputs of other Tools. This is a static definition. +- **`JSON DAG`** - JSON representation of a DAG with a Nexus provided schema. +- **`DAG Execution`** - When a static DAG is passed to the Workflow, a DAG Execution is instantiated. +- **`Workflow`** - The onchain Nexus package, that takes a DAG and its inputs to create and drive a DAG execution. It keeps all state related to the DAG execution and emits events to the off-chain world with instructions on how to proceed with the DAG execution. +- **`Primitives`** - Exports types that allow two Sui packages, A and B, to communicate with each other in an authenticated way without using explicit Move dependencies. +- **`TAP`** - Talus Agent Package is a smart contract that can accept a DAG, execute it and verify its execution. It also holds custom logic that can be hooked into the DAG execution process. +- **`Default TAP`** - Nexus provided TAP that only executes a DAG and verifies its execution. It does not have any custom logic. +- **`Leader`** - The offchain service that listens to Sui events and executes Tools as instructed by Workflow. +- **`Indexer`** - The offchain database service as a job queue. All events emitted by the Workflow are stored here, along with their execution status. +- **`Toolkit`** - Library that provides interfaces and boilerplate for Tool development. Currently implemented in Rust. +- **`CLI`** - Nexus CLI that provides commands for Tool registration, JSON DAG static analysis, publishing and execution via the Default TAP. +- **`SDK`** - Nexus Rust SDK that exposes various types, logic and transaction templates useful both internally and externally. + +## `DAG` Related Terms + +- **`Vertex`** - Abstraction of a Tool in the DAG. +- **`Edge`** - Connection between one Tool's output and other Tool's input. +- **`Input Port`** - A single input of a Tool. This can be one and only one of: + 1. Port that has an edge leading to it + 2. Port with a `Default Value` statically defined on the DAG + 3. Port as an `Entry Port` that accepts a value at execution time from the client +- **`Output Variant`** - A mutually exclusive variant of the tool's output. +- **`Output Port`** - A single output (field) of a Tool within an Output Variant. +- **`Entry Group`** - A named set of vertices that define an entry configuration for DAG execution. diff --git a/nexus/images/dag-rules/dag.1.png b/nexus/images/dag-rules/dag.1.png new file mode 100644 index 0000000000000000000000000000000000000000..76bed43a6735b6b8a9432f32a71c326cbd925947 GIT binary patch literal 7531 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 ld+YCuNMOEwOx4Be*xxoxzzvw literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.3.png b/nexus/images/dag-rules/dag.3.png new file mode 100644 index 0000000000000000000000000000000000000000..43df6884073a19c466da2927b7cef2e8be6c17cc GIT binary patch literal 7239 zcmds6hdUf!7Z!;i8$_a)MUbpcv=FO=C5W;lRtXX%qPOURXd5kv8lBZb)DXS*URLil zdJA^d_(tUSPkhfaXJ_WwdG6eM&ikJ8o;v}m%5p@vXl~));1J0_lTpLLxwZ^kn-bgv zes2gqalpZ$BbJwudf}|Ml}MNnGJMe{;pG37@26I~UVB_(anL5I&!!HGhaV@?#iq^| zR>h7HjC64U8~eDL?<|V}4LKX@+uQyPoY+5o*b4i$?%4CGuAY67g3;xf74a3ZmFm9Z z-j$>+H%=k)dP!WCtHWlNP!rOLts#!Rc9-$*!AqbTIezDv*Nw}gUJ1CHPQM>Z^zYvk z$de)>D)fF4xZDG;JuzARP+6uc{qLh>Hz1wL-+x6QxtzbJLmL$NJefTkyp^9P%7^0+ zhT#y3tzJHmJ?|L8IabXi&a}qpmK}34%P(=q#5i=E@YB~DmruodAJol zDdG=^@f5Xem>P3V$yy&?P4L>aK)ah9BHTh0N#n(=6_XUEA6Z2QSV@gYP1-K@^h;OA z2%b3Y^v*>%(_W4k=A}ib8j1eqqE&s^Fq!n&21m2H`(}X{hTFCo20DX&yq7Au%a?*! zah8ha+0Py=J@SR6WS|m4o5tP>K8lXNbi^YCue5|~YS&Ml=gwS6ERH#e+pVOxa#ZH# z@|k6y$0zFsbKP%Tzd&86Ul`-}`r;(sB|Q8c>NUe`;ar@FpYyV8&QDz9MUGNxFOJ8| z(@W=PGD*UFpS?upZd-BB2@i8{Yjxa=Kd}g4NE3IWrV_vz`>DsuGU`6m1=6e_TOAjK z?`S36d)H(V&-v~!{rse8zad*K!%Wt$-t8(>&n1BwDzhRy)2Y@=Ab#W=*g5S;qJlaM zd+X;|KW-?kVb5p!-M!x}c^gHTc{5UDmv5o!nK&7p*Qcw#^*wUW%MVQwzttp5EXP#P zh{<9Pe|rT7#%uV1yG1SMqhjJ0F+J}#C)^2`-U!<+n1dX_R6eAJvVjq5(w14D3F?n* z9p`KE|8%WUT3sk`J1^l_b%LCWsS9^JgfmhDOs1!TfPt!$?Mb>Fw&4J1&PLixa<% zIr^&>c{z1(4;T!a7^5;d8Fk7nLTPTQ{6q$ZL|kFF1i+i=i*x1*_r2$3ajo?CQ)E^5 zJA~?ZHN|S?x4z<+88-5zxZy-|lfg6!aFz|xZ~gRpkv6RBX_c>A`Xmxzhb#v<@vdXa=H=MufQM38?xK1&fT zaYpn!tP`}4UfMQR%!XNI)zBvZ!pPE{`2 zsS|(OEwlb;rdq+sKFprgJnio^ySOW88GaRHWv`~uZzaA`*N1MIvi=uxGB`DmU&B`jCfb7 z%EWvU?a0Y!7{qdwXrPzgSE?VomEmozY~7ty#y_8*oeepf=?);Q zkX{O*sy^)@EU_}L`?E|j7hkZM=H8M<;fa?`ZonSd8Bg2uWSE!Cv-NvV`{Kh|QR*ws zb}Iwke+pVf?`a*KaNskpSM^e2pVB#&&Lp*;ju?a11#pHM&7C>w7&oF6du}b$yl(du z-kZ#f;eE5r_a2cithk~cR8^TRb2i+yuY3lUvYf|;oi_(cJT)kEs1x1t{&?l10|2LN z0w2>VikY%?J~+E-_V1QK2@ddREGw9E$Q3nh&UjVV0ur1ZmRN%0A6;MJ_C;yKN|na5 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 literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.4.png b/nexus/images/dag-rules/dag.4.png new file mode 100644 index 0000000000000000000000000000000000000000..9638871c7fb3e5c0e3f18570da69cfcaba7e0865 GIT binary patch literal 7977 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 F{|~hp=&Aq! literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.5.png b/nexus/images/dag-rules/dag.5.png new file mode 100644 index 0000000000000000000000000000000000000000..23f369b0c6af37dcbc91abcc6139b5d8df33c4e2 GIT binary patch literal 11642 zcmeHtcQl;c_b<^)A_R#RBm@x=5)oZ6N<*U_N$E*(aB$8l z$jhkV;G7u*-`^1vg1zGEI0}>_cdJg(aB^U-DW2z1}5fBjP`I@PP)*5q|ZiD zv0#XhMoA{3N3$Jfy&`8rqJ+@;T)>splaijSlw@?0`p`D_KC&vw1 zPE5~Q8e#T6ApGjR$G8;7J3C}AFr(f`!5(zLf;Q%#?Y?a9am+zj#`yB9O>V|g$m;7; z;xs6qp&oSZ`_flI)ywQrf9Xk_8dNZi?)m|d`Sb1CdTTkDI~5`79IxcM>vbv?+~1cL ziK<4;gD!%a9haLjB(@UxTDHmgm_Ud6>5(PROMT7=GK+MQQ`?ige<3aBCd;gqbN&1! zPi5)31SSVD6XSO8_eYCP-zVQoyvP^eW?6To|GfhELV_CvC#m%q^D4IxQf5C1Gp}&@ zGI*tBqj6TyjyVIJJkeXW;JLS&>>qhbCzXjf5Y!jS5qnFf+2dUjB@B$4;E7Lk<3})k6>)7s`#9F0#RXN?+M$wkAloy z96L~p()Hfqw?A|IFkRXBu@L($A){gKk87qG0;5`eLu&3lKIxa}P4|*$3l7Y(U)8o= zE7{q-OJ2)+Cd>0sz`ddH&m158aUd4#0>-qBzF)E4ogma^#mDC-%6=8oy+!Te_5*JV zeuvm8@6Lduv$OOm@usl~Px#I|wI}SM7z!vmU$zaVwSG0jN< zUfoACl-1o{jJu%D*s*Fowy7dIxIm!MVfI$+qFx%^>20urZRimX zUFi6BXg|AVVlX>YGFau2LKgBw>W@8AWWX|hmexXL=Qwy7+a@Yd-Muj6cJYek4Ozd= zS8-Z=7rUe`oDvLIg;f%o%%#S`duV=?8T)~I37u$7UNl3$;3ZCL{lYyX$)JUC?(j?Z zN>8S&&Yj%RF7-8n{nHLQeZ}_*Z(cWBYIAEX_Ya-fB2M*^B)p7^Z~D5@rIy;i(~n<7 zaSzsOnY>vCg6`2{_4vM;AyL^Mz`l6G*4c_K*ZKnr1n zxtpWU_lZv!9YZL%q`&&%+xU%KS6En5r!@-t+#%i!*` zbK9MWN(rxK^dov98CUdE>tn~K+I1V*E_|zX#BK7kCU;b;GbN;694xvXhHvPo7CX{7R;~1k9(=<+y){37$w-<( zf918-u?gD@y$Ny7byqIFBIm<#Bi$Nz0s%&cqEf3xwUd{&v*I6fx^B;?e=yab*x32k zGXk*Bxc+(>(qio|n3infCMuE2k9fbfhqh{y<@L2R5L{%Ug>aL$Uuq!4%9FHmG7(_Yx>=S?}g-ETMB_}!y6t()-v9A+PU z(u*JmKitq*QMDbZp-N`zSa5SYLcKKBM;p|8g%SGhAeO9m=AAgbRsNhdHdT2LYAr}s z$0kl1=DD?=X<;&0(I0$(Lqd&>nA)=m%Ol^-He%)P z)Mq4`Xf-$JW_V3ED@Wgi(ofKDaVNPD6$PEjG%N-n5|n-{s@6BdVwRDYXELm$j)p0*DY`uDp`yx1zq0;& zk*>`!^xVcm6WL<&$NVdQPyy8^L7)s>Rdxf>@Rwoe&Th7owex+8$g3UuG16ImxWuHE zA^hwe^9*+=*l?+C#WmdS7Bwk!IKdPYdlQ*>jx$?s_g8Pd>j1Yj1mCQx0;CpC?Z-CT(T&O9^nCrW3ofdp}vm2}G&U%mkeervYNm1lMc zJ`XLG&S100U3RLim*A!~_U8BhfAfD*0%=W-@`Gw0LY|kZA6)HRb5DZY(!0=3e3+~y z%gHjoco7JSoE3r!VMZ!6Py=JUlX>9<3|bi~38fr-?!x$@-dkI@&Vyu=_Df4%xR&wa zf}Zgj^&tN%FucmH;i`iqn@B~nfBy`dA%pLaAJ0OVL}-0|eMeI=asuH5tjD{2j^8H< ziRw>i#Kpz0Y*PZ0%WhR#2UG31lh{Tjh`Z~@)c>Mb&m_^{a}5-;|=e78FKyZe&?>bL_t<&$S&8rj*|CLiAn zzu#%N{DI*prQ=BWY!<>@*7V+N`0)#T|DK%OhM0|&!K4;rWdsye47KV z*_kM)G9U+sUvF$|eE)1liqq=?hjR05OE456LjtXDk4L~lBA6n4D1p0$65}NUwEaG+ zA@5QCE!Q80y0z{!TwGj&0ai>1_oIExJ^OFOvLTmzA;GjpD?`X3p&F-^Yjt<@5Mx5w z`}Bp{;-O|4TmHYy?wq1oO9n7Ea$1IUsYD#ThXB5 z)@*)z+v1jG56i<;aVGU_Wtwo)t|XxgOu~+m`I^XQ+pF4J=&u*8h6^EY`OTW+vK2K% zP->Xy!0_`S9dQAa zOpJMr_oqr+j;3l%_88quG!J6ov-$5czMQa$4aJV9x9P(E%6iH#$ARUO#aeUy$9#nu3cN;x>OQG zy43v^qEPFOdCHWlUuu4qii+x%$L=#L8x+<0$B!R}0#IcNn}RDz@cC|vniIcc>3!op z9cF~W+;hmy2b)@7a<}oJU^YCbYVdm3mqbB1a0+C`lUlm~Mq^IVK2detHp;eMkO)HQ z-V$N;mHJCKLlE^?vLM>d+aHizd7I|ZUnw6jT3k+##W-&c!JA#b_oT}!D?8udaG|?y zaP`U+e*MP-k@XjO^_$C{^gr9*dUD5R{8H`n9a&RTHbh2-?c~f2+G-gT{adYK)vJZ` z9giCNRmhu4rBf~*aC|XPIX?)0&|PUaSKylGwwECr`hs3>8-tW6c_MDK^Lt@O zrvaS=_EDQ!t>Hs#hNM`Bi!_AB`kF#xH%zbtOy%=7JB+XY9j>f`@PkXp*@8<#n6U#p zb&q?l?J(w_pafHAdmL@Xq8)8^2tNOs`p-IlN6FLT0+GSgf7basMGaAt(3;W7gCT(( z|A+|Ik+Y(@fP2NvbS1wxGgQh%2QL%}cc zn-a&~(0p-06r1WZteT5s zY3K8`ie`@w*4sEzJzl6~JbnxQIDNm(sELZrCREom4E`@Z+gn^HO%;S&+T<7;a(`l8+^39jdkN zPP&!PaKj~z@4<%(o+wd9lsD=}$K==y7w6BN+>Fh!>B->|7Lmg(uy*S)CRkL{;Q`dm98ZOf-S_{)SPMD-WaJe0twPIJ-rFK2MMMCpl{a~Id_jB8aJt@_r+EeQ~>7G3omtsdV-MnFj<(UEsROJ#u=UV zj->PLZK8?^XNLWqxIn2OIGqHOf+t9vAq;}&dV7226%;<^Kr;$@%?u0-HuNm_=S(&T zO7H-Zsh|Y#CY^T`qb18>YsG{STraKkl3CbSgrbGz&4n$~^pn&;XeMBd8=_Z6)1B(9;zum6h8KKPw9gOBnwKIW=t-?INE|1kd6456J8 zl;_tBEGFm7vz~6tjJ==r3)DsUR3Y2}RvS_)g(rKX?A0{?{Ja_A-V#JJ)XL_DViIvq z0}#m-GpM!ENXFaH-A!?@vo42(p?>mXDyMGenz5Rwb?ia`F#yp{zCjm?6eS}imD1Hs znl}Xxv0Lb*Bd5PZ+@Gyd5sR1?9xR;tSWsVBsvc_$6*Ft6{&4uS!_9q$dQd?@VW`Uo z&b>BS`+P1VT)e%1sQ({i*TW@ zuaBP*v$1jC-Cg+gSq51sPo=N6H^C3b0Jy*dq-_2sTp5ZCQ@^4cXg7G*W;~!!uV%q) z4O$N@C%x&Ng*9^ zywZbT0|Ej_#C%FdMCol9`V&Juv^)Efu8;>J%jt@7xdyzVV`C6DE z^yes!VxuO)6|0woXM3uhtX7I2?){v%y;ll5CwJ76J2{5g-Y$RlYWLuvqpdMt~#<} zs|iJi3^d`nBV5AeZlz6KKui}O2mSaggK_8<^Z)++J2}Nbz-F?`WZ+y|xHM1l;!~&P zfseq!p(UoDS3W$-_!`BbQZB9@?zqs|d$rwE{@akBSS}4Kd!~-xot6?ot=YaTM^W)z zs7F0Uu1(#a%Gl>2obIn62LoLU*PFOZn4P{X#drZLTIHmBzJTf=K+VK;G1=wT^X;d6 zmoV#_iW$eSqt2Mvs2u%EViX)+D(5L|6js(lK0Rw@J|ZA#3m2lz=KPy+C1Yd`0O4X( zWR10-FS-46LFRQ}+{1^zbG9W<(>=RcnH%~aM)>48hj|f8%pg9L>GNwK^=h1lavVlW z%$TmMQ0aSYKt61bS<$f!7_W8#fAi)cL!PK{PdR(l z=0sIcO#=UZIIc_D6mDiBUOT>n~3rtFgR4wr9N`jK3^9z79o>W#&3i zUcP(YE9-&DSd!&K`rFj36Yf_sttcYBtCww`KE1KMI%;wj$N%+fghJG{EA>gOSD>Er z?J?vWK{VXX6A3ZPE^waD0Ql&%iT?a_b@xXTMUjK>X3}c8_1D>zqWU3w=-Qf$oZ45n zvwfMGbxL0I5Rdof1P?^0WKo&l*tV7@85}Hbod2~e&@#GedV(#p8Il1bx-4|Q1xx@B zAS|=m^}z~T11mm1ezW!f&)A`qZ7%kiBgrMb>>{rZD%>gFbqIb9vWxOjxqwL6og=hV*}H2C5T zzs)&oG@z6Z14I~U-9aX;u3+sRCF)h}G0&giVj1%2h%vosmhXP%>J1I7CZ~^P%c} zn@I1qK3-LdiFA*&18A(eumgGYT^=sNE158$Q(^nD}awnLH2&6|O{Z6$)m z#+c`_1;;a72zSi#psGXuqokPQ)d2-@CjqM?j6LzZ)n2VpG>)csix5ECUwc4G2uW2} zQ%loV)#zBs-Ciy2HvasEeb}i8zk+HqFIR}CD@O9?%FEogB_MZ^Qo_KTK7FW;O>Tg~ z4CQbYoq!2{SBl8X;S(>=rKmX5aKRR7@ zHe`L5Q(iKe#BS^sr1Tn3h<5ha1sPOA-))=md)z4x^26u_NCBm$;g#d08e9%lz^Ear z_cW}=%D5)I4y>){{S)NGoL3)XQ`lUc`U`E6#ZI2hHy%x|&q^D)nIf;|>M4yJjwnP4 zODsk(N<(?l+alp~Vt;9rA9u(=d+Ra4D7q=f>B{-X_Rr^Iw0>?)+j<``fW+n8WQWD9 zngx%IJPQ}}^O3;y0DM4v%8cCr=D`-XeDFo%a8`Nfl5MRU78PjeMqyg9dM}^MQcv~nkBn1|SOUK%7(N!uE5)xMGv=*Bi8@{k*EuL$5?m_7zzMmFru9G zfLLq49bUu-@uR&2>sdda_dB=FhEAB#N4-fW_b*vBaF5(FS46z zJ-~tls>&3fgvpt%NO+;T^*WOR01to*%vmJn9B=ijYz%4{uwK93?q2HlDT38?XTk-w zDhz%@Cj5_{MdN&GAWr(<9LeGHPUk+aYkRYA;i%-*>xb2g7k$1NHx%R(mZ@K2N;04` z6fU+MPw>c^s3?{+Pdz*A5`Przbx+hAm~$2={sLJ_W%D+J{KNJWhlSYcEskP(fR=hU zRjrs5kng-SkT4qf=*JjtEtn#K(p!E4fZQ*rbCD_}JI;uvtbV1Tv9T*Ka$lCP^fa&t z36JM8&lc`s&6}H>Loz`?5VqRe!UnmqvL`tjxo<|~x^ii+ni7HZCUkyU9<@5eNeWmN zrwd?NfYjeRY9g!_hB%d)e|hhD;QVy*!>D^xl_nBq93CvC#toe)vw?VQpzn`suAFnU z`ijHsRnBDA5p!{~KV|@)atP|Zk95DW|kZ23DdKtzbsN9*!$%djD3>WR8Pqm%0*l@4*=6u(!7nFzV zYwT4mmv+~6VAD}R#3?`zjX6~g*1p{{;K$O8C+{}La95v*zr(;tEp&BCBp_$53j5B}Q z8S$0yY&++Q`B{REze<|A`Ao?%Au?51}769O5Y^GMFR2}QnY3+AmOMtw+u$;zX}m&QZYW&t&Pm69Sf*p7$M z|KWp+rwUSWXR3t3g2pwg#jWter|hd+S2wk4BsxBPDJ~Bl8i@=$9Q@vOR*%N-5EYeL z$f4xCXSu({(+@%E*%{TdJu3zJwT*pzu5E?09W*OF54N?{@uG9;*6R*#88;K}?(PQE z-XMjHUXaau#A4AI?>jwhxV1c}$f#vO1PWl$!aubZ?yn6&SiRBT0OifVo&~uTgfzB@ zTi`@(wz$mPB{Z0zTUu=djqM$H6g?6K^Vq4NG+F&_v1oM}(D!}N zPO&lubx^#$oo)(a)00LCHJ*&E6NC4-RaT=OKA& z$n}^!%Wy+kXOBV^{QWKmzvKs2VTg5p|0;^R{UKi-H1vpx?>53jgYek zeK}k=cWztE++fSjoSqLXsvMWIdEIxIJahi=e=MAS+xZX8uT0QKn{5t!{K2(YV4v5z z(}3&#Sd0`$%`?(-dZ9cn&o5F7X-T)AGPx zA$2NXi>pJ(#CuzPhfG2Wf!IpRyc2h-obo>W#l+ z&M8%mk&z$1e5KxL{%<<@T?DWccsWQQJVSddpSNE9;W+KgZOQ_lP@5>^Ut6KrLe^E9kq2Z$Bh=%n|42 zq=?xP_8mX0G0NQkRBT{(YYpderGy5yLpRa7(P;WVb;m4@!~WeH>olh1mxP)UBn=2#&xeOpobC8YzhK@qVHs4i zhf@Gt-qix9;3RLqj8YAt$AdhK4Z-ynlZa z8~BO8CMAG|b{Fl1l%%GI(GD2*WxN)=59D7`8m3?qIm7sx#k%2B%!9o9xz@SX$p+lP zwppj{UGs8E&eo4>3QO2)9EZnj6u+gu(Wf2%nsoo$7`vn@F*z}Ez1ahoSj}T`P}Bir zDMsk9cbR(T*)}W)UJ^YMT=y_9#-Jd*i~idyfRt=Mqkxk~_|6A3+_-;U{?h(gDFgvn za{j+w@30;e1*09M7$iSVPLq3q@o36iJ387*@Aj|nQ7QRn&Gzooo{wSHJ^oU-FV$;b z;5n)F1Ugjo`;!z|B-HGlo73Z;w8Qx+AB~Ysq;00dVOnGY={?l8G{jZ6evObj1x>o< zu?UP_!YL-KHUiFP->@k|M62@k!(#9$vjoOJFKn`mg4WLE)Zwr1bb@bPlnyDM`pbHy zC01uZzt0{VbsB)_34224Y7JMw9@M{v94Dd2B%$V;90uzNEo=D^Y}n-AQ)b?kFF*9j z3}0^};tBY-N1ID9yxX<`5Y{4FZi1W=S3XSJm}AwRdtZ~ zGp#>JsexU=wc?8HBM{vyCOn`sU0g6FBydaNB1-eNA;C;wU;1KUx3u zusu53U#w+vNeJ=01^H&%DvIg^fAf6eTSGDF4EsM@0=%(EN0UCWFcomJl1)`m=!2dZ zFPr$>IR$+WAaaj>eq65x7m-)sP>C*$2`6)U=Nti7tI&ToWB|XR0=lbojfr&P+J`-z zxoNFSLttMTt+nTCaB4uD{##Va`S*P?E+vqXey3k}XQ5FvNSVk5QyHcihc)*kF(JmZS0lFad24K*XJfX#iJFgUHdPz>f29h>JV#_~Gh`PaFo*nNi%LzLZwsl*qwy3z1i`!KH zp8nhs!0xbgW{&fPNYz#?B35-pkOxR)Dw~SOT_d{l!&t5MafDO%vCboYcvz_tsJi4Mw2yy#pymv86^LU;5$Rb3Gf9tl60^`J;U-A$N?@mPSnEr2*q6Hheh^ zYFF-bVUyzp|6WYDbgkzcfNA0-?jc+>RYGXbVE@(N-0>h=2JiQf&b|qRG$U=S7*$}# zoym;*pDN_mG};8VZt$;c2Q0S8t`?;X$bm8WUWC8#b@EJDclwx#Hoxpg)0M z5>;ww;i38T8a+&R7J4OeP<-I zyE4t(==^M_L1)1)p0yRhN%Kt;=`zLYWYl{415!Y`99>`J9p^VRHlUrHQ#*4ckn5)g zYHP)(;(oLq-8hNZUw+Qvyy?d7&n~%d^;_~Yn_#1suDfGaUM1c?qBTi$uWPBz}n$gV=NlTJ?TV}Nw}j;zO2>Xi5*n$PJlts3}8CIy$a4jZjjLxqin zi%(8+?zCldIlWd9tZGQ__mg?x5zx^y#;NO-P-5Z&LI<_IlPfcv&sveh!X) z4f;ec!@kyfV~Muxho^*ZgI@9Q9gH{TlT`U|!3(kn?Ta!gSWngCv5G)OpjmM!gso_D zOqLs?jrMDMFSu#I<}!1MS(WqV)kztMA1V0kQRtnm)c`Nos1qgO1V4x1QKa>H0=pUx zbvb}WpR9q^4Su(`DSvwe;%@8V>6NjgGDkv=_U`Su2Kka#3(l!8?!23hbQJXo?R~}M z0H!`NhdzkSXPdU(0?6w$`+9H6ycpGZHjA`(;+BOcJ4SltXx^=c#is?>Pnr+7u2M>C z$zB0L>OhUoQM5h;-m1Jb-t8S0Il}KHO803Pe0hq*W1#OY7qiLh7~vJZKX87ou1PcN zu|a`L9)u?CdaqnvW7y<)-@&}_$Ht`}f?UgIl4RAEZ_;nl2xJ5y4Cv<&+k6UE0B2`H zI8Hu&s72Oa5Y~7c$ffDMJvn4Gcw%;vLJjTgv!fC33s{Qh!NZO79|Yoea706M@Ek^@ zL_WzIQhgtt?2nkfWvKtO{Uo|nnkydpzLbK!6=X-H%#tRf+jLWef+PU5NtD~1%3;I9 z73<=1rLB$G1UB!yagtP|sv^Qh2O$b9bk^{bgxI^+lo2(GI@Z->IL`GNo+D%N$!0Np z@1oZMu*`ucDqR!BfNGEz?c~7nOG{-rf{Vf;#e;~1z_N)%EySa3{34%-q4~^dLZ12@ zA|*E8WbE~>S%gGA68RM%M9~L$`#s`DmsE!IZ*LfjgHpvoizz+O>oe$Q;nA~j=q8b< z3S>-t(HtK75FFrF&xIagqqqIA$l~e&(5#d&@`wSfQ)I4|^UDuneOrK0ty`Q^o)TON za<+46csfnmBmDH5#>nrsqx)sA;01%=?94#o5JGfM)X7=O$tVh3L#Ty$A}@ntIyxj7 z1qtu-3la{pQH>ur=^22~yzjpVtOsN~TC^hn5FC6oY}SG2zPw6wmMOdEjU@l(b*dk3 zWt#_o=Bpotqj~&Xz0;cZdd|f-!tH_G%?|01<>QQ# zTy8zb@9H8j*8axVvZ~uNWSm2VPgu^6gRtc8h63cLgawF)e|e%D^VlSXJ6FpwX&Uty zjie5LXZ-;Wm8catqm0M*T4BoNQ2XomwzZ)*EICS7iF#KTSX-+~`(x^LHMjKM znD=H|A8~Kb96=u11^C7J&yfPl6r7f&-y4f+P-c@`f1CpcPy_pwmb1kkrKgJ#4~Ge! zC&&`Kf&C2X$7y!hPh@|oQTAwaqV(N;4jM*2COp0Mlad-78L;yj@j0!s8e+#9QS;Rkr1E%jOq{hpZ#qgg?||kiHu@r@bFH1 z-d7s5|Duee-P7U+PPY%CVvH6}^8^n4&N#XyAMbME;(%dUfx4*J!nr-|a8cMfnK&bx zsFFqakLJs^=i;k*AskB9IH=j77&H6TfA&^#Y*wJTgPp^SAc|eQ=GL~;Cjg7?tdZpq zx)w%^G?mF_FAEhotehW~l)Rz6;HTeuy3@l})*OgLct1dFXLq~sVxM?nwBSf(bRia2 z9K{xen0p87N+0g{10iGp5Y&X=^nVK>Mn*^EJWsbTh`6Kfsm_b>hhdSI1Wy;ni1{Dg zxF?Bg=P@PWCKJdK=h`pG#6y$T8X%{jb*Vp`9HB;b-()4B}>bSm+;M9*YRHbz~ zd)u#0s-||$Xfn?Q2fk8@HPBR`tSlC7FxhFt#gCqGExfh@3!tHyk9xS8pY2&i@x2_# zMq>4U$;L&`bvlvRDzN<7muGX{LZ*GCAPgJsF&cGm4{g;ZzXBJcbP4_=pF8;jN-b;a&1zpp^jSRPP6^k*|1IQ-9r)x+W8`5OIDquC_`v_k~3aW{s?+V89@+&V z91M0n<~1K2&vvi8wJ47gnp3glB;+!*A^HN2-?2gnXhL|*dS5b^?hsE>+hiU_QS&)$ zPxDNa8q&0Qope@O401dFRLndpmzp|X`Ka(RmI=|tqX8kRPq{vfoW4egHCLSPkWOuJ ze~Q?gPO_pEyb`C?dbZTpM@iS!WVV$MygQvcE`{Omw3DF&g)%SxrN)vD%S`F4jjV3I!n@n zO}iAAmZ1eu<*3iRIHPL5D-5b6n$0Hp$%-ELZWvMM+wy+bkmKlY^OVOp+A0@ zzY7h`w$^Z!3m^#2n#B$)3BeVZ&nM#%zTEW0p-^*viaZ@FF8not3(luR~MmBT9 zIJ@4&vBezXEIov33wegD)z(v^q?2_|tV0NGLS`SULE2y=OH!Ju)(?-?hZFA(S{AQ1 zxxmKe!5N3UH`mS=Cd*jv-=95gE(yaLU;_~9@Q$7sB`%ee1YTfH4)b7kR=1=i`aOj3 z9wIO(2rQ)0@^(RpR@969c?f=mQ*n)l{d|1|zZnO|5b>n(^}50_^#mE45dtSAWd5Pk zsG-zJ3cqcJbFpjc=co2Dz|KY13r5lLP3jzWRFxBD5BYKIg_;`BC&kA}2m-5PsEj$- z$B6#%rj=u=dq`uo?>JvC2?>eZix+Bh{T9#GRaH_2c-%X(=maMLX4zG3JxGj&E@7Zv@cJZAs*7UG$jUenugfosBM8iua`i@19#`+o& zgV=nE;pve^-`1^27>uEGx9wT;W7Yt5K)NurDez<>buh*#rT;|N1R^INj|rQ;R|KXu zEG%%#vu}J0{Hg`H{nQlh(uuIKRJ8yztHQA!6e*+&?#W@im2IEQnq4?6baM*=C=yBD zFPb#_Ne&=25BjiPaujieCh$!rGHD)P{u``(CdGwIVyq>hAg5mOI_|&>9vYPnL+G5k z0I+%q!0KGz2mbeHxGw|!VL`l>MHWhld;sRA7XCxW{^FQW8rj2?y$9zDZ?kOke;d{|v&70Z8m!GF!2Sxb;z}4#vgJm4aXlynd*${KfbF z_Z`3({$K4NPi{kU=9pAna6*@oXrkDx#D2EoIEXFkR7JSXyr0y}mmM!_R@Z})*6flHExnrIJibzUeB;Hr0kUE)f zaE>OpH(qC+`A`970MKOJb$tMuB~Z}(7g?4AHd@clKTBUG0bmhjim3m0%Ag?XPnfz3 z)Cra~zz=FYptbQ@D*?oj<`;4F2Z9JHIIy$=O_e`=`MlD{!Lj4CiP%lB`!1)>-s6kb&mv<@!tdBiL zC**2l^C&ATtK6gmBPT!KYD>(3OHhP zg%ETl@tARvPSJual&0u3fG-0BoymNbe?IIR)DU=Bu7bhb{h}rBJ<+Z9a8d8m zyZ7#)yKYWMh7!_oTaPin^gWqup#Pe}k2frK$$o`EI4-xNPm~!i=?(v^^qMKO*jOmk zh^6Ae2nYz!7OyA<@>cr82>_Sm%*!@v5QOz~`8|NDuulaB2C{z}Ei)EZOc$fm$Wuf= zE9CTSa?L-e`|&CDLD5ORG@|ez`sw0>V@`PS9YlX6FcIjbm-cSsR_Sw7Q^rR3y_g!? zDWTPdm-?*IHTZ2!E;bA9A%t`hw{AZ&L0(<(T6vb#6c{zS6uKTJbH=25g51|^U%Ytn zWoW2&$5W34H%j3Z@ws=ln-Va&& zBee4tSGVsz8q*Zf5cRwA=qp*l8Fdq4kpd0icI z(+Sn=VrI)kcYg1QgfzFWbcGsJ4f^@{@p=r*9v>4OU{+!P)o&e3M%zV9SeS&o{4HKS zzRbS4a5C1PoVq{G*=+CZaw7+B-nQdNDfBz&q0FqS8_fkM!1>`I*U^sAeh;Pb^7+9k z^m6K@vr=oYt}pz!BjPq&$yqg;{ah`CFTF~0>vev@3ef&8t>gR~uxS4PAi`K6X-xYL zbb3{oi|m_ro@_6*Eged55mP;VJAd2zXjGPAEzj3w3resxRn}%|FwW(YuZ&0>f;iki z!AG{(5j8hAzriICuEA&eQ;6`#`pUArd#MS!?sDIl&&GV@p(RlZH(ryEcU-6Y#^d^o zeG~f7XK(kr)W=nOM=L`gyKre&8JUn8)#a;;BW;L%8{)~^HiTP=e$6fKv)!7*qP^xL zWn1aDYXk9Ija5cDx#EtST1`Sk1|T}4Y1hg==jfWIuuc26xyUb>n9I4}&pIb49o-|L zwP=?439oYg1j#sw85Y&WK4XreOZ?4n(R5$S8xL2^MU1?+_E41H&1b(sAUJNdtYjxq zSCc`^hib|g7Han5XKH3wzS@V`F>Aa*HB_LbNDYT(Id;Co)uw~9foH0ANGx=Id?Mqj%+}aiP!L4Lcgr?p4P+b~73FeFe}1!dwnE2u>#jJ7>TIEzS(f9E{cDHg zsq~fA)ySBYg$ma}8Cw}dP5kG{r|jri8^bZ-ynciCTP#HY1q=pJt zIE8ky`LgAq_hOcYG`;V!+Ey+OgU?6`jFU)uw$Rn)Ds zr-O{5;POV~Wa$}(0o7qt#k;D)75ps4I89?DMc2{mjS!oD>|FW^ZsW#5^Ao6sWqb-8}Jkb{w#&d|%#9X3Bj z>wGAoViFwW;l3cRsTru1_~Vng%hnj3v*}uB()4KIp7k8MUDs|`bMnWL0!K2J#x@+I zG|EV?sR`MsPdOL-cDZq24FkGvl+0nd-Unrfs>a zd%0343X$t88}@B?cr*;PdyOsAtzNRLL5NDecwg0vQLClLCDmj1$yGDAq zp1JQW+VSff-r=_~&wctSlyPUm>%dZ1mnwnF_$`l^FZ{9B*{&T}s=;cMk%!Sbe@G-f z{AcDpUBpl9$Ms0;!So=uVvzj&t=CR7RbpCyK}8i!ZAI`?b1>_ds49Kckf znC{YnPB38;nxfcsZ)QkkV`wYxSGjCjd`sc)8D^scaj_IJcz&03!P{FoOtk^~!L|>k zV$3$co>>)UR~!iiP{A4@{tn0eL7?~@0f5bifOAg_8XW#w6tS539A{95%Y5QeKQYu) zHXk}iwke$kXJZA=3RVtP{!rtkz&(YO)h z%nT%uY&1h=ts;JB{YLo3d=j}ZIx893{utiHoTQ^~ZUHA}#nlu_69x28xr}Yna-RBjF}_8_opFlZZ4E;TDOYQ-)yp?( zkh+ehYvd)QeKSb>^|zE8U;~KwBrKJ=MR!!q2!wDw-dhqk_Wt^ro|Kw8{O~Zoh7PPh z>lj&?=w@RH&)Z4>$m3;Ss*NStYXr)^QRT&;1u67?d({KLR9W|>=GNS1>ZhjNM!Y4T zBc=C78_h7oCby2RE~X_{Ar;-dSCB|C5g8#&w&$2jJ|2G{VdGAJSal5e7Oqe@4Xiq?d|I=|J(a@N^#r*O+n3Cb1*bAVu&tC)rH z3*V9yHwJD5z)*JqYS@XlC;m|-Ngp&}3H*(u>_sO#C#KS1+iKIETUS8RLwAYX=N&_9 zrYeVSK>Ok-**(u1I8`FaPn;_?2JieazF+8>K@>qF=#*3A(N^AhphF=X)5hyp8@BnO zKBK30s1TYflIy3cnV7@>qs}o@8Pb+Hx&sym%r-be*HBmvKz&;9sVK|JEUq5m6sFgysmvCvW_U&TUtI~#9PJl_5HvtcRs6jyUq+0CdCiz2O@)kNk zZy}5*0krgXO$>P3XGcfJ1aC=6NjW(=i8en(Nk4AOZ3t}rW)y?ijAr$`uL4!FI^`t1 zx_x=s$!BMQdow5Q9p2}cmj-iZLJ>xT z@o8y%eMNgB~CWpYnqm8b%mZF~rH-W+>(>+lc zg7dkL0W>cJcUx@sL=k2>FkBwHwG;cfWm|%~YVA(uA3V8`c!c50#}6M$ylzchXzZjqG!33_(RxV~M%pZp98WC(gW&2GU`7zPaH8E{F zP{?O|{d-xnwYTg_<%mi>E$iQurcDARy}*0_;>)t3!e0y%vQT?pIW?)t2X9YQhSdW*)af*mAqgeC>vak(HT6=iDaNsA5~!jlb&$mXgpHU5 z@mmqrvg|*oRs!YiLxn^)k5q332ptqBnEMsPK7PGe%@$Dy;K$|E*qZ5a&axKRIM zX?{S)cBwcU;}3}Z zJiisB_r>BWukgoqHA|cIhyPwLnd;;9vw&@x&3qK|KurRKkeFsu3ix=W8G%6Jbj zCFS;2{h1PGie25lhSGIbQ5w!)?qQx(pdTQY-A9=`oG4vKK!an|((5{2ln(fW81R(S z3UeitOmNo(p7Ax=MsNHd3Ft2?@Cgek;A#x;plm`1LE!6s$u`vD)Hq(Hd))Ubx?3Hdmn@%t$;@IAHe{qfZ4xn1=WEy-#mEL zuy{{Wl9f;6g>ivuhj3yC!1_9-rYPTIrcu<0Mc9x4jap3qqdyOz%|zqgpt~1vr~n9# zjsLA-Kurw@B-nuR9x_{Fo2^r3q+9!j`t?xiL&6f5Jg?J0YB@n??BQteqto2-TRB-m z+PFxj(Cg6bHY7d+gRu+#UJS(GNoVo18_l&&d{+oeDZjb*8sLFc^$V z*o~cnL*M)xQUTAhC>P6G1>-f~Ha}q^JPyPf;Qu#?g_?yEFXDeP2WSln3+SJ`<_@$c zl$rSWyr)$_L37KiPboQQqo8%4s36R`iqdSJkL2do{Owm@dg2Q^z&9X#v(R`Opybb; zoOnCCx<;$5HQpN&nnl?hdI?CU@9I{EDTIc z9zaEUui_c~u*mLte}r6nF0iGB5$R7^(a-K?NV`rJX+c824Gj(XTJ`M>!IRJY;Pgl& zQVvjLTH0b`V^6gF%*}IjHEkW~DJVh`I1IjjdC#;lSI2dX?}_5tk_i!;5Mi<2ZtK^$owQ$W_b5CBqASb#?VtKQ+!sMVq*07?%ms1Y!Nm!H7)lzFm)u~CZ4n$$}tI%$GliS zjdj@6{7|EE@}(m?dOBa9OY^aBo__vu)E)kTi2R{JfPe`cZ&#+Lr*j+_R+@*`(`y6l zcUd1ec}v%Zn?3?!6$X0WF2jwU>Q$&4P$!6m&}GdgBVSk+fnlFZ=Y{~vd~O4vG+$b> z>jw!>0G15tvj;4J^Y)8u`ya-EGjLq|r*Uu>J&^=Bk@5fiKFJ@50F?MqsFV8t|MUO0 zMewJX%u9?L$n0(O$S|Ta^Bs@LWqA3j8U)-XRY;)PW1?dQC@pZqfZyQeZ~X0U(%8}N#kuhAO?iF!0*vSNXI&}c8Dm8D9bnFRh9)>soB literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.7.png b/nexus/images/dag-rules/dag.7.png new file mode 100644 index 0000000000000000000000000000000000000000..af4c1303ad5373259b5584cc27696d974b7d5d77 GIT binary patch literal 8573 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ℑ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 literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.8.png b/nexus/images/dag-rules/dag.8.png new file mode 100644 index 0000000000000000000000000000000000000000..47dbf8cab429178f7c52f4831018d1aabcc09c72 GIT binary patch literal 9638 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 literal 0 HcmV?d00001 diff --git a/nexus/images/dag-rules/dag.legend.png b/nexus/images/dag-rules/dag.legend.png new file mode 100644 index 0000000000000000000000000000000000000000..1138bf2ad48ad5abe2fcca0ab6e4c13b3f5a50ff GIT binary patch literal 24353 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

bmw6L` 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�l)MEv(2mR5Gi|x!<{Gjot&57@y}~{5e=it7RB9 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

bF}K3>bctG8d$p5wB2J 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 literal 0 HcmV?d00001 diff --git a/nexus/index.md b/nexus/index.md new file mode 100644 index 0000000..cc7b090 --- /dev/null +++ b/nexus/index.md @@ -0,0 +1,103 @@ +# 🫀 Nexus Core + +This section aims to document the technical side of all core Nexus components. We will split the documentation into several parts that, albeit distinct projects, work together to form Nexus as a whole. + +## Actors + +For the purposes of this documentation we make distinction between different user roles within the ecosystem: + +- **Nexus maintainer.** Core team member that maintains the Nexus codebase. +- **Tool developer.** Outside contributor that develops Tools to be used by Agents. +- **Agent developer.** Outside contributor that creates DAGs and subsequently deploys the Agent smart contract. +- **Agent user.** End-user that interacts with the ecosystem through clients built by us or outside contributors. + +## [Glossary][glossary] + +Ubiqutously used terms. Often these terms reference specific parts of the project so it is crucial that they be clearly defined. + +## Onchain Nexus + +The onchain part of Nexus. Holds the workflow state and requests [Tool][tool] execution from the [Leader][leader]. + +Docs: + +- [Workflow package][workflow] +- [Primitives package][primitives] +- [Nexus interface package][nexus-interface] + +{% hint style="info" %} +The Nexus core onchain packages are currently not open sourced. To find all of the function signatures and data structs, please refer to [the reference API documentation][ref-api] +{% endhint %} + +### [Sui Move Conventions][sui-move-conventions] + +We established a [set of conventions for writing Move code][sui-move-conventions], which may not be universally known and adopted among Sui Move developers. Skim through them before diving into the Move codebase. + +## Offchain Nexus + +The main offchain service. Consumes events produced by the onchain Workflow, invokes Tools and notifies the Workflow about the outcome of Tool invocations. This service is provided by the Talus Labs team for now, with plans to decentralize in the future. + +Docs: + +- [Leader][leader] + +## [Tools][tool] + +Tools are Vertices in the Nexus workflow DAG. They are services with [Nexus-defined interface][tool] schema that perform specific tasks. These Tools are what Agent Developers orchestrate in a workflow DAG to create an Agent. + +There are a few standard Nexus tools, they can be found in the [Nexus SDK repository's tools][nexus-sdk-tools] folder. + +Some examples of what a Tool is: + +- getting a chat completion from OpenAI +- posting a Tweet +- storing data on an external storage + +Docs: + +- [Tool][tool] + +## Flow Controls + +Nexus provides a set of tools to support branching, conditionals and looping for more complex workflows. + +Docs: + +- [Branching & Conditionals][branching_and_conditionals] +- [Looping][looping] + +## [Agent Development][agent-development] + +The components referenced above (onchain Nexus, offchain Nexus and tools) provide the infrastructure and building blocks for [agent developers][actors] to build Talus agents. + +Docs: + +- [Agent development][agent-development] +- [Default TAP template][default-tap] + +## Nexus SDK + +Nexus offers [tool and agent developers][actors] an easy-to-use SDK consisting of a CLI and Toolkit to streamline their development. The codebase resides in [this repository][nexus-sdk-repo] and is the main entry point for developers to interact with Nexus. It has a separate section in the developer docs dedicated to it. + +Docs: + +- [Nexus SDK documentation][nexus-sdk-gitbook] + + + +[glossary]: glossary.md +[workflow]: packages/workflow.md +[primitives]: packages/primitives.md +[nexus-interface]: packages/nexus-interface.md +[ref-api]: ../developer-docs/index/nexus-core-api-docs/README.md +[sui-move-conventions]: conventions/sui-move.md +[tool]: tool.md +[leader]: crates/leader.md +[actors]: #actors +[branching_and_conditionals]: ./flow-controls/branching_and_conditionals.md +[looping]: ./flow-controls/looping.md +[agent-development]: ./TAP/agent-development.md +[default-tap]: ./TAP/default-tap.md +[nexus-sdk-repo]: https://github.com/Talus-Network/nexus-sdk +[nexus-sdk-tools]: https://github.com/Talus-Network/nexus-sdk/tree/main/tools +[nexus-sdk-gitbook]: ../nexus-sdk/index.md diff --git a/nexus/packages/nexus-interface.md b/nexus/packages/nexus-interface.md new file mode 100644 index 0000000..a6ed362 --- /dev/null +++ b/nexus/packages/nexus-interface.md @@ -0,0 +1,53 @@ +# Nexus Interface + +Nexus components are designed to be composed into "Talus Agent Packages" (TAPs) that are published on Sui. +TAPs are typically template Sui packages that are customized for a specific product. +They would include business logic relevant to the product and configurations for the Nexus components. + +The configurations typically take the form of `SuiObjectID`s which represent shared objects. +These shared objects, such as [`DAG`](workflow.md), are unforced to be invoked in situations described by the business logic. + +Some examples of what the business logic in a TAP could do: + +- check token supply to feature-gate a workflow +- require a payment component +- require a minimal time elapsed between invocations +- reward OP for reaching some step in the workflow + +## `V1` + +Nexus first iteration is focused on the workflow component. +To comply with the Nexus V1 Interface the TAP must: + +1. Register itself with the [Nexus leader](../crates/leader.md) by emitting `nexus_interface::v1::AnnounceInterfacePackageEvent` that + - has a generic parameter `W` which is a type located in the package and module that implements the interface. + - contains property `shared_objects: vector` is a list of `SuiObjectID`s that the leader will inject as args into each following public function call. + The list is ordered, that is the shared objects will be injected in the order they are listed. +2. Export `public fun worksheet(...${shared_objects}): ProofOfUID` + - Returns a stamp collector hot-potato. + - Collects execution confirmations by Nexus components such as `DAG`. + - Hot potato must be constructed with a type. + That can be achieved by calling `nexus_primitives::proof_of_uid::new_with_type`. + This type must be defined in the package and module that implements the interface. + Note that types don't change their type name when doing package upgrades. +3. Export `public fun confirm_tool_eval_for_walk(...${shared_objects}, ProofOfUID)` + - Consumes the hot-potato. + - Can verify that required confirmations have been collected. + - Invoked by the Nexus Leader. + +![Diagram showing Nexus V1 Interface flow](../images/nexus-interface-v1.png) + +### Events + +- `nexus_interface::v1::AnnounceInterfacePackageEvent` + - has the aforementioned generic parameter `W`; + - can be emitted many times by the same `W` in case the shared objects change, but delay should be tolerated between emitting this call and the BE syncing the changes; + - `shared_objects` is a list of `SuiObjectID`s that the leader will inject as args into each following public function call; + - when a package is upgraded, it must be called again with a newly published `W` + +### Upgrades + +Unfortunately, Sui does not give us a way to get current package ID. +When upgrading a package you must create a new type that serves as a witness. +This new type needs to be used when emitting `AnnounceInterfacePackageEvent` as its generic. +This event _also_ needs to be emitted with every package upgrade. diff --git a/nexus/packages/primitives.md b/nexus/packages/primitives.md new file mode 100644 index 0000000..137313c --- /dev/null +++ b/nexus/packages/primitives.md @@ -0,0 +1,214 @@ +# Primitives Package + +Exports types that allow two Sui packages, A and B, to communicate with each other in an authenticated way without using explicit Move dependencies. Instead, A and B have a shared dependency on this package, `nexus_primitives`. + +## Why is this useful? + +Avoiding explicit dependencies shifts complexity from the Move code to the off-chain realm. +It is the off-chain realm's responsibility to craft programmable transaction blocks (PTBs) such that the expectations of the on-chain realm are met. + +Thanks to this trade-off the system is more friendly to plug-and-play components. + +Payments, workflows, verifications, those can have on-chain design that minimizes the need for explicit dependencies between each other. +Therefore, upgrading components is less likely to require changes to other components. + +## Core Modules + +### 1. ProofOfUID + +`UID` (Unique Identifier) proves that an object exist on-chain and its unique. By requiring passing a UID reference as the input and keeping a record of the ID of the UID that was passed as argument we can prove that only the module that defined that object did that operation. As only that module can access the UID. + +This is achieved with the [hot potato pattern][move-hot-potato]. + +{% hint style="info" %} +This is a step away from explicitness and towards conventions. +Prior similar work includes [Originbyte's `request` module][ob-request-module] and [Mysten's `transfer_policy` module][sui-transfer-policy]. +{% endhint %} + +A hot potato struct that enables authenticated communication between different packages without explicit dependencies. The `ProofOfUID` is created by one package and subsequently utilized across (potentially) multiple calls inside a PTB, allowing different participating packages to add their unique stamps. + +```rust +module module_a { + use nexus_primitives::proof_of_uid::{ProofOfUID}; + + struct OwnerObject has key { + id: UID, + } + + public entry fun initiate_action(owner: &mut OwnerObject, ctx: &mut TxContext) { + // Create a ProofOfUID tied to the OwnerObject + let mut proof = ProofOfUID::create(&owner.id, ctx); + + // Pass the ProofOfUID to module B for stamping + module_b::perform_action(&mut proof); + + // Consume the ProofOfUID after stamping + proof.consume(&owner.id); + } +} +``` + +```rust +module module_b { + use nexus_primitives::proof_of_uid::{ProofOfUID}; + + public fun perform_action(proof: &mut ProofOfUID) { + // Perform some operation and stamp the ProofOfUID + proof.stamp(); + } +} +``` + +Explanation of the Code + +1. Module A: + + - Creates a `ProofOfUID` tied to its OwnerObject. + - Passes the `ProofOfUID` to module B for stamping. + - Consumes the `ProofOfUID` after it has been stamped, ensuring it is used only once. + +1. Module B: + - Accepts the `ProofOfUID` as input. + - Performs an operation (e.g., stamping the proof) to authenticate its involvement. + +#### Key Features of the Pattern + +- **Authentication**: The `ProofOfUID` ensures that only the module owning the UID (module A) can create and consume the proof. +- **Cross-Module Communication**: Module B can interact with the proof (by stamping) within the same PTB without requiring explicit dependencies on module A. +- **Single Use**: The `ProofOfUID` is consumed after use, so that it's used exactly once, preventing duplication or misuse. + +This pattern allows modules to communicate securely and verifiably while maintaining modularity and avoiding tight coupling.1 + +### 2. OwnerCap + +Implements ownership capabilities with both cloneable and non-cloneable variants. Used to manage control and access rights over resources. + +**Key Structs:** + +- `OwnerCap`: Simple ownership capability (non-cloneable) +- `CloneableOwnerCap`: Ownership capability that can be cloned for multiple addresses or concurrent transactions + +**Real-World Usage Example (from agent):** + +```rust +// During initialization +let owner_cap_over_agent = owner_cap::new_cloneable_drop( + OverAgent {}, // Witness + &agent.id, // What is it for + ctx +); + +// Transfer to admin address +public_transfer(owner_cap_over_agent, AGENT_ADMIN); +``` + +**Common Use Cases:** + +- Admin controls: Managing administrative rights over resources +- Access delegation: Allowing multiple entities to have controlled access +- Resource management: Controlling who can modify or interact with resources + +### 3. ProvenValue + +A container that proves a value was created by a specific UID, optionally restricting who can unwrap it. + +**Key Features:** + +- `wrap`: Proves a value was created by a UID (anyone can unwrap) +- `wrap_for_recipient`: Creates a value that only a specific recipient can unwrap +- `unwrap`: Extracts the value (for unrestricted proven values) +- `unwrap_as_recipient`: Extracts the value if the caller is the intended recipient + +**Common Use Cases:** + +- Secure data transfer between modules +- Implementing restricted access to values +- Creating trusted communication channels + +### 4. NexusData + +A flexible data representation format supporting both inline and remote storage. + +**Key Constructors:** + +- `inline_one/inline_many`: Store small data directly on-chain +- `walrus_one/walrus_many`: Reference data stored remotely on Walrus + +**Real-World Usage Example (from agent):** + +```rust +// Storing input data in a DAG +.with_default_value( + dag::vertex_from_string(b"hellox".to_ascii_string()), + dag::input_port_from_string(b"prompt".to_ascii_string()), + nexus_primitives::data::inline_many(vector[b"Lorem ipsum"]), +) +``` + +**Common Use Cases:** + +- Flexible configuration storage +- Bridging on-chain and off-chain data +- Storage optimization for different data sizes + +**Available remote storage providers:** + +- Walrus (default) + +### 5. Event + +A simple wrapper for standardizing and emitting events. + +**Key Features:** + +- `emit`: Wraps and emits any type of event in a standardized format +- `EventWrapper`: Makes events searchable by type in client applications + +**Common Use Cases:** + +- Standardized event emission +- Improved indexing and filtering of events +- Consistent event handling across packages + +## Integration Patterns + +These primitives are designed to work synergistically, enabling the construction of robust, verifiable on-chain systems. Here are common patterns: + +1. **Agent / Workflow Systems (e.g., `agent`):** This pattern combines agent logic with workflow execution. + + - `OwnerCap` (often `CloneableOwnerCap`) grants administrative control over the agent. + - `ProofOfUID` serves as a verifiable "worksheet" to track and authenticate actions across transaction boundaries or different package interactions within a workflow. + - `NexusData` provides flexible storage for agent configuration, workflow inputs, and state, supporting both inline and remote data references. + - `Event` standardizes the emission of agent actions and workflow progress updates. + +1. **Access Control Systems:** Focuses on managing permissions and verifying actions. + + - `OwnerCap` defines and delegates control privileges. + - `ProofOfUID` can authenticate specific actions performed under those privileges. + - `ProvenValue` securely transfers capability-related data or attests to permissions. + - `Event` logs access changes or granted permissions. + +## Security Considerations + +1. **Hot Potato Pattern Security** + + - `ProofOfUID` cannot be duplicated or transferred + - Only the original creator can consume a `ProofOfUID` + - Stamping is tracked to prevent double-counting + +1. **Ownership Capabilities** + + - The phantom type parameter ensures type safety + - Cloneable capabilities must be explicitly created + - Object identity verification prevents spoofing + +1. **Proven Values** + - Source authentication guarantees origin + - Optional recipient restriction adds targeted security + - Clear verification of data provenance + + + +[move-hot-potato]: https://move-book.com/programmability/hot-potato-pattern.html +[ob-request-module]: https://github.com/Origin-Byte/nft-protocol/tree/main/contracts/request/sources/request +[sui-transfer-policy]: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/kiosk/transfer_policy.move diff --git a/nexus/packages/reference/nexus_interface/v1.md b/nexus/packages/reference/nexus_interface/v1.md new file mode 100644 index 0000000..3d3c206 --- /dev/null +++ b/nexus/packages/reference/nexus_interface/v1.md @@ -0,0 +1,87 @@ + + + +# Module `(nexus_interface=0x0)::v1` + + + +- [Struct `AnnounceInterfacePackageEvent`](#(nexus_interface=0x0)_v1_AnnounceInterfacePackageEvent) +- [Function `announce_interface_package`](#(nexus_interface=0x0)_v1_announce_interface_package) + + +

use (nexus_primitives=0x0)::event;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::vector;
+use sui::address;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::tx_context;
+
+ + + + + +## Struct `AnnounceInterfacePackageEvent` + + + +
public struct AnnounceInterfacePackageEvent<phantom W> has copy, drop
+
+ + + +
+Fields + + +
+
+shared_objects: vector<sui::object::ID> +
+
+
+
+ + +
+ + + +## Function `announce_interface_package` + +Announce configuration for a package that implements V1 interface. + +The witness type W must be a type in the same package and module where the +interface is implemented. + +Whoever has access to W can announce a new list of shared_objects via +this function. + +If a package is upgraded, call this with a new witness type created on the +package upgrade. + + +
public fun announce_interface_package<W>(_witness: &W, shared_objects: vector<sui::object::ID>)
+
+ + + +
+Implementation + + +
public fun announce_interface_package<W>(_witness: &W, shared_objects: vector<ID>) {
+    event::emit(AnnounceInterfacePackageEvent<W> {
+        shared_objects,
+    });
+}
+
+ + + +
diff --git a/nexus/packages/reference/nexus_interface/version.md b/nexus/packages/reference/nexus_interface/version.md new file mode 100644 index 0000000..ecaab5b --- /dev/null +++ b/nexus/packages/reference/nexus_interface/version.md @@ -0,0 +1,130 @@ + + + +# Module `(nexus_interface=0x0)::version` + + + +- [Struct `InterfaceVersion`](#(nexus_interface=0x0)_version_InterfaceVersion) +- [Constants](#@Constants_0) +- [Function `v`](#(nexus_interface=0x0)_version_v) +- [Function `number`](#(nexus_interface=0x0)_version_number) +- [Function `expect_v`](#(nexus_interface=0x0)_version_expect_v) + + +
+ + + + + +## Struct `InterfaceVersion` + + + +
public struct InterfaceVersion has copy, drop, store
+
+ + + +
+Fields + + +
+
+inner: u64 +
+
+
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const EInterfaceVersionMismatch: vector<u8> = b"Interface version mismatch";
+
+ + + + + +## Function `v` + + + +
public fun v(version: u64): (nexus_interface=0x0)::version::InterfaceVersion
+
+ + + +
+Implementation + + +
public fun v(version: u64): InterfaceVersion {
+    InterfaceVersion { inner: version }
+}
+
+ + + +
+ + + +## Function `number` + + + +
public fun number(self: &(nexus_interface=0x0)::version::InterfaceVersion): u64
+
+ + + +
+Implementation + + +
public fun number(self: &InterfaceVersion): u64 {
+    self.inner
+}
+
+ + + +
+ + + +## Function `expect_v` + + + +
public fun expect_v(self: &(nexus_interface=0x0)::version::InterfaceVersion, expected: u64)
+
+ + + +
+Implementation + + +
public fun expect_v(self: &InterfaceVersion, expected: u64) {
+    assert!(self.inner == expected, EInterfaceVersionMismatch)
+}
+
+ + + +
diff --git a/nexus/packages/reference/nexus_primitives/data.md b/nexus/packages/reference/nexus_primitives/data.md new file mode 100644 index 0000000..25f72a4 --- /dev/null +++ b/nexus/packages/reference/nexus_primitives/data.md @@ -0,0 +1,169 @@ + + + +# Module `nexus_primitives::data` + + + +- [Struct `NexusData`](#nexus_primitives_data_NexusData) +- [Constants](#@Constants_0) +- [Function `inline_many`](#nexus_primitives_data_inline_many) +- [Function `inline_one`](#nexus_primitives_data_inline_one) +- [Function `remote_many`](#nexus_primitives_data_remote_many) +- [Function `remote_one`](#nexus_primitives_data_remote_one) + + +
+ + + + + +## Struct `NexusData` + +References some some specific data. + +Serialization conventions are agreed on by the off-chain realm. + + +
public struct NexusData has copy, drop, store
+
+ + + +
+Fields + + +
+
+storage: vector<u8> +
+
+ Will be b"inline" if stored on chain, or an identifier of the storage. + Inline data are useful for short data that can be stored on-chain such + as configurations. +
+
+data: vector<vector<u8>> +
+
+ If storage is not inline then use these keys to fetch the data from + the storage. + For array types there might be more than one element. + None is an empty vector which is a useful representation. +
+
+ + +
+ + + +## Constants + + + + + + +
const INLINE_STORAGE_KEY: vector<u8> = vector[105, 110, 108, 105, 110, 101];
+
+ + + + + +## Function `inline_many` + + + +
public fun inline_many(data: vector<vector<u8>>): nexus_primitives::data::NexusData
+
+ + + +
+Implementation + + +
public fun inline_many(data: vector<vector<u8>>): NexusData {
+    NexusData { storage: INLINE_STORAGE_KEY, data }
+}
+
+ + + +
+ + + +## Function `inline_one` + + + +
public fun inline_one(data: vector<u8>): nexus_primitives::data::NexusData
+
+ + + +
+Implementation + + +
public fun inline_one(data: vector<u8>): NexusData {
+    NexusData { storage: INLINE_STORAGE_KEY, data: vector[data] }
+}
+
+ + + +
+ + + +## Function `remote_many` + + + +
public fun remote_many(storage: vector<u8>, keys: vector<vector<u8>>): nexus_primitives::data::NexusData
+
+ + + +
+Implementation + + +
public fun remote_many(storage: vector<u8>, keys: vector<vector<u8>>): NexusData {
+    NexusData { storage, data: keys }
+}
+
+ + + +
+ + + +## Function `remote_one` + + + +
public fun remote_one(storage: vector<u8>, key: vector<u8>): nexus_primitives::data::NexusData
+
+ + + +
+Implementation + + +
public fun remote_one(storage: vector<u8>, key: vector<u8>): NexusData {
+    NexusData { storage, data: vector[key] }
+}
+
+ + + +
diff --git a/nexus/packages/reference/nexus_primitives/event.md b/nexus/packages/reference/nexus_primitives/event.md new file mode 100644 index 0000000..7f3ae2d --- /dev/null +++ b/nexus/packages/reference/nexus_primitives/event.md @@ -0,0 +1,65 @@ + + + +# Module `nexus_primitives::event` + + + +- [Struct `EventWrapper`](#nexus_primitives_event_EventWrapper) +- [Function `emit`](#nexus_primitives_event_emit) + + +
use sui::event;
+
+ + + + + +## Struct `EventWrapper` + + + +
public struct EventWrapper<T> has copy, drop
+
+ + + +
+Fields + + +
+
+event: T +
+
+
+
+ + +
+ + + +## Function `emit` + + + +
public fun emit<T: copy, drop>(event: T)
+
+ + + +
+Implementation + + +
public fun emit<T: copy + drop>(event: T) {
+    sui::event::emit(EventWrapper { event })
+}
+
+ + + +
diff --git a/nexus/packages/reference/nexus_primitives/owner_cap.md b/nexus/packages/reference/nexus_primitives/owner_cap.md new file mode 100644 index 0000000..e0274bf --- /dev/null +++ b/nexus/packages/reference/nexus_primitives/owner_cap.md @@ -0,0 +1,478 @@ + + + +# Module `nexus_primitives::owner_cap` + + + +- [Struct `CloneableOwnerCap`](#nexus_primitives_owner_cap_CloneableOwnerCap) + - [Important](#@Important_0) +- [Struct `OwnerCap`](#nexus_primitives_owner_cap_OwnerCap) + - [Important](#@Important_1) +- [Constants](#@Constants_2) +- [Function `new_uncloneable`](#nexus_primitives_owner_cap_new_uncloneable) +- [Function `new_cloneable_drop`](#nexus_primitives_owner_cap_new_cloneable_drop) +- [Function `new_cloneable_key`](#nexus_primitives_owner_cap_new_cloneable_key) +- [Function `clone`](#nexus_primitives_owner_cap_clone) +- [Function `clone_n`](#nexus_primitives_owner_cap_clone_n) +- [Function `clone_for_receiver`](#nexus_primitives_owner_cap_clone_for_receiver) +- [Function `destroy`](#nexus_primitives_owner_cap_destroy) +- [Function `id`](#nexus_primitives_owner_cap_id) +- [Function `what_for`](#nexus_primitives_owner_cap_what_for) +- [Function `is_for`](#nexus_primitives_owner_cap_is_for) +- [Function `is_for_id`](#nexus_primitives_owner_cap_is_for_id) +- [Function `as_ref`](#nexus_primitives_owner_cap_as_ref) + + +
use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::vector;
+use sui::address;
+use sui::hex;
+use sui::object;
+use sui::transfer;
+use sui::tx_context;
+
+ + + + + +## Struct `CloneableOwnerCap` + +When you want to prove you own something. + +The pattern of usage: +Authorize with what_for UID value of the resource and assert what_for +from the resource's module. +In this case [clone] is useful to allow multiple addresses or +multiple concurrent txs. + + + + +### Important + +The generic parameter T is a phantom type that is used to distinguish +between different things that can be owned. + +a. T has drop then [CloneableOwnerCap] can be created only by giving T +b. T has store then [CloneableOwnerCap] can be created by giving &T +and associated [UID]. + + +
public struct CloneableOwnerCap<phantom T> has key, store
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+what_for: sui::object::ID +
+
+ What does this [CloneableOwnerCap] actually own? +
+
+inner: nexus_primitives::owner_cap::OwnerCap<T> +
+
+ Some packages might not care about the what_for field, so they accept + [OwnerCap]. + This allows us to emulate Rust's AsRef trait. + Similar pattern for [sui::coin] and [sui::balance]. +
+
+ + +
+ + + +## Struct `OwnerCap` + +When you want to prove you own something. + +The pattern of usage: +Store the [OwnerCap] ID in a resource and assert from the resource's +module that the sender has the right [OwnerCap]'s ID. + + + + +### Important + +The generic parameter T is a phantom type that is used to distinguish +between different things that can be owned. +Anyone with a reference to [T] can create an [OwnerCap] with its generic. + + +
public struct OwnerCap<phantom T> has drop, store
+
+ + + +
+Fields + + +
+
+unique: sui::object::ID +
+
+
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const EObjectUidMismatch: vector<u8> = b"Object key must match provided UID";
+
+ + + + + +## Function `new_uncloneable` + + + +
public fun new_uncloneable<T>(_: &T, ctx: &mut sui::tx_context::TxContext): nexus_primitives::owner_cap::OwnerCap<T>
+
+ + + +
+Implementation + + +
public fun new_uncloneable<T>(_: &T, ctx: &mut TxContext): OwnerCap<T> {
+    OwnerCap {
+        unique: {
+            let uid = object::new(ctx);
+            let id = uid.to_inner();
+            object::delete(uid);
+            id
+        }
+    }
+}
+
+ + + +
+ + + +## Function `new_cloneable_drop` + +Creates a new [CloneableOwnerCap] for a given _thing_. + + +
public fun new_cloneable_drop<T: drop>(witness: T, what_for: &sui::object::UID, ctx: &mut sui::tx_context::TxContext): nexus_primitives::owner_cap::CloneableOwnerCap<T>
+
+ + + +
+Implementation + + +
public fun new_cloneable_drop<T: drop>(
+    witness: T, what_for: &UID, ctx: &mut TxContext,
+): CloneableOwnerCap<T> {
+    CloneableOwnerCap {
+        id: object::new(ctx),
+        what_for: what_for.to_inner(),
+        inner: new_uncloneable(&witness, ctx),
+    }
+}
+
+ + + +
+ + + +## Function `new_cloneable_key` + +Creates a new [CloneableOwnerCap] for a given _object_. + + +
public fun new_cloneable_key<T: key>(object: &T, its_uid: &sui::object::UID, ctx: &mut sui::tx_context::TxContext): nexus_primitives::owner_cap::CloneableOwnerCap<T>
+
+ + + +
+Implementation + + +
public fun new_cloneable_key<T: key>(
+    object: &T, its_uid: &UID, ctx: &mut TxContext,
+): CloneableOwnerCap<T> {
+    assert!(object::id(object) == its_uid.to_inner(), EObjectUidMismatch);
+    CloneableOwnerCap {
+        id: object::new(ctx),
+        what_for: object::id(object),
+        inner: new_uncloneable(object, ctx),
+    }
+}
+
+ + + +
+ + + +## Function `clone` + +Creates a new [CloneableOwnerCap] with the same what_for. + + +
public fun clone<T>(cap: &nexus_primitives::owner_cap::CloneableOwnerCap<T>, ctx: &mut sui::tx_context::TxContext): nexus_primitives::owner_cap::CloneableOwnerCap<T>
+
+ + + +
+Implementation + + +
public fun clone<T>(cap: &CloneableOwnerCap<T>, ctx: &mut TxContext): CloneableOwnerCap<T> {
+    CloneableOwnerCap {
+        id: object::new(ctx),
+        what_for: cap.what_for,
+        inner: OwnerCap {
+            unique: cap.inner.unique,
+        },
+    }
+}
+
+ + + +
+ + + +## Function `clone_n` + +Creates N new [CloneableOwnerCap]s with the same what_for. + + +
public fun clone_n<T>(cap: &nexus_primitives::owner_cap::CloneableOwnerCap<T>, n: u64, ctx: &mut sui::tx_context::TxContext): vector<nexus_primitives::owner_cap::CloneableOwnerCap<T>>
+
+ + + +
+Implementation + + +
public fun clone_n<T>(cap: &CloneableOwnerCap<T>, n: u64, ctx: &mut TxContext): vector<CloneableOwnerCap<T>> {
+    let mut v = vector[];
+    let mut i = 0;
+    while (i < n) {
+        v.push_back(clone(cap, ctx));
+        i = i + 1;
+    };
+    v
+}
+
+ + + +
+ + + +## Function `clone_for_receiver` + +Creates a new [CloneableOwnerCap] with the same what_for and transfers it to the +given address. + + +
public entry fun clone_for_receiver<T>(cap: &nexus_primitives::owner_cap::CloneableOwnerCap<T>, receiver: address, ctx: &mut sui::tx_context::TxContext)
+
+ + + +
+Implementation + + +
public entry fun clone_for_receiver<T>(
+    cap: &CloneableOwnerCap<T>,
+    receiver: address,
+    ctx: &mut TxContext,
+) {
+    public_transfer(clone(cap, ctx), receiver);
+}
+
+ + + +
+ + + +## Function `destroy` + +Destroy existing [CloneableOwnerCap]. + +Inner [OwnerCap] will be dropped. + + +
public entry fun destroy<T>(self: nexus_primitives::owner_cap::CloneableOwnerCap<T>)
+
+ + + +
+Implementation + + +
public entry fun destroy<T>(self: CloneableOwnerCap<T>) {
+    let CloneableOwnerCap { id, .. } = self;
+    object::delete(id);
+}
+
+ + + +
+ + + +## Function `id` + + + +
public fun id<T>(self: &nexus_primitives::owner_cap::OwnerCap<T>): sui::object::ID
+
+ + + +
+Implementation + + +
public fun id<T>(self: &OwnerCap<T>): ID { self.unique }
+
+ + + +
+ + + +## Function `what_for` + + + +
public fun what_for<T>(self: &nexus_primitives::owner_cap::CloneableOwnerCap<T>): sui::object::ID
+
+ + + +
+Implementation + + +
public fun what_for<T>(self: &CloneableOwnerCap<T>): ID { self.what_for }
+
+ + + +
+ + + +## Function `is_for` + + + +
public fun is_for<T, U: key>(self: &nexus_primitives::owner_cap::CloneableOwnerCap<T>, what: &U): bool
+
+ + + +
+Implementation + + +
public fun is_for<T, U: key>(self: &CloneableOwnerCap<T>, what: &U): bool { self.what_for == object::id(what) }
+
+ + + +
+ + + +## Function `is_for_id` + + + +
public fun is_for_id<T>(self: &nexus_primitives::owner_cap::CloneableOwnerCap<T>, what_id: sui::object::ID): bool
+
+ + + +
+Implementation + + +
public fun is_for_id<T>(self: &CloneableOwnerCap<T>, what_id: ID): bool { self.what_for == what_id }
+
+ + + +
+ + + +## Function `as_ref` + + + +
public fun as_ref<T>(self: &nexus_primitives::owner_cap::CloneableOwnerCap<T>): &nexus_primitives::owner_cap::OwnerCap<T>
+
+ + + +
+Implementation + + +
public fun as_ref<T>(self: &CloneableOwnerCap<T>): &OwnerCap<T> { &self.inner }
+
+ + + +
diff --git a/nexus/packages/reference/nexus_primitives/proof_of_uid.md b/nexus/packages/reference/nexus_primitives/proof_of_uid.md new file mode 100644 index 0000000..98f2532 --- /dev/null +++ b/nexus/packages/reference/nexus_primitives/proof_of_uid.md @@ -0,0 +1,347 @@ + + + +# Module `nexus_primitives::proof_of_uid` + + + +- [Struct `ProofOfUID`](#nexus_primitives_proof_of_uid_ProofOfUID) +- [Function `new`](#nexus_primitives_proof_of_uid_new) +- [Function `new_with_type`](#nexus_primitives_proof_of_uid_new_with_type) +- [Function `consume`](#nexus_primitives_proof_of_uid_consume) +- [Function `stamp_with_data`](#nexus_primitives_proof_of_uid_stamp_with_data) +- [Function `stamp`](#nexus_primitives_proof_of_uid_stamp) +- [Function `unstamp`](#nexus_primitives_proof_of_uid_unstamp) +- [Function `stamps`](#nexus_primitives_proof_of_uid_stamps) +- [Function `stamps_len`](#nexus_primitives_proof_of_uid_stamps_len) +- [Function `has_stamp`](#nexus_primitives_proof_of_uid_has_stamp) +- [Function `created_from`](#nexus_primitives_proof_of_uid_created_from) +- [Function `type_name`](#nexus_primitives_proof_of_uid_type_name) + + +
use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::hex;
+use sui::object;
+use sui::tx_context;
+use sui::vec_map;
+
+ + + + + +## Struct `ProofOfUID` + +Hot potato that can collect stamps. + +If [ProofOfUID] contains an [ID] stamp it means that the corresponding [UID] +has been referenced in [stamp] method. + + +
public struct ProofOfUID
+
+ + + +
+Fields + + +
+
+from_uid: sui::object::ID +
+
+ Only the [UID] that created this proof in [new] constructor can consume it. +
+
+from_type: std::option::Option<std::type_name::TypeName> +
+
+ Optionally, the creator of the proof can also prove the type of the [UID]. + Some nexus components may require this information to be present. +
+
+stamps: sui::vec_map::VecMap<sui::object::ID, vector<u8>> +
+
+ Stampers can attach arbitrary data to their stamp. +
+
+ + +
+ + + +## Function `new` + + + +
public fun new(uid: &sui::object::UID): nexus_primitives::proof_of_uid::ProofOfUID
+
+ + + +
+Implementation + + +
public fun new(uid: &UID): ProofOfUID {
+    ProofOfUID {
+        from_uid: object::uid_to_inner(uid),
+        from_type: option::none(),
+        stamps: vec_map::empty()
+    }
+}
+
+ + + +
+ + + +## Function `new_with_type` + + + +
public fun new_with_type<T: key>(uid: &sui::object::UID, o: &T): nexus_primitives::proof_of_uid::ProofOfUID
+
+ + + +
+Implementation + + +
public fun new_with_type<T: key>(uid: &UID, o: &T): ProofOfUID {
+    assert!(object::uid_to_inner(uid) == object::id(o));
+    ProofOfUID {
+        from_uid: object::uid_to_inner(uid),
+        from_type: option::some(type_name::get<T>()),
+        stamps: vec_map::empty()
+    }
+}
+
+ + + +
+ + + +## Function `consume` + + + +
public fun consume(self: nexus_primitives::proof_of_uid::ProofOfUID, uid: &sui::object::UID): sui::vec_map::VecMap<sui::object::ID, vector<u8>>
+
+ + + +
+Implementation + + +
public fun consume(self: ProofOfUID, uid: &UID): VecMap<ID, vector<u8>> {
+    let ProofOfUID { stamps, from_uid, .. } = self;
+    assert!(object::uid_to_inner(uid) == from_uid);
+    stamps
+}
+
+ + + +
+ + + +## Function `stamp_with_data` + +Add a stamp to the proof with some data. + + +
public fun stamp_with_data(self: &mut nexus_primitives::proof_of_uid::ProofOfUID, uid: &sui::object::UID, data: vector<u8>)
+
+ + + +
+Implementation + + +
public fun stamp_with_data(self: &mut ProofOfUID, uid: &UID, data: vector<u8>) {
+    self.stamps.insert(object::uid_to_inner(uid), data)
+}
+
+ + + +
+ + + +## Function `stamp` + +Add a stamp to the proof. + + +
public fun stamp(self: &mut nexus_primitives::proof_of_uid::ProofOfUID, uid: &sui::object::UID)
+
+ + + +
+Implementation + + +
public fun stamp(self: &mut ProofOfUID, uid: &UID) {
+    self.stamp_with_data(uid, vector::empty())
+}
+
+ + + +
+ + + +## Function `unstamp` + + + +
public fun unstamp(self: &mut nexus_primitives::proof_of_uid::ProofOfUID, uid: &sui::object::UID)
+
+ + + +
+Implementation + + +
public fun unstamp(self: &mut ProofOfUID, uid: &UID) {
+    self.stamps.remove(&object::uid_to_inner(uid));
+}
+
+ + + +
+ + + +## Function `stamps` + + + +
public fun stamps(self: &nexus_primitives::proof_of_uid::ProofOfUID): &sui::vec_map::VecMap<sui::object::ID, vector<u8>>
+
+ + + +
+Implementation + + +
public fun stamps(self: &ProofOfUID): &VecMap<ID, vector<u8>> { &self.stamps }
+
+ + + +
+ + + +## Function `stamps_len` + + + +
public fun stamps_len(self: &nexus_primitives::proof_of_uid::ProofOfUID): u64
+
+ + + +
+Implementation + + +
public fun stamps_len(self: &ProofOfUID): u64 { self.stamps.size() }
+
+ + + +
+ + + +## Function `has_stamp` + + + +
public fun has_stamp(self: &nexus_primitives::proof_of_uid::ProofOfUID, of_uid: sui::object::ID): bool
+
+ + + +
+Implementation + + +
public fun has_stamp(self: &ProofOfUID, of_uid: ID): bool { self.stamps.contains(&of_uid) }
+
+ + + +
+ + + +## Function `created_from` + + + +
public fun created_from(self: &nexus_primitives::proof_of_uid::ProofOfUID): sui::object::ID
+
+ + + +
+Implementation + + +
public fun created_from(self: &ProofOfUID): ID { self.from_uid }
+
+ + + +
+ + + +## Function `type_name` + + + +
public fun type_name(self: &nexus_primitives::proof_of_uid::ProofOfUID): std::option::Option<std::type_name::TypeName>
+
+ + + +
+Implementation + + +
public fun type_name(self: &ProofOfUID): Option<TypeName> { self.from_type }
+
+ + + +
diff --git a/nexus/packages/reference/nexus_primitives/proven_value.md b/nexus/packages/reference/nexus_primitives/proven_value.md new file mode 100644 index 0000000..b754bc6 --- /dev/null +++ b/nexus/packages/reference/nexus_primitives/proven_value.md @@ -0,0 +1,259 @@ + + + +# Module `nexus_primitives::proven_value` + + + +- [Struct `ProvenValue`](#nexus_primitives_proven_value_ProvenValue) +- [Constants](#@Constants_0) +- [Function `wrap`](#nexus_primitives_proven_value_wrap) +- [Function `wrap_for_recipient`](#nexus_primitives_proven_value_wrap_for_recipient) +- [Function `unwrap`](#nexus_primitives_proven_value_unwrap) +- [Function `unwrap_as_recipient`](#nexus_primitives_proven_value_unwrap_as_recipient) +- [Function `drop`](#nexus_primitives_proven_value_drop) +- [Function `by`](#nexus_primitives_proven_value_by) +- [Function `recipient`](#nexus_primitives_proven_value_recipient) + + +
use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::vector;
+use sui::address;
+use sui::hex;
+use sui::object;
+use sui::tx_context;
+
+ + + + + +## Struct `ProvenValue` + +Value that can be trusted to have been created by a UID. +Optionally, the value can also be locked to be received only by a given +recipient. + + +
public struct ProvenValue<T>
+
+ + + +
+Fields + + +
+
+value: T +
+
+
+
+by: sui::object::ID +
+
+
+
+recipient: std::option::Option<sui::object::ID> +
+
+
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const ENotRecipient: vector<u8> = b"Not the expected recipient";
+
+ + + + + +## Function `wrap` + +Proves that the value was wrapped by the given UID. +Anyone can unwrap the value. + + +
public fun wrap<T>(by: &sui::object::UID, value: T): nexus_primitives::proven_value::ProvenValue<T>
+
+ + + +
+Implementation + + +
public fun wrap<T>(by: &UID, value: T): ProvenValue<T> {
+    ProvenValue { value, by: by.to_inner(), recipient: option::none() }
+}
+
+ + + +
+ + + +## Function `wrap_for_recipient` + +Proves that the value was wrapped by the given UID. +Only the recipient UID can unwrap the value. + + +
public fun wrap_for_recipient<T>(by: &sui::object::UID, value: T, recipient: sui::object::ID): nexus_primitives::proven_value::ProvenValue<T>
+
+ + + +
+Implementation + + +
public fun wrap_for_recipient<T>(by: &UID, value: T, recipient: ID): ProvenValue<T> {
+    ProvenValue { value, by: by.to_inner(), recipient: option::some(recipient) }
+}
+
+ + + +
+ + + +## Function `unwrap` + + + +
public fun unwrap<T>(self: nexus_primitives::proven_value::ProvenValue<T>): T
+
+ + + +
+Implementation + + +
public fun unwrap<T>(self: ProvenValue<T>): T {
+    let ProvenValue { value, .. } = self;
+    value
+}
+
+ + + +
+ + + +## Function `unwrap_as_recipient` + + + +
public fun unwrap_as_recipient<T>(self: nexus_primitives::proven_value::ProvenValue<T>, recipient: &sui::object::UID): T
+
+ + + +
+Implementation + + +
public fun unwrap_as_recipient<T>(self: ProvenValue<T>, recipient: &UID): T {
+    let ProvenValue { value, recipient: expected_recipient, .. } = self;
+    if (expected_recipient.is_some()) {
+        assert!(expected_recipient.destroy_some() == recipient.to_inner(), ENotRecipient);
+    };
+    value
+}
+
+ + + +
+ + + +## Function `drop` + + + +
public fun drop<T: drop>(self: nexus_primitives::proven_value::ProvenValue<T>)
+
+ + + +
+Implementation + + +
public fun drop<T: drop>(self: ProvenValue<T>) {
+    let ProvenValue { .. } = self;
+}
+
+ + + +
+ + + +## Function `by` + +Who wrapped the value. + + +
public fun by<T>(self: &nexus_primitives::proven_value::ProvenValue<T>): sui::object::ID
+
+ + + +
+Implementation + + +
public fun by<T>(self: &ProvenValue<T>): ID { self.by }
+
+ + + +
+ + + +## Function `recipient` + +Who is the recipient of the value, if any. + + +
public fun recipient<T>(self: &nexus_primitives::proven_value::ProvenValue<T>): std::option::Option<sui::object::ID>
+
+ + + +
+Implementation + + +
public fun recipient<T>(self: &ProvenValue<T>): Option<ID> { self.recipient }
+
+ + + +
diff --git a/nexus/packages/reference/nexus_workflow/dag.md b/nexus/packages/reference/nexus_workflow/dag.md new file mode 100644 index 0000000..342c039 --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/dag.md @@ -0,0 +1,2084 @@ + + + +# Module `(nexus_workflow=0x0)::dag` + + + +- [Struct `Vertex`](#(nexus_workflow=0x0)_dag_Vertex) +- [Struct `EntryGroup`](#(nexus_workflow=0x0)_dag_EntryGroup) +- [Struct `VertexInfo`](#(nexus_workflow=0x0)_dag_VertexInfo) +- [Struct `OutputVariant`](#(nexus_workflow=0x0)_dag_OutputVariant) +- [Struct `OutputPort`](#(nexus_workflow=0x0)_dag_OutputPort) + - [Example](#@Example_0) +- [Struct `OutputVariantPort`](#(nexus_workflow=0x0)_dag_OutputVariantPort) +- [Struct `InputPort`](#(nexus_workflow=0x0)_dag_InputPort) +- [Struct `VertexInputPort`](#(nexus_workflow=0x0)_dag_VertexInputPort) +- [Struct `Edge`](#(nexus_workflow=0x0)_dag_Edge) +- [Struct `DAG`](#(nexus_workflow=0x0)_dag_DAG) +- [Struct `DAGExecution`](#(nexus_workflow=0x0)_dag_DAGExecution) +- [Struct `VertexEvaluations`](#(nexus_workflow=0x0)_dag_VertexEvaluations) +- [Struct `DAGWalk`](#(nexus_workflow=0x0)_dag_DAGWalk) +- [Struct `RequestWalkExecution`](#(nexus_workflow=0x0)_dag_RequestWalkExecution) +- [Struct `DAGCreatedEvent`](#(nexus_workflow=0x0)_dag_DAGCreatedEvent) +- [Struct `DAGVertexAddedEvent`](#(nexus_workflow=0x0)_dag_DAGVertexAddedEvent) +- [Struct `DAGEntryVertexInputPortAddedEvent`](#(nexus_workflow=0x0)_dag_DAGEntryVertexInputPortAddedEvent) +- [Struct `DAGEdgeAddedEvent`](#(nexus_workflow=0x0)_dag_DAGEdgeAddedEvent) +- [Struct `DAGDefaultValueAddedEvent`](#(nexus_workflow=0x0)_dag_DAGDefaultValueAddedEvent) +- [Struct `RequestWalkExecutionEvent`](#(nexus_workflow=0x0)_dag_RequestWalkExecutionEvent) +- [Struct `EndStateReachedEvent`](#(nexus_workflow=0x0)_dag_EndStateReachedEvent) +- [Struct `WalkAdvancedEvent`](#(nexus_workflow=0x0)_dag_WalkAdvancedEvent) +- [Struct `ExecutionFinishedEvent`](#(nexus_workflow=0x0)_dag_ExecutionFinishedEvent) +- [Enum `VertexKind`](#(nexus_workflow=0x0)_dag_VertexKind) +- [Enum `DAGWalkStatus`](#(nexus_workflow=0x0)_dag_DAGWalkStatus) +- [Constants](#@Constants_1) +- [Function `vertex_on_chain`](#(nexus_workflow=0x0)_dag_vertex_on_chain) +- [Function `vertex_off_chain`](#(nexus_workflow=0x0)_dag_vertex_off_chain) +- [Function `vertex_from_string`](#(nexus_workflow=0x0)_dag_vertex_from_string) +- [Function `vertex_into_string`](#(nexus_workflow=0x0)_dag_vertex_into_string) +- [Function `output_variant_from_string`](#(nexus_workflow=0x0)_dag_output_variant_from_string) +- [Function `output_variant_into_string`](#(nexus_workflow=0x0)_dag_output_variant_into_string) +- [Function `output_port_from_string`](#(nexus_workflow=0x0)_dag_output_port_from_string) +- [Function `output_port_from_raw`](#(nexus_workflow=0x0)_dag_output_port_from_raw) +- [Function `output_port_into_string`](#(nexus_workflow=0x0)_dag_output_port_into_string) +- [Function `input_port_from_string`](#(nexus_workflow=0x0)_dag_input_port_from_string) +- [Function `input_port_into_string`](#(nexus_workflow=0x0)_dag_input_port_into_string) +- [Function `vertex_input_port`](#(nexus_workflow=0x0)_dag_vertex_input_port) +- [Function `vertex_input_port_from_string`](#(nexus_workflow=0x0)_dag_vertex_input_port_from_string) +- [Function `default_entry_group`](#(nexus_workflow=0x0)_dag_default_entry_group) +- [Function `entry_group_from_string`](#(nexus_workflow=0x0)_dag_entry_group_from_string) +- [Function `entry_group_into_string`](#(nexus_workflow=0x0)_dag_entry_group_into_string) +- [Function `inputs_to_begin_execution`](#(nexus_workflow=0x0)_dag_inputs_to_begin_execution) +- [Function `request_walk_execution`](#(nexus_workflow=0x0)_dag_request_walk_execution) +- [Function `new`](#(nexus_workflow=0x0)_dag_new) +- [Function `with_vertex`](#(nexus_workflow=0x0)_dag_with_vertex) +- [Function `with_entry_port`](#(nexus_workflow=0x0)_dag_with_entry_port) +- [Function `with_entry_port_in_group`](#(nexus_workflow=0x0)_dag_with_entry_port_in_group) +- [Function `with_entry`](#(nexus_workflow=0x0)_dag_with_entry) +- [Function `with_entry_in_group`](#(nexus_workflow=0x0)_dag_with_entry_in_group) +- [Function `with_edge`](#(nexus_workflow=0x0)_dag_with_edge) +- [Function `with_default_value`](#(nexus_workflow=0x0)_dag_with_default_value) +- [Function `rebuild`](#(nexus_workflow=0x0)_dag_rebuild) +- [Function `begin_execution`](#(nexus_workflow=0x0)_dag_begin_execution) +- [Function `begin_execution_of_entry_group`](#(nexus_workflow=0x0)_dag_begin_execution_of_entry_group) +- [Function `request_network_to_execute_walks`](#(nexus_workflow=0x0)_dag_request_network_to_execute_walks) +- [Function `submit_off_chain_tool_eval_for_walk`](#(nexus_workflow=0x0)_dag_submit_off_chain_tool_eval_for_walk) +- [Function `submit_on_chain_tool_eval_for_walk`](#(nexus_workflow=0x0)_dag_submit_on_chain_tool_eval_for_walk) +- [Function `request_walk_execution_walks_indices`](#(nexus_workflow=0x0)_dag_request_walk_execution_walks_indices) +- [Function `execution_created_at`](#(nexus_workflow=0x0)_dag_execution_created_at) +- [Function `execution_entry_group`](#(nexus_workflow=0x0)_dag_execution_entry_group) +- [Function `execution_worksheet_type_name`](#(nexus_workflow=0x0)_dag_execution_worksheet_type_name) +- [Function `execution_invoker`](#(nexus_workflow=0x0)_dag_execution_invoker) +- [Function `execution_network`](#(nexus_workflow=0x0)_dag_execution_network) +- [Function `dag_vertex_tool_fqn`](#(nexus_workflow=0x0)_dag_dag_vertex_tool_fqn) +- [Function `execution_is_vertex_invoked`](#(nexus_workflow=0x0)_dag_execution_is_vertex_invoked) +- [Function `request_walk_execution_vertices`](#(nexus_workflow=0x0)_dag_request_walk_execution_vertices) +- [Function `execution_is_finished`](#(nexus_workflow=0x0)_dag_execution_is_finished) +- [Function `assert_execution_matches_leader_cap`](#(nexus_workflow=0x0)_dag_assert_execution_matches_leader_cap) +- [Function `assert_request_walk_execution_is_for`](#(nexus_workflow=0x0)_dag_assert_request_walk_execution_is_for) +- [Function `assert_execution_is_for`](#(nexus_workflow=0x0)_dag_assert_execution_is_for) +- [Function `assert_matches_worksheet`](#(nexus_workflow=0x0)_dag_assert_matches_worksheet) +- [Function `for_walk`](#(nexus_workflow=0x0)_dag_for_walk) +- [Function `advance_walk`](#(nexus_workflow=0x0)_dag_advance_walk) +- [Function `if_finished_emit_final_event`](#(nexus_workflow=0x0)_dag_if_finished_emit_final_event) + + +
use (nexus_primitives=0x0)::data;
+use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use (nexus_primitives=0x0)::proof_of_uid;
+use (nexus_workflow=0x0)::leader_cap;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::clock;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_table;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::vec_map;
+use sui::vec_set;
+
+ + + + + +## Struct `Vertex` + + + +
public struct Vertex has copy, drop, store
+
+ + + +
+Fields + + +
+
+name: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `EntryGroup` + +DAGs can group several [Vertex]s into a single entry point. + + +
public struct EntryGroup has copy, drop, store
+
+ + + +
+Fields + + +
+
+name: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `VertexInfo` + + + +
public struct VertexInfo has copy, drop, store
+
+ + + +
+Fields + + +
+
+kind: (nexus_workflow=0x0)::dag::VertexKind +
+
+
+
+input_ports: sui::vec_set::VecSet<(nexus_workflow=0x0)::dag::InputPort> +
+
+ We want to know when all input ports are evaluated so that we can start + the execution of the vertex. + To know when all are evaluated we need to know how many there are. + Note that these DO NOT include input ports with default values, only + those that need to be evaluated by tools. +
+
+ + +
+ + + +## Struct `OutputVariant` + +Each [Vertex] has a list of exclusive output states. +These could be as simple as "ok" and "err" to many variants. + +[OutputVariant] names a variant, e.g. "ok". + + +
public struct OutputVariant has copy, drop, store
+
+ + + +
+Fields + + +
+
+name: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `OutputPort` + +An [OutputVariant] is exclusive. +However, it can contain multiple ports if instantiated. + +This models algebraic enumeration type of first degree. + + + + +### Example + +Let's model computation that can fail, or it can succeed with some image +data and some JSON metadata. + +There would be a [Vertex] which with two [OutputVariant]s: "ok" and "err". +The "err" [OutputVariant] has one [OutputPort]: "reason". +The "ok" [OutputVariant] has two [OutputPort]s: "img" and "metadata". + + +
public struct OutputPort has copy, drop, store
+
+ + + +
+Fields + + +
+
+name: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `OutputVariantPort` + +A union of [OutputVariant], and [OutputPort] identifies an output port of +a [Vertex]. + + +
public struct OutputVariantPort has copy, drop, store
+
+ + + +
+Fields + + +
+
+variant: (nexus_workflow=0x0)::dag::OutputVariant +
+
+
+
+port: (nexus_workflow=0x0)::dag::OutputPort +
+
+
+
+ + +
+ + + +## Struct `InputPort` + +An input port is a named input to a vertex. + + +
public struct InputPort has copy, drop, store
+
+ + + +
+Fields + + +
+
+name: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `VertexInputPort` + +A union of [Vertex] and [InputPort] uniquely identifies an input port within +an DAG graph. + + +
public struct VertexInputPort has copy, drop, store
+
+ + + +
+Fields + + +
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+port: (nexus_workflow=0x0)::dag::InputPort +
+
+
+
+ + +
+ + + +## Struct `Edge` + +Maps output from unique output port to an input of vertex B. +By storing it in a map with [Vertex] as key we have a unique identification +for a output port in the DAG. + + +
public struct Edge has copy, drop, store
+
+ + + +
+Fields + + +
+
+from: (nexus_workflow=0x0)::dag::OutputVariantPort +
+
+
+
+to: (nexus_workflow=0x0)::dag::VertexInputPort +
+
+
+
+ + +
+ + + +## Struct `DAG` + +A directed acyclic graph (DAG) is a collection of [Vertex]s and [Edge]s +that describe how to walk the graph. + + +
public struct DAG has key, store
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+vertices: sui::table::Table<(nexus_workflow=0x0)::dag::Vertex, (nexus_workflow=0x0)::dag::VertexInfo> +
+
+ Lists all vertices in the graph and maps them to their [VertexInfo]. +
+
+entry_groups: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::EntryGroup, sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::Vertex, sui::vec_set::VecSet<(nexus_workflow=0x0)::dag::InputPort>>> +
+
+ Defines entry points to the DAG. + The outer map is a list of groups. + Only one group can be used to start the execution. + Those DAGs that don't care about entry groups can use the default one + defined in [default_entry_group]. + Within a group, all vertex-port pairs are entry and must all be provided + with a value when beginning an execution. + For each vertex in the group the DAG will spawn a walk if all vertex + input ports defined in the vertices map have a value. + It is possible to provide entry values for vertices that will be + executed at a later point and not when the execution begins. + Default values cannot be overridden. +
+
+edges: sui::table::Table<(nexus_workflow=0x0)::dag::Vertex, vector<(nexus_workflow=0x0)::dag::Edge>> +
+
+ Maps the outputs of vertices to the inputs of other vertices. + The value is never an empty vector. +
+
+defaults_to_input_ports: sui::table::Table<(nexus_workflow=0x0)::dag::VertexInputPort, (nexus_primitives=0x0)::data::NexusData> +
+
+ Maps [VertexInputPort] to default values. + No [Edge] may lead to this [VertexInputPort]. + We'll ignore any default value if there's an [Edge] leading to the port. + Useful for configurations such as LLM system prompts. +
+
+ + +
+ + + +## Struct `DAGExecution` + +Runtime execution of [DAG] comprises walking the graph by evaluating +vertices and following the edges. + + +
public struct DAGExecution has key, store
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+dag: sui::object::ID +
+
+ For which [DAG] this execution is. +
+
+entry_group: (nexus_workflow=0x0)::dag::EntryGroup +
+
+ One of the [DAG] entry groups has to be picked to start an execution. +
+
+invoker: address +
+
+ The sender of the tx that invoked [DAG]. +
+
+created_at: u64 +
+
+ The state of the [Clock] when the execution started. +
+
+worksheet_from_type: std::type_name::TypeName +
+
+ The off-chain realm can determine the Talus Agent Package to invoke + thanks to the package address and module name from this field. +
+
+last_request_for_execution_emitted_at_digest: vector<u8> +
+
+ What's the digest of the latest tx in which we emitted an event + requesting off-chain realm to execute next tool. + Will be empty if no such event [RequestWalkExecutionEvent] was emitted yet. + Helps us guarantee that when a tx is over it will have emitted only + valid events, not ones that were superseded by a newer request for + execution. + This would happen if e.g. two on-chain tools were invoked in a single tx. + It's still possible, but thanks to [RequestWalkExecution] and this field + we can skip emitting events for all but the last tool(s). +
+
+network: sui::object::ID +
+
+ Only those with [OwnerCap] with type [OverNetwork] for this network can + walk the [DAG]. + This forms an agreement on where computation is executed when it's + delegated to the off-chain realm. +
+
+evaluations: sui::object_table::ObjectTable<(nexus_workflow=0x0)::dag::Vertex, (nexus_workflow=0x0)::dag::VertexEvaluations> +
+
+ Submitted values that are fed to the relevant input ports. + Dynamic object fields so that they can be queried. +
+
+walks: vector<(nexus_workflow=0x0)::dag::DAGWalk> +
+
+ List of concurrent [DAGWalk]s with their states. +
+
+ + +
+ + + +## Struct `VertexEvaluations` + +Holds information about evaluated vertices, concretely input values to ports. + +TODO: misnomer + + +
public struct VertexEvaluations has key, store
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::InputPort, (nexus_primitives=0x0)::data::NexusData> +
+
+ Contains ports their values. +
+
+ + +
+ + + +## Struct `DAGWalk` + +Each [DAGWalk] represents a unique path through the [DAG]. + + +
public struct DAGWalk has store
+
+ + + +
+Fields + + +
+
+invoked_vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+ What's the vertex that should be executed next. +
+
+status: (nexus_workflow=0x0)::dag::DAGWalkStatus +
+
+ What's going on with the walk? +
+
+ + +
+ + + +## Struct `RequestWalkExecution` + +Hot-potato object that tracks what is the next thing the off-chain realm +should do which will be emitted with [RequestWalkExecutionEvent]. + +Having this object allows us to skip emitting events for tools that were +invoked atomically in a single tx. + +E.g. if we have a pipeline workflow with on-chain tools A, B, C and +off-chain tool D (A -> B -> C -> D), then we can craft a PT such that A, B +and C are all invoked within in. +In this case we emit an event requesting the off-chain realm to execute D. +However, we could also craft a PT that invokes only A and B. +Then, we emit an event requesting the off-chain realm to execute C. + + +
public struct RequestWalkExecution
+
+ + + +
+Fields + + +
+
+execution: sui::object::ID +
+
+ Which [DAGExecution] does this request belong to. +
+
+for_walks_indices: vector<u64> +
+
+ We'll emit an event requesting the off-chain realm to execute next + vertex on these walks. + Indices into [DAGExecution.walks]. + Use [request_walk_execution_for_walk] to add a walk. +
+
+ + +
+ + + +## Struct `DAGCreatedEvent` + + + +
public struct DAGCreatedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+ + +
+ + + +## Struct `DAGVertexAddedEvent` + + + +
public struct DAGVertexAddedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+kind: (nexus_workflow=0x0)::dag::VertexKind +
+
+
+
+ + +
+ + + +## Struct `DAGEntryVertexInputPortAddedEvent` + + + +
public struct DAGEntryVertexInputPortAddedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+entry_port: (nexus_workflow=0x0)::dag::InputPort +
+
+
+
+entry_group: (nexus_workflow=0x0)::dag::EntryGroup +
+
+
+
+ + +
+ + + +## Struct `DAGEdgeAddedEvent` + + + +
public struct DAGEdgeAddedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+from_vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+edge: (nexus_workflow=0x0)::dag::Edge +
+
+
+
+ + +
+ + + +## Struct `DAGDefaultValueAddedEvent` + + + +
public struct DAGDefaultValueAddedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+port: (nexus_workflow=0x0)::dag::InputPort +
+
+
+
+value: (nexus_primitives=0x0)::data::NexusData +
+
+
+
+ + +
+ + + +## Struct `RequestWalkExecutionEvent` + +See [RequestWalkExecution] and [DAGExecution.last_request_for_execution_emitted_at_digest] +for more information. + + +
public struct RequestWalkExecutionEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+execution: sui::object::ID +
+
+
+
+walk_index: u64 +
+
+
+
+next_vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+evaluations: sui::object::ID +
+
+ Dynamic object field which contains [VertexEvaluations] for the next + vertex. + Note that this does not include default values which can be known from + the DAG itself. +
+
+worksheet_from_type: std::type_name::TypeName +
+
+ The off-chain realm can determine the Talus Agent Package to invoke + thanks to the package address and module name from this field. +
+
+ + +
+ + + +## Struct `EndStateReachedEvent` + +Emitted when a walk reaches an end state, ie. a vertex that has no further +outgoing edges. + + +
public struct EndStateReachedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+execution: sui::object::ID +
+
+
+
+walk_index: u64 +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+ Which vertex was the end state. +
+
+variant: (nexus_workflow=0x0)::dag::OutputVariant +
+
+
+
+variant_ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::OutputPort, (nexus_primitives=0x0)::data::NexusData> +
+
+ We don't store the end state vertex evaluations anywhere, you can get + them from here only. +
+
+ + +
+ + + +## Struct `WalkAdvancedEvent` + +Emitted when a walk advances to next vertex/vertices. + +Includes copy of the vertex evaluations for debugging purposes. + + +
public struct WalkAdvancedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+execution: sui::object::ID +
+
+
+
+walk_index: u64 +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+variant: (nexus_workflow=0x0)::dag::OutputVariant +
+
+
+
+variant_ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::OutputPort, (nexus_primitives=0x0)::data::NexusData> +
+
+
+
+ + +
+ + + +## Struct `ExecutionFinishedEvent` + +Emitted when no more work needs to be done. + + +
public struct ExecutionFinishedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+dag: sui::object::ID +
+
+
+
+execution: sui::object::ID +
+
+
+
+has_any_walk_failed: bool +
+
+
+
+has_any_walk_succeeded: bool +
+
+
+
+ + +
+ + + +## Enum `VertexKind` + + + +
public enum VertexKind has copy, drop, store
+
+ + + +
+Variants + + +
+
+Variant OnChain +
+
+ Execution is verified with [ProofOfUID]. +
+ +
+
+_variant_name: std::ascii::String +
+
+
+
+ + +
+
+tool: sui::object::ID +
+
+
+
+ + +
+
+tool_fqn: std::ascii::String +
+
+
+
+ +
+Variant OffChain +
+
+ Scheduled to be executed off-chain. + Output of the execution is expected in another tx. +
+ +
+
+_variant_name: std::ascii::String +
+
+
+
+ + +
+
+tool_fqn: std::ascii::String +
+
+
+
+ +
+ + +
+ + + +## Enum `DAGWalkStatus` + + + +
public enum DAGWalkStatus has copy, drop, store
+
+ + + +
+Variants + + +
+
+Variant Active +
+
+
+
+Variant Successful +
+
+
+
+Variant Failed +
+
+
+
+Variant Consumed +
+
+
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const ENotAnEntryVertex: vector<u8> = b"This vertex is not in the entry group";
+
+ + + + + + + +
#[error]
+const ENotAnEntryVertexPort: vector<u8> = b"This vertex-port pair is not in the entry group";
+
+ + + + + + + +
#[error]
+const EAlreadyRequestedWalkExecutionInThisTx: vector<u8> = b"Already requested walk execution in this tx";
+
+ + + + + + + +
#[error]
+const ENetworkMismatch: vector<u8> = b"Provided network ID does not match expected network ID";
+
+ + + + + + + +
#[error]
+const EExpectedOffchainTool: vector<u8> = b"Expected an off-chain tool";
+
+ + + + + + + +
#[error]
+const EVertexNotFound: vector<u8> = b"Input vertex does not exist";
+
+ + + + + + + +
#[error]
+const EWalkNotActive: vector<u8> = b"The walk must be active to do this";
+
+ + + + + + + +
#[error]
+const EWalkDoesNotExpectThisVertex: vector<u8> = b"The client provided vertex that the walk does not expect";
+
+ + + + + + + +
#[error]
+const EInvokedVertexNotEvaluated: vector<u8> = b"The invoked vertex has not been evaluated";
+
+ + + + + + + +
#[error]
+const EEdgeAndDefaultValueAreMutuallyExclusive: vector<u8> = b"An edge and a default value are mutually exclusive";
+
+ + + + + + + +
#[error]
+const EVertexInputPortAlreadyHasDefaultValue: vector<u8> = b"Vertex input port already has a default value";
+
+ + + + + + + +
#[error]
+const EWorksheetMustBeConstructedWithType: vector<u8> = b"Worksheet must be constructed with a type";
+
+ + + + + + + +
#[error]
+const EWorksheetTypeMismatch: vector<u8> = b"Worksheet was constructed without type or with a different type";
+
+ + + + + + + +
#[error]
+const ENotEntryGroup: vector<u8> = b"Provided entry group is not this DAG's entry group";
+
+ + + + + + + +
#[error]
+const EEntryGroupWrongNumberOfInputs: vector<u8> = b"Entry group input does not have the expected number of vertices";
+
+ + + + + + + +
#[error]
+const EExecutionMismatch: vector<u8> = b"Provided execution ID does not match expected execution ID";
+
+ + + + + + + +
#[error]
+const EDAGMismatch: vector<u8> = b"Provided DAG ID does not match expected DAG ID";
+
+ + + + + + + +
#[error]
+const EDAGEntryGroupResultsInNoWork: vector<u8> = b"The DAG is likely misconfigured as there are no walks to spawn";
+
+ + + + + +## Function `vertex_on_chain` + + + +
public fun vertex_on_chain(tool_fqn: std::ascii::String, tool: sui::object::ID): (nexus_workflow=0x0)::dag::VertexKind
+
+ + + + + + + +## Function `vertex_off_chain` + + + +
public fun vertex_off_chain(tool_fqn: std::ascii::String): (nexus_workflow=0x0)::dag::VertexKind
+
+ + + + + + + +## Function `vertex_from_string` + + + +
public fun vertex_from_string(name: std::ascii::String): (nexus_workflow=0x0)::dag::Vertex
+
+ + + + + + + +## Function `vertex_into_string` + + + +
public fun vertex_into_string(vertex: (nexus_workflow=0x0)::dag::Vertex): std::ascii::String
+
+ + + + + + + +## Function `output_variant_from_string` + + + +
public fun output_variant_from_string(name: std::ascii::String): (nexus_workflow=0x0)::dag::OutputVariant
+
+ + + + + + + +## Function `output_variant_into_string` + + + +
public fun output_variant_into_string(variant: (nexus_workflow=0x0)::dag::OutputVariant): std::ascii::String
+
+ + + + + + + +## Function `output_port_from_string` + + + +
public fun output_port_from_string(name: std::ascii::String): (nexus_workflow=0x0)::dag::OutputPort
+
+ + + + + + + +## Function `output_port_from_raw` + + + +
public fun output_port_from_raw(name: vector<u8>): (nexus_workflow=0x0)::dag::OutputPort
+
+ + + + + + + +## Function `output_port_into_string` + + + +
public fun output_port_into_string(port: (nexus_workflow=0x0)::dag::OutputPort): std::ascii::String
+
+ + + + + + + +## Function `input_port_from_string` + + + +
public fun input_port_from_string(name: std::ascii::String): (nexus_workflow=0x0)::dag::InputPort
+
+ + + + + + + +## Function `input_port_into_string` + + + +
public fun input_port_into_string(port: (nexus_workflow=0x0)::dag::InputPort): std::ascii::String
+
+ + + + + + + +## Function `vertex_input_port` + + + +
public fun vertex_input_port(vertex: (nexus_workflow=0x0)::dag::Vertex, port: (nexus_workflow=0x0)::dag::InputPort): (nexus_workflow=0x0)::dag::VertexInputPort
+
+ + + + + + + +## Function `vertex_input_port_from_string` + + + +
public fun vertex_input_port_from_string(vertex: std::ascii::String, port: std::ascii::String): (nexus_workflow=0x0)::dag::VertexInputPort
+
+ + + + + + + +## Function `default_entry_group` + + + +
public fun default_entry_group(): (nexus_workflow=0x0)::dag::EntryGroup
+
+ + + + + + + +## Function `entry_group_from_string` + + + +
public fun entry_group_from_string(name: std::ascii::String): (nexus_workflow=0x0)::dag::EntryGroup
+
+ + + + + + + +## Function `entry_group_into_string` + + + +
public fun entry_group_into_string(group: (nexus_workflow=0x0)::dag::EntryGroup): std::ascii::String
+
+ + + + + + + +## Function `inputs_to_begin_execution` + +Helper to construct inputs for [begin_execution]. +Creates a map from a row based input (ie. repeat vertex name for each row). + + +
public fun inputs_to_begin_execution(vertices: vector<(nexus_workflow=0x0)::dag::Vertex>, ports: vector<(nexus_workflow=0x0)::dag::InputPort>, data: vector<(nexus_primitives=0x0)::data::NexusData>): sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::Vertex, sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::InputPort, (nexus_primitives=0x0)::data::NexusData>>
+
+ + + + + + + +## Function `request_walk_execution` + +Creates a hot potato object that can be destroyed with [request_network_to_execute_walks]. + + +
public fun request_walk_execution(execution: &(nexus_workflow=0x0)::dag::DAGExecution): (nexus_workflow=0x0)::dag::RequestWalkExecution
+
+ + + + + + + +## Function `new` + +Creates an empty [DAG]. + +A [DAG] can be edited only while it's an owned object. +It becomes immutable once it's shared. + + +
public fun new(ctx: &mut sui::tx_context::TxContext): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_vertex` + +Adds a new vertex. + +The list of entry ports is added to the default entry group. + + +
public fun with_vertex(self: (nexus_workflow=0x0)::dag::DAG, name: (nexus_workflow=0x0)::dag::Vertex, kind: (nexus_workflow=0x0)::dag::VertexKind): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_entry_port` + +Add a new input port to the vertex which has to be provided by the client +when they begin execution with the default entry group. + + +
public fun with_entry_port(self: (nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex, entry_port: (nexus_workflow=0x0)::dag::InputPort): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_entry_port_in_group` + +Add a new input port to the vertex which has to be provided by the client +when they begin execution with the provided entry group. + + +
public fun with_entry_port_in_group(self: (nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex, entry_port: (nexus_workflow=0x0)::dag::InputPort, entry_group: (nexus_workflow=0x0)::dag::EntryGroup): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_entry` + +Use this after [with_vertex] to add that vertex to the default entry group. + +See also [with_entry_in_group] + + +
public fun with_entry(self: (nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_entry_in_group` + +Use this after [with_vertex] to add that vertex to the given entry group. + +Special case for vertices that are meant to have no input ports and should +be executed as soon as the execution begins. + +[begin_execution] will start execution of this vertex only if it has no +input ports. + + +
public fun with_entry_in_group(self: (nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex, entry_group: (nexus_workflow=0x0)::dag::EntryGroup): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_edge` + +Adds a new edge to the DAG. +The caller must ensure all constraints required by the workflow engine are +upheld. +See README for more information. + + +
public fun with_edge(self: (nexus_workflow=0x0)::dag::DAG, from_vertex: (nexus_workflow=0x0)::dag::Vertex, from_variant: (nexus_workflow=0x0)::dag::OutputVariant, from_port: (nexus_workflow=0x0)::dag::OutputPort, to_vertex: (nexus_workflow=0x0)::dag::Vertex, to_port: (nexus_workflow=0x0)::dag::InputPort): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `with_default_value` + +This [InputPort] mustn't have any [Edge] leading to it. + + +
public fun with_default_value(self: (nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex, port: (nexus_workflow=0x0)::dag::InputPort, value: (nexus_primitives=0x0)::data::NexusData): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `rebuild` + +Consumes the input [DAG] and returns a copy of it with a new ID. + +This is useful if you want to build a [DAG] over multiple txs or ahead of +time. + +The [DAG] can be an owned object only to be rebuilt and shared at a more +convenient time. + +(On Sui an object can be shared only in the tx that created it.) + + +
public fun rebuild(self: (nexus_workflow=0x0)::dag::DAG, ctx: &mut sui::tx_context::TxContext): (nexus_workflow=0x0)::dag::DAG
+
+ + + + + + + +## Function `begin_execution` + +Permission-lessly creates a new [DAGExecution] object using the default +entry group. +This will fail if there isn't a default entry group. + +See also [begin_execution_of_entry_group]. + + +
public fun begin_execution(self: &(nexus_workflow=0x0)::dag::DAG, worksheet: &mut (nexus_primitives=0x0)::proof_of_uid::ProofOfUID, network: sui::object::ID, with_inputs: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::Vertex, sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::InputPort, (nexus_primitives=0x0)::data::NexusData>>, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext): ((nexus_workflow=0x0)::dag::DAGExecution, (nexus_workflow=0x0)::dag::RequestWalkExecution)
+
+ + + + + + + +## Function `begin_execution_of_entry_group` + +Permission-lessly creates a new [DAGExecution] object. + +This method stamps [ProofOfUID] with proof that the [DAG] created +this [DAGExecution]. + +The only way to form a valid tx is to eventually call +[request_network_to_execute_walks]. + + +
public fun begin_execution_of_entry_group(self: &(nexus_workflow=0x0)::dag::DAG, worksheet: &mut (nexus_primitives=0x0)::proof_of_uid::ProofOfUID, network: sui::object::ID, entry_group: (nexus_workflow=0x0)::dag::EntryGroup, with_inputs: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::Vertex, sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::InputPort, (nexus_primitives=0x0)::data::NexusData>>, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext): ((nexus_workflow=0x0)::dag::DAGExecution, (nexus_workflow=0x0)::dag::RequestWalkExecution)
+
+ + + + + + + +## Function `request_network_to_execute_walks` + +Consumes [RequestWalkExecution] and emits [RequestWalkExecutionEvent] to +the off-chain realm to carry on with the execution in another tx. + + +
public fun request_network_to_execute_walks(self: &(nexus_workflow=0x0)::dag::DAG, execution: &mut (nexus_workflow=0x0)::dag::DAGExecution, request: (nexus_workflow=0x0)::dag::RequestWalkExecution, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `submit_off_chain_tool_eval_for_walk` + +This is meant to be invoked by the off-chain network after it evaluated a +tool (which is represented by a vertex in DAG). + +Which vertex to evaluate is derived from the walk index and then compared to +the provided sanity-check argument expected_vertex. + +The [Worksheet] is must be created with a type that matches the worksheet +of [RequestWalkExecution]. +This type will be emitted in the [RequestWalkExecutionEvent]. + + +
public fun submit_off_chain_tool_eval_for_walk(dag: &(nexus_workflow=0x0)::dag::DAG, execution: &mut (nexus_workflow=0x0)::dag::DAGExecution, worksheet: &mut (nexus_primitives=0x0)::proof_of_uid::ProofOfUID, leader_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::leader_cap::OverNetwork>, request_walk_execution: &mut (nexus_workflow=0x0)::dag::RequestWalkExecution, walk_index: u64, expected_vertex: (nexus_workflow=0x0)::dag::Vertex, variant: (nexus_workflow=0x0)::dag::OutputVariant, variant_ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::OutputPort, (nexus_primitives=0x0)::data::NexusData>, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `submit_on_chain_tool_eval_for_walk` + +After invoking a tool on-chain, this function is called to submit the result +of the evaluation. + +The [Worksheet] is must be created with a type that matches the worksheet +of [RequestWalkExecution]. +This type will be emitted in the [RequestWalkExecutionEvent]. + + +
public fun submit_on_chain_tool_eval_for_walk(dag: &(nexus_workflow=0x0)::dag::DAG, execution: &mut (nexus_workflow=0x0)::dag::DAGExecution, worksheet: &mut (nexus_primitives=0x0)::proof_of_uid::ProofOfUID, leader_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::leader_cap::OverNetwork>, request_walk_execution: &mut (nexus_workflow=0x0)::dag::RequestWalkExecution, walk_index: u64, expected_vertex: (nexus_workflow=0x0)::dag::Vertex, variant: (nexus_workflow=0x0)::dag::OutputVariant, variant_ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::OutputPort, (nexus_primitives=0x0)::data::NexusData>, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `request_walk_execution_walks_indices` + + + +
public fun request_walk_execution_walks_indices(self: &(nexus_workflow=0x0)::dag::RequestWalkExecution): &vector<u64>
+
+ + + + + + + +## Function `execution_created_at` + + + +
public fun execution_created_at(self: &(nexus_workflow=0x0)::dag::DAGExecution): u64
+
+ + + + + + + +## Function `execution_entry_group` + + + +
public fun execution_entry_group(self: &(nexus_workflow=0x0)::dag::DAGExecution): &(nexus_workflow=0x0)::dag::EntryGroup
+
+ + + + + + + +## Function `execution_worksheet_type_name` + + + +
public fun execution_worksheet_type_name(self: &(nexus_workflow=0x0)::dag::DAGExecution): &std::type_name::TypeName
+
+ + + + + + + +## Function `execution_invoker` + + + +
public fun execution_invoker(self: &(nexus_workflow=0x0)::dag::DAGExecution): address
+
+ + + + + + + +## Function `execution_network` + + + +
public fun execution_network(self: &(nexus_workflow=0x0)::dag::DAGExecution): sui::object::ID
+
+ + + + + + + +## Function `dag_vertex_tool_fqn` + + + +
public fun dag_vertex_tool_fqn(self: &(nexus_workflow=0x0)::dag::DAG, vertex: (nexus_workflow=0x0)::dag::Vertex): std::ascii::String
+
+ + + + + + + +## Function `execution_is_vertex_invoked` + + + +
public fun execution_is_vertex_invoked(self: &(nexus_workflow=0x0)::dag::DAGExecution, vertex: &(nexus_workflow=0x0)::dag::Vertex): bool
+
+ + + + + + + +## Function `request_walk_execution_vertices` + + + +
public fun request_walk_execution_vertices(self: &(nexus_workflow=0x0)::dag::RequestWalkExecution, dag: &(nexus_workflow=0x0)::dag::DAG, execution: &(nexus_workflow=0x0)::dag::DAGExecution): vector<(nexus_workflow=0x0)::dag::Vertex>
+
+ + + + + + + +## Function `execution_is_finished` + +Returns true if all walks are in a terminal state. + + +
public fun execution_is_finished(self: &(nexus_workflow=0x0)::dag::DAGExecution): bool
+
+ + + + + + + +## Function `assert_execution_matches_leader_cap` + + + +
public fun assert_execution_matches_leader_cap(self: &(nexus_workflow=0x0)::dag::DAGExecution, leader_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::leader_cap::OverNetwork>)
+
+ + + + + + + +## Function `assert_request_walk_execution_is_for` + + + +
public fun assert_request_walk_execution_is_for(self: &(nexus_workflow=0x0)::dag::RequestWalkExecution, execution: &(nexus_workflow=0x0)::dag::DAGExecution)
+
+ + + + + + + +## Function `assert_execution_is_for` + + + +
public fun assert_execution_is_for(self: &(nexus_workflow=0x0)::dag::DAGExecution, dag: &(nexus_workflow=0x0)::dag::DAG)
+
+ + + + + + + +## Function `assert_matches_worksheet` + + + +
public fun assert_matches_worksheet(self: &(nexus_workflow=0x0)::dag::DAGExecution, worksheet: &(nexus_primitives=0x0)::proof_of_uid::ProofOfUID)
+
+ + + + + + + +## Function `for_walk` + +Won't add the walk if it's already there. + + +
fun for_walk(self: &mut (nexus_workflow=0x0)::dag::RequestWalkExecution, walk_index: u64)
+
+ + + + + + + +## Function `advance_walk` + +Advances the walk by evaluating the vertex and following the edges if possible. + + +
fun advance_walk(dag: &(nexus_workflow=0x0)::dag::DAG, execution: &mut (nexus_workflow=0x0)::dag::DAGExecution, request_walk_execution: &mut (nexus_workflow=0x0)::dag::RequestWalkExecution, walk_index: u64, expected_vertex: (nexus_workflow=0x0)::dag::Vertex, variant: (nexus_workflow=0x0)::dag::OutputVariant, variant_ports_to_data: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::OutputPort, (nexus_primitives=0x0)::data::NexusData>, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `if_finished_emit_final_event` + +Will emit an event if the execution is done. + + +
fun if_finished_emit_final_event(self: &(nexus_workflow=0x0)::dag::DAGExecution)
+
+ + + + diff --git a/nexus/packages/reference/nexus_workflow/default_gas_extension.md b/nexus/packages/reference/nexus_workflow/default_gas_extension.md new file mode 100644 index 0000000..a01d823 --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/default_gas_extension.md @@ -0,0 +1,158 @@ + + + +# Module `(nexus_workflow=0x0)::default_gas_extension` + + + +- [Struct `ExpiryCostPerMinuteKey`](#(nexus_workflow=0x0)_default_gas_extension_ExpiryCostPerMinuteKey) +- [Struct `ExpiryGasOwnerCapKey`](#(nexus_workflow=0x0)_default_gas_extension_ExpiryGasOwnerCapKey) +- [Constants](#@Constants_0) +- [Function `buy_expiry_gas_ticket`](#(nexus_workflow=0x0)_default_gas_extension_buy_expiry_gas_ticket) +- [Function `enable_expiry`](#(nexus_workflow=0x0)_default_gas_extension_enable_expiry) +- [Function `disable_expiry`](#(nexus_workflow=0x0)_default_gas_extension_disable_expiry) + + +
use (nexus_primitives=0x0)::data;
+use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use (nexus_primitives=0x0)::proof_of_uid;
+use (nexus_workflow=0x0)::dag;
+use (nexus_workflow=0x0)::gas;
+use (nexus_workflow=0x0)::leader_cap;
+use (nexus_workflow=0x0)::tool_registry;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::bag;
+use sui::balance;
+use sui::clock;
+use sui::coin;
+use sui::config;
+use sui::deny_list;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_bag;
+use sui::object_table;
+use sui::sui;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::types;
+use sui::url;
+use sui::vec_map;
+use sui::vec_set;
+
+ + + + + +## Struct `ExpiryCostPerMinuteKey` + +How many [SUI] tokens should be paid for each minute. + + +
public struct ExpiryCostPerMinuteKey has copy, drop, store
+
+ + + +
+Fields + + +
+
+ + +
+ + + +## Struct `ExpiryGasOwnerCapKey` + +Will hold the gas owner cap to do requests on behalf of the tool owner. + + +
public struct ExpiryGasOwnerCapKey has copy, drop, store
+
+ + + +
+Fields + + +
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const EThisMethodIsNotEnabled: vector<u8> = b"This method is not enabled";
+
+ + + + + +## Function `buy_expiry_gas_ticket` + + + +
public entry fun buy_expiry_gas_ticket(gas_service: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String, minutes: u64, pay_with: &mut sui::coin::Coin<sui::sui::SUI>, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `enable_expiry` + +Enables buying gas tickets that expire after a certain time in token +[SUI] for [cost_per_minute] of those tokens. + +To update the cost just call this function again with the new value. + + +
public fun enable_expiry(gas_service: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, cost_per_minute: u64, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `disable_expiry` + +Disables this extension. + + +
public fun disable_expiry(gas_service: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext)
+
+ + + + diff --git a/nexus/packages/reference/nexus_workflow/default_tap.md b/nexus/packages/reference/nexus_workflow/default_tap.md new file mode 100644 index 0000000..6e6178e --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/default_tap.md @@ -0,0 +1,236 @@ + + +# Module `(nexus_workflow=0x0)::default_tap` + +- [Struct `DefaultTAP`](<#(nexus_workflow=0x0)_default_tap_DefaultTAP>) +- [Struct `DefaultTAPV1Witness`](<#(nexus_workflow=0x0)_default_tap_DefaultTAPV1Witness>) +- [Function `new`](<#(nexus_workflow=0x0)_default_tap_new>) +- [Function `begin_dag_execution`](<#(nexus_workflow=0x0)_default_tap_begin_dag_execution>) +- [Function `worksheet`](<#(nexus_workflow=0x0)_default_tap_worksheet>) +- [Function `confirm_tool_eval_for_walk`](<#(nexus_workflow=0x0)_default_tap_confirm_tool_eval_for_walk>) +- [Function `get_witness`](<#(nexus_workflow=0x0)_default_tap_get_witness>) + +
use (nexus_interface=0x0)::v1;
+use (nexus_interface=0x0)::version;
+use (nexus_primitives=0x0)::data;
+use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use (nexus_primitives=0x0)::proof_of_uid;
+use (nexus_workflow=0x0)::dag;
+use (nexus_workflow=0x0)::leader_cap;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::bag;
+use sui::clock;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_table;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::vec_map;
+use sui::vec_set;
+
+ + + +## Struct `DefaultTAP` + +Shared object. + +
public struct DefaultTAP has key
+
+ +
+Fields + +
+
+id: sui::object::UID +
+
+
+
+witness: sui::bag::Bag +
+
+ On package upgrade, we'll want to replace the previous witness with a + new one that identifies the new package. + This is because as of now Sui doesn't give us access to the package ID + so we need to always create a new type and query it from the type via + its type name. +
+
+iv: (nexus_interface=0x0)::version::InterfaceVersion +
+
+ Clients can search the talus agent shared object for this type to + determine what interface to expect. +
+
+ +
+ + + +## Struct `DefaultTAPV1Witness` + +Used to identify the package::module deployment. + +Will also serve as authorization token to change list of shared objects for +nexus interface v1. + +
public struct DefaultTAPV1Witness has key, store
+
+ +
+Fields + +
+
+id: sui::object::UID +
+
+
+
+ +
+ + + +## Function `new` + +
public(package) fun new(ctx: &mut sui::tx_context::TxContext)
+
+ +
+Implementation + +
public(package) fun new(ctx: &mut TxContext) {
+    let default_tap = DefaultTAP {
+        id: object::new(ctx),
+        witness: {
+            let mut b = bag::new(ctx);
+            b.add(b"witness", DefaultTAPV1Witness { id: object::new(ctx) });
+            b
+        },
+        iv: nexus_interface::version::v(1),
+    };
+    nexus_interface::v1::announce_interface_package(
+        default_tap.get_witness(),
+        vector[object::id(&default_tap)],
+    );
+    share_object(default_tap);
+}
+
+ +
+ + + +## Function `begin_dag_execution` + +Invokes the provided entry vertex on a DAG with the provided input data for +each input port. + +
public fun begin_dag_execution(self: &mut (nexus_workflow=0x0)::default_tap::DefaultTAP, dag: &(nexus_workflow=0x0)::dag::DAG, network: sui::object::ID, entry_group: (nexus_workflow=0x0)::dag::EntryGroup, with_vertex_inputs: sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::Vertex, sui::vec_map::VecMap<(nexus_workflow=0x0)::dag::InputPort, (nexus_primitives=0x0)::data::NexusData>>, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext)
+
+ +
+Implementation + +
public fun begin_dag_execution(
+    self: &mut DefaultTAP,
+    dag: &DAG,
+    network: ID,
+    entry_group: EntryGroup,
+    with_vertex_inputs: VecMap<Vertex, VecMap<InputPort, NexusData>>,
+    clock: &Clock,
+    ctx: &mut TxContext,
+) {
+    self.iv.expect_v(1);
+    let mut worksheet = self.worksheet();
+    let (mut execution, ticket) = dag.begin_execution_of_entry_group(
+        &mut worksheet,
+        network,
+        entry_group,
+        with_vertex_inputs,
+        clock,
+        ctx
+    );
+    worksheet.consume(&self.get_witness().id);
+    dag.request_network_to_execute_walks(&mut execution, ticket, ctx);
+    public_share_object(execution);
+}
+
+ +
+ + + +## Function `worksheet` + +Nexus resources can prove that they did a thing. + +
public fun worksheet(self: &(nexus_workflow=0x0)::default_tap::DefaultTAP): (nexus_primitives=0x0)::proof_of_uid::ProofOfUID
+
+ +
+Implementation + +
public fun worksheet(self: &DefaultTAP): ProofOfUID {
+    self.iv.expect_v(1);
+    proof_of_uid::new_with_type(&self.get_witness().id, self.get_witness())
+}
+
+ +
+ + + +## Function `confirm_tool_eval_for_walk` + +One calls this after workflow contract is done with advancing the DAG. + +
public fun confirm_tool_eval_for_walk(self: &mut (nexus_workflow=0x0)::default_tap::DefaultTAP, worksheet: (nexus_primitives=0x0)::proof_of_uid::ProofOfUID)
+
+ +
+Implementation + +
public fun confirm_tool_eval_for_walk(
+    self: &mut DefaultTAP,
+    worksheet: ProofOfUID,
+) {
+    self.iv.expect_v(1);
+    worksheet.consume(&self.get_witness().id);
+}
+
+ +
+ + + +## Function `get_witness` + +
fun get_witness(self: &(nexus_workflow=0x0)::default_tap::DefaultTAP): &(nexus_workflow=0x0)::default_tap::DefaultTAPV1Witness
+
+ +
+Implementation + +
fun get_witness(self: &DefaultTAP): &DefaultTAPV1Witness {
+    self.witness.borrow(b"witness")
+}
+
+ +
diff --git a/nexus/packages/reference/nexus_workflow/gas.md b/nexus/packages/reference/nexus_workflow/gas.md new file mode 100644 index 0000000..d91247a --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/gas.md @@ -0,0 +1,868 @@ + + +# Module `(nexus_workflow=0x0)::gas` + +- [Struct `GasService`](<#(nexus_workflow=0x0)_gas_GasService>) +- [Struct `ExecutionGas`](<#(nexus_workflow=0x0)_gas_ExecutionGas>) +- [Struct `GasBudgets`](<#(nexus_workflow=0x0)_gas_GasBudgets>) +- [Struct `ToolGas`](<#(nexus_workflow=0x0)_gas_ToolGas>) +- [Struct `GasTicket`](<#(nexus_workflow=0x0)_gas_GasTicket>) +- [Struct `OverGas`](<#(nexus_workflow=0x0)_gas_OverGas>) +- [Struct `GasSettlementUpdateEvent`](<#(nexus_workflow=0x0)_gas_GasSettlementUpdateEvent>) +- [Struct `LeaderClaimedGasEvent`](<#(nexus_workflow=0x0)_gas_LeaderClaimedGasEvent>) +- [Enum `ModusOperandi`](<#(nexus_workflow=0x0)_gas_ModusOperandi>) +- [Enum `Scope`](<#(nexus_workflow=0x0)_gas_Scope>) + - [For Clients](#@For_Clients_0) +- [Constants](#@Constants_1) +- [Function `modus_operandi_limited_invocations`](<#(nexus_workflow=0x0)_gas_modus_operandi_limited_invocations>) +- [Function `modus_operandi_expiry`](<#(nexus_workflow=0x0)_gas_modus_operandi_expiry>) +- [Function `modus_operandi_upon_discretion_of_the_tool`](<#(nexus_workflow=0x0)_gas_modus_operandi_upon_discretion_of_the_tool>) +- [Function `scope_invoker_address`](<#(nexus_workflow=0x0)_gas_scope_invoker_address>) +- [Function `scope_worksheet_type`](<#(nexus_workflow=0x0)_gas_scope_worksheet_type>) +- [Function `scope_execution`](<#(nexus_workflow=0x0)_gas_scope_execution>) +- [Function `new_service`](<#(nexus_workflow=0x0)_gas_new_service>) +- [Function `share_service`](<#(nexus_workflow=0x0)_gas_share_service>) +- [Function `set_single_invocation_cost_mist`](<#(nexus_workflow=0x0)_gas_set_single_invocation_cost_mist>) +- [Function `claim_gas`](<#(nexus_workflow=0x0)_gas_claim_gas>) +- [Function `add_gas_ticket`](<#(nexus_workflow=0x0)_gas_add_gas_ticket>) +- [Function `revoke_gas_ticket`](<#(nexus_workflow=0x0)_gas_revoke_gas_ticket>) +- [Function `get_tool_gas_setting_mut`](<#(nexus_workflow=0x0)_gas_get_tool_gas_setting_mut>) +- [Function `deescalate`](<#(nexus_workflow=0x0)_gas_deescalate>) +- [Function `claim_leader_gas`](<#(nexus_workflow=0x0)_gas_claim_leader_gas>) + - [How does the leader estimate the amount?](#@How_does_the_leader_estimate_the_amount?_2) + - [Trust the leader?](#@Trust_the_leader?_3) +- [Function `sync_gas_state`](<#(nexus_workflow=0x0)_gas_sync_gas_state>) +- [Function `sync_gas_state_for_vertex`](<#(nexus_workflow=0x0)_gas_sync_gas_state_for_vertex>) +- [Function `donate_to_tool`](<#(nexus_workflow=0x0)_gas_donate_to_tool>) +- [Function `add_gas_budget`](<#(nexus_workflow=0x0)_gas_add_gas_budget>) +- [Function `refund_execution_gas_budget`](<#(nexus_workflow=0x0)_gas_refund_execution_gas_budget>) +- [Function `refund_invoker_gas_budget`](<#(nexus_workflow=0x0)_gas_refund_invoker_gas_budget>) +- [Function `refund_worksheet_gas_budget`](<#(nexus_workflow=0x0)_gas_refund_worksheet_gas_budget>) +- [Function `is_execution_vertex_settled`](<#(nexus_workflow=0x0)_gas_is_execution_vertex_settled>) +- [Function `get_tool_gas_setting`](<#(nexus_workflow=0x0)_gas_get_tool_gas_setting>) +- [Function `try_settle_execution_for_vertex`](<#(nexus_workflow=0x0)_gas_try_settle_execution_for_vertex>) +- [Function `try_stamp`](<#(nexus_workflow=0x0)_gas_try_stamp>) +- [Function `try_stamp_scope`](<#(nexus_workflow=0x0)_gas_try_stamp_scope>) + - [Important](#@Important_4) +- [Function `try_one_time_charge`](<#(nexus_workflow=0x0)_gas_try_one_time_charge>) +- [Function `get_or_insert_tool_gas_mut`](<#(nexus_workflow=0x0)_gas_get_or_insert_tool_gas_mut>) + +
use (nexus_primitives=0x0)::data;
+use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use (nexus_primitives=0x0)::proof_of_uid;
+use (nexus_workflow=0x0)::dag;
+use (nexus_workflow=0x0)::leader_cap;
+use (nexus_workflow=0x0)::tool_registry;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::bag;
+use sui::balance;
+use sui::clock;
+use sui::coin;
+use sui::config;
+use sui::deny_list;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_bag;
+use sui::object_table;
+use sui::sui;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::types;
+use sui::url;
+use sui::vec_map;
+use sui::vec_set;
+
+ + + +## Struct `GasService` + +Shared object. + +
public struct GasService has key
+
+ +
+Fields + +
+
+id: sui::object::UID +
+
+
+
+executions_gas: sui::object_table::ObjectTable<sui::object::ID, (nexus_workflow=0x0)::gas::ExecutionGas> +
+
+ Can be queried to see which vertices in the execution have their gas + settled. + The key is the [DAGExecution]'s ID. +
+
+tools_gas: sui::table::Table<std::ascii::String, (nexus_workflow=0x0)::gas::ToolGas> +
+
+ Holds information about who has prepaid for gas, gas configuration + for tools, etc. + The keys is the tool's FQN. +
+
+gas_budgets: (nexus_workflow=0x0)::gas::GasBudgets +
+
+ Gas budget is used to pay for [DAGExecution]. +
+
+ +
+ + + +## Struct `ExecutionGas` + +Dynamic object field. + +
public struct ExecutionGas has key, store
+
+ +
+Fields + +
+
+id: sui::object::UID +
+
+
+
+settled_vertices: sui::vec_set::VecSet<(nexus_workflow=0x0)::dag::Vertex> +
+
+ Gas for these vertices has been settled and they can be invoked once. +
+
+ +
+ + + +## Struct `GasBudgets` + +The value is always Balance<SUI>. + +This budget will be used to buy invocations from tools unless there +exist gas tickets that can be stamped instead. + +This budget will also be used to pay for leader gas. + +The key is a [Scope]. + +
public struct GasBudgets has store
+
+ +
+Fields + +
+
+inner: sui::bag::Bag +
+
+
+
+ +
+ + + +## Struct `ToolGas` + +Dynamic field. + +Holds prepaid gas tickets for a tool that can get stamped resulting in gas +settlement for an execution's vertex. + +When resolving a gas ticket for a [DAGExecution] the gas service: + +1. looks for a ticket belonging to an execution. +2. If none ok then it looks for a ticket for the worksheet type. +3. If none ok then it looks for a ticket for the address. +4. If none ok then the the gas payment is invalid. + +
public struct ToolGas has store
+
+ +
+Fields + +
+
+vault: sui::balance::Balance<sui::sui::SUI> +
+
+ Holds funds collected for gas settlements. +
+
+single_invocation_cost_mist: u64 +
+
+ If this is set to 0 then the tool doesn't require gas to be invoked. + Otherwise if no other existing gas tickets are found then the gas + service will take this amount from the gas budget if possible. + The complexity of gas settlement cannot be captured in a single number. + However, a single number is a good enough default for most tools. + More complex gas settlement can be achieved with [add_gas_ticket]. + For tools that expect other modes it is recommended to set this value + higher than alternatives to have this as a last resort option but + provide a less expensive alternative for the happy path. + Those tools that don't want to allow payments for a single invocation + at all can set this to u64::MAX. + TODO: +
+
+settings: sui::bag::Bag +
+
+ Settings for gas extensions. + Can be read without any extra access permissions and written to with + tool's owner cap. +
+
+tickets: sui::bag::Bag +
+
+ The value is always [GasTicket]. + Keys: + - execution ID + - worksheet type + - invoker address + If the key is an execution ID: + The gas tickets that apply to a specific execution only. + If the key is a worksheet's [TypeName]: + The gas tickets that apply to a specific worksheet type only. + If the key is an address: + The gas tickets that apply to a specific address only. +
+
+ +
+ + + +## Struct `GasTicket` + +Someone has prepaid for tool with expectations defined in this state. + +See [try_stamp] for info on mutations to this state. + +
public struct GasTicket has drop, store
+
+ +
+Fields + +
+
+created_at_ms: u64 +
+
+ State of the [Clock] when the ticket was created. +
+
+modus_operandi: (nexus_workflow=0x0)::gas::ModusOperandi +
+
+ How should the ticket behave. +
+
+ +
+ + + +## Struct `OverGas` + +Generic for owner cap that allows adding and removing gas tickets and +changing settings. + +This is a de-escalated version of the [OverTool] owner cap. +It should make tool owners more comfortable using gas extensions as they +don't give them access to important state. + +
public struct OverGas has drop
+
+ +
+Fields + +
+
+ +
+ + + +## Struct `GasSettlementUpdateEvent` + +
public struct GasSettlementUpdateEvent has copy, drop
+
+ +
+Fields + +
+
+execution: sui::object::ID +
+
+
+
+vertex: (nexus_workflow=0x0)::dag::Vertex +
+
+
+
+tool_fqn: std::ascii::String +
+
+
+
+was_settled: bool +
+
+ Whether the gas ticket was stamped ok. + Multiple of these events can be emitted for the same execution/vertex/tool_fqn + combination, but only one of them will have this field set to true. +
+
+ +
+ + + +## Struct `LeaderClaimedGasEvent` + +If anyone wants to verify that the leader is claiming an honest amount of +gas they can use this event to track the gas claimed. +The event's ID comprises also the tx hash and so the verifier can use it to +cross check the gas claimed with the tx gas budget spent. + +
public struct LeaderClaimedGasEvent has copy, drop
+
+ +
+Fields + +
+
+network: sui::object::ID +
+
+ Who is the leader. +
+
+amount: u64 +
+
+ How much was claimed. +
+
+ +
+ + + +## Enum `ModusOperandi` + +How should the gas ticket behave. + +
public enum ModusOperandi has copy, drop, store
+
+ +
+Variants + +
+
+Variant Expiry +
+
+ How long is the ticket valid for. +
+ +
+
+_variant_name: std::ascii::String +
+
+
+
+ +
+
+valid_for_ms: u64 +
+
+ Since the ticket was created. +
+
+ +
+Variant LimitedInvocations +
+
+ How many invocations are allowed. +
+ +
+
+_variant_name: std::ascii::String +
+
+
+
+ +
+
+total: u64 +
+
+ How many invocations to begin with. + Is always greater than or equal to the used invocations. +
+
+ +
+
+used: u64 +
+
+ When this equals the total then all invocations are used up. +
+
+ +
+Variant UponDiscretionOfTheTool +
+
+ The tool has a complete control over whether the ticket is valid or not. + This means it can add or remove gas tickets at will. +
+ +
+
+_variant_name: std::ascii::String +
+
+
+
+ +
+ +
+ + + +## Enum `Scope` + +See the docs to understand how scopes work. + + + +### For Clients + +We ignore enum variant convention out of convenience as in this specific +example the variant is unambiguous. + + + +
public enum Scope has copy, drop, store
+
+ +
+Variants + +
+
+Variant Execution +
+
+
+ +
+
+0: sui::object::ID +
+
+
+
+ +
+Variant WorksheetType +
+
+
+ +
+
+0: std::type_name::TypeName +
+
+
+
+ +
+Variant InvokerAddress +
+
+
+ +
+
+0: address +
+
+
+
+ +
+ +
+ + + +## Constants + + + +
#[error]
+const ECannotRevokeGasTicket: vector<u8> = b"Tickets with expiry or limited number of invocations cannot be revoked";
+
+ + + +
#[error]
+const ECannotClaimLeaderGas: vector<u8> = b"There is not enough gas in the budget to pay for leader gas";
+
+ + + +## Function `modus_operandi_limited_invocations` + +
public fun modus_operandi_limited_invocations(total: u64, used: u64): (nexus_workflow=0x0)::gas::ModusOperandi
+
+ + + +## Function `modus_operandi_expiry` + +
public fun modus_operandi_expiry(valid_for_ms: u64): (nexus_workflow=0x0)::gas::ModusOperandi
+
+ + + +## Function `modus_operandi_upon_discretion_of_the_tool` + +
public fun modus_operandi_upon_discretion_of_the_tool(): (nexus_workflow=0x0)::gas::ModusOperandi
+
+ + + +## Function `scope_invoker_address` + +
public fun scope_invoker_address(invoker_address: address): (nexus_workflow=0x0)::gas::Scope
+
+ + + +## Function `scope_worksheet_type` + +
public fun scope_worksheet_type(worksheet_type_name: std::type_name::TypeName): (nexus_workflow=0x0)::gas::Scope
+
+ + + +## Function `scope_execution` + +
public fun scope_execution(execution_id: sui::object::ID): (nexus_workflow=0x0)::gas::Scope
+
+ + + +## Function `new_service` + +
public(package) fun new_service(ctx: &mut sui::tx_context::TxContext): (nexus_workflow=0x0)::gas::GasService
+
+ + + +## Function `share_service` + +
public(package) fun share_service(self: (nexus_workflow=0x0)::gas::GasService)
+
+ + + +## Function `set_single_invocation_cost_mist` + +Changes how much tool charges per single invocation. + +Note that this enables gas collection by the tool so the tool owner should +be interested in calling this. + +
public fun set_single_invocation_cost_mist(self: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, fqn: std::ascii::String, single_invocation_cost_mist: u64, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `claim_gas` + +Claims all gas settlement for the given tool. + +
public fun claim_gas(self: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext): sui::balance::Balance<sui::sui::SUI>
+
+ + + +## Function `add_gas_ticket` + +The tool owner can add a gas ticket at will. + +By using either "invocations" or "expiry" modes the tool owner promises to +deliver the service otherwise they might be slashed, subject to the rules +defined by the Talus/Nexus terms - see the docs. + +The tool owner can use "upon discretion of the tool" mode to be able to +[revoke_gas_ticket] at will. + +
public fun add_gas_ticket(self: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, fqn: std::ascii::String, scope: (nexus_workflow=0x0)::gas::Scope, modus_operandi: (nexus_workflow=0x0)::gas::ModusOperandi, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `revoke_gas_ticket` + +The tool owner can revoke a gas ticket with mode "upon discretion of the tool". + +This means that the tool owner can invalidate a gas ticket at any time. + +
public fun revoke_gas_ticket(self: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, fqn: std::ascii::String, scope: (nexus_workflow=0x0)::gas::Scope, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `get_tool_gas_setting_mut` + +The tool owner can set the gas settings for the tool. + +
public fun get_tool_gas_setting_mut(self: &mut (nexus_workflow=0x0)::gas::GasService, tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext): &mut sui::bag::Bag
+
+ + + +## Function `deescalate` + +Lowers privileges of the tool owner cap into a gas owner cap. + +
public fun deescalate(tool_registry: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext): (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::gas::OverGas>
+
+ + + +## Function `claim_leader_gas` + +The leader claims just about the amount of gas it costs to pay for the tx. + + + +### How does the leader estimate the amount? + +The leader dry runs the tx and reads how much gas it would cost to pay for +the tx. +Then it sets the gas budget for the tx to the amount obtained in the dry-run +(with a few % extra as a buffer) and actually submits the tx. +The submitted tx will use claim_leader_gas with the amount obtained in the +dry-run. + + + +### Trust the leader? + +At the moment the leader can claim any amount of gas it wants from anybody +who uploads gas budgets. +To hold the leader accountable we emit LeaderClaimedGasEvent event which +can be read by 3rd parties that check that the amount claimed is not over +the top. + +
public fun claim_leader_gas(self: &mut (nexus_workflow=0x0)::gas::GasService, execution: &(nexus_workflow=0x0)::dag::DAGExecution, leader_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::leader_cap::OverNetwork>, amount: u64): sui::balance::Balance<sui::sui::SUI>
+
+ + + +## Function `sync_gas_state` + +Calls [sync_gas_state_for_vertex] for each vertex invoked in this tx. + +
public fun sync_gas_state(self: &mut (nexus_workflow=0x0)::gas::GasService, dag: &(nexus_workflow=0x0)::dag::DAG, execution: &(nexus_workflow=0x0)::dag::DAGExecution, request_walk_execution: &(nexus_workflow=0x0)::dag::RequestWalkExecution, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `sync_gas_state_for_vertex` + +Syncs gas payment with an execution. + +This function is a no-op if + +1. The vertex is not in the registry. +2. The vertex is not invoked. +3. The vertex is already settled. + +This function aborts if + +1. The DAG doesn't contain the provided vertex. + +This function is permission-less and idempotent. +Anyone can sync the gas state for a vertex and this function will only do +work provided the above conditions are met. + +
public fun sync_gas_state_for_vertex(self: &mut (nexus_workflow=0x0)::gas::GasService, dag: &(nexus_workflow=0x0)::dag::DAG, execution: &(nexus_workflow=0x0)::dag::DAGExecution, vertex: (nexus_workflow=0x0)::dag::Vertex, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `donate_to_tool` + +Donate given balance to the tool's gas total. + +
public fun donate_to_tool(self: &mut (nexus_workflow=0x0)::gas::GasService, fqn: std::ascii::String, amount: sui::balance::Balance<sui::sui::SUI>)
+
+ + + +## Function `add_gas_budget` + +Adds gas budget for the given scope. +Gas budget will be used to pay for invocation if no gas ticket is present. + +
public fun add_gas_budget(self: &mut (nexus_workflow=0x0)::gas::GasService, scope: (nexus_workflow=0x0)::gas::Scope, budget: sui::balance::Balance<sui::sui::SUI>)
+
+ + + +## Function `refund_execution_gas_budget` + +Can be called once the execution finishes. + +The gas will be refunded to the invoker's address if finished. + +
public fun refund_execution_gas_budget(self: &mut (nexus_workflow=0x0)::gas::GasService, execution: &(nexus_workflow=0x0)::dag::DAGExecution, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `refund_invoker_gas_budget` + +Can be called by the invoker. +Will be empty if not present. + +
public fun refund_invoker_gas_budget(self: &mut (nexus_workflow=0x0)::gas::GasService, ctx: &mut sui::tx_context::TxContext): sui::balance::Balance<sui::sui::SUI>
+
+ + + +## Function `refund_worksheet_gas_budget` + +Can be called by the TAP. +Will be empty if not present. + +
public fun refund_worksheet_gas_budget<T>(self: &mut (nexus_workflow=0x0)::gas::GasService, _witness: &T): sui::balance::Balance<sui::sui::SUI>
+
+ + + +## Function `is_execution_vertex_settled` + +Can the vertex be invoked once? + +
public fun is_execution_vertex_settled(self: &(nexus_workflow=0x0)::gas::GasService, execution: &(nexus_workflow=0x0)::dag::DAGExecution, vertex: (nexus_workflow=0x0)::dag::Vertex): bool
+
+ + + +## Function `get_tool_gas_setting` + +Anyone can read the tool gas settings. + +
public fun get_tool_gas_setting(self: &(nexus_workflow=0x0)::gas::GasService, fqn: std::ascii::String): &sui::bag::Bag
+
+ + + +## Function `try_settle_execution_for_vertex` + +Updates state of gas settlement for the given execution. + +
fun try_settle_execution_for_vertex(self: &mut (nexus_workflow=0x0)::gas::GasService, dag: &(nexus_workflow=0x0)::dag::DAG, execution: &(nexus_workflow=0x0)::dag::DAGExecution, vertex: (nexus_workflow=0x0)::dag::Vertex, ctx: &mut sui::tx_context::TxContext)
+
+ + + +## Function `try_stamp` + +Tries to stamp a ticket for the given execution. + +The caller must ensure that it is appropriate to stamp the ticket. + +
fun try_stamp(self: &mut (nexus_workflow=0x0)::gas::ToolGas, execution: &(nexus_workflow=0x0)::dag::DAGExecution): bool
+
+ + + +## Function `try_stamp_scope` + +Returns true if the gas ticket is valid. +The key determines the scope we're stamping. + + + +### Important + +This function mutates the ticket state. +It's important that the return values are used to update the state of the +caller. + +Once a [GasTicket] cannot be stamped anymore it is removed. + +
fun try_stamp_scope<K: copy, drop, store>(self: &mut (nexus_workflow=0x0)::gas::ToolGas, key: K, execution_created_at: u64): bool
+
+ + + +## Function `try_one_time_charge` + +Balance is either empty if no gas could be used or it contains the exact +amount requested. + +
fun try_one_time_charge(self: &mut (nexus_workflow=0x0)::gas::GasBudgets, execution: &(nexus_workflow=0x0)::dag::DAGExecution, amount: u64): sui::balance::Balance<sui::sui::SUI>
+
+ + + +## Function `get_or_insert_tool_gas_mut` + +
fun get_or_insert_tool_gas_mut(self: &mut (nexus_workflow=0x0)::gas::GasService, fqn: std::ascii::String, ctx: &mut sui::tx_context::TxContext): &mut (nexus_workflow=0x0)::gas::ToolGas
+
diff --git a/nexus/packages/reference/nexus_workflow/leader_cap.md b/nexus/packages/reference/nexus_workflow/leader_cap.md new file mode 100644 index 0000000..be12a7f --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/leader_cap.md @@ -0,0 +1,110 @@ + + + +# Module `(nexus_workflow=0x0)::leader_cap` + + + +- [Struct `OverNetwork`](#(nexus_workflow=0x0)_leader_cap_OverNetwork) +- [Struct `FoundingLeaderCapCreatedEvent`](#(nexus_workflow=0x0)_leader_cap_FoundingLeaderCapCreatedEvent) +- [Function `new`](#(nexus_workflow=0x0)_leader_cap_new) +- [Function `create_for_self_and_addresses`](#(nexus_workflow=0x0)_leader_cap_create_for_self_and_addresses) + + +
use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::vector;
+use sui::address;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::transfer;
+use sui::tx_context;
+
+ + + + + +## Struct `OverNetwork` + +Type of [OwnerCap] that can be held by the leader of an off-chain network. +An off-chain network has one or more equally trusted leaders. + + +
public struct OverNetwork has drop
+
+ + + +
+Fields + + +
+
+ + +
+ + + +## Struct `FoundingLeaderCapCreatedEvent` + + + +
public struct FoundingLeaderCapCreatedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+leader_cap: sui::object::ID +
+
+
+
+network: sui::object::ID +
+
+
+
+ + +
+ + + +## Function `new` + +Creates a founding [CloneableOwnerCap] with a random network ID. + + +
public fun new(ctx: &mut sui::tx_context::TxContext): (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::leader_cap::OverNetwork>
+
+ + + + + + + +## Function `create_for_self_and_addresses` + + + +
public entry fun create_for_self_and_addresses(n_per_address: u64, addresses: vector<address>, ctx: &mut sui::tx_context::TxContext)
+
+ + + + diff --git a/nexus/packages/reference/nexus_workflow/main.md b/nexus/packages/reference/nexus_workflow/main.md new file mode 100644 index 0000000..69c62af --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/main.md @@ -0,0 +1,84 @@ + + +# Module `(nexus_workflow=0x0)::main` + +- [Constants](#@Constants_0) + - [Important](#@Important_1) +- [Function `init`](<#(nexus_workflow=0x0)_main_init>) + +
use (nexus_interface=0x0)::v1;
+use (nexus_interface=0x0)::version;
+use (nexus_primitives=0x0)::data;
+use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use (nexus_primitives=0x0)::proof_of_uid;
+use (nexus_workflow=0x0)::dag;
+use (nexus_workflow=0x0)::default_tap;
+use (nexus_workflow=0x0)::gas;
+use (nexus_workflow=0x0)::leader_cap;
+use (nexus_workflow=0x0)::tool_registry;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::bag;
+use sui::balance;
+use sui::clock;
+use sui::coin;
+use sui::config;
+use sui::deny_list;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_bag;
+use sui::object_table;
+use sui::sui;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::types;
+use sui::url;
+use sui::vec_map;
+use sui::vec_set;
+
+ + + +## Constants + + + +Each dev can list their local addresses and when we deploy the contract +relevant addresses will obtain leader cap. +Useful mainly for testing and going into testnet. + + + +### Important + +This is development only configuration. +When we go into later stages of development, we will remove this in favour +of a proper deploy script. + +
const DEV_ADDRESSES: vector<address> = vector[0x9f205ddfe4be3c9c07a57d8e1ef233f7b7537a1248dd46b1514ba93261621d52, 0x4fbfd7d8b5683bf9adc9936c026ec79a559e5d6420123ebcdfb85e1c9b2fd84c];
+
+ + + +Amount of leader caps to clone per [DEV_ADDRESSES] address. + +
const LEADER_CAPS_PER_ADDRESS: u64 = 5;
+
+ + + +## Function `init` + +
fun init(ctx: &mut sui::tx_context::TxContext)
+
diff --git a/nexus/packages/reference/nexus_workflow/tool_registry.md b/nexus/packages/reference/nexus_workflow/tool_registry.md new file mode 100644 index 0000000..3c33aa3 --- /dev/null +++ b/nexus/packages/reference/nexus_workflow/tool_registry.md @@ -0,0 +1,745 @@ + + + +# Module `(nexus_workflow=0x0)::tool_registry` + + + +- [Struct `ToolRegistry`](#(nexus_workflow=0x0)_tool_registry_ToolRegistry) +- [Struct `OffChainTool`](#(nexus_workflow=0x0)_tool_registry_OffChainTool) +- [Struct `OverSlashing`](#(nexus_workflow=0x0)_tool_registry_OverSlashing) +- [Struct `OverTool`](#(nexus_workflow=0x0)_tool_registry_OverTool) +- [Struct `ToolRegistryCreatedEvent`](#(nexus_workflow=0x0)_tool_registry_ToolRegistryCreatedEvent) +- [Struct `OffChainToolRegisteredEvent`](#(nexus_workflow=0x0)_tool_registry_OffChainToolRegisteredEvent) +- [Struct `ToolUnregisteredEvent`](#(nexus_workflow=0x0)_tool_registry_ToolUnregisteredEvent) +- [Struct `ToolSlashedEvent`](#(nexus_workflow=0x0)_tool_registry_ToolSlashedEvent) +- [Constants](#@Constants_0) +- [Function `new`](#(nexus_workflow=0x0)_tool_registry_new) +- [Function `share`](#(nexus_workflow=0x0)_tool_registry_share) +- [Function `slash_off_chain_tool`](#(nexus_workflow=0x0)_tool_registry_slash_off_chain_tool) +- [Function `set_mist_collateral_to_lock`](#(nexus_workflow=0x0)_tool_registry_set_mist_collateral_to_lock) +- [Function `set_lock_duration_ms`](#(nexus_workflow=0x0)_tool_registry_set_lock_duration_ms) +- [Function `register_off_chain_tool_for_self`](#(nexus_workflow=0x0)_tool_registry_register_off_chain_tool_for_self) + - [Slashing](#@Slashing_1) + - [Owner Cap](#@Owner_Cap_2) + - [Gas Tickets](#@Gas_Tickets_3) +- [Function `register_off_chain_tool`](#(nexus_workflow=0x0)_tool_registry_register_off_chain_tool) +- [Function `unregister_off_chain_tool`](#(nexus_workflow=0x0)_tool_registry_unregister_off_chain_tool) +- [Function `claim_collateral_for_self`](#(nexus_workflow=0x0)_tool_registry_claim_collateral_for_self) +- [Function `claim_collateral_for_off_chain_tool`](#(nexus_workflow=0x0)_tool_registry_claim_collateral_for_off_chain_tool) +- [Function `deescalate`](#(nexus_workflow=0x0)_tool_registry_deescalate) +- [Function `did_unregister_period_pass`](#(nexus_workflow=0x0)_tool_registry_did_unregister_period_pass) +- [Function `assert_tool_registered`](#(nexus_workflow=0x0)_tool_registry_assert_tool_registered) +- [Function `assert_tool_owner`](#(nexus_workflow=0x0)_tool_registry_assert_tool_owner) +- [Function `assert_tool_owner_unchecked_generic`](#(nexus_workflow=0x0)_tool_registry_assert_tool_owner_unchecked_generic) +- [Function `register_off_chain_tool_`](#(nexus_workflow=0x0)_tool_registry_register_off_chain_tool_) +- [Function `did_unregister_period_pass_`](#(nexus_workflow=0x0)_tool_registry_did_unregister_period_pass_) + + +
use (nexus_primitives=0x0)::event;
+use (nexus_primitives=0x0)::owner_cap;
+use std::address;
+use std::ascii;
+use std::bcs;
+use std::option;
+use std::string;
+use std::type_name;
+use std::vector;
+use sui::address;
+use sui::bag;
+use sui::balance;
+use sui::clock;
+use sui::coin;
+use sui::config;
+use sui::deny_list;
+use sui::dynamic_field;
+use sui::dynamic_object_field;
+use sui::event;
+use sui::hex;
+use sui::object;
+use sui::object_bag;
+use sui::sui;
+use sui::table;
+use sui::transfer;
+use sui::tx_context;
+use sui::types;
+use sui::url;
+use sui::vec_set;
+
+ + + + + +## Struct `ToolRegistry` + + + +
public struct ToolRegistry has key
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+tools: sui::object_bag::ObjectBag +
+
+ Maps tool FQNs to their definitions. +
+
+mist_collateral_to_lock: u64 +
+
+ How much [SUI] (in MIST) to lock to register a tool. +
+
+lock_duration_ms: u64 +
+
+ How long is the collateral locked for in milliseconds after unregistering. + Copied to tool upon registration. +
+
+ + +
+ + + +## Struct `OffChainTool` + +Dynamic object. + + +
public struct OffChainTool has key, store
+
+ + + +
+Fields + + +
+
+id: sui::object::UID +
+
+
+
+url: vector<u8> +
+
+ How can we reach this tool? +
+
+input_schema: vector<u8> +
+
+ Must follow our meta schema for input schemas. +
+
+output_schema: vector<u8> +
+
+ Must follow our meta schema for output schemas. +
+
+vault: sui::balance::Balance<sui::sui::SUI> +
+
+ The tool's vault holds the tool's locked minimum of collateral to + register the tool; + To register a tool one must lock [ToolRegistry::mist_collateral_to_lock] + [SUI]. +
+
+lock_duration_ms: u64 +
+
+ How long is the collateral locked for in milliseconds after unregistering. +
+
+was_unregistered_at_ms: std::option::Option<u64> +
+
+ If the creator unregisters the tool, we keep track of when it happened. + After some time we release the collateral back to the creator. +
+
+ + +
+ + + +## Struct `OverSlashing` + +Will identify admin cap for slashing permissions. + + +
public struct OverSlashing has drop
+
+ + + +
+Fields + + +
+
+ + +
+ + + +## Struct `OverTool` + +Will identify admin cap over a tool in this registry. + + +
public struct OverTool has drop
+
+ + + +
+Fields + + +
+
+ + +
+ + + +## Struct `ToolRegistryCreatedEvent` + + + +
public struct ToolRegistryCreatedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+registry: sui::object::ID +
+
+
+
+slashing_cap: sui::object::ID +
+
+
+
+ + +
+ + + +## Struct `OffChainToolRegisteredEvent` + + + +
public struct OffChainToolRegisteredEvent has copy, drop
+
+ + + +
+Fields + + +
+
+registry: sui::object::ID +
+
+ [ID] of [ToolRegistry]. +
+
+tool: sui::object::ID +
+
+ New [ID] of the [OffChainTool]. +
+
+fqn: std::ascii::String +
+
+ The unique identifier for this tool. +
+
+url: vector<u8> +
+
+ How can we reach this tool? +
+
+input_schema: vector<u8> +
+
+ Must follow our meta schema for input schemas. +
+
+output_schema: vector<u8> +
+
+ Must follow our meta schema for output schemas. +
+
+ + +
+ + + +## Struct `ToolUnregisteredEvent` + + + +
public struct ToolUnregisteredEvent has copy, drop
+
+ + + +
+Fields + + +
+
+tool: sui::object::ID +
+
+
+
+fqn: std::ascii::String +
+
+
+
+ + +
+ + + +## Struct `ToolSlashedEvent` + +The admin has slashed some collateral from a tool. + + +
public struct ToolSlashedEvent has copy, drop
+
+ + + +
+Fields + + +
+
+tool: sui::object::ID +
+
+
+
+fqn: std::ascii::String +
+
+
+
+amount: u64 +
+
+ Amount of (MIST) collateral slashed. +
+
+ + +
+ + + +## Constants + + + + + + +
#[error]
+const ENotEnoughSuiToLock: vector<u8> = b"Not enough SUI to lock collateral";
+
+ + + + + + + +
#[error]
+const ECollateralNotReadyToClaim: vector<u8> = b"Collateral not ready to reclaim";
+
+ + + + + + + +
#[error]
+const EFqnNotFound: vector<u8> = b"FQN not found";
+
+ + + + + + + +
#[error]
+const EUnauthorized: vector<u8> = b"Only the creator can unregister a tool";
+
+ + + + + + + +
#[error]
+const EToolUnregistered: vector<u8> = b"Tool has been unregistered";
+
+ + + + + + + +
#[error]
+const EFqnAlreadyExists: vector<u8> = b"FQN already exists";
+
+ + + + + +How much [SUI] (in MIST) to lock to register a tool. + +TODO: This is only a testing value. + + +
const DEFAULT_MIST_COLLATERAL_TO_LOCK: u64 = 1;
+
+ + + + + +How long is the collateral locked for in milliseconds after unregistering. + +TODO: This is only a testing value. + + +
const DEFAULT_LOCK_DURATION_MS: u64 = 1;
+
+ + + + + +## Function `new` + + + +
public(package) fun new(ctx: &mut sui::tx_context::TxContext): ((nexus_workflow=0x0)::tool_registry::ToolRegistry, (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverSlashing>)
+
+ + + + + + + +## Function `share` + + + +
public(package) fun share(self: (nexus_workflow=0x0)::tool_registry::ToolRegistry)
+
+ + + + + + + +## Function `slash_off_chain_tool` + +Takes given amount of collateral from a tool. + + +
public fun slash_off_chain_tool(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, _: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverSlashing>, fqn: std::ascii::String, amount: u64, clock: &sui::clock::Clock): sui::balance::Balance<sui::sui::SUI>
+
+ + + + + + + +## Function `set_mist_collateral_to_lock` + + + +
public fun set_mist_collateral_to_lock(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, _: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverSlashing>, new_mist_collateral_to_lock: u64)
+
+ + + + + + + +## Function `set_lock_duration_ms` + + + +
public fun set_lock_duration_ms(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, _: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverSlashing>, new_duration_ms: u64)
+
+ + + + + + + +## Function `register_off_chain_tool_for_self` + +Register a new off-chain tool. + +To prevent spamming, we lock [ToolRegistry.mist_collateral_to_lock] +[SUI] as collateral. +If the tool is unregistered, the collateral is available after +[ToolRegistry.lock_duration_ms] milliseconds. + +See +to learn about the expected format of the input and output schemas and the +expected format of the FQN and URL. + + + + +### Slashing + + +If the tool does not uphold its contract defined by off-chain process the +admin has access to [slash_off_chain_tool] to slash collateral. + + + + +### Owner Cap + + +Is transferred to the sender of this tx. + + + + +### Gas Tickets + + +By default the tool does not require gas for its invocations. +Gas needs to be configured, see the nexus_workflow::gas module. + + +
public entry fun register_off_chain_tool_for_self(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String, url: vector<u8>, input_schema: vector<u8>, output_schema: vector<u8>, pay_with: &mut sui::coin::Coin<sui::sui::SUI>, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `register_off_chain_tool` + + + +
public fun register_off_chain_tool(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String, url: vector<u8>, input_schema: vector<u8>, output_schema: vector<u8>, pay_with: &mut sui::coin::Coin<sui::sui::SUI>, ctx: &mut sui::tx_context::TxContext): (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>
+
+ + + + + + + +## Function `unregister_off_chain_tool` + +Permissioned way to unregister a tool. + +Soon after this point all DAGs that use this tool will stop working. + + +
public entry fun unregister_off_chain_tool(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, clock: &sui::clock::Clock)
+
+ + + + + + + +## Function `claim_collateral_for_self` + +Same as [claim_collateral_for_off_chain_tool] but transfers the collateral +to the sender of this tx. + + +
public entry fun claim_collateral_for_self(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, clock: &sui::clock::Clock, ctx: &mut sui::tx_context::TxContext)
+
+ + + + + + + +## Function `claim_collateral_for_off_chain_tool` + +Return collateral to a tool owner. + + +
public fun claim_collateral_for_off_chain_tool(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, clock: &sui::clock::Clock): sui::balance::Balance<sui::sui::SUI>
+
+ + + + + + + +## Function `deescalate` + +Returns a new [CloneableOwnerCap] for the given tool but with the given +generic type that doesn't have any permissions within this module. + +See also [assert_tool_owner_unchecked_generic]. + + +
public fun deescalate<T: drop>(self: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String, witness: T, ctx: &mut sui::tx_context::TxContext): (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<T>
+
+ + + + + + + +## Function `did_unregister_period_pass` + + + +
public fun did_unregister_period_pass(self: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String, clock: &sui::clock::Clock): bool
+
+ + + + + + + +## Function `assert_tool_registered` + + + +
public fun assert_tool_registered(self: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String)
+
+ + + + + + + +## Function `assert_tool_owner` + + + +
public fun assert_tool_owner(self: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>, fqn: std::ascii::String)
+
+ + + + + + + +## Function `assert_tool_owner_unchecked_generic` + +Assert that the owner cap is for the given tool but allows any generic +type to be used. + +See also [deescalate]. + + +
public fun assert_tool_owner_unchecked_generic<T>(self: &(nexus_workflow=0x0)::tool_registry::ToolRegistry, owner_cap: &(nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<T>, fqn: std::ascii::String)
+
+ + + + + + + +## Function `register_off_chain_tool_` + + + +
fun register_off_chain_tool_(self: &mut (nexus_workflow=0x0)::tool_registry::ToolRegistry, fqn: std::ascii::String, url: vector<u8>, input_schema: vector<u8>, output_schema: vector<u8>, vault: sui::balance::Balance<sui::sui::SUI>, ctx: &mut sui::tx_context::TxContext): (nexus_primitives=0x0)::owner_cap::CloneableOwnerCap<(nexus_workflow=0x0)::tool_registry::OverTool>
+
+ + + + + + + +## Function `did_unregister_period_pass_` + +Has the tool been unregistered for longer than the lock duration? + + +
fun did_unregister_period_pass_(self: &(nexus_workflow=0x0)::tool_registry::OffChainTool, clock: &sui::clock::Clock): bool
+
+ + + + diff --git a/nexus/packages/workflow.md b/nexus/packages/workflow.md new file mode 100644 index 0000000..763d992 --- /dev/null +++ b/nexus/packages/workflow.md @@ -0,0 +1,244 @@ +# Workflow Package + +This Sui package exports data structures and algorithms which orchestrate offchain computation. +The workflow engine is an integral part of the onchain control plane. + +In a nutshell, the workflow engine executes walks over a directed acyclic graph (DAG) of `Vertex`s that represent Nexus Tools. + +{% hint style="info" %} +The terms used in the context of the DAG can be found in the [glossary](../glossary.md#dag-related-terms). Familiarize yourself with them before moving on. +{% endhint %} + +![Shows legend](../images/dag-rules/dag.legend.png) + +{% hint style="success" %} Mental model for Nexus Tools as the workflow vertices + +- A tool is represented by a vertex in the DAG. +- A tool has at least one input represented by input ports. +- These input ports are akin to arguments to a function. +- A tool returns a first degree enumeration variant. +- These are mutually exclusive descriptions of the tool's output. +- An output variant has at least one output represented by output ports. + +{% endhint %} + +## Properties of Nexus DAG design + +- An end state is a `Vertex` with no outgoing `Edge`s. + +- A walk can either halt as _(i)_ successful, _(ii)_ failed, or _(iii)_ consumed. + + - _(i)_ successful halt means that the walk has reached an end state; + - _(ii)_ failed halt means that the walk cannot reach an end state; + - _(iii)_ consumed halt means that the walk has been joined with another walk. + +- A successful execution is one where all walks halt in an end state. + +- A partial execution is one where at least one walk halts in an end state. + +- A failed execution is one where no walk halts in an end state. + +- A default value is a value that is static with respect to walk execution. Think configurations. + +- An output variant is the resulting _state_ of a Tool evaluation. A Tool can define N Output Variants but will always evaluate into exactly 1 at runtime. + +- All specified entry ports for all vertices within an entry group must receive data to start execution. Vertices listed without any ports are executed immediately if they have no other input dependencies. + +- A default entry group is used when no specific group name is provided during execution initiation. All entry ports are assumed to be part of the default entry group. + +## Rules + +1. If a cycle is detected, the walk halts as failed. +1. DAG can have multiple named entry points (`EntryGroup`s) and multiple end states. +1. Relationships between `Vertex`s are encoded into `Edge`s. +1. An `InputPort` can either have an incoming `OutputPort`, default value or client input as an entry port. These options are mutually exclusive. +1. An `InputPort` receiving a value from an `OutputPort` at runtime must receive a value from exactly one `OutputPort`. + - This rule can be translated to "no race conditions and no unreachable vertices" +1. `N` edges from the identical `OutputVariantPort` will fork the walk into `N` concurrent walks. +1. DAG execution ends when all walks halt. +1. If a walk reaches an end state then it halts as successful. +1. Vertex is not scheduled for execution until all edges leading to it are evaluated. +1. If a walk reaches a vertex that cannot be immediately scheduled for execution then the walk halts as consumed. +1. Input ports designated as part of an `EntryGroup` cannot have default values. Default values are only permitted for input ports that are _not_ part of any entry group. + +### Looping rules + +1. Loops must not nest. +1. Vertices between `for_each` and `collect` must not evaluate to array types. +1. A `for_each` edge must originate from a `Vertex` whose `OutputPort` evaluates to an array type. +1. `do_while` and `break` edges must each originate from a unique `OutputVariant` of the same `Vertex`. +1. Loop iterations are limited to 255 to prevent unbounded execution. + +{% hint style="info" %} +These rules are statically validated by the [Nexus CLI](../../nexus-sdk/cli.md) if using JSON definitions of DAGs. +{% endhint %} + +--- + +![Shows two concurrent branches](../images/dag-rules/dag.1.png) + +This DAG has three vertices. +Once vertex `A` is done, the walk will fork into two concurrent walks, ie. vertex `B` and `C` will be executed concurrently. + +--- + +![Shows mutual exclusivity of variants](../images/dag-rules/dag.2.png) + +Because output variants are mutually exclusive, only one of those two branches will be taken. +That means there's no race condition in this DAG. + +--- + +![Shows a simple race condition](../images/dag-rules/dag.3.png) + +This DAG is _UNACCEPTABLE_. +It violates the rule `5.`. +Because these two output ports are connected to the same input port on vertex `B`, there are two sources of data for one input port + +--- + +![Shows branching from two output ports](../images/dag-rules/dag.4.png) + +This DAG demonstrates how one vertex can again branch into two concurrent walks. +This time, both walks were started from a different output port of the same output variant. + +--- + +![Shows branching](../images/dag-rules/dag.5.png) + +This DAG demonstrates that having two edges leading to the same input port is acceptable if they come from different output variants of the same vertex. +Only one path through the DAG can be taken at runtime and therefore there's no race condition to vertex `D`. + +--- + +![Shows a more complicated race condition](../images/dag-rules/dag.6.png) + +This DAG is _UNACCEPTABLE_. +It violates the rule `5.`. +Both walks will be spawned and therefore there's a race condition to vertex `D`. + +--- + +![Shows default value](../images/dag-rules/dag.7.png) + +Default values are great for static configuration. + +--- + +![Shows misused default value](../images/dag-rules/dag.8.png) + +This DAG is _UNACCEPTABLE_. +You cannot have a default value and an edge leading to the same input port. + +### Validation + +Most of our validation is done in form of static analysis and the onchain realm just assumes that the DAG was constructed correctly. +If DAG ends up in a situation from which it cannot continue, it just halts. + +More design and documentation regarding the static analysis to follow. + +### Reserved output variants + +Output variants are usually evaluated by vertices (tools) in the DAG. However, a tool can be broken, unreachable or there can be other issues with invocation. The workflow reserves these output variants to evaluate into if this happens. + +Currently, the workflow engine reserves the following output variants: + +- `_err_eval`: Error when evaluating a Tool, this output variant is used in the following scenarios: + 1. Tool is not registered or there was another issue fetching the tool's FQN + 1. Default values could not be fetched for the tool + 1. Tool definition could not be fetched + 1. Input data for the tool does not pass the tool's validation schema + 1. Tool invocation failed + 1. Output data could not be parsed or does not pass the tool's validation schema + +### Entry ports + +Entry ports are input ports that must receive data from the client before execution can begin. These ports are explicitly defined via `with_entry_port` or `with_entry_port_in_group`. They cannot have any default values or incoming edges. + +Key characteristics of entry ports: + +- They must be provided with data when starting DAG execution +- They cannot have default values assigned to them +- They are defined as part of an entry group configuration +- Multiple entry ports can be defined for a single vertex +- Entry ports help define valid starting states for DAG execution + +For example, if a vertex `A` has input ports `x` and `y`, and port `x` is designated as an entry port in the default entry group, then: + +- Port `x` must receive data from the client before execution can start +- Port `x` cannot have a default value +- Port `y` can optionally have a default value or receive data via an edge +- The DAG execution will not begin until port `x` receives its required input data + +This mechanism allows DAGs to enforce specific initialization requirements while maintaining flexibility in how other inputs are handled during execution. + +### Entry groups + +A DAG can define multiple named `EntryGroup`s, each specifying a distinct starting configuration. + +When initiating a DAG execution, the user must select an `EntryGroup` by name (or use the default one if none are explicitly defined). + +An `EntryGroup` consists of a set of vertices. + +To start the execution via a specific entry group, the user must provide input data for each _entry port_ of each vertex included in the entry group. If a vertex has no entry ports, the vertex name _must_ still be specified with an empty `VecMap` of entry ports. + +Vertices included in an entry group but with an _empty_ set of input ports will be scheduled for execution immediately at the start, provided they don't have any incoming edges. If a vertex in this case _does_ have incoming edges, this is a no-op - it has no effect on the execution and the vertex is treated as-if it was not included in the entry group. + +A default entry group (named `_default_group`) is available for convenience. + +## Events + +Events in our Nexus packages can contain these data types: + +- `SuiObjectID` + - a unique identifier for an object + - can be used to fetch the object from the RPCs/GQL +- `Vertex` + - a unique name of a vertex in a DAG + - `{ name: String }` +- `VertexKind` + - enumeration over the kind of vertices in a DAG + - see the `dag` module for this type +- `InputPort` + - a unique name of an input port in a vertex + - `{ name: String }` +- `Edge` + - unique path (at runtime) through the DAG + - `{ from: { variant: String, port: String }, to: { vertex: String, port: String } }` +- `NexusData` + - represents some data within Nexus framework + - see the `data` module for this type +- `VertexEvaluation` + - represents what should be the inputs to a vertex + - `{ ports_to_data: Map }` + +For a list of all event types, refer to [the reference docs for the Workflow package](./reference/nexus_workflow/dag.md). + +## Leader cap + +The leader cap(_ability_) is an owned object given to the [leader](../crates/leader.md) wallet. +With this object the leader authorizes itself to submit tool outputs to the workflow. + +The leader cap can be cloned and given to other wallets, or multiple times to the same wallet. +This is desirable if parallel txs are submitted to the workflow to prevent equivocation related errors. + +## Tool registry + +The tool registry is an onchain shared object that holds [tool definitions](../tool.md#tool-definitions). + +To register a tool the creator must deposit a time-locked collateral to prevent spamming the registry. +The amount of `SUI` locked and the interval after which they can be reclaimed is configured in the `nexus-next` repo. + +## PreKey vault + +The pre_key vault is an onchain shared object that holds pre_keys for initiating encrypted communication with the workflow. + +To claim a pre_key, the user must first deposit some gas budget in `SUI` to the gas service. Claiming a pre_key is also rate limited per wallet. + +## Notes + +- We have considered a stricter rule `5.` where `InputPort` can have only one incoming `OutputVariantPort`. + However, relaxing this rule into its current form meant expressive workflows and simpler runtime state management in exchange for more complex static analysis algorithm. + This trade-off was deemed worthy. +- When using the `ProofOfUid` primitive, it must be created with a type that matches the `UID`. + The type should be considered an authorization ticket and should be treated just as any other capability type. diff --git a/nexus/tokenomics/default-gas-extension.md b/nexus/tokenomics/default-gas-extension.md new file mode 100644 index 0000000..3e53727 --- /dev/null +++ b/nexus/tokenomics/default-gas-extension.md @@ -0,0 +1,133 @@ +# Gas extensions example + +Gas extensions provide the ability for tool owners to provide gas tickets to users for custom payment strategies. + +Nexus considers two default modes of operation for gas tickets: expiry and limited invocations. The implementation in move code is provided below as an inspiration for custom gas extensions that leverage the `UponDiscretionOfTool` modus operandi. + +## Implementation of default gas extension (expiry based) + +Toggle to see the full module code for the default gas extension move module: +
+ +Toggle code + +```rust +use nexus_workflow::gas; +use nexus_workflow::tool_registry::ToolRegistry; +use std::ascii::String as AsciiString; +use sui::clock::Clock; +use sui::coin::Coin; +use sui::sui::SUI; + +// === Errors === + +#[error] +const EThisMethodIsNotEnabled: vector = b"This method is not enabled"; + +// === Expiry Extension === + +/// How many [SUI] tokens should be paid for each minute. +public struct ExpiryCostPerMinuteKey has copy, drop, store {} +/// Will hold the gas owner cap to do requests on behalf of the tool owner. +public struct ExpiryGasOwnerCapKey has copy, drop, store {} + +public entry fun buy_expiry_gas_ticket( + gas_service: &mut gas::GasService, + tool_registry: &ToolRegistry, + fqn: AsciiString, + minutes: u64, + pay_with: &mut Coin, + clock: &Clock, + ctx: &mut TxContext, +) { + let settings = gas_service.get_tool_gas_setting(fqn); + + assert!(settings.contains(ExpiryGasOwnerCapKey {}), EThisMethodIsNotEnabled); + let owner_cap = settings + .borrow<_, CloneableOwnerCap>(ExpiryGasOwnerCapKey {}) + .clone(ctx); + + let cost_per_minute = *settings.borrow<_, u64>(ExpiryCostPerMinuteKey {}); + + gas_service.add_gas_ticket( + tool_registry, + &owner_cap, + fqn, + gas::scope_invoker_address(ctx.sender()), + gas::modus_operandi_expiry(minutes * 60 * 1000), + clock, + ctx, + ); + owner_cap.destroy(); + + gas_service.donate_to_tool(fqn, pay_with.balance_mut().split(cost_per_minute * minutes)); +} + +/// Enables buying gas tickets that expire after a certain time in token +/// [SUI] for [cost_per_minute] of those tokens. +/// +/// To update the cost just call this function again with the new value. +public fun enable_expiry( + gas_service: &mut gas::GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + cost_per_minute: u64, + fqn: AsciiString, + ctx: &mut TxContext, +) { + let owner_cap = owner_cap.clone(ctx); + + let settings = gas_service.get_tool_gas_setting_mut( + tool_registry, + &owner_cap, + fqn, + ctx, + ); + + if (!settings.contains(ExpiryGasOwnerCapKey {})) { + settings.add(ExpiryGasOwnerCapKey {}, owner_cap); + } else { + owner_cap.destroy(); + }; + + let cost_per_minute_key = ExpiryCostPerMinuteKey {}; + + if (settings.contains(cost_per_minute_key)) { + // already configured, let's reset it + + settings.remove<_, u64>(cost_per_minute_key); + }; + + settings.add(cost_per_minute_key, cost_per_minute); +} + +/// Disables this extension. +public fun disable_expiry( + gas_service: &mut gas::GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + ctx: &mut TxContext, +) { + let settings = gas_service.get_tool_gas_setting_mut( + tool_registry, + owner_cap, + fqn, + ctx, + ); + + if (settings.contains(ExpiryGasOwnerCapKey {})) { + settings + .remove<_, CloneableOwnerCap>(ExpiryGasOwnerCapKey {}) + .destroy(); + }; + + let cost_per_minute_key = ExpiryCostPerMinuteKey {}; + + if (settings.contains(cost_per_minute_key)) { + settings.remove<_, u64>(cost_per_minute_key); + }; +} +``` + +
diff --git a/nexus/tokenomics/gas-service.md b/nexus/tokenomics/gas-service.md new file mode 100644 index 0000000..9e7b810 --- /dev/null +++ b/nexus/tokenomics/gas-service.md @@ -0,0 +1,359 @@ +# Gas Service + +The concepts related to tokenomics as explained in [the tokenomics section][tokenomics] are implemented in the `GasService` shared object in Nexus. + +## Overview + +The gas payment and settlement system in Nexus provides a flexible way to handle gas payments for tool invocations. It supports multiple modes of operation and allows for different payment strategies through gas extensions. + +## Core Concepts + +### Gas Service + +The `GasService` is a shared object that manages all gas-related operations. It maintains: + +- Gas tickets for tools. +- Gas budgets for different scopes. +- Execution gas settlement state. +- Tool-specific gas settings. + +### Gas Tickets + +Gas tickets represent prepaid access to tool invocations. They can be configured with different modes of operation: + +1. **Expiry Mode**: Allows unlimited invocations within a specific time period. +1. **Limited Invocations Mode**: Allows a fixed number of invocations. +1. **Upon Discretion of Tool Mode**: Tool owner has complete control over ticket validity. + +### Scopes + +Gas tickets and budgets can be associated with different scopes: + +1. **Execution Scope**: Specific to a single DAG execution. +1. **Worksheet Type Scope**: Applies to all executions of a specific worksheet type, i.e. the TAP. +1. **Invoker Address Scope**: Applies to all executions initiated by a specific address. + +{% hint style="warning" %} +Gas is secured before the tool is invoked to ensure that the tool always gets paid for its usage according to the selected mode. + +This does expose the tool owner to slashing risk, if they do not deliver the service according to the Nexus defined protocol. +{% endhint %} + +### Other Data Structures + +#### ExecutionGas + +Tracks gas settlement state for a specific DAG execution. This struct maintains a record of which vertices in the execution have had their gas settled. Once a vertex is marked as settled, it can be invoked once. + +#### GasBudgets + +Manages gas budgets for different scopes in the system. The inner Bag stores balances of SUI coins, with the key being a Scope. These budgets are used as a fallback payment method when no gas tickets are available. See [Default Gas Budget](#1-default-gas-budget). + +#### ToolGas + +Manages all gas-related state for a specific tool. This includes: + +- Collected gas payments in the vault +- Default cost per invocation +- Tool-specific gas settings +- Available gas tickets for different scopes + +## Gas Payment Modes + +### 1. Default Gas Budget + +Tools can set a default cost per invocation. When no gas tickets are available, the system will attempt to charge from the gas budget in the following order: + +1. Execution-specific budget +1. Worksheet type budget +1. Invoker address budget + +### 2. Gas Extensions + +Gas extensions provide alternative payment strategies. The [default extension][default-gas-extension] implements an expiry-based system where users can: + +- Buy access for a specific duration (e.g., 10 minutes). +- Pay a fixed rate per minute. +- Get unlimited invocations during the purchased period. + +## Gas Settlement Process + +1. When a vertex (tool) is invoked, the system checks if gas has been settled. +1. If not settled, it attempts to find a valid gas ticket for the different scopes in order. +1. Gas tickets are validated against the execution's creation timestamp, not the current time. +1. If no valid ticket is found, it attempts to charge from the gas budget. +1. Once settled, the vertex can be invoked. +1. Gas settlement is idempotent - subsequent checks won't charge again. + +### Events + +The system emits several types of events for gas-related operations: + +1. `GasSettlementUpdateEvent` for each gas settlement attempt: + +```rust +public struct GasSettlementUpdateEvent has copy, drop { + execution: ID, + vertex: dag::Vertex, + tool_fqn: AsciiString, + /// Whether the gas ticket was stamped ok. + /// + /// Multiple of these events can be emitted for the same execution/vertex/tool_fqn + /// combination, but only one of them will have this field set to true. + was_settled: bool, +} +``` + +1. `LeaderClaimedGasEvent` for tracking gas claims by leaders: + +```rust +public struct LeaderClaimedGasEvent has copy, drop { + /// Who is the leader. + network: ID, + /// How much was claimed. + amount: u64, +} +``` + +### Checking Gas Payment Status + +There are several ways to check if gas has been paid for a tool invocation: + +1. **Using GasService State** + - Anyone can check if gas has been settled for a specific vertex in an execution using [`is_execution_vertex_settled`](#view-operations). + - This is useful both for onchain and offchain actors. + - This is the most direct way to verify gas payment status. + - Returns a boolean indicating whether the vertex can be invoked. +1. **Listening to Events** + - The system emits `GasSettlementUpdateEvent` for each gas settlement attempt. + - Anyone can listen to these events to track gas settlement status. +1. **Checking Gas Tickets** + - Tool owners can check their tool's gas tickets and settings. + - Users can check their own gas budgets and tickets. + - This is useful _before_ the invocation is requested to know whether it's possible to execute with given gas tickets. +1. **Viewing Gas Budgets** + - Users can check their remaining gas budgets for different scopes. + - This is useful _before_ the invocation is requested to know whether it's possible to execute with given gas budgets. + +## Owner Capabilities + +Tool owners have two levels of capabilities for managing their tool's gas operations: + +1. `OverTool` - The main owner cap that provides full control over the tool, including gas operations +1. `OverGas` - A de-escalated version of `OverTool` that provides limited permissions focused only on gas-related operations + +The `OverGas` cap is designed to make tool owners more comfortable using gas extensions by providing a more restricted set of permissions. It allows them to: + +- Add and remove gas tickets. +- Change gas settings. +- Manage gas-related operations. + +without giving them access to other important tool state. This separation of concerns helps maintain security while enabling tool owners to manage their gas operations effectively. + +## Security Considerations + +1. Gas tickets with expiry or limited invocations cannot be revoked. +1. Only tickets in "Upon Discretion of Tool" mode can be revoked. +1. Tool owners can claim gas at any time. +1. Gas budgets can be refunded if the execution is finished. +1. Workflows that want to pay gas on behalf of the user must assert that they execute in a network with a trusted leader. See [current limitation below](#current-limitation) + +### Current limitation + +In the current implementation the Nexus leader can claim any amount of gas it wants from anybody who uploads gas budgets.\ +To hold the leader accountable we emit `LeaderClaimedGasEvent` event which can be read by 3rd parties that check that the amount claimed is not over the top. + +However, this implies that as of right now, the leader is has to be trusted. + +## Key Operations + +
+ +Tool Owner Operations + +### Gas Cost Management + +1. **Setting Default Cost**\ + Sets the default cost in MIST for a single tool invocation. Calling this function enables gas collection by the tool so it's imperative the tool owners calls it to collect fees for tool execution. + + ```rust + public fun set_single_invocation_cost_mist( + gas_service: &mut GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + single_invocation_cost_mist: u64, + ctx: &mut TxContext, + ) + ``` + + > Set `single_invocation_cost_mist` to 2^64-1 to enable gas collection but require a gas extension to do it. +1. **Claiming Gas**\ + Allows the tool owner to withdraw all collected gas payments for their tool. + + ```rust + public fun claim_gas( + gas_service: &mut GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + ctx: &mut TxContext, + ): Balance + ``` + +### Gas Ticket Management + +1. **Adding Gas Tickets**\ + Creates a new gas ticket with specified scope and mode of operation. + + ```rust + public fun add_gas_ticket( + gas_service: &mut GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + scope: Scope, + modus_operandi: ModusOperandi, + clock: &Clock, + ctx: &mut TxContext, + ) + ``` + +The tool owner can use "upon discretion of the tool" mode to be able to `revoke_gas_ticket` _at will_. + +1. **Revoking Gas Tickets**\ + Revokes a gas ticket that was created with the "Upon Discretion of Tool" mode. + + ```rust + public fun revoke_gas_ticket( + gas_service: &mut GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + scope: Scope, + ctx: &mut TxContext, + ) + ``` + +1. **Managing Gas Settings**\ + The tool owner can set the gas settings for the tool. + + ```rust + public fun get_tool_gas_setting_mut( + gas_service: &mut GasService, + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + ctx: &mut TxContext, + ): &mut Bag + ``` + +1. **De-escalating Permissions**\ + Converts a tool owner cap into a gas owner cap with reduced permissions. + + ```rust + public fun deescalate( + tool_registry: &ToolRegistry, + owner_cap: &CloneableOwnerCap, + fqn: AsciiString, + ctx: &mut TxContext, + ): CloneableOwnerCap + ``` + +
+ +
+ +User Operations + +#### Gas Budget Management + +1. **Donating to Tool**\ + Donate given balance to the tool's gas total. This is used to charge the user from gas extensions and make it available to the tool. + +```rust +public fun donate_to_tool( + self: &mut GasService, fqn: AsciiString, amount: Balance, +) { + let tool_gas = self.tools_gas.borrow_mut(fqn); + tool_gas.vault.join(amount); +} +``` + +1. **Adding Gas Budget**\ + Adds a gas budget for a specific scope (execution, worksheet type, or invoker address). + + ```rust + public fun add_gas_budget( + gas_service: &mut GasService, + scope: Scope, + budget: Balance, + ) + ``` + +1. **Refunding Execution Gas Budget**\ + Refunds any remaining gas budget for a completed execution to the invoker. This operation also cleans up storage by removing the execution gas state, helping to reduce storage costs. + + ```rust + public fun refund_execution_gas_budget( + gas_service: &mut GasService, + execution: &dag::DAGExecution, + ctx: &mut TxContext, + ) + ``` + +1. **Refunding Invoker Gas Budget**\ + Refunds any remaining gas budget associated with the invoker's address. + + ```rust + public fun refund_invoker_gas_budget( + gas_service: &mut GasService, + ctx: &mut TxContext, + ): Balance + ``` + +1. **Refunding Worksheet Gas Budget**\ + Refunds any remaining gas budget associated with a specific worksheet type. + + ```rust + public fun refund_worksheet_gas_budget( + gas_service: &mut GasService, + _witness: &T, + ): Balance + ``` + +
+ +
+ +View Operations + +#### View Operations + +1. **Checking Vertex Settlement**\ + Verifies if gas has been settled for a specific vertex in an execution. + + ```rust + public fun is_execution_vertex_settled( + gas_service: &GasService, + execution: &dag::DAGExecution, + vertex: dag::Vertex, + ): bool + ``` + +1. **Reading Tool Gas Settings**\ + Gets read-only access to a tool's gas settings. + + ```rust + public fun get_tool_gas_setting( + gas_service: &GasService, + fqn: AsciiString, + ): &Bag + ``` + +
+ + + +[tokenomics]: tokenomics.md +[default-gas-extension]: default-gas-extension.md diff --git a/nexus/tokenomics/tokenomics.md b/nexus/tokenomics/tokenomics.md new file mode 100644 index 0000000..6b64f30 --- /dev/null +++ b/nexus/tokenomics/tokenomics.md @@ -0,0 +1,66 @@ +# Nexus Tokenomics + +In blockchain systems, typically users pay a gas fee to submit transactions to account for the computational work provided by the validators running the chain. + +In Nexus an agentic workflow is usually triggered by a user action, but the additional work on behalf of the user (or Talus Agent, TAP) during workflow execution is delegated to other parties. This includes: + +- the gas fees the [Leader][leader] incurs when submitting the results of the tool invocations back onchain +- the fees for the service the tool provides + +To manage payment for those fees, Nexus introduces a tokenomics model to manage payments for the agentic (AI) workflow services it provides. + +{% hint style="success" %} +One of the value propositions for Nexus, is its positioning as _Market for Agents_ or _MFA_. It provides the ability for service providers (be it AI or not, offchain or onchain) to offer their service to Talus agent developers using blockchain infrastructure as a unified payment layer. + +This creates a market for agents where users can benefit from blockchain's unique properties, like providing a global, transparent and censorship resistant payment layer and the ability to allow for micro-payments for otherwise inaccessible services. +{% endhint %} + +## Relevant Actors + +From a tokenomics perspective, consider the following actors: + +- the user (human or TAP) +- the tool owner +- the Nexus Leader +- other actors with read access into tokenomics (e.g. explorers) + +## Gas Service Concepts + +The implementation of the `GasService`, a shared object that manages all gas-related operations, can be found in the [dedicated section][gas-service]. + +### Where to draw gas from? + +Broadly speaking, there's two ways a user gets charged for workflow execution. + +1. They can purchase **prepaid access** to tool invocations, through `GasTickets`. +1. They can add funds directed for gas payments as `GasBudget` + +Tool owners can provide different flavors of gas tickets by using existing or creating custom _gas extensions_ that allow for different modes of operation for the tool to consume gas tickets. The tool owners can also set a cost per single invocation. If no gas tickets are present, tools will charge from a non-zero gas budget instead. + +Both gas tickets and budgets have different _scopes_ to protect users against unintended use of their tickets or budget. + +{% hint style="info" %} +The provision of gas tickets and budgets, gas extensions and different scopes allows for flexibility in handling gas fees for workflow execution. +{% endhint %} + +#### Leader + +The leader requires gas to submit tool invocation outputs onchain and will use gas budget of appropriate scope to do so. + +### Library of gas extensions + +The Talus Labs team will provide some gas exentsion Move packages that can will be able to make up the bulk of use cases, including gas tickets with expiry and limited number of invocations. + +However, the tool developers are free to write custom gas extensions Move packages that correspond to custom mode of operation for their gas tickets. + +For more information and details on the gas service, refer to the [dedicated section][gas-service]. + +## Nexus CLI + +You'll be able to manage interaction with the gas service through the [Nexus CLI][nexus-cli]. Please refer to the commands and inspect the gas-related arguments. + + + +[leader]: ../crates/leader.md +[gas-service]: gas-service.md +[nexus-cli]: ../../nexus-sdk/cli.md diff --git a/nexus/tool.md b/nexus/tool.md new file mode 100644 index 0000000..79b4bf7 --- /dev/null +++ b/nexus/tool.md @@ -0,0 +1,77 @@ +# Tool + +A Tool is an offchain HTTP service or an onchain smart contract. These are invoked by the [Leader](crates/leader.md) based on instructions provided by the onchain [Workflow](packages/workflow.md). + +## Tool definitions + +Each Tool must provide a standardized definition, telling us where and how to interact with it. This definition is stored in the Tool Registry onchain and should be retrievable publicly. Definition should be a JSON file in the following format: + +```json +{ + "fqn": "{domain}.{name}@{version}", + "type": "onchain/offchain", + "url": "{url/move_ident}", + "description": "A UTF-8 string", + "input_schema": "draft-2020-12 JSON schema", + "output_schema": "draft-2020-12 JSON schema" +} +``` + +1. `fqn` + +Uniform resource name. Tool `fqn` consists of 3 parts - `domain`, `name` and `version`. When registering a tool, this structure should be checked. + +``` +xyz.taluslabs.llm.openai-chat-completion@1 +``` + +2. `type` + +Straightforward, and enum with `offchain` and `onchain` values. + +3. `url` + +This will determine _how_ the Leader contacts the Tool. For offchain Tools, this field will be an actual URL. This can point to the server directly or to a load balancer. + +Onchain Tools will require a similar `url` but it would likely be a move call definition in the format `pkg_id::module::fn` or similar. Confirmation will follow soon. + +4. `description` + +A description of what the tool does. Currently we do not enforce a maximum length but this is +subject to change. In addition we assume a UTF-8 representation. + +5. `input_schema` and `output_schema` + +These will be `draft-2020-12` JSON schema definitions of the Tool inputs and outputs. This lets the Leader parse and verify data going in and coming out of the Tool. + +Note that for `output_schema`, top-level `oneOf` has to be enforced by the Tool deployment process to adhere to the `OutputVariant` DAG definition. + +## Off-chain Tool interface + +Off-chain Tools will expose 3 HTTP endpoints: + +1. `GET /health` - operational health, should retfqn 200 if and only if the Tool is ready to be invoked +2. `GET /meta` - retfqns the Tool definition JSON +3. `POST /invoke` - this endpoints invokes the Tool logic, it accepts data in its `input_schema` format and outputs data in its `output_schema` format + +## Onchain Tool interface + +Coming soon. + +## Tool registration + +Tools should be register in the Tool Registry using our [CLI](../nexus-sdk/cli.md#nexus-tool) + +### Offchain tools + +The CLI leverages functions in module `tool_registry` in the `nexus_workflow` Sui package. + +The registration locks `SUI` tokens as collateral to combat spam. The creator can at any point unregister the tool again, which starts a timer defined by the `tool_registry` module. After the timer expires, the collateral can be reclaimed. + +### Onchain tools + +Coming soon. + +## Tool authorization + +Once there are community Tools, we will need a way to authorize communication between the Leader and a Tool. This has been discussed superficially and it needs to be researched in depth in the future. From 142c774fb535f1e38c9d7775d0b76d730bfb5041 Mon Sep 17 00:00:00 2001 From: devops-talus Date: Mon, 3 Nov 2025 10:58:55 +0100 Subject: [PATCH 2/8] Docs update from nexus-sdk (#150) * Update docs from nexus-sdk (commit: dabf95e9adae1147ec479e719c12809939f1d2ff) * Update SUMMARY.md with new files under 'Looking for a home' * chore(docs): format markdown links --------- Co-authored-by: kouks <3684840+kouks@users.noreply.github.com> Co-authored-by: Pavel Koch --- SUMMARY.md | 40 +----- nexus-sdk/cli.md | 8 +- nexus-sdk/guides/dag-construction.md | 13 +- nexus-sdk/guides/setup.md | 1 + tools/exchanges-coinbase/README.md | 143 +++++++++++++++++++ tools/http/README.md | 155 +++++++++++++++++++++ tools/llm-openai-chat-completion/README.md | 6 +- tools/math/README.md | 26 ++++ 8 files changed, 352 insertions(+), 40 deletions(-) create mode 100644 tools/exchanges-coinbase/README.md create mode 100644 tools/http/README.md diff --git a/SUMMARY.md b/SUMMARY.md index 08bd815..906a9f5 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -38,6 +38,9 @@ - [Leader](nexus-next/crates/leader.md) - [Leader Sui TXs](nexus-next/crates/sui-tx.md) - [Agent Development](nexus-next/TAP/agent-development.md) + - [Flow Controls] + - [Branching & Conditionals](nexus/flow-controls/branching_and_conditionals.md) + - [Looping](nexus/flow-controls/looping.md) - [Default TAP](nexus-next/TAP/default-tap.md) - [Tool](nexus-next/tool.md) - [Tokenomics](nexus-next/tokenomics/tokenomics.md) @@ -70,38 +73,7 @@ - [⚓ Nexus Standard Tools](tools/nexus-standard-tools.md) - [Math](tools/math/README.md) - [LLM: OpenAI Chat Completion](tools/llm-openai-chat-completion/README.md) - - [Social : X](tools/social-twitter/README.md) + - [Social: X](tools/social-twitter/README.md) - [Storage: Walrus](tools/storage-walrus/README.md) - -## Looking for a home - -* [nexus/TAP/agent-development.md](nexus/TAP/agent-development.md) -* [nexus/TAP/default-tap.md](nexus/TAP/default-tap.md) -* [nexus/conventions/sui-move.md](nexus/conventions/sui-move.md) -* [nexus/crates/leader.md](nexus/crates/leader.md) -* [nexus/crates/sui-tx.md](nexus/crates/sui-tx.md) -* [nexus/flow-controls/branching_and_conditionals.md](nexus/flow-controls/branching_and_conditionals.md) -* [nexus/flow-controls/looping.md](nexus/flow-controls/looping.md) -* [nexus/glossary.md](nexus/glossary.md) -* [nexus/index.md](nexus/index.md) -* [nexus/packages/nexus-interface.md](nexus/packages/nexus-interface.md) -* [nexus/packages/primitives.md](nexus/packages/primitives.md) -* [nexus/packages/reference/nexus_interface/v1.md](nexus/packages/reference/nexus_interface/v1.md) -* [nexus/packages/reference/nexus_interface/version.md](nexus/packages/reference/nexus_interface/version.md) -* [nexus/packages/reference/nexus_primitives/data.md](nexus/packages/reference/nexus_primitives/data.md) -* [nexus/packages/reference/nexus_primitives/event.md](nexus/packages/reference/nexus_primitives/event.md) -* [nexus/packages/reference/nexus_primitives/owner_cap.md](nexus/packages/reference/nexus_primitives/owner_cap.md) -* [nexus/packages/reference/nexus_primitives/proof_of_uid.md](nexus/packages/reference/nexus_primitives/proof_of_uid.md) -* [nexus/packages/reference/nexus_primitives/proven_value.md](nexus/packages/reference/nexus_primitives/proven_value.md) -* [nexus/packages/reference/nexus_workflow/dag.md](nexus/packages/reference/nexus_workflow/dag.md) -* [nexus/packages/reference/nexus_workflow/default_gas_extension.md](nexus/packages/reference/nexus_workflow/default_gas_extension.md) -* [nexus/packages/reference/nexus_workflow/default_tap.md](nexus/packages/reference/nexus_workflow/default_tap.md) -* [nexus/packages/reference/nexus_workflow/gas.md](nexus/packages/reference/nexus_workflow/gas.md) -* [nexus/packages/reference/nexus_workflow/leader_cap.md](nexus/packages/reference/nexus_workflow/leader_cap.md) -* [nexus/packages/reference/nexus_workflow/main.md](nexus/packages/reference/nexus_workflow/main.md) -* [nexus/packages/reference/nexus_workflow/tool_registry.md](nexus/packages/reference/nexus_workflow/tool_registry.md) -* [nexus/packages/workflow.md](nexus/packages/workflow.md) -* [nexus/tokenomics/default-gas-extension.md](nexus/tokenomics/default-gas-extension.md) -* [nexus/tokenomics/gas-service.md](nexus/tokenomics/gas-service.md) -* [nexus/tokenomics/tokenomics.md](nexus/tokenomics/tokenomics.md) -* [nexus/tool.md](nexus/tool.md) + - [Echanges: Coinbase](tools/exchanges-coinbase/README.md) + - [HTTP](tools/http/README.md) diff --git a/nexus-sdk/cli.md b/nexus-sdk/cli.md index 6157d81..b1bda45 100644 --- a/nexus-sdk/cli.md +++ b/nexus-sdk/cli.md @@ -162,7 +162,7 @@ This command requires that a wallet is connected to the CLI... --- -**`nexus dag execute --dag-id --input-json --entry-group [group] [--inspect]`** +**`nexus dag execute --dag-id --input-json --entry-group [group] --remote [field1,field2,...] [--inspect]`** Execute a DAG with the provided ``. This command also accepts an entry `` of vertices to be invoked. Find out more about entry groups in [[Package: Workflow]]. Entry `` defaults to a starndardized `_default_group` string. @@ -176,6 +176,12 @@ Data for encrypted ports are automatically encrypted before being sent on-chain. The `--inspect` argument automatically triggers `nexus dag inspect-execution` upon submitting the execution transaction. +The `--remote` argument accepts a list of `{vertex}.{port}` strings that refer to entry ports and their vertices. The data associated with these ports is stored remotely based on the configured preferred remote storage provider. Note that it is required that the user configure these remote storage providers via the `$ nexus conf set --help` command. + +Supported remote storage providers are: + +- Walrus + {% hint style="info" %} This command requires that a wallet is connected to the CLI... {% endhint %} diff --git a/nexus-sdk/guides/dag-construction.md b/nexus-sdk/guides/dag-construction.md index 5598269..4d9170f 100644 --- a/nexus-sdk/guides/dag-construction.md +++ b/nexus-sdk/guides/dag-construction.md @@ -75,7 +75,8 @@ Edges define the flow of data between vertices, connecting an output port of a s "to": { "vertex": "target_vertex_name", // Name from the "vertices" list "input_port": "target_input_port_name" - } + }, + "kind": "normal" // Optional, default is "normal" } ``` @@ -83,6 +84,16 @@ Edges define the flow of data between vertices, connecting an output port of a s - The `target_input_port_name` must be a valid input port for the tool used by the `target_vertex_name`. - If the `encrypted` field is set to `true`, the data will be encrypted before being sent on-chain. This is useful for sensitive data that should not be exposed in plaintext. +## 3.1 Special edge types + +- **`normal`** → The default edge type. Data flows from the source vertex to the target vertex as soon as the source vertex produces output on the specified port. +- **`for_each`** → Spawns parallel executions of the downstream vertex, once per element of an array. This must be followed by a `collect` (with any number of intermediate `normal` edges) to gather the results back into a single array. +- **`collect`** → Gathers the results of a `for_each` branch back into a single ordered array. +- **`do_while`** → Repeats execution by looping back as long as a condition is satisfied. +- **`break`** → Exits a `do_while` loop when the condition is no longer met. + +Read more about looping and flow controls in the [looping documentation](../../nexus-next/flow-controls/looping.md). + ## 4. Default Values Default values provide static inputs to vertices: diff --git a/nexus-sdk/guides/setup.md b/nexus-sdk/guides/setup.md index f0b612c..d20ab5d 100644 --- a/nexus-sdk/guides/setup.md +++ b/nexus-sdk/guides/setup.md @@ -160,3 +160,4 @@ Open the [Talus Sui Explorer](https://explorer.devnet.taluslabs.dev/). --- After completing these steps, you are ready to build and execute workflows using the Nexus SDK. To build your first workflow, check the [Dev Quickstart guide](math-branching-quickstart.md). + diff --git a/tools/exchanges-coinbase/README.md b/tools/exchanges-coinbase/README.md new file mode 100644 index 0000000..7ec1f68 --- /dev/null +++ b/tools/exchanges-coinbase/README.md @@ -0,0 +1,143 @@ +# `xyz.taluslabs.exchanges.coinbase.get-spot-price@1` + +Standard Nexus Tool that retrieves the current spot price for a currency pair from Coinbase. Coinbase API [reference](https://docs.cdp.coinbase.com/coinbase-app/track-apis/prices) + +## Input + +**`currency_pair`: [`String` | `Vec`]** + +The currency pair to get spot price for. Can be provided in multiple formats: + +- **Full pair string**: `"BTC-USD"`, `"ETH-EUR"`, `"SUI-USD"` +- **Array format**: `["BTC", "USD"]`, `["ETH", "EUR"]`, `["SUI", "USD"]` +- **Base currency only**: `"BTC"`, `"ETH"`, `"SUI"` (when `quote_currency` is provided) + +**`quote_currency`: [`String`] (optional)** + +The quote currency to pair with the base currency. When provided, `currency_pair` should contain only the base currency (e.g., `"BTC"` with `quote_currency: "USD"`). + +**`date`: [`String`] (optional)** + +The date for historical spot price in YYYY-MM-DD format (e.g., `"2025-08-21"`). If not provided, returns the current spot price. + +## Output Variants & Ports + +**`ok`** + +The spot price was retrieved successfully. + +- **`ok.amount`: [`String`]** - The price amount as a string +- **`ok.base`: [`String`]** - The base currency (e.g., "BTC", "ETH") +- **`ok.currency`: [`String`]** - The quote currency (e.g., "USD", "USDT") + +**`err`** + +The spot price request failed due to an error. + +- **`err.reason`: [`String`]** - A detailed error message describing what went wrong +- **`err.kind`: [`String`]** - Type of error (invalid_request, not_found, parse, etc.) +- **`err.status_code`: [`u16`] (optional)** - HTTP status code if available + +--- + +# `xyz.taluslabs.exchanges.coinbase.get-product-ticker@1` + +Standard Nexus Tool that retrieves the current ticker information for a product from Coinbase Exchange API. Coinbase Exchange API [reference](https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/products/get-product-ticker) + +## Input + +**`product_id`: [`String` | `Vec`]** + +The product ID (currency pair) to get ticker for. Can be provided in multiple formats: + +- **Full pair string**: `"BTC-USD"`, `"ETH-EUR"`, `"SUI-USD"` +- **Array format**: `["BTC", "USD"]`, `["ETH", "EUR"]`, `["SUI", "USD"]` +- **Base currency only**: `"BTC"`, `"ETH"`, `"SUI"` (when `quote_currency` is provided) + +**`quote_currency`: [`String`] (optional)** + +The quote currency to pair with the base currency. When provided, `product_id` should contain only the base currency (e.g., `"BTC"` with `quote_currency: "USD"`). + +## Output Variants & Ports + +**`ok`** + +The ticker information was retrieved successfully. + +- **`ok.ask`: [`String`]** - Best ask price +- **`ok.bid`: [`String`]** - Best bid price +- **`ok.volume`: [`String`]** - 24h volume +- **`ok.trade_id`: [`u64`]** - Trade ID of the last trade +- **`ok.price`: [`String`]** - Last trade price +- **`ok.size`: [`String`]** - Last trade size +- **`ok.time`: [`String`]** - Time of the last trade +- **`ok.rfq_volume`: [`String`] (optional)** - RFQ volume (only included if present) +- **`ok.conversions_volume`: [`String`] (optional)** - Conversions volume (only included if present) + +**`err`** + +The ticker request failed due to an error. + +- **`err.reason`: [`String`]** - A detailed error message describing what went wrong +- **`err.kind`: [`String`]** - Type of error (invalid_request, not_found, parse, etc.) +- **`err.status_code`: [`u16`] (optional)** - HTTP status code if available + +--- + +# `xyz.taluslabs.exchanges.coinbase.get-product-stats@1` + +Standard Nexus Tool that retrieves 24-hour and 30-day statistics for a product from Coinbase Exchange API. Coinbase Exchange API [reference](https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/products/get-product-stats) + +## Input + +**`product_id`: [`String` | `Vec`]** + +The product ID (currency pair) to get stats for. Can be provided in multiple formats: + +- **Full pair string**: `"BTC-USD"`, `"ETH-EUR"`, `"SUI-USD"` +- **Array format**: `["BTC", "USD"]`, `["ETH", "EUR"]`, `["SUI", "USD"]` +- **Base currency only**: `"BTC"`, `"ETH"`, `"SUI"` (when `quote_currency` is provided) + +**`quote_currency`: [`String`] (optional)** + +The quote currency to pair with the base currency. When provided, `product_id` should contain only the base currency (e.g., `"BTC"` with `quote_currency: "USD"`). + +## Output Variants & Ports + +**`ok`** + +The product statistics were retrieved successfully. + +- **`ok.open`: [`String`]** - Opening price (in quote currency) +- **`ok.high`: [`String`]** - Highest price (in quote currency) +- **`ok.low`: [`String`]** - Lowest price (in quote currency) +- **`ok.volume`: [`String`]** - 24h volume (in base currency) +- **`ok.last`: [`String`]** - Last price (in quote currency) +- **`ok.volume_30day`: [`String`] (optional)** - 30-day volume (in base currency) (only included if present) +- **`ok.rfq_volume_24hour`: [`String`] (optional)** - 24h RFQ volume (only included if present) +- **`ok.conversions_volume_24hour`: [`String`] (optional)** - 24h conversions volume (only included if present) +- **`ok.rfq_volume_30day`: [`String`] (optional)** - 30-day RFQ volume (only included if present) +- **`ok.conversions_volume_30day`: [`String`] (optional)** - 30-day conversions volume (only included if present) + +They can return decimal values like + +```json + "open": "5414.18000000", + "high": "6441.37000000", + "low": "5261.69000000", + "volume": "53687.76764233", + "last": "6250.02000000", + "volume_30day": "786763.72930864", + "rfq_volume_24hour": "78.23", + "conversions_volume_24hour": "0.000000", + "rfq_volume_30day": "0.000000", + "conversions_volume_30day": "0.000000" +``` + +**`err`** + +The stats request failed due to an error. + +- **`err.reason`: [`String`]** - A detailed error message describing what went wrong +- **`err.kind`: [`String`]** - Type of error (invalid_request, not_found, parse, etc.) +- **`err.status_code`: [`u16`] (optional)** - HTTP status code if available diff --git a/tools/http/README.md b/tools/http/README.md new file mode 100644 index 0000000..714839f --- /dev/null +++ b/tools/http/README.md @@ -0,0 +1,155 @@ +# `xyz.taluslabs.http.request@1` + +Standard Nexus Tool that makes HTTP requests with support for various authentication methods, body types, and response validation. + +## Input + +**`method`: [`HttpMethod`]** + +The HTTP method to use for the request. + +- **`GET`** - Retrieve data from the server +- **`POST`** - Send data to the server +- **`PUT`** - Update existing data on the server +- **`DELETE`** - Remove data from the server +- **`PATCH`** - Partially update data on the server +- **`HEAD`** - Retrieve only headers from the server +- **`OPTIONS`** - Get allowed methods for a resource + +**`url`: [`UrlInput`]** + +The URL for the request. Can be provided in two formats: + +- **Full URL**: `"https://api.example.com/users"` +- **Split URL**: `{ "base_url": "https://api.example.com", "path": "/users" }` + +_opt_ **`headers`: [`Option>`]** _default_: [`None`] + +Custom HTTP headers to include in the request. + +_opt_ **`query`: [`Option>`]** _default_: [`None`] + +Query parameters to append to the URL. + +_opt_ **`auth`: [`Option`]** _default_: [`None`] + +Authentication configuration for the request. + +### AuthConfig Options + +- **`None`** - No authentication +- **`BearerToken { token }`** - Bearer token authentication +- **`ApiKeyHeader { key, header_name }`** - API key in header (default: "X-API-Key") +- **`ApiKeyQuery { key, param_name }`** - API key in query parameter (default: "api_key") +- **`BasicAuth { username, password }`** - Basic authentication + +_opt_ **`body`: [`Option`]** _default_: [`None`] + +Request body configuration. + +### RequestBody Options + +- **`Json { data }`** - JSON request body +- **`Form { data }`** - URL-encoded form data +- **`Multipart { fields }`** - Multipart form data for text fields +- **`Raw { data, content_type }`** - Raw bytes (base64 encoded) + +### MultipartField Structure + +- **`name`: [`String`]** - Field name +- **`value`: [`String`]** - Field value (text only) +- **`content_type`: [`Option`]** - Content type for the field + +_opt_ **`expect_json`: [`Option`]** _default_: [`None`] + +Whether to expect a JSON response. If true, attempts to parse the response as JSON. + +_opt_ **`json_schema`: [`Option`]** _default_: [`None`] + +Optional JSON schema to validate the response against. + +### HttpJsonSchema Structure + +- **`name`: [`String`]** - The name of the schema +- **`schema`: [`schemars::Schema`]** - The JSON schema for validation +- **`description`: [`Option`]** - Description of the expected format +- **`strict`: [`Option`]** - Whether to enable strict schema adherence + +_opt_ **`timeout_ms`: [`Option`]** _default_: [`5000`] + +Request timeout in milliseconds. Maximum allowed value is 30000ms (30 seconds). The timeout applies to the **entire request chain**, including all redirects when `follow_redirects` is enabled. For example, if `timeout_ms: 5000` is set and a request follows 3 redirects + +_opt_ **`retries`: [`Option`]** _default_: [`0`] + +Number of retries on failure. Maximum allowed value is 5. + +_opt_ **`follow_redirects`: [`Option`]** _default_: [`false`] + +Whether to follow HTTP redirects. Defaults to false, following curl's philosophy of not following redirects unless explicitly requested. When enabled, follows up to 3 redirects maximum + +## Security Features + +**Localhost Blocking**: Requests to `localhost` and `127.0.0.1` are blocked for security reasons. This prevents internal network scanning and ensures the tool only makes external requests. + +## Output Variants & Ports + +**`ok`** + +The HTTP request was successful. + +```json +{ + "type": "ok", + "status": 200, + "headers": {...}, + "raw_base64": "...", + "text": "...", + "json": {...}, + "schema_validation": {...} +} +``` + +- **`type`: [`"ok"`]** - Response type identifier +- **`status`: [`u16`]** - HTTP status code +- **`headers`: [`HashMap`]** - Response headers +- **`raw_base64`: [`String`]** - Raw response body (base64 encoded) +- **`text`: [`Option`]** - Text representation (if UTF-8 decodable) +- **`json`: [`Option`]** - JSON data (if parseable) +- **`schema_validation`: [`Option`]** - Schema validation details (if validation was performed) + +### SchemaValidationDetails Structure + +- **`name`: [`String`]** - Name of the schema that was used +- **`description`: [`Option`]** - Description of the schema +- **`strict`: [`Option`]** - Whether strict mode was enabled +- **`valid`: [`bool`]** - Validation result +- **`errors`: [`Vec`]** - Validation errors (if any) + +**`err`** + +An error occurred during the request. + +```json +{ + "type": "err", + "reason": "HTTP error 404: Not Found", + "kind": "err_http", + "status_code": 404 +} +``` + +- **`type`: [`"err"`]** - Response type identifier +- **`reason`: [`String`]** - Detailed error message +- **`kind`: [`HttpErrorKind`]** - Type of error +- **`status_code`: [`Option`]** - HTTP status code if available + +### HttpErrorKind Values + +- **`err_http`** - HTTP error response (4xx, 5xx) +- **`err_json_parse`** - JSON parsing error +- **`err_schema_validation`** - Schema validation error +- **`err_network`** - Network connectivity error +- **`err_timeout`** - Request timeout error +- **`err_input`** - Input validation error +- **`err_url_parse`** - URL parsing error +- **`err_base64_decode`** - Base64 decoding error diff --git a/tools/llm-openai-chat-completion/README.md b/tools/llm-openai-chat-completion/README.md index b2b990a..5d51a4e 100644 --- a/tools/llm-openai-chat-completion/README.md +++ b/tools/llm-openai-chat-completion/README.md @@ -8,11 +8,9 @@ It uses the [`async_openai`] crate to interact with the OpenAI API. ## Input -**`api_key`: [`Secret`]** +**`api_key`: [`String`]** -The API key to invoke the OpenAI API with. Encrypted with the Tool's key pair. - -TODO: . +The API key to invoke the OpenAI API with. **`prompt`: [`MessageBag`]** diff --git a/tools/math/README.md b/tools/math/README.md index 16c8de0..5dfa074 100644 --- a/tools/math/README.md +++ b/tools/math/README.md @@ -94,3 +94,29 @@ The first number is less than the second. - **`lt.a`: [`prim@i64`]** - The first number. - **`lt.b`: [`prim@i64`]** - The second number. + +--- + +# `xyz.taluslabs.math.i64.sum@1` + +Standard Nexus Tool that sums an array of [`i64`] numbers and returns the result. + +## Input + +**`vec`: [`Vec`]** + +The array of numbers to sum. + +## Output Variants & Ports + +**`ok`** + +The summation was successful. + +- **`ok.result`: [`prim@i64`]** - The result of the summation. + +**`err`** + +The summation failed due to overflow. + +- **`err.reason`: [`String`]** - The reason for the error. This is always overflow. From 12a4ea97378fd872ed8cc5bdc14ba9648fcae068 Mon Sep 17 00:00:00 2001 From: devops-talus Date: Mon, 10 Nov 2025 14:25:16 +0100 Subject: [PATCH 3/8] Docs update from nexus-sdk (#154) * Update docs from nexus-sdk (commit: dcbf49aaf26e3d3ca88725cf701dac4e54759acd) * Update SUMMARY.md with new files under 'Looking for a home' * chore(docs): format markdown links --------- Co-authored-by: kouks <3684840+kouks@users.noreply.github.com> Co-authored-by: devops-talus <184664591+devops-talus@users.noreply.github.com> --- SUMMARY.md | 5 + nexus-sdk/client.md | 272 +++++++++++++++++++++++++++ nexus-sdk/guides/dag-construction.md | 2 +- nexus-sdk/guides/setup.md | 4 +- nexus-sdk/toolkit-rust.md | 2 +- tools/templating-jinja/README.md | 181 ++++++++++++++++++ 6 files changed, 462 insertions(+), 4 deletions(-) create mode 100644 nexus-sdk/client.md create mode 100644 tools/templating-jinja/README.md diff --git a/SUMMARY.md b/SUMMARY.md index 906a9f5..07d0d2c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -77,3 +77,8 @@ - [Storage: Walrus](tools/storage-walrus/README.md) - [Echanges: Coinbase](tools/exchanges-coinbase/README.md) - [HTTP](tools/http/README.md) + +## Looking for a home + +* [nexus-sdk/client.md](nexus-sdk/client.md) +* [tools/templating-jinja/README.md](tools/templating-jinja/README.md) diff --git a/nexus-sdk/client.md b/nexus-sdk/client.md new file mode 100644 index 0000000..e0278d5 --- /dev/null +++ b/nexus-sdk/client.md @@ -0,0 +1,272 @@ +# 🪐 [`NexusClient`] (Rust) + +The [`NexusClient`] provides a high-level interface for interacting with Nexus. It wraps key functionality including transaction signing and execution, gas management, cryptographic handshakes, and DAG workflow execution. + +--- + +## ✨ Overview + +The [`NexusClient`] provides access to: + +- [`GasActions`] — manage gas coins and budgets +- [`CryptoActions`] — perform cryptographic handshakes with Nexus +- [`WorkflowActions`] — publish and execute workflows (DAGs) + +You can initialize a `NexusClient` via [`NexusClient::builder()`] using either: + +- a **Sui wallet context**, or +- a **Secret mnemonic and Sui client** + +--- + +## ⚙️ Building a `NexusClient` + +```rust +use nexus_sdk::nexus::client::NexusClient; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Build the Nexus client + let client = NexusClient::builder() + .with_wallet_context(/* your `sui_sdk::wallet_context::WalletContext */) + .with_gas(vec![/* your gas coins */], 10_000_000)? + .with_nexus_objects(/* your `nexus_sdk::types::NexusObjects` */) + .build() + .await?; + + println!("✅ Nexus client initialized!"); + + Ok(()) +} +``` + +Alternatively, you can initialize using a mnemonic: + +```rust +use nexus_sdk::nexus::client::NexusClient; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Build the Nexus client + let client = NexusClient::builder() + .with_mnemonic(/* secret mnemonic */, /* your `sui_sdk::SuiClient` */)? + .with_gas(vec![/* your gas coins */], 10_000_000)? + .with_nexus_objects(/* your `nexus_sdk::types::NexusObjects` */) + .build() + .await?; + + println!("✅ Nexus client initialized!"); + + Ok(()) +} +``` + +--- + +## 🔑 Signer + +### Supported Signer Types + +| Type | Description | +| ------------------ | ----------------------------------------------------------------- | +| `Signer::Wallet` | Uses a local [`WalletContext`] for signing transactions | +| `Signer::Mnemonic` | Uses a [`SuiClient`] + in-memory keystore derived from a mnemonic | + +### Key Public Behaviors + +- Automatically signs and executes transactions. +- Keeps track of active wallet address. +- Keeps track of gas coins and updates the gas coin references after a transaction. +- Handles errors as [`NexusError`]. + +--- + +## ⛽ Nexus Gas Budget Management + +Nexus gas budget is managed through the [`GasActions`] struct. + +### Add Budget + +```rust +use nexus_sdk::nexus::client::NexusClient; + +let coin_object_id = sui_types::base_types::ObjectID::random(); + +let result = nexus_client.gas().add_budget(coin_object_id).await?; + +println!("Gas budget added in tx: {:?}", result.tx_digest); +``` + +**What it does:** + +- Fetches the given coin object. +- Adds it to Nexus as available gas budget for workflows. +- Returns the transaction digest. + +**Returns:** + +[`AddBudgetResult`] — includes the transaction digest. + +--- + +## 🔐 Cryptographic Actions + +These actions manage secure handshakes and session setup. + +### Perform a Handshake + +```rust +use nexus_sdk::crypto::x3dh::IdentityKey; + +let ik = IdentityKey::generate(); + +let handshake = nexus_client.crypto().handshake(&ik).await?; + +println!("Session established!"); +println!("Claim TX: {:?}", handshake.claim_tx_digest); +println!("Associate TX: {:?}", handshake.associate_tx_digest); + +assert!(handshake.session); +``` + +**What it does:** + +1. Claims a pre-key object from Nexus. +1. Fetches the corresponding [`PreKeyBundle`]. +1. Initiates an [`X3DH`] handshake to create a secure [`Session`]. +1. Associates the pre-key with the sender on-chain. + +**Returns:** + +[`HandshakeResult`] — includes session data and transaction digests for claim and association steps. + +--- + +## ⚡ Workflow Actions + +The [`WorkflowActions`] API allows you to publish, execute, and inspect **DAG-based workflows** on Nexus. + +### 1. Publish a DAG + +```rust +use nexus_sdk::types::Dag; + +let dag = serde_json::from_str::(include_str!(/* path to your DAG JSON file */))?; + +let publish_result = nexus_client.workflow().publish(dag).await?; + +println!("Published DAG ID: {:?}", publish_result.dag_object_id); +``` + +**What it does:** + +- Builds and submits a programmable transaction that creates and publishes the DAG object. +- Returns the transaction digest and created DAG object ID. + +**Returns:** + +[`PublishResult`] — includes the transaction digest and DAG object ID. + +--- + +### 2. Execute a Workflow + +```rust +use nexus_sdk::types::{PortsData, StorageConf}; +use nexus_sdk::crypto::session::Session; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::Mutex; + +// Prepare input ports data +let mut entry_data: HashMap = /* your input data */ + +// Prepare storage and session +let storage_conf = StorageConf::default(); +let session = Arc::new(Mutex::new(Session::default())); + +let execute_result = nexus_client + .workflow() + .execute( + publish_result.dag_object_id, + entry_data, + None, // use default entry group + &storage_conf, + session, + ) + .await?; + +println!("Execution object ID: {:?}", execute_result.execution_object_id); +``` + +**What it does:** + +1. Commits input data to the appropriate storage (inline, Walrus). +1. Constructs and executes a transaction to start the workflow execution. +1. Returns the transaction digest and new DAG execution object ID. + +**Returns:** + +[`ExecuteResult`] — includes the transaction digest and execution object ID. + +--- + +### 3. Inspect Workflow Execution + +```rust +use tokio::time::Duration; + +let inspect = nexus_client + .workflow() + .inspect_execution( + execute_result.execution_object_id, + execute_result.tx_digest, + Some(Duration::from_secs(180)), // timeout + ) + .await?; + +let mut event_stream = inspect.next_event; + +// Listen for events +while let Some(event) = event_stream.recv().await { + println!("Event: {:?}", event); +} + +// Await the poller completion +inspect.poller.await??; + +println!("✅ Execution finished successfully!"); +``` + +**What it does:** + +- Polls Nexus events on Sui to stream live workflow execution updates. +- Automatically stops when an `ExecutionFinished` event is detected or timeout is reached. + +**Returns:** + +[`InspectExecutionResult`] — includes an event stream and a poller handle. + +--- + +## 🧱 Module Summary + +| Module | Purpose | +| ----------------- | ------------------------------------------------------- | +| `nexus::client` | Core client, builder, signer, gas management | +| `nexus::crypto` | Cryptographic operations (X3DH, sessions, handshakes) | +| `nexus::gas` | Gas management for Nexus workflows | +| `nexus::workflow` | DAG workflow publishing, execution, and event streaming | + +--- + +## 🧭 Error Handling + +All methods return a `Result`. +The `NexusError` enum categorizes issues from configuration errors to RPC and transaction issues. + +--- + +## 🪶 Summary + +The [`NexusClient`] aims to make building, publishing, and executing Nexus workflows _simple, safe, and async-ready_. It abstracts away Sui transaction signing and gas management while providing a clean modular interface. + diff --git a/nexus-sdk/guides/dag-construction.md b/nexus-sdk/guides/dag-construction.md index 4d9170f..30e96ca 100644 --- a/nexus-sdk/guides/dag-construction.md +++ b/nexus-sdk/guides/dag-construction.md @@ -241,5 +241,5 @@ For working examples, see the following files in the `cli/src/dag/_dags` directo - `ig_story_planner_valid.json`: Example of a complex workflow - `entry_groups_valid.json`: Example of using entry groups. -For examples of invalid DAGs and common mistakes to avoid (especially regarding Rule 5 - Race Conditions), see the diagrams in [workflow documentation](../../nexus-next/packages/workflow.md) and the `*_invalid.json` files in the [testing DAG directory](https://github.com/Talus-Network/nexus-sdk/tree/v0.1.0/cli/src/dag/_dags). +For examples of invalid DAGs and common mistakes to avoid (especially regarding Rule 5 - Race Conditions), see the diagrams in [workflow documentation](../../nexus-next/packages/workflow.md) and the `*_invalid.json` files in the [testing DAG directory](https://github.com/Talus-Network/nexus-sdk/tree/v0.3.0/sdk/src/dag/_dags). diff --git a/nexus-sdk/guides/setup.md b/nexus-sdk/guides/setup.md index d20ab5d..83eb607 100644 --- a/nexus-sdk/guides/setup.md +++ b/nexus-sdk/guides/setup.md @@ -38,7 +38,7 @@ To install directly from the source using `cargo`, run: ```bash cargo install nexus-cli \ --git https://github.com/talus-network/nexus-sdk \ - --tag v0.2.0 \ + --tag v0.3.0 \ --locked ``` @@ -51,7 +51,7 @@ nexus --version ## Download the Nexus objects ```bash -wget https://storage.googleapis.com/production-talus-sui-objects/v0.2.0/objects.devnet.toml +wget https://storage.googleapis.com/production-talus-sui-objects/v0.3.0/objects.devnet.toml ``` ## Configure the Talus devnet diff --git a/nexus-sdk/toolkit-rust.md b/nexus-sdk/toolkit-rust.md index f23c95a..9b871d7 100644 --- a/nexus-sdk/toolkit-rust.md +++ b/nexus-sdk/toolkit-rust.md @@ -15,7 +15,7 @@ Alternatively, you can add the following to your `Cargo.toml` file: ```toml [dependencies.nexus-toolkit] git = "https://github.com/Talus-Network/nexus-sdk" -tag = "v0.1.0" +tag = "v0.3.0" package = "nexus-toolkit" ``` diff --git a/tools/templating-jinja/README.md b/tools/templating-jinja/README.md new file mode 100644 index 0000000..92829b9 --- /dev/null +++ b/tools/templating-jinja/README.md @@ -0,0 +1,181 @@ +# `xyz.taluslabs.templating.jinja@1` + +Standard Nexus Tool that parses templates using the Jinja2 (MiniJinja) templating engine with flexible input options. Undefined variables are preserved as `{{placeholders}}` to support chaining with other tools. + +## Input + +**`template`: [`String`]** + +The template string to render. Supports template syntax with variable substitution using double curly braces (e.g., `{{variable_name}}`). Undefined variables are preserved in the output (not an error), allowing partial rendering. + +_opt_ **`args`: [`HashMap`]** _default_: `{}` + +Template arguments as a HashMap mapping variable names to their values. This parameter can be combined with `name`/`value` parameters to provide additional variables. At least one of `args` or the `name`/`value` pair must be provided. + +_opt_ **`value`: [`Option`]** _default_: [`None`] + +Optional single value to substitute in the template. Must be used together with the `name` parameter. Can be combined with the `args` parameter to add an additional variable. + +_opt_ **`name`: [`Option`]** _default_: [`None`] + +Optional name for the single variable. Must be used together with the `value` parameter. This allows you to specify an additional variable beyond those provided in `args`. + +## Output Variants & Ports + +**`ok`** + +The template was rendered successfully. + +- **`ok.result`: [`String`]** - The rendered template with all variables substituted + +**`err`** + +The template processing failed due to an error. + +- **`err.reason`: [`String`]** - A detailed error message describing what went wrong. This could be: + - `"Either 'args' or 'name'/'value' parameters must be provided"` - No parameters were provided or args is empty + - `"name and value must both be provided or both be None"` - Only one of name/value was provided + - `"Template syntax error: [error details]"` - Template has invalid syntax + - `"Template rendering failed: [error details]"` - Rendering error + +--- + +## Examples + +### Example 1: Using args HashMap + +```json +{ + "template": "Hello {{name}} from {{city}}!", + "args": { + "name": "Alice", + "city": "Paris" + } +} +``` + +**Output:** + +```json +{ + "type": "ok", + "result": "Hello Alice from Paris!" +} +``` + +### Example 2: Using name and value + +```json +{ + "template": "Welcome, {{username}}!", + "name": "username", + "value": "Bob" +} +``` + +**Output:** + +```json +{ + "type": "ok", + "result": "Welcome, Bob!" +} +``` + +### Example 3: Combining args and name/value + +```json +{ + "template": "{{greeting}} {{name}} from {{city}}!", + "args": { + "greeting": "Hello", + "city": "Tokyo" + }, + "name": "name", + "value": "Dave" +} +``` + +**Output:** + +```json +{ + "type": "ok", + "result": "Hello Dave from Tokyo!" +} +``` + +### Example 4: Using empty args with name/value + +```json +{ + "template": "{{message}}", + "args": {}, + "name": "message", + "value": "Test message" +} +``` + +**Output:** + +```json +{ + "type": "ok", + "result": "Test message" +} +``` + +### Example 5: Error - No parameters provided + +```json +{ + "template": "Hello {{name}}!" +} +``` + +**Output:** + +```json +{ + "type": "err", + "reason": "Either 'args' or 'name'/'value' parameters must be provided" +} +``` + +### Example 6: Error - Only name provided + +```json +{ + "template": "Hello {{name}}!", + "name": "name" +} +``` + +**Output:** + +```json +{ + "type": "err", + "reason": "name and value must both be provided or both be None" +} +``` + +### Example 7: Undefined variables are preserved + +```json +{ + "template": "Hi, this is {{name}}, from {{city}}", + "args": { + "name": "Alice" + } +} +``` + +**Output:** + +```json +{ + "type": "ok", + "result": "Hi, this is Alice, from {{city}}" +} +``` From 1e2ff1525bc7e6c61685c83c491dfdd678580b66 Mon Sep 17 00:00:00 2001 From: tuky191 <25343132+tuky191@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:05:55 +0000 Subject: [PATCH 4/8] Update docs from vision (commit: 15c7a450cfa763aec9d96c9a09b313986c5f71e6) --- vision/README.md | 21 ++ vision/SUMMARY.md | 22 ++ .../about-previously-deployed-workflows.md | 38 ++ vision/talus-vision/about-sidebar/README.md | 19 + .../about-sidebar/deploy-workflows-tab.md | 29 ++ .../about-sidebar/execute-workflow-tab.md | 26 ++ .../about-sidebar/gas-management-tab.md | 27 ++ .../about-sidebar/json-editor-tab.md | 33 ++ .../talus-vision/about-sidebar/tools-tab.md | 27 ++ .../about-sidebar/workflow-discovery-tab.md | 32 ++ .../talus-vision/connect-with-sui-wallet.md | 31 ++ vision/talus-vision/crypto-auth.md | 35 ++ .../draw-first-workflow/README.md | 20 ++ .../draw-first-workflow/about-playground.md | 36 ++ .../deploy-my-first-workflow.md | 34 ++ .../execute-my-first-worklow.md | 34 ++ .../draw-first-workflow/node-component.md | 51 +++ .../step-by-step-my-first-workflow.md | 334 ++++++++++++++++++ .../draw-first-workflow/summary.md | 29 ++ .../user-workflows-and-profile.md | 38 ++ .../talus-vision/welcome-to-talus-vision.md | 15 + 21 files changed, 931 insertions(+) create mode 100644 vision/README.md create mode 100644 vision/SUMMARY.md create mode 100644 vision/talus-vision/about-previously-deployed-workflows.md create mode 100644 vision/talus-vision/about-sidebar/README.md create mode 100644 vision/talus-vision/about-sidebar/deploy-workflows-tab.md create mode 100644 vision/talus-vision/about-sidebar/execute-workflow-tab.md create mode 100644 vision/talus-vision/about-sidebar/gas-management-tab.md create mode 100644 vision/talus-vision/about-sidebar/json-editor-tab.md create mode 100644 vision/talus-vision/about-sidebar/tools-tab.md create mode 100644 vision/talus-vision/about-sidebar/workflow-discovery-tab.md create mode 100644 vision/talus-vision/connect-with-sui-wallet.md create mode 100644 vision/talus-vision/crypto-auth.md create mode 100644 vision/talus-vision/draw-first-workflow/README.md create mode 100644 vision/talus-vision/draw-first-workflow/about-playground.md create mode 100644 vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md create mode 100644 vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md create mode 100644 vision/talus-vision/draw-first-workflow/node-component.md create mode 100644 vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md create mode 100644 vision/talus-vision/draw-first-workflow/summary.md create mode 100644 vision/talus-vision/user-workflows-and-profile.md create mode 100644 vision/talus-vision/welcome-to-talus-vision.md diff --git a/vision/README.md b/vision/README.md new file mode 100644 index 0000000..d532265 --- /dev/null +++ b/vision/README.md @@ -0,0 +1,21 @@ +# Talus Vision + +**Talus Vision** is a **low-code platform** developed by the **Talus Core Team**. It features an architecture designed to offer user-friendly interfaces and seamless integration with **Nexus**. By connecting their **Sui wallets** to the application, users can build and customize their own workflows. + +This **devnet demo** provides users with the following capabilities: + +* Easy connection with multiple **Sui wallets** +* **VS Code–style multi-tab interface** for efficient workflow editing +* **Autosave** or **manual save** options for reusable sessions +* Ability to **create and visualize custom workflows** +* Comprehensive **input/output details** and **descriptions** for all tools +* An easily editable **JSON editor** with real-time **Nexus validations** +* Access to **previously deployed workflows** +* **Crypto Auth** feature for encryption operations +* **One-click deployment**, allowing users to deploy their workflows on-chain using their **Sui wallet** +* **Workflow execution**, enabling on-chain transaction processing from deployed workflows +* **Multiple entry group management**, allowing users to organize and control their entry groups + + + +
diff --git a/vision/SUMMARY.md b/vision/SUMMARY.md new file mode 100644 index 0000000..a18d408 --- /dev/null +++ b/vision/SUMMARY.md @@ -0,0 +1,22 @@ +# Table of contents + +* [Talus Vision](README.md) + * [Welcome to Talus Vision ](talus-vision/welcome-to-talus-vision.md) + * [Connect with Sui Wallet](talus-vision/connect-with-sui-wallet.md) + * [About Sidebar](talus-vision/about-sidebar/README.md) + * [Tools Tab](talus-vision/about-sidebar/tools-tab.md) + * [Json Editor Tab](talus-vision/about-sidebar/json-editor-tab.md) + * [Workflow Discovery Tab](talus-vision/about-sidebar/workflow-discovery-tab.md) + * [Deploy Workflows Tab](talus-vision/about-sidebar/deploy-workflows-tab.md) + * [Gas Management Tab](talus-vision/about-sidebar/gas-management-tab.md) + * [Execute Workflow Tab](talus-vision/about-sidebar/execute-workflow-tab.md) + * [Crypto Auth](talus-vision/crypto-auth.md) + * [Draw First Workflow](talus-vision/draw-first-workflow/README.md) + * [About Playground](talus-vision/draw-first-workflow/about-playground.md) + * [Node Component](talus-vision/draw-first-workflow/node-component.md) + * [Step-by-step My first Workflow](talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) + * [Deploy My first workflow](talus-vision/draw-first-workflow/deploy-my-first-workflow.md) + * [Execute My first Worklow](talus-vision/draw-first-workflow/execute-my-first-worklow.md) + * [Summary](talus-vision/draw-first-workflow/summary.md) + * [User Workflows & Profile](talus-vision/user-workflows-and-profile.md) + * [About previously deployed workflows](talus-vision/about-previously-deployed-workflows.md) diff --git a/vision/talus-vision/about-previously-deployed-workflows.md b/vision/talus-vision/about-previously-deployed-workflows.md new file mode 100644 index 0000000..bbdd5cb --- /dev/null +++ b/vision/talus-vision/about-previously-deployed-workflows.md @@ -0,0 +1,38 @@ +--- +description: >- + Previously deployed workflows are accessible on-chain under the Workflow + Discovery tab, as mentioned earlier in this document. Users can also access + workflows deployed by other Nexus users. Nexus use +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# About previously deployed workflows + +Previously deployed workflows are **accessible on-chain** under the **Workflow Discovery tab**, as mentioned earlier in this document. Users can also access workflows deployed by other **Nexus users**. + +Nexus users can create workflows in different designs, and all users are free to use any of these workflows. There are **two ways** to interact with them: + +1. **Use as a template:** Users can take an existing workflow, edit its design in the playground, and redeploy it. +2. **Execute directly:** Users can run the existing deployed workflows repeatedly without modification. + +The **playground editor** helps users distinguish between **templates** and **deployed workflows**. Deployed workflow objects are **shared objects**, meaning they can be reused by multiple users. + +
+ +\ +On the **Deployments page**, if the **“+ New Workflow”** button is clicked, the **“Use Workflow as Template”** option allows users to draw a workflow as a template, provided they already have a deployment **object ID**. + +
diff --git a/vision/talus-vision/about-sidebar/README.md b/vision/talus-vision/about-sidebar/README.md new file mode 100644 index 0000000..f3a2662 --- /dev/null +++ b/vision/talus-vision/about-sidebar/README.md @@ -0,0 +1,19 @@ +--- +description: >- + The Sidebar component provides a VS Code–style user experience. All essential + development tools are conveniently located within the sidebar component. +--- + +# About Sidebar + +[Tools Tab](tools-tab.md) + +[Json Editor Tab](json-editor-tab.md) + +[Worfklow Discovery Tab](json-editor-tab.md) + +[Deploy Workflows Tab](deploy-workflows-tab.md) + +[Gas Management Tab](gas-management-tab.md) + +[Execute Workflow Tab](execute-workflow-tab.md) diff --git a/vision/talus-vision/about-sidebar/deploy-workflows-tab.md b/vision/talus-vision/about-sidebar/deploy-workflows-tab.md new file mode 100644 index 0000000..139b7ca --- /dev/null +++ b/vision/talus-vision/about-sidebar/deploy-workflows-tab.md @@ -0,0 +1,29 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Deploy Workflows Tab + +It provides a feature that allows users to **deploy workflows developed on the playground with a single click**. It works in conjunction with the **JSON Editor**, tracking the validation status of user-created workflows and enabling **on-chain deployment** using the **Sui wallet**.\ +\ +During the demo, the platform operates with a **default gas budget of 0.1**. Users can increase the gas budget if desired.\ +\ +If the deployment is successful, the playground automatically switches from **Edit Mode** to **View Mode**, and the user is provided with the **on-chain object ID**. + +
+ +\ +\ diff --git a/vision/talus-vision/about-sidebar/execute-workflow-tab.md b/vision/talus-vision/about-sidebar/execute-workflow-tab.md new file mode 100644 index 0000000..9a43711 --- /dev/null +++ b/vision/talus-vision/about-sidebar/execute-workflow-tab.md @@ -0,0 +1,26 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Execute Workflow Tab + +The **Execute Workflow tab** allows users to **execute workflows they have previously deployed**. After filling in the required inputs in the playground, users can execute their workflows directly from this tab. + +* The **transaction output** is displayed in the console after execution, providing users access to **execution transaction details**. +* All operations are performed **on-chain**. +* If a user has **multiple entry groups**, input validations are performed after selecting the desired entry group, ensuring a **smooth and user-friendly execution experience**. + +
diff --git a/vision/talus-vision/about-sidebar/gas-management-tab.md b/vision/talus-vision/about-sidebar/gas-management-tab.md new file mode 100644 index 0000000..dcdb953 --- /dev/null +++ b/vision/talus-vision/about-sidebar/gas-management-tab.md @@ -0,0 +1,27 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Gas Management Tab + +The **Gas Management tab** allows users to **create a budget** for their **Nexus operations**. + +* Users can **add SUI balance** from their **Sui wallets** to their gas budgets. +* Users can **view their gas budget** in real-time. +* In the **demo version**, it provides **Sui faucet support** on the **Talus Devnet**.\ + + +
diff --git a/vision/talus-vision/about-sidebar/json-editor-tab.md b/vision/talus-vision/about-sidebar/json-editor-tab.md new file mode 100644 index 0000000..5a014df --- /dev/null +++ b/vision/talus-vision/about-sidebar/json-editor-tab.md @@ -0,0 +1,33 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Json Editor Tab + +The **JSON Editor tab** provides **real-time Nexus validation** while users build their workflows, offering guidance as needed. It features both **Preview Mode** and **Editor Mode**, optimizing the user experience during workflow creation. + +The **JSON Editor tab** offers the following features: + +* **Preview Mode:** Provides real-time workflow validation +* **Editor Mode:** Supports a **VS Code–style editor** +* **Easy Copying:** Quickly copy JSON content +* **Export JSON:** Download workflows as a **JSON file** +* **Import JSON:** Load previously created **JSON files** into **Talus Vision** for use + +
+ +
+ diff --git a/vision/talus-vision/about-sidebar/tools-tab.md b/vision/talus-vision/about-sidebar/tools-tab.md new file mode 100644 index 0000000..13ddd27 --- /dev/null +++ b/vision/talus-vision/about-sidebar/tools-tab.md @@ -0,0 +1,27 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Tools Tab + +The Tools tab lists all previously developed Nexus tools, providing users with easy access. All available tools can also be found in the[ nexus-sdk repository](https://github.com/Talus-Network/nexus-sdk/tree/main/tools). + +* All tools are categorized based on their functionality. +* The **Tools tab** is divided into **subcategories** to enhance accessibility. Tools that are **frequently used** or **marked as favorites** by the user are easily accessible. +* Tools listed under each category can be **easily added to the playground** using the **drag-and-drop feature**. Each tool includes a **description**, along with detailed **input and output properties**. Users can review these details thoroughly and access the tools they wish to use with ease. Tools that are **registered** are clearly marked, and their usage is **recommended**.\ + + +
diff --git a/vision/talus-vision/about-sidebar/workflow-discovery-tab.md b/vision/talus-vision/about-sidebar/workflow-discovery-tab.md new file mode 100644 index 0000000..fc807cb --- /dev/null +++ b/vision/talus-vision/about-sidebar/workflow-discovery-tab.md @@ -0,0 +1,32 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Workflow Discovery Tab + +All deployed workflows are **accessible on-chain**. Under this tab, users can explore workflows previously deployed by other users. + +Within the **Workflows Discovery tab**, users have access to the following features: + +* **Featured Workflows:** Workflows frequently executed by other users +* **All User-Created Workflows:** Access to workflows created by other **Nexus** users +* **Workflow Details:** Information on tools used, deployment date, and workflow owner +* **Template Reuse:** Ability to use workflows again as templates +* **Easy Execution:** Simplified execution of workflows\ + \ + + +
diff --git a/vision/talus-vision/connect-with-sui-wallet.md b/vision/talus-vision/connect-with-sui-wallet.md new file mode 100644 index 0000000..2bc2220 --- /dev/null +++ b/vision/talus-vision/connect-with-sui-wallet.md @@ -0,0 +1,31 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Connect with Sui Wallet + +Talus Devnet Rpc : https://rpc.ssfn.devnet.production.taluslabs.dev \ +Talus Devnet Explorer : https://explorer.devnet.taluslabs.dev/ + +For **Sui wallets** that support **custom RPC**, you can log in after configuring the network using the RPC information provided above.\ +The **recommended wallet** for this process is [**Surf Wallet**](https://www.surf.tech/). + +
+ + + + + diff --git a/vision/talus-vision/crypto-auth.md b/vision/talus-vision/crypto-auth.md new file mode 100644 index 0000000..e4f3059 --- /dev/null +++ b/vision/talus-vision/crypto-auth.md @@ -0,0 +1,35 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Crypto Auth + +**Crypto Auth** is a **secure authentication system** required to execute **encrypted workflows**. On first use, a unique **master key** is generated in your browser and stored in encrypted form. + +Subsequently, two operations are performed on the **Sui blockchain**: + +1. A **PreKey (encryption key)** is requested on your behalf. +2. An **encrypted session** is initiated using the **X3DH protocol** and recorded on the blockchain. + +The session data is stored locally in your browser and is used to **encrypt and decrypt your data** when executing encrypted workflows. All keys are stored **exclusively in your browser**, and all blockchain operations are **signed with your Sui wallet**. + +After logging in with your wallet, if the **Crypto Auth** process has not been completed previously, you will need to perform it. A dedicated button for this action is available in the **application header**. + +All you need to do is **sign the generated transactions**. Completing this step is required before you can perform any **Nexus operations**.\ +\ + + +
diff --git a/vision/talus-vision/draw-first-workflow/README.md b/vision/talus-vision/draw-first-workflow/README.md new file mode 100644 index 0000000..ea61d9b --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/README.md @@ -0,0 +1,20 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Draw First Workflow + +In this document, we will **build our first workflow**. After successfully passing all **validation processes**, we will complete the **first on-chain deployment** and then **execute the deployed workflow**. diff --git a/vision/talus-vision/draw-first-workflow/about-playground.md b/vision/talus-vision/draw-first-workflow/about-playground.md new file mode 100644 index 0000000..792aeac --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/about-playground.md @@ -0,0 +1,36 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# About Playground + +**Talus Vision** provides users with a **playground** where they can design and build their workflows. Similar to many low-code platforms, the **user experience** in Talus Vision is based on a **node-based playground**. + +Users can **drag and drop tools** onto the playground and make any desired **node-based adjustments** directly within the workspace.\ + + +Some key features of the **Playground** include: + +* **Multi-tab support**, allowing users to develop multiple workflows simultaneously +* **Automatic or manual save options**, enabling easier reuse across sessions (login required) +* **Edit Mode** and **View Mode**, providing a smoother experience during workflow execution + + + +As shown in the figure, multiple workflows can be developed simultaneously across different tabs. All changes made in the playground are **instantly reflected in the JSON Editor**. + +
+ diff --git a/vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md b/vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md new file mode 100644 index 0000000..6bfef42 --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md @@ -0,0 +1,34 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Deploy My first workflow + +On the previous page, we created a **valid workflow**. The next step is to **deploy this workflow on-chain**. Up to this point, all actions were focused on generating a valid workflow, continuously checked by built-in **validation mechanisms**. Now, we need to deploy the workflow on the **Talus Devnet**. + +Before proceeding, ensure that the following **two steps** have been completed: + +1. **Gas Management:** Navigate to the **Gas Management tab** and add a **SUI gas budget** of sufficient amount. +2. **Crypto Auth:** If a **key icon** appears in the application header, it indicates that **Crypto Auth** has not yet been performed. Complete this process and wait for the generated transactions to finalize.\ + + +After completing the necessary preparations, navigate to the **DEPLOY WORKFLOWS tab** to deploy your workflow. + +Since the deployment occurs **on-chain**, you will need to use your **Sui wallet**, and a **transaction approval** will be requested. Once the transaction is successfully completed, the current drawing in the editor will automatically switch from **Edit Mode** to **View Mode**. + +
+ +
diff --git a/vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md b/vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md new file mode 100644 index 0000000..781dd3b --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md @@ -0,0 +1,34 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Execute My first Worklow + +So far, we have successfully built a workflow according to our desired logic. After passing all **validation steps**, we deployed this workflow, turning it into an **on-chain object**. The next step is to **execute it and observe the results**. + +Navigate to the **Execute Workflow tab**, fill in the required inputs, and execute the DAG **on-chain**. + +In this example, the workflow expects **one numeric input** at the **`add_input_and_default`** node. At this point, the playground is in **View Mode**, and only the necessary input fields are visible. Once the input is provided, we can monitor the **output in the console**.\ + + +I chose **12** as the input, and the expected step-by-step results are as follows: + +* **`add_input_and_default` output:** 12 + 3 = **15** +* **`is_negative` output:** 15 compared to 0 → **greater than**, so the **gt output** is triggered +* **Final result:** The workflow ends at the **`mul_by_7`** node + +
+ diff --git a/vision/talus-vision/draw-first-workflow/node-component.md b/vision/talus-vision/draw-first-workflow/node-component.md new file mode 100644 index 0000000..5f7a666 --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/node-component.md @@ -0,0 +1,51 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Node Component + +Each tool is added to the playground via **drag-and-drop** and is then transformed into a **node object** within the workspace. These node objects are, in turn, converted into **vertices** following **Nexus standards**. Users can freely **edit their nodes** directly within the playground. + +When examining a node closely, the following features can be observed: + +* Node names can be **easily edited** (click the **pencil icon** to rename). +* Nodes can be **quickly deleted** from the playground. +* Node positions can be **locked** during design (using the **anchor icon**). + +
+ + + +Each node consists of **two main sections**: + +**Inputs:**\ +The _Inputs_ section allows users to conveniently edit all input parameters. It supports setting **default values** and **enabling encryption** where necessary. Any input changes are instantly reflected in the **DAG JSON** within the JSON Editor. Users can also add multiple **entry groups** using the **Add Entry Group** button. + +
+ + + +
+ + + +**Outputs:**\ +The _Outputs_ section is designed for creating **edges** (connections) between nodes. When an output is connected to another node, users are prompted to select the appropriate **port**, ensuring a smooth and intuitive **drawing experience**. + +
+ +
+ diff --git a/vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md b/vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md new file mode 100644 index 0000000..b3207b3 --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md @@ -0,0 +1,334 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Step-by-step My first Workflow + +Let's build our first workflow together. We'll reconstruct this example from the Nexus documentation step-by-step and monitor the changes in the JSON files in real time. [https://docs.talus.network/getting-started/math-branching-quickstart](https://docs.talus.network/getting-started/math-branching-quickstart)\ +\ +This example accepts a numeric input from the user and performs the following sequence of operations: + +1. Add **3** to the input value. +2. Evaluate the sign of the resulting number and apply one of three branches: + * **If the number is positive:** multiply it by **7**. + * **If the number equals 0:** add **1** to it. + * **If the number is negative:** multiply it by **-3**. + +We will now construct the complete workflow step by step.\ +\ +Initial state for JSON editor : + +```json +{ +"vertices": [ +], +"edges": [ +], +"default_values": [ +] +} +``` + +Step - 1\ +The first node we need in our workflow will perform the operation of **adding the number 3** to the user’s input. + +Under the **Math** category, the **Add** tool is designed precisely for this purpose. From the **Tools tab**, navigate to the **Math** category, locate the **Add** tool, and **drag and drop** it into the playground. + +For naming, set the node name to **`add_input_and_default`**.\ +Then, assign **port B** as the **default value**, and set this value to **3**. + +
+ +JSON editor current state is : + +```json +{ + "vertices": [ + { + "entry_ports": [ + { + "name": "a", + "encrypted": false + } + ], + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.add@1" + }, + "name": "add_input_and_default" + } + ], + "edges": [], + "default_values": [ + { + "vertex": "add_input_and_default", + "input_port": "b", + "value": { + "storage": "inline", + "data": 3 + } + } + ] +} +``` + +Step - 2 + +In this step, we need to check whether the result from the first node is negative. + +The most suitable node for this task is the **Compare (cmp)** tool, located under the **Math** category. This tool takes **two inputs** and compares them, producing **three distinct outputs**: **greater than**, **equal to**, and **less than** — perfectly fitting our example’s logic. + +Drag and drop the **Compare (cmp)** tool into the playground and rename it to **`is_negative`**.\ +Next, set the **second input** as a **default value of 0** to establish the desired comparison logic. + +Finally, connect the **result output** from the **`add_input_and_default`** node to the **first input** of the **`is_negative`** node. + +
+ +JSON editor current state is : + +```json +{ + "vertices": [ + { + "entry_ports": [ + { + "name": "a", + "encrypted": false + } + ], + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.add@1" + }, + "name": "add_input_and_default" + }, + { + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.cmp@1" + }, + "name": "is_negative" + } + ], + "edges": [ + { + "from": { + "vertex": "add_input_and_default", + "output_variant": "ok", + "output_port": "result" + }, + "to": { + "vertex": "is_negative", + "input_port": "a" + } + } + ], + "default_values": [ + { + "vertex": "add_input_and_default", + "input_port": "b", + "value": { + "storage": "inline", + "data": 3 + } + }, + { + "vertex": "is_negative", + "input_port": "b", + "value": { + "storage": "inline", + "data": 0 + } + } + ] +} +``` + +Step - 3 + +In this step, we will generate new outputs based on the results from the **Compare (cmp)** node, following the target logic: + +* **If the number is greater than 0:** multiply it by **7** +* **If the number equals 0:** add **1** +* **If the number is less than 0:** multiply it by **-3** + +Based on this logic, the required tools are: + +* **Multiply (Mul) tool** from the **Math** category for multiplying by 7 and -3 +* **Add tool** from the **Math** category for adding 1 + +#### Steps to implement: + +1. **Locate the tools** in the **Tools tab → Math category** and **drag and drop** them into the playground. +2. **Rename and configure the nodes** as follows: + + * **Multiply by 7 node:** + * Name: **`mul_by_7`** + * **B input:** 7 + * Connect to the **gt output** of the **`is_negative`** node + * **Add 1 node:** + * Name: **`add_1`** + * **B input:** 1 + * Connect to the **eq output** of the **`is_negative`** node + * **Multiply by -3 node:** + * Name: **`mul_by_neg_3`** + * **B input:** -3 + * Connect to the **lt output** of the **`is_negative`** node + + + +
+ + + +Json editor final state is : + +```json +{ + "vertices": [ + { + "entry_ports": [ + { + "name": "a", + "encrypted": false + } + ], + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.add@1" + }, + "name": "add_input_and_default" + }, + { + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.cmp@1" + }, + "name": "is_negative" + }, + { + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.mul@1" + }, + "name": "mul_by_7" + }, + { + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.add@1" + }, + "name": "add_1" + }, + { + "kind": { + "variant": "off_chain", + "tool_fqn": "xyz.taluslabs.math.i64.mul@1" + }, + "name": "mul_by_neg_3" + } + ], + "edges": [ + { + "from": { + "vertex": "add_input_and_default", + "output_variant": "ok", + "output_port": "result" + }, + "to": { + "vertex": "is_negative", + "input_port": "a" + } + }, + { + "from": { + "vertex": "is_negative", + "output_variant": "gt", + "output_port": "a" + }, + "to": { + "vertex": "mul_by_7", + "input_port": "a" + } + }, + { + "from": { + "vertex": "is_negative", + "output_variant": "eq", + "output_port": "a" + }, + "to": { + "vertex": "add_1", + "input_port": "a" + } + }, + { + "from": { + "vertex": "is_negative", + "output_variant": "lt", + "output_port": "a" + }, + "to": { + "vertex": "mul_by_neg_3", + "input_port": "a" + } + } + ], + "default_values": [ + { + "vertex": "add_input_and_default", + "input_port": "b", + "value": { + "storage": "inline", + "data": 3 + } + }, + { + "vertex": "is_negative", + "input_port": "b", + "value": { + "storage": "inline", + "data": 0 + } + }, + { + "vertex": "mul_by_7", + "input_port": "b", + "value": { + "storage": "inline", + "data": 7 + } + }, + { + "vertex": "add_1", + "input_port": "b", + "value": { + "storage": "inline", + "data": 1 + } + }, + { + "vertex": "mul_by_neg_3", + "input_port": "b", + "value": { + "storage": "inline", + "data": -3 + } + } + ] +} +``` diff --git a/vision/talus-vision/draw-first-workflow/summary.md b/vision/talus-vision/draw-first-workflow/summary.md new file mode 100644 index 0000000..6358f71 --- /dev/null +++ b/vision/talus-vision/draw-first-workflow/summary.md @@ -0,0 +1,29 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# Summary + +We have successfully completed and executed our **first workflow**. The steps we followed are summarized as follows: + +1. **Defined our requirements**, opting to use a quick example from the **Nexus documentation**. +2. **Identified the necessary tools** for the workflow. +3. **Determined the input structure** for each tool. +4. **Mapped the logical relationships** between the tools. +5. **Monitored real-time validations** in the editor to ensure a fully **validated workflow**. +6. For the initial deployment, navigated to the **Deploy Workflows tab**, ensuring that the **Gas Budget** and **Crypto Auth** steps were completed. +7. **Performed the on-chain deployment** and observed the workflow in the editor switch to **View Mode**. +8. Finally, navigated to the **Execute Workflow tab**, provided the required inputs, and completed the **first DAG execution**. diff --git a/vision/talus-vision/user-workflows-and-profile.md b/vision/talus-vision/user-workflows-and-profile.md new file mode 100644 index 0000000..97bae94 --- /dev/null +++ b/vision/talus-vision/user-workflows-and-profile.md @@ -0,0 +1,38 @@ +--- +layout: + width: default + title: + visible: true + description: + visible: false + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true + metadata: + visible: true +--- + +# User Workflows & Profile + +User activity within the application is continuously tracked, and there are **two user-specific sections**: + +1. **Profile Section (top-right corner):** + * Displays the **number of draft workflows** currently in progress. + * Shows how many workflows have been **deployed** with the connected wallet. + * Additional features will be supported in future versions after **Devnet**. +2. **Deployments Navigation Button (left of the profile button):** + * Redirects users to the **Deployments page**, which provides access to workflows across **two tabs**: + * **My Workflows:** Displays workflows that are still being edited in the playground. Users can click on any workflow to return to the playground with the selected workflow loaded. + + + +
+ + + +**Deployments:** Displays workflows that have been **deployed on-chain**. Clicking a workflow opens it in **View Mode** within the playground, allowing users to interact with the deployed workflow. + +
diff --git a/vision/talus-vision/welcome-to-talus-vision.md b/vision/talus-vision/welcome-to-talus-vision.md new file mode 100644 index 0000000..3050b4c --- /dev/null +++ b/vision/talus-vision/welcome-to-talus-vision.md @@ -0,0 +1,15 @@ +--- +description: >- + Talus Vision greets users with three different options, designed to help you + create your first workflow with ease: +--- + +# Welcome to Talus Vision + +**Blank Workflow** : If you’re already familiar with **Talus Vision**, you can start directly with an empty editor and build from scratch.\ +**Quickstart Template:** Adds a [sample template](https://docs.talus.network/getting-started/math-branching-quickstart) to help you explore **Talus Vision** and provides a related documentation file. This document will help you gain a deeper understanding of **Nexus** and its structure.\ +**Browse Templates:** Redirects you to workflows previously created by other **Nexus users**, allowing you to review and reuse any of them as templates for your own projects. + + + +
From 7c2f481c56426fb72ef0347e03496a886db11e41 Mon Sep 17 00:00:00 2001 From: devops-talus Date: Thu, 13 Nov 2025 10:06:11 +0000 Subject: [PATCH 5/8] Update SUMMARY.md with new files under 'Looking for a home' --- SUMMARY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SUMMARY.md b/SUMMARY.md index 07d0d2c..5f7849e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -82,3 +82,24 @@ * [nexus-sdk/client.md](nexus-sdk/client.md) * [tools/templating-jinja/README.md](tools/templating-jinja/README.md) +* [vision/README.md](vision/README.md) +* [vision/SUMMARY.md](vision/SUMMARY.md) +* [vision/talus-vision/about-previously-deployed-workflows.md](vision/talus-vision/about-previously-deployed-workflows.md) +* [vision/talus-vision/about-sidebar/README.md](vision/talus-vision/about-sidebar/README.md) +* [vision/talus-vision/about-sidebar/deploy-workflows-tab.md](vision/talus-vision/about-sidebar/deploy-workflows-tab.md) +* [vision/talus-vision/about-sidebar/execute-workflow-tab.md](vision/talus-vision/about-sidebar/execute-workflow-tab.md) +* [vision/talus-vision/about-sidebar/gas-management-tab.md](vision/talus-vision/about-sidebar/gas-management-tab.md) +* [vision/talus-vision/about-sidebar/json-editor-tab.md](vision/talus-vision/about-sidebar/json-editor-tab.md) +* [vision/talus-vision/about-sidebar/tools-tab.md](vision/talus-vision/about-sidebar/tools-tab.md) +* [vision/talus-vision/about-sidebar/workflow-discovery-tab.md](vision/talus-vision/about-sidebar/workflow-discovery-tab.md) +* [vision/talus-vision/connect-with-sui-wallet.md](vision/talus-vision/connect-with-sui-wallet.md) +* [vision/talus-vision/crypto-auth.md](vision/talus-vision/crypto-auth.md) +* [vision/talus-vision/draw-first-workflow/README.md](vision/talus-vision/draw-first-workflow/README.md) +* [vision/talus-vision/draw-first-workflow/about-playground.md](vision/talus-vision/draw-first-workflow/about-playground.md) +* [vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md](vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md) +* [vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md](vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md) +* [vision/talus-vision/draw-first-workflow/node-component.md](vision/talus-vision/draw-first-workflow/node-component.md) +* [vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md](vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) +* [vision/talus-vision/draw-first-workflow/summary.md](vision/talus-vision/draw-first-workflow/summary.md) +* [vision/talus-vision/user-workflows-and-profile.md](vision/talus-vision/user-workflows-and-profile.md) +* [vision/talus-vision/welcome-to-talus-vision.md](vision/talus-vision/welcome-to-talus-vision.md) From 485b457f702589ccfcd1fe45b08e86f0a84ba021 Mon Sep 17 00:00:00 2001 From: tuky191 Date: Thu, 13 Nov 2025 12:50:35 +0100 Subject: [PATCH 6/8] add vision to the summary --- SUMMARY.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 5f7849e..3cb4e31 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -63,10 +63,31 @@ - [nexus\_workflow/tool\_registry](nexus-next/packages/reference/nexus_workflow/tool_registry.md) - [🧰 Nexus SDK](nexus-sdk/index.md) - [CLI](nexus-sdk/cli.md) + - [Client](nexus-sdk/client.md) - [Tool Development](nexus-sdk/tool-development.md) - [Toolkit Rust](nexus-sdk/toolkit-rust.md) - [How to Build a DAG](nexus-sdk/guides/dag-construction.md) - [Technical Glossary](nexus-next/glossary.md) +- [🔮 Vision](vision/README.md) + - [Welcome to Talus Vision](vision/talus-vision/welcome-to-talus-vision.md) + - [Crypto Auth](vision/talus-vision/crypto-auth.md) + - [Connect with Sui Wallet](vision/talus-vision/connect-with-sui-wallet.md) + - [User Workflows and Profile](vision/talus-vision/user-workflows-and-profile.md) + - [About Previously Deployed Workflows](vision/talus-vision/about-previously-deployed-workflows.md) + - [Draw Your First Workflow](vision/talus-vision/draw-first-workflow/README.md) + - [About Playground](vision/talus-vision/draw-first-workflow/about-playground.md) + - [Node Component](vision/talus-vision/draw-first-workflow/node-component.md) + - [Step by Step: My First Workflow](vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) + - [Deploy My First Workflow](vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md) + - [Execute My First Workflow](vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md) + - [Summary](vision/talus-vision/draw-first-workflow/summary.md) + - [About Sidebar](vision/talus-vision/about-sidebar/README.md) + - [Workflow Discovery Tab](vision/talus-vision/about-sidebar/workflow-discovery-tab.md) + - [Deploy Workflows Tab](vision/talus-vision/about-sidebar/deploy-workflows-tab.md) + - [Execute Workflow Tab](vision/talus-vision/about-sidebar/execute-workflow-tab.md) + - [Tools Tab](vision/talus-vision/about-sidebar/tools-tab.md) + - [JSON Editor Tab](vision/talus-vision/about-sidebar/json-editor-tab.md) + - [Gas Management Tab](vision/talus-vision/about-sidebar/gas-management-tab.md) ## 🛠️ Tools @@ -77,29 +98,4 @@ - [Storage: Walrus](tools/storage-walrus/README.md) - [Echanges: Coinbase](tools/exchanges-coinbase/README.md) - [HTTP](tools/http/README.md) - -## Looking for a home - -* [nexus-sdk/client.md](nexus-sdk/client.md) -* [tools/templating-jinja/README.md](tools/templating-jinja/README.md) -* [vision/README.md](vision/README.md) -* [vision/SUMMARY.md](vision/SUMMARY.md) -* [vision/talus-vision/about-previously-deployed-workflows.md](vision/talus-vision/about-previously-deployed-workflows.md) -* [vision/talus-vision/about-sidebar/README.md](vision/talus-vision/about-sidebar/README.md) -* [vision/talus-vision/about-sidebar/deploy-workflows-tab.md](vision/talus-vision/about-sidebar/deploy-workflows-tab.md) -* [vision/talus-vision/about-sidebar/execute-workflow-tab.md](vision/talus-vision/about-sidebar/execute-workflow-tab.md) -* [vision/talus-vision/about-sidebar/gas-management-tab.md](vision/talus-vision/about-sidebar/gas-management-tab.md) -* [vision/talus-vision/about-sidebar/json-editor-tab.md](vision/talus-vision/about-sidebar/json-editor-tab.md) -* [vision/talus-vision/about-sidebar/tools-tab.md](vision/talus-vision/about-sidebar/tools-tab.md) -* [vision/talus-vision/about-sidebar/workflow-discovery-tab.md](vision/talus-vision/about-sidebar/workflow-discovery-tab.md) -* [vision/talus-vision/connect-with-sui-wallet.md](vision/talus-vision/connect-with-sui-wallet.md) -* [vision/talus-vision/crypto-auth.md](vision/talus-vision/crypto-auth.md) -* [vision/talus-vision/draw-first-workflow/README.md](vision/talus-vision/draw-first-workflow/README.md) -* [vision/talus-vision/draw-first-workflow/about-playground.md](vision/talus-vision/draw-first-workflow/about-playground.md) -* [vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md](vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md) -* [vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md](vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md) -* [vision/talus-vision/draw-first-workflow/node-component.md](vision/talus-vision/draw-first-workflow/node-component.md) -* [vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md](vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) -* [vision/talus-vision/draw-first-workflow/summary.md](vision/talus-vision/draw-first-workflow/summary.md) -* [vision/talus-vision/user-workflows-and-profile.md](vision/talus-vision/user-workflows-and-profile.md) -* [vision/talus-vision/welcome-to-talus-vision.md](vision/talus-vision/welcome-to-talus-vision.md) + - [Templating: Jinja](tools/templating-jinja/README.md) From 326ef82add838227c1c487c876da18903bb0030b Mon Sep 17 00:00:00 2001 From: tuky191 Date: Thu, 13 Nov 2025 13:00:13 +0100 Subject: [PATCH 7/8] fix typos --- SUMMARY.md | 2 +- vision/SUMMARY.md | 2 +- vision/talus-vision/about-sidebar/README.md | 2 +- ...execute-my-first-worklow.md => execute-my-first-workflow.md} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename vision/talus-vision/draw-first-workflow/{execute-my-first-worklow.md => execute-my-first-workflow.md} (97%) diff --git a/SUMMARY.md b/SUMMARY.md index 3cb4e31..a19bd72 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -79,7 +79,7 @@ - [Node Component](vision/talus-vision/draw-first-workflow/node-component.md) - [Step by Step: My First Workflow](vision/talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) - [Deploy My First Workflow](vision/talus-vision/draw-first-workflow/deploy-my-first-workflow.md) - - [Execute My First Workflow](vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md) + - [Execute My First Workflow](vision/talus-vision/draw-first-workflow/execute-my-first-workflow.md) - [Summary](vision/talus-vision/draw-first-workflow/summary.md) - [About Sidebar](vision/talus-vision/about-sidebar/README.md) - [Workflow Discovery Tab](vision/talus-vision/about-sidebar/workflow-discovery-tab.md) diff --git a/vision/SUMMARY.md b/vision/SUMMARY.md index a18d408..4070a29 100644 --- a/vision/SUMMARY.md +++ b/vision/SUMMARY.md @@ -16,7 +16,7 @@ * [Node Component](talus-vision/draw-first-workflow/node-component.md) * [Step-by-step My first Workflow](talus-vision/draw-first-workflow/step-by-step-my-first-workflow.md) * [Deploy My first workflow](talus-vision/draw-first-workflow/deploy-my-first-workflow.md) - * [Execute My first Worklow](talus-vision/draw-first-workflow/execute-my-first-worklow.md) + * [Execute My first Workflow](talus-vision/draw-first-workflow/execute-my-first-workflow.md) * [Summary](talus-vision/draw-first-workflow/summary.md) * [User Workflows & Profile](talus-vision/user-workflows-and-profile.md) * [About previously deployed workflows](talus-vision/about-previously-deployed-workflows.md) diff --git a/vision/talus-vision/about-sidebar/README.md b/vision/talus-vision/about-sidebar/README.md index f3a2662..5aea920 100644 --- a/vision/talus-vision/about-sidebar/README.md +++ b/vision/talus-vision/about-sidebar/README.md @@ -10,7 +10,7 @@ description: >- [Json Editor Tab](json-editor-tab.md) -[Worfklow Discovery Tab](json-editor-tab.md) +[Workflow Discovery Tab](workflow-discovery-tab.md) [Deploy Workflows Tab](deploy-workflows-tab.md) diff --git a/vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md b/vision/talus-vision/draw-first-workflow/execute-my-first-workflow.md similarity index 97% rename from vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md rename to vision/talus-vision/draw-first-workflow/execute-my-first-workflow.md index 781dd3b..8927f04 100644 --- a/vision/talus-vision/draw-first-workflow/execute-my-first-worklow.md +++ b/vision/talus-vision/draw-first-workflow/execute-my-first-workflow.md @@ -15,7 +15,7 @@ layout: visible: true --- -# Execute My first Worklow +# Execute My first Workflow So far, we have successfully built a workflow according to our desired logic. After passing all **validation steps**, we deployed this workflow, turning it into an **on-chain object**. The next step is to **execute it and observe the results**. From af8ba97b54d6a10c24d3e7f82886731262c6a860 Mon Sep 17 00:00:00 2001 From: tuky191 Date: Thu, 13 Nov 2025 13:02:42 +0100 Subject: [PATCH 8/8] fix typos --- nexus-next/crates/leader.md | 2 +- nexus-next/tool.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nexus-next/crates/leader.md b/nexus-next/crates/leader.md index 6def119..5dffc16 100644 --- a/nexus-next/crates/leader.md +++ b/nexus-next/crates/leader.md @@ -63,7 +63,7 @@ sequenceDiagram There are multiple processes running in parallel in the leader node. These are as follows: -1. **`RequestWalkExectuionEvent` listener** +1. **`RequestWalkExecutionEvent` listener** - This process repeatedly queries Sui RPC for new events coming from the Workflow. It then saves these events on the Indexer. - It persists the last visited cursor, a pointer to the event it has written to our Indexer last. This way we ensure no events are lost in case the Leader goes down. diff --git a/nexus-next/tool.md b/nexus-next/tool.md index e07d83c..fbc99ec 100644 --- a/nexus-next/tool.md +++ b/nexus-next/tool.md @@ -27,7 +27,7 @@ xyz.taluslabs.llm.openai-chat-completion@1 2. `type` -Straighforward, and enum with `offchain` and `onchain` values. +Straightforward, an enum with `offchain` and `onchain` values. 3. `url`