> For the complete documentation index, see [llms.txt](https://docs.talus.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.talus.network/talus-docs-v2.1.0/guides/tap-development/onchain-transfer-tool.md).

# Write the On-Chain Transfer Tool

{% hint style="info" %}
**Audience:** Nexus adopters and integrators.

**Goal:** Build a Standard-mode Talus Agent Package (TAP) transfer Tool for disposable demonstration assets and understand why it is not an authorized production pattern.
{% endhint %}

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

{% hint style="danger" %}
This implementation is intentionally unauthenticated and disposable. Any compatible DAG/caller path that reaches the Tool can choose the recipient and drain the demo treasury. Never use real assets or copy this authorization model into production.
{% endhint %}

The general mechanics of on-chain Move Tools (`UIDRequirements`, `TaggedOutput`, `OnchainToolResult`, witness types, and the registration round-trip) are covered in detail in the [On-chain Tool Development guide](/talus-docs-v2.1.0/guides/tool-development/build-onchain-tool.md). This consumer package installs its `nexus_primitives` and `nexus_interface` dependencies from the public [primitives](https://www.moveregistry.com/package/@talus/nexus-primitives) and [interface](https://www.moveregistry.com/package/@talus/nexus-interface) Move Registry records; use the public [Nexus Move Packages](https://github.com/Talus-Network/nexus-move-packages) source only for offline closure inspection or provenance. 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:

```
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_interface::onchain_tool_result::{Self as onchain_tool_result, OnchainToolResult};
use nexus_primitives::data;
use nexus_primitives::proof_of_uid::UIDRequirements;
use nexus_primitives::tagged_output;
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):
///   requirements — workflow-supplied witness requirements.
///   result       — workflow-supplied durable result object.
///   port 0      — `state: &mut TutorialState` (shared)
///   port 1      — `recipient: address`
///
/// The body drains the treasury, transfers the coin, satisfies the witness,
/// and finalizes a durable tagged result that the workflow records on chain.
public fun execute(
    requirements: UIDRequirements,
    result: OnchainToolResult,
    state: &mut TutorialState,
    recipient: address,
    ctx: &mut TxContext,
) {
    let mut requirements = requirements;
    let coin = tutorial_transfer::take_treasury(state);
    let amount = coin.value();
    public_transfer(coin, recipient);
    tutorial_transfer::satisfy_transfer_vertex_requirement(state, &mut requirements);

    let mut encoded_recipient = b"\"0x";
    encoded_recipient.append(recipient.to_ascii_string().into_bytes());
    encoded_recipient.append(b"\"");
    let output = tagged_output::new(b"transferred")
        .with_named_payload(
            b"amount",
            data::inline_data_value(amount.to_string().into_bytes()),
        )
        .with_named_payload(
            b"recipient",
            data::inline_data_value(encoded_recipient),
        );
    onchain_tool_result::finalize_and_share(result, requirements, output, ctx);
}
```

#### 3. What each line is doing

* **`requirements: UIDRequirements`** and **`result: OnchainToolResult`** — supplied by the workflow. The Tool must satisfy its registered witness and finalize the result; otherwise the workflow rejects the call or cannot consume its output.
* **`take_treasury` + `public_transfer`** — the actual SUI move. Once the walk succeeds the recipient owns those funds outright.
* **`tagged_output::new(b"transferred")`** — the transient structured output. `finalize_and_share` combines it with the satisfied requirements in a durable `OnchainToolResult` that downstream vertices and `nexus execution inspect` can observe.

#### 4. Re-validate locally

Re-run the metadata validator, then compile and test the Move package to confirm both modules agree:

```bash
nexus tap validate-skill --config skill.tap.json
(
  cd tap
  sui move build --build-env "${SUI_BUILD_ENV:-testnet}"
)
```

You should still see `[✓] Validating TAP package skill config...`, followed by a green Move build. Both modules are now present, and only the sibling Tool module can call the package-private treasury drain helper. The release TAP package has no Nexus-calling test in this step, so do not run its plain Move test as a substitute for the published-bytecode harness.

Run the separate `move-tests-published/` arrangement from [Build the TAP Move package](/talus-docs-v2.1.0/guides/tap-development/build-tap-move-package.md) when you need a published-Nexus check:

```bash
nexus tap test --path move-tests-published --build-env "${SUI_BUILD_ENV:-testnet}"
```

This local VM reads the selected public Nexus bytecode and overlays only test extensions; it does not publish, register, schedule, or move the tutorial treasury. The separate arrangement proves its published data call; use the public [full-flow example](https://github.com/Talus-Network/nexus-move-packages/tree/main/examples/local_testing) for an on-chain Tool `execute`/authorization/result-finalization test. A successful local check still needs the publication, Tool registration, DAG/skill binding, and Testnet execution/readback steps on the following pages.

Next: [DAG and skill config](/talus-docs-v2.1.0/guides/tap-development/dag-and-skill-config.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.talus.network/talus-docs-v2.1.0/guides/tap-development/onchain-transfer-tool.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `build a script that syncs our docs to a CMS` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
