Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
373 changes: 325 additions & 48 deletions nexus-sdk/cli.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions nexus-sdk/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ The `Signer` struct accepts a [`sui::crypto::Ed25519PrivateKey`] and is responsi

Nexus gas budget is managed through the [`GasActions`] struct.

Standard Talus agent-funded execution uses agent payment vaults in the TAP interface. Every Talus agent has a vault object, and SDK callers can fetch that object, deposit or withdraw funds through standard TAP builders, and create typed `AgentVault` payment sources for skill execution. Agent-scoped workflow gas budget helpers are also available when execution settlement should resolve `Execution -> Agent -> Invoker`.

### Add Budget

```rust
Expand Down Expand Up @@ -168,7 +170,6 @@ let inspect = nexus_client
.workflow()
.inspect_execution(
execute_result.execution_object_id,
execute_result.tx_digest,
Some(Duration::from_secs(180)), // timeout
)
.await?;
Expand Down Expand Up @@ -362,8 +363,7 @@ nexus_client

## 🧭 Error Handling

All methods return a `Result<T, NexusError>`.
The `NexusError` enum categorizes issues from configuration errors to RPC and transaction issues.
All methods return a `Result<T, NexusError>`. The `NexusError` enum categorizes issues from configuration errors to RPC and transaction issues.

---

Expand Down
80 changes: 80 additions & 0 deletions nexus-sdk/guides/1-tap-development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# TAP Development

This series teaches how to build, register, and operate a **standard TAP skill** end-to-end. Each page is short and self-contained; together they take you from an empty directory to a working agent that calls an on-chain Move tool which transfers SUI to a recipient address.

> **Prereqs.** You should already be comfortable with the [Setup guide](setup.md) (CLI install, Sui wallet, `nexus conf set`) and have read the [Onchain Tool Development guide](onchain-tool-development.md) for Move tool fundamentals (witness types, `TaggedOutput`, registration mechanics).

## What a TAP skill is

A standard Talus Agent Protocol (TAP) **skill** wraps up to three things behind one on-chain identity:

1. A **TAP Move package** — your custom Move code: shared state objects, the witness type that ties a vertex tool to your package, and any business-logic helpers the tool needs (e.g. coin custody). At the protocol level this is optional: `register_skill` itself doesn't take a package id, so a skill whose DAG uses only off-chain HTTP tools and no on-chain state doesn't need one. This tutorial's skill uses an on-chain transfer tool, so the package contents below are required.
1. A **DAG** — the workflow definition the leader executes when the skill runs. For this tutorial the DAG has a single vertex that calls one on-chain Move tool.
1. A **skill config** (`skill.tap.json`) — declares the DAG, the TAP package path, the skill's payment/schedule/authorization requirements, and the shared objects the workflow needs to touch.

A skill lives under an **agent** (also on-chain). The agent record carries the operator address that's allowed to drive executions; the skill record carries the DAG + requirements. Each `(agent, skill)` pair has one or more **endpoint revisions** — a version-pinned bundle of `(shared_objects, requirements, config_digest)` that the workflow reads at runtime. `nexus tap bind` / `nexus tap register-skill` always create `interface_revision(1)` atomically with the skill record, and subsequent revisions are appended via `nexus tap announce`; the registry's endpoints table is append-only, so revisions never drop to zero.

Three things sit inside that bundle:

- **`shared_objects`** is the list of *skill-author-owned* shared Move objects that the skill's vertex tools will read or write at execution time, each tagged with a mutability bit (`{ id, mutable }`). It does **not** include workflow framework objects like `AgentRegistry`, `ToolRegistry`, `Clock`, the `Agent`, the `DAG`, or `ToolGas` cells — those are wired into every execute PTB automatically by the SDK. It's advisory metadata: it's committed into `config_digest` so the advertisement can't be swapped after announcement, but the PTB builder still has to fetch and pass these object refs explicitly per execution. An empty list is valid for a skill with no custom on-chain state. For this tutorial, the only entry will be the `TutorialState` shared object that holds the treasury `Balance<SUI>`, declared mutable because the transfer tool drains it.
- **`requirements`** carries the four commitments (input schema, workflow, metadata, capability schema) plus the payment policy, schedule policy, and vertex authorization schema (`fixed_tools` + `requires_payment`).
- **`config_digest`** is `sha2_256(BCS({ interface_revision, shared_objects, requirements }))`, checked by `assert_valid_config_digest` before any announcement is accepted.

## What we'll build

The tutorial's skill exposes a single vertex tool that does one job: **drain a SUI treasury sitting in the TAP package's shared state into a recipient address** passed as a workflow input. The state is funded out-of-band (we'll add a `fund_treasury` helper), and each skill execution moves the treasury balance to the recipient. The workflow dispatches the walk, the leader runs the Move tool, the recipient receives SUI.

{% hint style="warning" %}
**This tutorial is intentionally unauthorized.** The on-chain transfer tool is registered through the plain `register_on_chain_tool` entry point and the skill config carries an empty `fixed_tools` list, so any workflow execution against this skill can drain the treasury — there is no per-call authorization check. The end of the last page covers what that means in practice and points at the follow-up guide for cap-gated authorization (`VertexAuthorizationCheckCap`, `WorkflowVertexAuthorizationGrant`, `fixed_tools` with `requires_payment: true`), which is the production-ready way to wrap the same transfer logic.
{% endhint %}

## End-to-end flow

```text
nexus tap scaffold → empty TAP package skeleton + skill.tap.json
edit Move source → add state object + on-chain transfer tool
nexus tap validate-skill → local-only checks (no chain)
nexus tap publish-skill → publishes Move package + DAG, writes artifact.json
nexus tool register → registers the on-chain transfer tool in ToolRegistry
onchain
nexus tap bind → creates an agent + registers the skill atomically
fund the treasury → one-shot Move call that deposits SUI into state
nexus tap execute → submits the TAP execution (payment, DAG inputs)
verify recipient balance → the treasury arrived in the destination wallet
```

Each arrow is one `nexus` command and one short stop on the way. The next five pages walk through them.

## Pages

1. [Scaffold the TAP package](2-tap-scaffold-and-package.md) — `nexus tap scaffold`, plus the Move state module the scaffold doesn't write for you.
1. [Write the on-chain transfer tool](3-tap-transfer-tool.md) — the `transfer_vertex` Move module that the workflow invokes.
1. [DAG and skill config](4-tap-dag-and-skill-config.md) — wire the on-chain tool's FQN into `dag.json` and adjust `skill.tap.json`.
1. [Publish, register, bind](5-tap-publish-and-register.md) — `tap publish-skill`, `tool register onchain`, `tap bind`, and the on-chain confirmations.
1. [Execute and verify the transfer](6-tap-execute-and-settle.md) — fund the treasury, run `tap execute`, watch the recipient balance go up.

## What this guide does **not** cover

The TAP CLI surface is broader than what one tutorial can show. After you finish the series, the [CLI reference](../cli.md) covers:

- Vault funding and vault-funded scheduling (`nexus tap vault deposit`, `nexus tap schedule-from-vault`).
- Address-funded scheduling and the default-executor variant (`nexus tap schedule-address-funded`, `nexus tap schedule-default-address-funded`).
- Endpoint revision announcements for already-bound skills (`nexus tap announce`).
- Inspecting payment receipts and execution costs (`nexus tap payments list`, `nexus dag execution-cost`).

171 changes: 171 additions & 0 deletions nexus-sdk/guides/2-tap-scaffold-and-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Scaffold the TAP package

This page generates the on-disk shape of a TAP skill and replaces the scaffolded Move stub with a real state module that holds a SUI treasury and the per-vertex witness object the on-chain tool will be registered against.

## 1. Generate the scaffold

Pick a working directory and run:

```bash
nexus tap scaffold --name "tutorial transfer" --target .
cd tutorial-transfer
```

The scaffold writes four files under `tutorial-transfer/`:

```text
tutorial-transfer/
├── dag.json # workflow definition (we'll edit later)
├── skill.tap.json # skill config (we'll edit later)
└── tap/
├── Move.toml # the TAP package manifest
└── sources/
└── tutorial_transfer.move
```

Each generated file is a stub. The scaffolded `tutorial_transfer.move` is a single drop witness with an `init_for_test` helper — enough to compile, but nothing the workflow can actually call.

## 2. Trim the scaffolded `tap/Move.toml` and stage `nexus_primitives`

The scaffold ships with four `[dependencies]` entries (`nexus_primitives`, `nexus_interface`, `nexus_registry`, `nexus_workflow`) so authors who reach for the full standard-TAP surface don't have to add deps mid-build. The minimal vertex tool we're about to write only touches `nexus_primitives` — `data`, `proof_of_uid`, and `tagged_output`. Drop the other three entries so the build resolves the smallest possible dep tree:

```toml
[dependencies]
nexus_primitives = { local = "deps/primitives" }
```

Then stage the `nexus_primitives` package under `tap/deps/primitives/`. It lives in `nexus-next/sui/primitives/`; either copy the directory in or point the `local` path at wherever your installation keeps it. The staged dependency needs its own `Move.toml` with an `[environments]` table and a `Published.toml` carrying the deployed package address.

The scaffold writes an `[environments]` table pre-filled with the public-testnet chain id, which is what this guide targets:

```toml
[environments]
testnet = "4c78adac"
```

Leave that as-is unless you're publishing to a different network. If you are, replace the row with `<env_alias> = "<chain-id>"` for your target network (the alias must match a name in `sui client envs`, and the chain id is what `sui client chain-identifier` prints while that env is active).

The end result looks like:

```toml
[package]
name = "tutorial_transfer"
version = "1.0.0"
edition = "2024"

