SAP Explorer Docs
Working Examples

Tool Publishing

Complete example of publishing a tool descriptor, inscribing schemas, and registering in category indexes.

Tool Publishing

This example demonstrates publishing a tool descriptor on-chain, inscribing full JSON schemas into transaction logs, and registering the tool in a category index for discoverability.

Full Example

import { SapClient } from "@oobe-protocol-labs/synapse-sap-sdk";
import { AnchorProvider } from "@coral-xyz/anchor";
import { sha256, hashToArray } from "@oobe-protocol-labs/synapse-sap-sdk/utils";
import { HTTP_METHOD_VALUES, TOOL_CATEGORY_VALUES } from "@oobe-protocol-labs/synapse-sap-sdk";
import { deriveAgent } from "@oobe-protocol-labs/synapse-sap-sdk/pda";

const client = SapClient.from(AnchorProvider.env());

// 1. Define schemas.
// These are standard JSON Schemas that describe your tool's input and output.
// They will be stored permanently in Solana TX logs (zero ongoing rent).
const inputSchema = JSON.stringify({
  type: "object",
  properties: {
    tokenA: {
      type: "string",
      description: "Input token mint address",
    },
    tokenB: {
      type: "string",
      description: "Output token mint address",
    },
    amount: {
      type: "number",
      description: "Amount in base units (lamports)",
    },
    slippage: {
      type: "number",
      description: "Maximum slippage percentage (e.g. 0.5 for 0.5%)",
    },
  },
  required: ["tokenA", "tokenB", "amount"],
});

const outputSchema = JSON.stringify({
  type: "object",
  properties: {
    txSignature: { type: "string" },
    amountOut: { type: "number" },
    priceImpact: { type: "number" },
    route: {
      type: "array",
      items: { type: "string" },
    },
  },
});

// 2. Publish tool descriptor on-chain.
// publishByName takes 10 positional arguments:
//   toolName     — unique per agent (max 32 chars, hashed for PDA)
//   providerName — display name of the underlying service
//   description  — what this tool does (max 256 chars)
//   inputSchema  — JSON string (only the hash is stored on the PDA)
//   outputSchema — JSON string (only the hash is stored on the PDA)
//   httpMethod   — HTTP_METHOD_VALUES: Get=0, Post=1, Put=2, Delete=3, Compound=4
//   category     — TOOL_CATEGORY_VALUES: Swap=0, Lend=1, Stake=2, Nft=3,
//                  Payment=4, Data=5, Governance=6, Bridge=7, Analytics=8, Custom=9
//   paramsCount  — total number of parameters (input schema properties)
//   requiredParams — how many are required (input schema required array length)
//   isCompound   — true if this tool wraps multiple sub-tools into one call
await client.tools.publishByName(
  "jupiterSwap",                              // toolName
  "jupiter",                                  // providerName
  "Execute a token swap via Jupiter aggregator with optimal routing", // description
  inputSchema,                                // inputSchema JSON
  outputSchema,                               // outputSchema JSON
  HTTP_METHOD_VALUES.Post,                    // httpMethod = 1 (Post)
  TOOL_CATEGORY_VALUES.Swap,                  // category = 0 (Swap)
  4,                                          // paramsCount (total params)
  3,                                          // requiredParams
  false,                                      // isCompound
);

console.log("Tool published.");

// 3. Inscribe full schemas into TX logs (zero ongoing rent).
// The schema data goes into the TX log, not the PDA. This means:
//   - No rent cost for the data itself
//   - Schemas are immutable once inscribed
//   - Reading requires parsing TX logs (use client.parser)
// Parameters:
//   schemaType:  0 = input, 1 = output
//   schemaData:  Buffer of the raw schema JSON
//   schemaHash:  32-byte SHA-256 hash (must match on-chain hash)
//   compression: 0 = none, 1 = zstd (for schemas > 500 bytes)
await client.tools.inscribeSchema("jupiterSwap", {
  schemaType: 0,                              // 0 = input schema
  schemaData: Buffer.from(inputSchema),       // raw JSON bytes
  schemaHash: hashToArray(sha256(inputSchema)),  // 32-byte SHA-256
  compression: 0,                             // 0 = no compression
});

await client.tools.inscribeSchema("jupiterSwap", {
  schemaType: 1,                              // 1 = output schema
  schemaData: Buffer.from(outputSchema),
  schemaHash: hashToArray(sha256(outputSchema)),
  compression: 0,
});

console.log("Schemas inscribed.");

// 4. Register in category index for discoverability.
// initToolCategoryIndex creates the index PDA (one-time, idempotent).
// addToToolCategory adds the tool to the index (max 100 tools per index).
const [agentPda] = deriveAgent(client.walletPubkey);
const [toolPda] = client.tools.deriveTool(agentPda, "jupiterSwap");

await client.indexing.initToolCategoryIndex(TOOL_CATEGORY_VALUES.Swap);
await client.indexing.addToToolCategory(TOOL_CATEGORY_VALUES.Swap, toolPda);

console.log("Tool indexed under Swap category.");

// 5. Verify the tool was published correctly.
const tool = await client.tools.fetch(agentPda, "jupiterSwap");
console.log("Name:", tool.toolName);                       // "jupiterSwap"
console.log("Version:", tool.version);                     // 1 (auto-incrementing)
console.log("Active:", tool.isActive);                     // true
console.log("Category: Swap");                             // TOOL_CATEGORY_VALUES.Swap
console.log("HTTP method: Post");                          // HTTP_METHOD_VALUES.Post
console.log("Params:", tool.paramsCount, "(", tool.requiredParams, "required)");
console.log("Invocations:", tool.totalInvocations.toString()); // starts at 0

Updating a Tool

When you update a tool's schema, inscribe the new versions and the on-chain version counter increments automatically:

// Update schema hash (version auto-increments from 1 to 2).
// Only pass the fields you want to change — all others are left untouched.
await client.tools.update("jupiterSwap", {
  inputSchemaHash: hashToArray(sha256(newInputSchema)),  // new 32-byte hash
  paramsCount: 5,           // updated total params
  requiredParams: 3,        // unchanged, but must still pass it
  // description: "...",    // optional: override description
  // outputSchemaHash: ..., // optional: update output schema too
});

// Inscribe the updated schema (same call as before).
// Each inscription is tied to the current tool version.
await client.tools.inscribeSchema("jupiterSwap", {
  schemaType: 0,                              // input
  schemaData: Buffer.from(newInputSchema),
  schemaHash: hashToArray(sha256(newInputSchema)),
  compression: 0,                             // 0 = none, 1 = zstd
});