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
359 changes: 311 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.

Talus agent-funded execution uses agent payment vaults in the `nexus_interface::tap` Move interface. Every Talus agent has a vault object, and SDK callers can fetch that object, deposit or withdraw funds through agent payment 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
82 changes: 82 additions & 0 deletions nexus-sdk/guides/1-tap-development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Custom TAP Package Development

This series teaches how to build, register, and operate a **custom TAP package 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 package skill is

A custom TAP package skill combines up to three things behind one registry 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, and the skill's input, payment, schedule, and fixed-tool requirements.

A skill lives under an **agent** (also on-chain). Mutable custody of the `Agent` object is the lifecycle authorization handle, and the registry stores the agent's active flag plus skill records. Each skill record carries the DAG binding, simplified requirements, and a `current_interface_revision` that fresh executions and scheduled-task creation use. `nexus tap bind` / `nexus tap register-skill` create revision `1` with the skill record, and `nexus tap update-skill` moves the current skill contract to a new revision for future starts.

The current skill contract has these important parts:

- **DAG binding** points to the published DAG that workflow execution should run. The concrete shared objects a skill needs, such as this tutorial's `TutorialState`, are supplied as execution inputs rather than stored in a separate endpoint-revision table.
- **`requirements.input_schema_commitment`** is an opaque byte vector that identifies the expected input shape for tooling and dry-run checks.
- **`requirements.payment_policy`** is either `UserFunded` or `AgentFunded { max_budget }`; user-funded execution supplies the payment coin at call time, while agent-funded execution draws from the agent's payment vault.
- **`requirements.schedule_policy`** declares whether scheduled execution is one-shot or recursive, including `allow_recursive` and recurrence bounds.
- **`requirements.fixed_tools`** is the canonical set of registry-verified tools that must remain present in the bound DAG. It is a preservation requirement, not the old authorized-tool or vertex-authorization schema.

## 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 minimal.** 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`, and `fixed_tools`), 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 agent skill 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 package 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-task --payment-source agent-vault`).
- Address-funded scheduling (`nexus tap schedule-task --payment-source address-funded`).
- Current skill updates for already-bound skills (`nexus tap update-skill`).
- 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