[dependencies]
nexus_primitives = { local = "deps/primitives" }

[environments]
testnet = "4c78adac"
```

{% hint style="info" %}
`nexus tap validate-skill` enforces the new-style 2024 layout: the manifest must have `[package].version`, `edition = "2024"` (no `.beta`), an `[environments]` table, and **no** `[addresses]` section. Old-style packages can't resolve their dependency graph against the new-style published Nexus packages, so the validator rejects them up front with a pointer at the field that needs fixing.
{% endhint %}

## 3. Replace the scaffold's Move source

The interesting work is in `tap/sources/tutorial_transfer.move`. Replace its contents with the module below. The state object holds a SUI treasury that the vertex tool will drain on each execution, plus the per-vertex witness whose UID feeds `nexus tool register onchain --tool-witness-id` and the worksheet stamp at runtime.

```move
module tutorial_transfer::tutorial_transfer;

use std::ascii::String as AsciiString;
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::transfer::public_share_object;

/// One-time witness — guarantees `init` runs exactly once at publish.
public struct TUTORIAL_TRANSFER has drop {}

/// Per-vertex witness. Its UID becomes the on-chain tool's `tool_witness_id`
/// (passed to `nexus tool register onchain`) and the seed the vertex stamps
/// onto the workflow worksheet at runtime.
public struct TransferVertexWitness has key, store {
id: UID,
}

/// Shared state for the tutorial skill. Holds the SUI treasury that the
/// transfer vertex drains on each execution plus the per-vertex witness
/// the on-chain tool registers against.
public struct TutorialState has key, store {
id: UID,
transfer_vertex_witness: TransferVertexWitness,
treasury: option::Option<Coin<SUI>>,
}

fun init(_otw: TUTORIAL_TRANSFER, ctx: &mut TxContext) {
public_share_object(TutorialState {
id: object::new(ctx),
transfer_vertex_witness: TransferVertexWitness { id: object::new(ctx) },
treasury: option::none(),
});
}

/// Canonical FQN for the on-chain transfer vertex tool. The DAG references
/// this FQN, the tool registration uses this FQN, and the vertex module
/// returns this FQN from its own `fqn()` helper.
public fun transfer_vertex_fqn(): AsciiString {
b"tutorial.local.transfer_vertex@1".to_ascii_string()
}

/// The id passed to `nexus tool register onchain --tool-witness-id`.
public fun transfer_vertex_tool_witness_id(state: &TutorialState): ID {
object::uid_to_inner(&state.transfer_vertex_witness.id)
}

/// The witness UID the vertex stamps onto its worksheet at runtime.
public(package) fun transfer_vertex_witness_uid(state: &TutorialState): &UID {
&state.transfer_vertex_witness.id
}

/// Top up the treasury that future skill executions will drain.
public fun fund_treasury(state: &mut TutorialState, coin: Coin<SUI>) {
if (state.treasury.is_some()) {
let mut existing = option::extract(&mut state.treasury);
coin::join(&mut existing, coin);
option::fill(&mut state.treasury, existing);
} else {
option::fill(&mut state.treasury, coin);
}
}

/// Drain the treasury. `public(package)` keeps the function callable only
/// from sibling modules in `tutorial_transfer` (i.e. `transfer_vertex`), so
/// nothing outside this package can pull funds out directly.
public(package) fun take_treasury(state: &mut TutorialState): Coin<SUI> {
option::extract(&mut state.treasury)
}
```

Key things to notice:

- `TutorialState` is shared (`public_share_object`) so the workflow and the treasury funder can both reach it.
- `TransferVertexWitness` is stored inside `TutorialState` so its `UID` is stable for the package's lifetime — the on-chain tool registers against it once.
- `take_treasury` is `public(package)`. The sibling `transfer_vertex` module (next page) is the only caller; nothing outside the `tutorial_transfer` package can extract the coin directly.

## 4. Validate locally

Run the local validator before going anywhere near the chain:

```bash
nexus tap validate-skill --config skill.tap.json
```

The validator resolves `tap_package_path` and `dag_path` relative to `--config`, so you do not need to point at the Move package separately. You should see:

```text
[✓] Validating TAP skill config...
```

If the Move package fails to compile, the error surfaces here — fix the source and re-run. `validate-skill` doesn't touch the network, so iterations are fast.

## What you have now

- A `tap/Move.toml` and `tap/sources/tutorial_transfer.move` that compile against the published Nexus deps for your network.
- A `TutorialState` shared object with a SUI-coin treasury, a witness object, and a `fund_treasury` entry function.
- A `skill.tap.json` and `dag.json` still in their scaffold-default shapes — we'll edit those two pages from now.

Next: [Write the on-chain transfer tool](3-tap-transfer-tool.md).

96 changes: 96 additions & 0 deletions nexus-sdk/guides/3-tap-transfer-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Write the on-chain transfer tool

The previous page set up `TutorialState` and the `take_treasury` helper. This page adds the actual on-chain Move tool — a sibling module named `transfer_vertex` whose `execute` function is what the workflow invokes per skill execution.

The general mechanics of on-chain Move tools (`TaggedOutput`, witness types, the registration round-trip) are covered in detail in the [Onchain Tool Development guide](onchain-tool-development.md). This page only walks through what's _new_ for our TAP-bound transfer tool.

## 1. Why a second Move module

The on-chain tool the workflow calls is identified by `(package_id, module_name, function_name)`. The `module_name` ends up as a registry key, so each on-chain tool gets its own Move module. Our package will end up with two modules:

```text
tutorial_transfer::tutorial_transfer // state + witness + helpers
tutorial_transfer::transfer_vertex // the on-chain vertex tool
```

`transfer_vertex` is what gets registered. The leader looks up `transfer_vertex::execute` by module + function name when it picks the walk up.

## 2. Add `tap/sources/transfer_vertex.move`

Drop the file alongside `tutorial_transfer.move`:

```move
module tutorial_transfer::transfer_vertex;

use nexus_primitives::data;
use nexus_primitives::proof_of_uid::ProofOfUID;
use nexus_primitives::tagged_output::{Self as tagged_output, TaggedOutput};
use std::ascii::String as AsciiString;
use sui::transfer::public_transfer;
use tutorial_transfer::tutorial_transfer::{Self, TutorialState};

public struct TRANSFER_VERTEX has drop {}

public enum Output {
Transferred {
amount: u64,
recipient: address,
},
}

public fun fqn(): AsciiString {
tutorial_transfer::transfer_vertex_fqn()
}

/// On-chain transfer.
///
/// Inputs (the workflow passes these via the DAG entry ports):
/// worksheet — workflow-supplied `ProofOfUID` for stamping.
/// port 0 — `state: &mut TutorialState` (shared)
/// port 1 — `recipient: address`
///
/// The body drains the treasury, fires `public_transfer`, stamps the
/// worksheet, and returns a tagged output the workflow records on chain.
public fun execute(
worksheet: &mut ProofOfUID,
state: &mut TutorialState,
recipient: address,
): TaggedOutput {
let coin = tutorial_transfer::take_treasury(state);
let amount = coin.value();
public_transfer(coin, recipient);
worksheet.stamp_with_data(
tutorial_transfer::transfer_vertex_witness_uid(state),
b"tutorial_transfer_done",
);

tagged_output::new(b"transferred")
.with_named_payload(
b"amount",
data::inline_one(amount.to_string().into_bytes()).as_number(),
)
.with_named_payload(
b"recipient",
data::inline_one(recipient.to_ascii_string().into_bytes()).as_address(),
)
}
```

## 3. What each line is doing

- **`worksheet: &mut ProofOfUID`** — supplied by the workflow. Every on-chain vertex tool takes it. Calling `worksheet.stamp_with_data(witness_uid, b"…")` proves to the workflow that _this_ registered tool ran on _this_ walk. Skip the stamp and the workflow rejects the walk.
- **`take_treasury` + `public_transfer`** — the actual SUI move. Once the walk succeeds the recipient owns those funds outright.
- **`TaggedOutput::Transferred { amount, recipient }`** — the structured output the workflow records on chain. Downstream consumers can read it back via `nexus dag inspect-execution`.

## 4. Re-validate locally

Re-run the validator to confirm both modules still compile:

```bash
nexus tap validate-skill --config skill.tap.json
```

You should still see `[✓] Validating TAP skill config...` — but now both modules are present and the package gates withdrawal correctly.

Next: [DAG and skill config](4-tap-dag-and-skill-config.md).

Loading