> Solaris AI Flow Docs
> Version: 2026.05.23+5254444
> Pages: 81

# How It Works (/docs/how-it-works)

The simple mental model behind Solaris AI Flow: build, trigger, execute.



Every automation in Solaris AI Flow follows three steps:

1\. Build [#1-build]

Open the visual editor. Drag nodes onto the canvas: triggers, AI models, DeFi protocols, messaging bots, and utility steps. Connect them with edges to define data flow.

Each node takes input from connected upstream nodes and passes its result downstream.

2\. Trigger [#2-trigger]

Choose what starts your workflow:

* **Manual** - click Run in the editor
* **Webhook** - an external service POSTs to your unique URL
* **Cron** - runs on a schedule (every 5 minutes, hourly, daily, weekly)

3\. Execute [#3-execute]

When triggered, the runtime follows your graph in dependency order:

```
Trigger → Node A → Node B → Node C → Output
```

Each node executes as its dependencies become available. If Node B needs data from Node A, it reads Node A's output via template expressions like `{nodeAResponse.result.price}`.

Builder vs. Runtime [#builder-vs-runtime]

The **canvas** is where you design. It's a visual editor: drag, connect, configure.

The **runtime** is what runs. It reads your saved workflow, resolves credentials, calls external APIs, and records every node's output and errors for debugging.

You design on the canvas. The runtime executes what you designed.

Next steps [#next-steps]

* [Quickstart](/docs/getting-started/quickstart) - build your first workflow
* [Core Concepts](/docs/concepts) - understand workflows, nodes, and edges


---

# Introduction (/docs)

What Solaris AI Flow is and who it's for.



Solaris AI Flow is a no-code automation platform for Solana and Web3 workflows. Build visual workflows that connect on-chain data, DeFi protocols, AI models, Telegram, Discord, webhooks, and scheduled jobs.

Use it to monitor tokens, send alerts, analyze market data, automate community operations, run wallet-aware workflows, and publish reusable templates on the marketplace. Solaris AI Flow is built around Solana-native wallets, payments, and on-chain integrations.

What it solves [#what-it-solves]

* **Manual on-chain operations**: Stop copy-pasting transactions. Automate swaps, portfolio rebalancing, and token monitoring.
* **AI-powered reactions**: Connect LLMs to on-chain data. Let AI analyze market conditions and trigger actions.
* **Workflow distribution**: Build once, sell on the marketplace. Buyers receive a permanent clone they can edit and run in their own workspace.
* **Beta marketplace revenue**: 0% platform fees during beta. Sellers keep 100% of marketplace revenue.

Who it's for [#who-its-for]

* **Web3 communities** running Telegram or Discord automations
* **DeFi users** monitoring wallets, tokens, prices, and protocol data
* **Creators** selling reusable workflow templates
* **Non-developers** who want automation without writing backend code
* **Power users** combining AI, webhooks, schedules, and on-chain actions

Quick links [#quick-links]

* [Quickstart](/docs/getting-started/quickstart) - your first workflow in 5 minutes
* [Node Reference](/docs/nodes) - the growing node library
* [Marketplace](/docs/marketplace) - buy and sell workflows


---

# Billing (/docs/billing)

Plans, pricing, and subscription management.



Solaris AI Flow has three plans: <PlanName plan="free" />, <PlanName plan="pro" />, and <PlanName plan="ultra" />.

Paid plans are one-time SOL purchases for a fixed <CycleDays />-day billing cycle. They do not auto-renew, and Solaris AI Flow does not keep a card on file. When the cycle ends, your account returns to <PlanName plan="free" /> unless you purchase another cycle.

* [Plans and Limits](/docs/billing/plans) - feature comparison and pricing
* [Upgrading Your Plan](/docs/billing/upgrading) - how to purchase, upgrade, and downgrade


---

# Plans and Limits (/docs/billing/plans)

Feature comparison across Free, Pro, and Ultra plans.



Solaris AI Flow has three plans: Free, Pro, and Ultra. All plans include the visual editor, all node types, and marketplace access.

Plan comparison [#plan-comparison]

<PlansTable />

Pricing [#pricing]

Paid plans are one-time SOL purchases for a fixed billing cycle. They do not auto-renew, and Solaris AI Flow does not keep a card on file. When the cycle ends, your account returns to Free unless you purchase another cycle.

The USD price is fixed. The SOL amount adjusts based on the live SOL/USD rate at checkout.

<PlansPricing />

**0% platform fees during beta** - marketplace sellers keep 100% of revenue.

What happens at limits [#what-happens-at-limits]

* **Workflow limit reached** - cannot create new workflows until you delete or upgrade
* **Execution limit reached** - runs are blocked until next billing cycle
* **Trigger downgrade** - if you downgrade from Pro to Free, hourly/5-min crons and excess webhook workflows are automatically disabled

Usage tracking [#usage-tracking]

View your current usage on the **Plans** page: execution count and days remaining in the billing cycle.

Next steps [#next-steps]

* [Upgrading Your Plan](/docs/billing/upgrading) - how to subscribe with SOL


---

# Upgrading Your Plan (/docs/billing/upgrading)

How to subscribe, upgrade, downgrade, and cancel.



Plans are purchased with a one-time SOL payment from your embedded Privy wallet. They do not auto-renew, and Solaris AI Flow does not keep a card on file. Your plan activates instantly for <CycleDays /> days. When the cycle ends, your account returns to <PlanName plan="free" /> unless you purchase another cycle.

Purchasing a plan [#purchasing-a-plan]

1. Go to **Plans** from the sidebar
2. Select **Pro** or **Ultra**
3. Review the SOL amount (computed from live SOL/USD rate)
4. If your wallet is short on SOL, click **Fund Wallet** in the confirmation dialog
5. Click **Pay & Upgrade**. Solaris AI Flow charges your embedded wallet. If confirmation is delayed, your plan activates automatically once the transaction lands.
6. Plan activates immediately for <CycleDays /> days

Price lock [#price-lock]

The SOL price you see is locked for your transaction. If the SOL/USD rate moves more than 2% between viewing and confirming, you'll be asked to review the updated price.

Upgrading mid-cycle [#upgrading-mid-cycle]

Upgrading from Pro to Ultra takes effect immediately. Your old plan is expired and the new plan starts a fresh <CycleDays />-day cycle.

Downgrading [#downgrading]

1. Go to **Plans** from the sidebar
2. Click &#x2A;*Downgrade to <PlanName plan="free" />**
3. Your paid plan stays active until the current <CycleDays />-day cycle ends
4. After expiry, you revert to the <PlanName plan="free" /> plan

Since plans do not auto-renew, you can also just let your cycle expire without doing anything.

When reverting to <PlanName plan="free" />:

* Workflows exceeding the limit remain but you can't create new ones
* Active cron/webhook triggers exceeding Free limits are automatically disabled
* Execution history is preserved

Failed payments [#failed-payments]

If your wallet has insufficient SOL, the transaction fails. No plan change occurs. You can retry immediately.

If the app cannot check your balance because RPC is unavailable, you may still continue. The server performs the final payment check before activating the plan.

Next steps [#next-steps]

* [Plans and Limits](/docs/billing/plans) - what each plan includes


---

# Edges (/docs/concepts/edges)

How nodes are connected and how data passes between them.



An edge is a connection between two nodes. It defines execution order and enables data passing.

Drawing edges [#drawing-edges]

1. Hover over a node's output port (right side) - it highlights
2. Click and drag from the output port
3. Release on another node's input port (left side)

The edge appears as a line connecting the two nodes.

Data passing [#data-passing]

When Node A connects to Node B via an edge:

* Node A executes first
* Node A's output becomes available to Node B
* Node B can reference Node A's data using `{nodeAResponse.result.field}`

Deleting edges [#deleting-edges]

Click an edge to select it, then press **Delete** or **Backspace**.

Multiple outputs [#multiple-outputs]

A node can have multiple outgoing edges - its output is available to all downstream nodes. Execution follows the graph topology (nodes with no unresolved dependencies run next).

Condition node branching [#condition-node-branching]

The Condition node supports multiple output routes. Based on expression evaluation, execution follows one branch and skips others. Each route is a separate edge from the Condition node to different downstream nodes.

Next steps [#next-steps]

* [Triggers](/docs/concepts/triggers) - what starts the execution chain
* [Configuring Nodes](/docs/editor/configuring-nodes) - using upstream data in node settings


---

# Executions (/docs/concepts/executions)

What happens when a workflow runs.



An execution is one run of a workflow. It records every node's output, timing, and status.

States [#states]

| State   | Meaning                                                                                    |
| ------- | ------------------------------------------------------------------------------------------ |
| Pending | Queued, waiting to start                                                                   |
| Running | Nodes are executing                                                                        |
| Waiting | Paused mid-run, typically a ForEach loop between iteration batches. Resumes automatically. |
| Success | All nodes completed without error                                                          |
| Failed  | One or more nodes hit an error (including timeouts)                                        |

Timed-out executions are stored as `failed` with a timeout error message.

What's recorded [#whats-recorded]

For each execution:

* **Start time** and **completion time**
* **Per-node data** - output, status, duration, error message

Duplicate protection [#duplicate-protection]

**Manual and preview runs** are guarded: if a workflow already has an active execution (pending, running, or waiting), a new run of the same kind is rejected. For workflows that contain side-effecting nodes, a manual run and a preview run also can't overlap.

**Cron-triggered runs** are skipped if any execution is already active for that workflow.

**Webhook-triggered runs** are not gated by this active-execution check — rejecting an inbound delivery would drop a legitimate external event. Webhook bursts can create overlapping executions; duplicate deliveries are instead suppressed by provider delivery-ID dedup, rate limiting, and plan concurrency limits.

Timeout [#timeout]

Executions have a hard 10-minute timeout. If a workflow hasn't completed by then, it's marked as failed with a timeout error. Stuck executions are cleaned up automatically by a background reconciliation job.

Next steps [#next-steps]

* [Execution History](/docs/executions/history) - viewing past runs
* [Run Detail](/docs/executions/run-detail) - inspecting node-level results


---

# Core Concepts (/docs/concepts)

The building blocks of Solaris AI Flow.



Understand the fundamentals before building workflows.

* [Workflows](/docs/concepts/workflows) - what a workflow is and how it's managed
* [Nodes](/docs/concepts/nodes) - the building blocks and their categories
* [Edges](/docs/concepts/edges) - how nodes connect and pass data
* [Triggers](/docs/concepts/triggers) - what starts a workflow
* [Executions](/docs/concepts/executions) - what happens when a workflow runs


---

# Nodes (/docs/concepts/nodes)

What nodes are, their categories, and how data flows through them.



Nodes are the building blocks of a workflow. Each node performs one action - fetch a price, call an AI model, send a message, execute a swap.

Anatomy [#anatomy]

Every node has:

* **Input port** (left) - receives data from upstream nodes
* **Output port** (right) - passes results to downstream nodes
* **Config panel** - opens on click, contains node-specific settings
* **Status indicator** - shows idle, running, success, or error during execution

Categories [#categories]

| Category  | Nodes                                                                                        | Purpose                                                                                                |
| --------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| AI        | OpenRouter                                                                                   | LLM inference (GPT, Claude, Gemini, etc.)                                                              |
| DeFi      | Jupiter, Raydium, Pump.fun, Orca, Meteora, Kamino, Sanctum, DFlow, Drift, Phoenix            | Swaps, quotes, liquidity, lending, perps trading                                                       |
| Data      | Birdeye, Helius, Pyth, CoinGecko, Polymarket, Dune, DefiTheOdds                              | Prices, on-chain data, analytics                                                                       |
| Messaging | Telegram, Discord                                                                            | Send alerts and notifications                                                                          |
| Utility   | HTTP, x402, Balance, Storage, Log, Delay, Transform, Merge, Code, Filter, Condition, ForEach | Requests, paid requests, wallet reads, storage, data shaping, merging, control flow, batched iteration |

Data flow [#data-flow]

Each node outputs a JSON object. Downstream nodes reference upstream outputs using template expressions:

```
{responseName.field.path}
```

For example, if a Birdeye node outputs `{ data: { value: 150.23 } }`, a downstream Log node can reference it as `{birdeyeResponse.data.value}`.

Status states [#status-states]

| State   | Meaning                                        |
| ------- | ---------------------------------------------- |
| Idle    | Not yet executed in this run                   |
| Running | Currently processing                           |
| Success | Completed without error                        |
| Error   | Failed - check the error message in run detail |

Next steps [#next-steps]

* [Edges](/docs/concepts/edges) - connecting nodes together
* [Node Reference](/docs/nodes) - detailed docs for each node type


---

# Triggers (/docs/concepts/triggers)

The three trigger types that start a workflow execution.



A trigger is the event that starts a workflow run. Workflows use trigger nodes to define how they start. Manual and cron triggers are limited to one per workflow. Webhook triggers can have multiple.

Trigger types [#trigger-types]

| Type    | How it fires                                 | Use case                        |
| ------- | -------------------------------------------- | ------------------------------- |
| Manual  | You click **Run** in the editor or dashboard | Testing, one-off tasks          |
| Webhook | External service sends a POST request        | Real-time reactions to events   |
| Cron    | Runs on a schedule                           | Periodic monitoring, batch jobs |

Plan restrictions [#plan-restrictions]

| Feature          | Free              | Pro             | Ultra           |
| ---------------- | ----------------- | --------------- | --------------- |
| Manual triggers  | Yes               | Yes             | Yes             |
| Cron triggers    | Daily/Weekly only | All frequencies | All frequencies |
| Webhook triggers | 1 active workflow | Unlimited       | Unlimited       |

When to use which [#when-to-use-which]

* **Manual** - prototyping, debugging, human-in-the-loop flows
* **Webhook** - reacting to external events (price alerts, GitHub pushes, form submissions)
* **Cron** - periodic checks (portfolio snapshots, token monitoring, scheduled reports)

Next steps [#next-steps]

* [Manual Trigger](/docs/triggers/manual)
* [Webhook Trigger](/docs/triggers/webhook)
* [Cron Trigger](/docs/triggers/cron)


---

# Workflows (/docs/concepts/workflows)

What a workflow is and how it's managed.



A workflow is a directed graph of nodes connected by edges. It defines an automation - what to do, in what order, and how data flows between steps.

Lifecycle [#lifecycle]

* **Draft** - created but never run
* **Saved** - configured with nodes and edges, ready to execute
* **Running** - currently executing (one active execution at a time per workflow)
* **Completed/Failed** - last run finished with success or error

Metadata [#metadata]

Each workflow has:

| Field         | Description                                            |
| ------------- | ------------------------------------------------------ |
| Name          | Display name (max 200 characters)                      |
| Description   | Optional summary (max 5000 characters)                 |
| Folder        | Organizational grouping                                |
| Trigger type  | Manual, webhook, or cron                               |
| Enabled state | Whether automated triggers are active                  |
| Schedule      | Cron expression, when the workflow uses a cron trigger |
| Version       | Incremented on marketplace publish updates             |

Plan limits [#plan-limits]

| Plan                      | Max Workflows                                   |
| ------------------------- | ----------------------------------------------- |
| <PlanName plan="free" />  | <PlanLimit plan="free" field="maxWorkflows" />  |
| <PlanName plan="pro" />   | <PlanLimit plan="pro" field="maxWorkflows" />   |
| <PlanName plan="ultra" /> | <PlanLimit plan="ultra" field="maxWorkflows" /> |

When you hit your workflow limit, you must upgrade or delete existing workflows to create new ones.

Next steps [#next-steps]

* [Nodes](/docs/concepts/nodes) - the building blocks of workflows
* [Triggers](/docs/concepts/triggers) - what starts a workflow


---

# Adding Nodes (/docs/editor/adding-nodes)

How to add nodes from the library to the canvas.



Nodes are added from the node library on the left side of the editor.

Steps [#steps]

1. Open the node library (always visible on the left)
2. Browse categories or search by name
3. Drag a node onto the canvas

The node appears at the drop position with a default label.

Node categories [#node-categories]

Nodes are grouped into four categories in the editor sidebar: Triggers, Integrations, Logic, and Utilities. Use the search tab to find nodes across all categories.

Next steps [#next-steps]

* [Connecting Nodes](/docs/editor/connecting-nodes) - wire nodes together
* [Configuring Nodes](/docs/editor/configuring-nodes) - set up node parameters


---

# Canvas Annotations (/docs/editor/annotations)

Use Sticky Notes and Arrows to document workflows without changing execution.



Annotations are canvas-only items for documenting a workflow. Use them to explain assumptions, call out risk, leave handoff notes, or make a complex graph easier to understand.

Annotations are for humans. They do not run, pass data, validate configuration, or create executable flow.

Quick reference [#quick-reference]

| Annotation  | Best for                                                                             | Execution impact |
| ----------- | ------------------------------------------------------------------------------------ | ---------------- |
| Sticky Note | Instructions, TODOs, risks, decisions, variable reminders, marketplace handoff notes | None             |
| Arrow       | Visual direction, emphasis, or relationships that should not move data               | None             |

Use an executable edge when data must flow between nodes. Use an annotation when you only want to explain or point.

Add annotations [#add-annotations]

Use either path:

1. Click the Sticky Note or Arrow button in the editor toolbar.
2. Right-click empty canvas and choose **Add sticky note** or **Add arrow**.

New annotations are saved with the workflow like other canvas items. Sticky Notes and Arrows sit behind executable nodes so they do not block node interaction.

Sticky Notes [#sticky-notes]

Sticky Notes are editable documentation blocks on the canvas.

Edit text [#edit-text]

* Double-click a note to edit it.
* Select one note and press **Enter** or **F2** to edit from the keyboard.
* Press **Cmd/Ctrl + Enter** to save while editing.
* Press **Esc** to cancel the current edit.
* Use **Copy text** from the note context menu to copy only the note content.

Sticky Note text is capped to keep workflows fast and safe to save. Keep notes focused. For long documentation, link to an external doc instead of pasting the full document into the canvas.

Use markdown-lite formatting [#use-markdown-lite-formatting]

Sticky Notes support lightweight formatting in read mode. Type plain text while editing; the note renders formatting after you save.

| Type this                     | Renders as     |
| ----------------------------- | -------------- |
| `# Heading`                   | Heading        |
| `- item`                      | Bullet         |
| `- [ ] task`                  | Unchecked task |
| `- [x] task`                  | Checked task   |
| `**important**`               | Bold text      |
| `*emphasis*`                  | Italic text    |
| `` `fieldName` ``             | Inline code    |
| `[docs](https://example.com)` | Link           |

You can also right-click a note and use **Formatting** to insert common patterns such as Heading, Bullet, Checkbox, Bold, Italic, or Link.

Mention nodes and variables [#mention-nodes-and-variables]

Sticky Notes can highlight node and variable references so readers can connect the note to the workflow.

| Pattern                | Use it for                                   |
| ---------------------- | -------------------------------------------- |
| `@httpResponse`        | Mention a node output alias or response name |
| `{httpResponse.price}` | Mention a specific variable path             |
| `{json httpResponse}`  | Mention a JSON-style output reference        |

Use **Insert mention** from the context menu to insert a reference from workflow nodes. The toolbar braces button inserts a quick mention when available.

Mentions are documentation only. They do not bind data, execute templates, or change runtime behavior.

Add status badges [#add-status-badges]

Sticky Notes can carry a small status badge:

* **TODO** - work still needed.
* **Risk** - something needs extra care before running or sharing.
* **Decision** - records why the workflow is built a certain way.
* **Done** - marks a documented task as complete.

Use the status button in the selected-note toolbar to cycle statuses, or right-click the note and choose **Status** for an explicit selection.

Attach notes to nodes [#attach-notes-to-nodes]

Attach a note when it documents a specific executable node.

1. Right-click the note.
2. Open **Attach to node**.
3. Choose the target node.

When the attached node is dragged or tidied, the note moves with it. If the target node is deleted, the note is kept and automatically detached.

Attachment is visual only. It does not create an edge, dependency, or data relationship.

Resize and fit [#resize-and-fit]

* Drag the resize handles to change note size.
* Use **Fit to text** from the toolbar or context menu to resize the note around its current content.
* Press **Cmd/Ctrl + Shift + F** when a single note is selected to fit it to text.

Fit to text is an estimate based on the note content. You can still resize manually afterward.

Toolbar and context menu [#toolbar-and-context-menu]

When a note is selected, the inline toolbar gives quick access to:

* Color swatches
* Status
* Detach
* Fit to text
* Insert mention
* Edit, save, and cancel
* Duplicate
* Delete

Right-click a note for the full menu: edit, copy text, fit, attach, status, formatting, insert mention, color, duplicate, and delete.

If a note is near the top of the canvas, the toolbar appears below the note so it stays reachable.

Keyboard shortcuts [#keyboard-shortcuts]

| Shortcut                 | Action                            |
| ------------------------ | --------------------------------- |
| **Enter** or **F2**      | Edit selected note                |
| **Cmd/Ctrl + Enter**     | Save while editing                |
| **Esc**                  | Cancel current edit               |
| **Tab**                  | Insert indentation while editing  |
| **Cmd/Ctrl + B**         | Wrap selection in bold markdown   |
| **Cmd/Ctrl + I**         | Wrap selection in italic markdown |
| **Cmd/Ctrl + K**         | Wrap selection as a markdown link |
| **Cmd/Ctrl + Shift + F** | Fit selected note to text         |

Arrows [#arrows]

Arrows are visual pointers. They are not workflow edges.

* Resize the arrow box to change its length and angle.
* Right-click the arrow to change direction, line style, or color.
* Choose solid or dashed line style.
* Use arrows for visual guidance only; use edges when data must flow between nodes.

What annotations do not do [#what-annotations-do-not-do]

Annotations:

* Do not execute
* Do not count as runnable nodes
* Do not have input or output handles
* Do not affect workflow validation
* Cannot pass data downstream
* Cannot trigger, pause, filter, transform, or branch a run

If you need workflow data to move between nodes, create an edge. If you need a branch to pause, log, filter, or transform data, use the matching node instead.

Copying, duplicating, and tidy layout [#copying-duplicating-and-tidy-layout]

Annotations behave like canvas items during editing:

* Duplicate or copy/paste keeps Sticky Note text, color, status, size, and attachment metadata.
* If you duplicate or paste both an attached note and its target node together, the copied note attaches to the copied target.
* If you duplicate only a note, it keeps its original attachment unless you change it.
* **Tidy up layout** moves attached notes along with their target nodes.

Marketplace behavior [#marketplace-behavior]

When you list or sell a workflow, annotations clone with the workflow.

Buyers can see:

* Sticky Note text
* Sticky Note color, size, status, and attachment labels
* Arrow direction, style, color, and size

Do not put secrets, private webhook URLs, API keys, wallet-sensitive information, or non-public trading instructions in annotation text. Treat annotations as visible documentation for anyone who can view or clone the workflow.

Recommended uses [#recommended-uses]

Good Sticky Notes:

* Explain why a node exists.
* Record assumptions, thresholds, and safe ranges.
* Mark a TODO before publishing.
* Warn that a node can send a message, broadcast a transaction, or spend funds.
* Tell marketplace buyers what they must configure after cloning.
* Link to external runbooks or source documentation.

Good Arrows:

* Point from a note to the node it explains.
* Emphasize a branch or fallback path.
* Show a conceptual relationship without creating executable flow.

Avoid using annotations as the only place where required runtime configuration is stored. If the workflow needs a value to run, configure it on the node.

Next steps [#next-steps]

* [Canvas Overview](/docs/editor/canvas) - editor layout and controls
* [Connecting Nodes](/docs/editor/connecting-nodes) - executable data flow
* [Marketplace Listing](/docs/marketplace/listing) - what buyers receive


---

# Canvas Overview (/docs/editor/canvas)

The visual workflow editor layout and controls.



The workflow editor is where you build automations visually.

Layout [#layout]

* **Top toolbar** - workflow name, save status, run button, Activate/Active control, and editor actions
* **Left panel** - node library (drag nodes onto canvas)
* **Center** - the canvas (your workflow graph)
* **Node settings dialog** - opens when you click a node. On wide screens it uses three panes: **Input**, **Parameters**, and **Output**. On narrow screens the same panes appear as tabs.
* **Executions panel** - opens from the editor to inspect recent runs, node output, errors, notes, and run metrics

Canvas controls [#canvas-controls]

| Action        | How                                                               |
| ------------- | ----------------------------------------------------------------- |
| Pan           | Click and drag on empty canvas                                    |
| Zoom          | Scroll wheel or pinch                                             |
| Select node   | Click a node                                                      |
| Multi-select  | Shift + click, or drag a selection box                            |
| Delete        | Select node/edge, press Delete or Backspace                       |
| Add from menu | Right-click empty canvas and choose a node, Sticky Note, or Arrow |

Canvas annotations [#canvas-annotations]

Sticky Notes and Arrows help explain the workflow without changing how it runs. They sit behind executable nodes, have no input or output ports, and cannot be connected by edges.

* **Sticky Note** - add text instructions, assumptions, warnings, or marketplace handoff notes
* **Arrow** - point at a node, branch, or important section of the workflow

See [Canvas Annotations](/docs/editor/annotations) for editing, resizing, colors, and marketplace behavior.

Execution inspection [#execution-inspection]

After a run starts, the editor shows live node statuses on the canvas and in the executions panel. The panel includes:

* A recent-run list with status, timing, node counts, source, automatic labels, and AI cost/token chips when available
* A detail header with the run status, duration, source, optional user note, and rerun controls when supported
* A node mini-map for longer runs so you can jump to a node run quickly
* Filters for **All** and **Failed** node runs plus node-run search
* Per-node output, errors, and copy/debug details

Next steps [#next-steps]

* [Adding Nodes](/docs/editor/adding-nodes)
* [Canvas Annotations](/docs/editor/annotations)
* [Connecting Nodes](/docs/editor/connecting-nodes)
* [Configuring Nodes](/docs/editor/configuring-nodes)
* [Running a Workflow](/docs/editor/running)


---

# Configuring Nodes (/docs/editor/configuring-nodes)

Using node settings, input/output previews, and upstream data.



Click a node to open its settings. On wide screens the settings open as a three-pane dialog; on narrow screens the same panes are available as tabs.

Settings layout [#settings-layout]

| Pane           | What it shows                                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Input**      | Upstream data available to this node. Use it to find variables, inspect sample payloads, and run previous nodes for fresh input. |
| **Parameters** | The node's editable configuration fields and the main execute button for this node/trigger.                                      |
| **Output**     | The node's live preview (for supported nodes) and last-run output.                                                               |

The **Input** pane supports these data views:

| View          | Use it for                                                                                                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Variables** | Default input view. Shows an n8n-style variable browser with friendly chips instead of raw JSON syntax. Click a row to insert it into the focused parameter field, or copy it when no field is focused. |
| **JSON**      | Exact JSON tree with expand/collapse, path copying, and syntax highlighting.                                                                                                                            |
| **Schema**    | Shape-focused view for scanning object structure without reading every value.                                                                                                                           |
| **Table**     | Tabular view for arrays of objects or primitive arrays.                                                                                                                                                 |

The **Output** pane supports **JSON**, **Schema**, and **Table** for last-run output. For Code and Transform nodes, the Output pane also has **Live preview** and **Last run** tabs.

Search is available from the view toolbar. Horizontal scrolling is scoped to the data area so section headers and notices stay fixed while you inspect long keys or values.

Common fields [#common-fields]

* **Label** - display name shown on the canvas
* **Credential** - which stored credential to use (for integration nodes)
* **Operation** - what action to perform (e.g., "getPrice", "swap", "sendMessage")

Some fields appear based on your operation selection. For example, selecting "swap" on Jupiter shows input/output mint fields.

Template expressions [#template-expressions]

Reference data from upstream nodes in any template-aware field using the node's response name:

```text
{responseName.field.path}
```

Each node has a default response name (e.g., `birdeyeResponse`, `jupiterResponse`, `aiResponse`). You can change it in node settings.

Examples:

* `{birdeyeResponse.data.value}` - price from a Birdeye node
* `{aiResponse.data}` - AI text or parsed JSON output
* `{webhook.triggeredAt}` - timestamp from webhook trigger
* `{json jupiterResponse}` - full output as a JSON string

Use the `json` prefix when inserting an object or array into a JSON body or prompt and you need valid JSON instead of JavaScript string coercion.

Inserting variables [#inserting-variables]

For template-aware fields, click a value in the **Input** pane or use the variable picker beside the field. Solaris AI Flow inserts the correct expression at the cursor when possible, or copies it to your clipboard.

For Code node JavaScript fields, Input-pane clicks insert `$input` access such as:

```js
$input.pumpfunResponse.data[0].symbol
```

For normal template fields, clicks insert template expressions such as:

```text
{pumpfunResponse.data[0].symbol}
```

If a node has no friendly response name, variable hints may fall back to the raw node-id path. Setting a clear response name makes downstream expressions easier to read.

Response name [#response-name]

Each node has a `responseName` field, or an equivalent output/result name, that controls how downstream nodes reference it. Defaults:

| Node type       | Default response name                                  |
| --------------- | ------------------------------------------------------ |
| Manual Trigger  | `trigger`                                              |
| Webhook Trigger | `webhook`                                              |
| Cron Trigger    | `cron`                                                 |
| Birdeye         | `birdeyeResponse`                                      |
| Jupiter         | `jupiterResponse`                                      |
| AI (OpenRouter) | `aiResponse`                                           |
| Telegram        | `telegramResponse`                                     |
| HTTP            | `httpResponse`                                         |
| x402            | `x402Response`                                         |
| Balance         | `balanceResponse`                                      |
| Transform       | Set **Name this result** yourself, such as `topTokens` |
| Merge           | `mergeResponse`                                        |
| Code            | `codeResponse`                                         |

For Transform nodes, always name the result when another node needs to reference it. The UI previews examples such as `{topTokens.data}` because unnamed Transform outputs are harder to reference reliably.

Running previews and node tests [#running-previews-and-node-tests]

* Use the **Parameters** pane execute button to run the selected node, trigger, or workflow action shown by the button label.
* Use the **Input** pane button to execute previous nodes when a node needs fresh input from upstream. The editor shows the nodes that will run and warns before running side-effecting ancestors.
* Code and Transform nodes publish a **Live** preview in the **Output** pane while you edit. The **Last** tab still shows the most recent persisted run output.

Preview/upstream runs count as monthly runs. They are hidden from normal execution history but still included in lifetime usage totals.

Save and validation [#save-and-validation]

Most node settings block **Save** until required fields are filled. If a node was imported or cloned with unsupported settings, the Parameters pane may ask you to choose a valid option before saving so the workflow does not silently change behavior.

If you edit parameters, execution buttons may be disabled until you save. This prevents testing stale persisted config while unsaved edits are visible on screen.

Next steps [#next-steps]

* [Saving](/docs/editor/saving) - how workflows are saved
* [Running](/docs/editor/running) - executing from the editor
* [Run a Single Node](/docs/editor/run-node) - execute one node and its dependencies


---

# Connecting Nodes (/docs/editor/connecting-nodes)

How to draw edges between nodes on the canvas.



Edges define execution order and data flow between nodes.

Drawing an edge [#drawing-an-edge]

1. Hover over a node's output port (right side circle)
2. Click and drag from the port
3. Release on another node's input port (left side circle)

Deleting an edge [#deleting-an-edge]

Click the edge line to select it, then press **Delete** or **Backspace**.

Rules [#rules]

* A node can have multiple outgoing edges (fan-out)
* A node can have multiple incoming edges (fan-in)
* Cycles are not allowed (the graph must be acyclic)

Next steps [#next-steps]

* [Configuring Nodes](/docs/editor/configuring-nodes) - referencing upstream data


---

# Folders (/docs/editor/folders)

Organize workflows into nested folders from the dashboard.



Folders let you group related workflows. They support nesting, renaming, moving, and deleting - all managed from the dashboard.

Creating a folder [#creating-a-folder]

From the dashboard, click the **New Folder** button (folder-plus icon) in the toolbar. Give it a name up to 120 characters. New folders are created at your current location in the tree, so creating one while inside another folder makes it a subfolder.

Navigating folders [#navigating-folders]

Folders appear as cards alongside workflow cards. Click a folder card to enter it. The **breadcrumb** at the top of the dashboard shows your current path - click any segment (including **Home**) to jump back up the tree.

Your search, status, trigger, and sort filters stay applied as you navigate, but the workflow list refreshes to show only items inside the current folder.

Folder operations [#folder-operations]

Hover (or tap on touch devices) a folder card to reveal its action menu:

| Action | Description                                                                                     |
| ------ | ----------------------------------------------------------------------------------------------- |
| Open   | Enter the folder                                                                                |
| Rename | Change the folder name                                                                          |
| Move   | Move the folder to a different parent (you cannot move a folder into itself or its own subtree) |
| Delete | Remove an empty folder - it must contain no subfolders or workflows                             |

Moving workflows [#moving-workflows]

Open a workflow card's action menu and choose **Move to folder**. A dialog shows your full folder tree - pick a destination, or choose **Home** to move the workflow back to the root. A workflow can only belong to one folder at a time.

Next steps [#next-steps]

* [Dashboard Overview](/docs/getting-started/dashboard) - navigating the main hub
* [Workflows](/docs/concepts/workflows) - workflow management


---

# Workflow Editor (/docs/editor)

The visual editor for building workflows.



The editor is where you design workflows visually on a canvas, configure nodes, preview data, and inspect recent executions.

* [Canvas Overview](/docs/editor/canvas) - layout, canvas controls, annotations, and the executions panel
* [Adding Nodes](/docs/editor/adding-nodes) - dragging nodes from the library
* [Connecting Nodes](/docs/editor/connecting-nodes) - drawing edges
* [Configuring Nodes](/docs/editor/configuring-nodes) - Input/Parameters/Output panes, variables, and template expressions
* [Saving](/docs/editor/saving) - how auto-save and manual save work
* [Running a Workflow](/docs/editor/running) - executing and inspecting runs from the editor
* [Folders](/docs/editor/folders) - organizing workflows into folders
* [Run a Single Node](/docs/editor/run-node) - executing one node and its dependencies


---

# Run a Single Node (/docs/editor/run-node)

Execute a single node along with its upstream dependencies and downstream nodes, without firing the workflow's trigger.



You can run a single node directly from the canvas or from the node settings dialog. This executes the selected node, all upstream dependencies it needs for input, and the downstream nodes it feeds — unrelated branches are skipped.

How to use from the canvas [#how-to-use-from-the-canvas]

1. Right-click or use the action menu on any node in the canvas
2. Select **Run this node**
3. The engine executes the target node, its upstream ancestors, and its downstream descendants
4. Results appear in the executions panel

How to use from node settings [#how-to-use-from-node-settings]

1. Click a node to open settings
2. Review the **Input** pane to confirm which upstream values are available
3. Use the execute button in the **Parameters** pane to run the selected step, trigger, or workflow action shown by the button label
4. Check the **Output** pane for last-run output or live preview output when supported

For Code and Transform nodes, use the **Input** pane button to execute previous nodes when you only need fresh sample data for the live preview. The editor shows which previous nodes will run and asks for confirmation before executing side-effecting ancestors.

What runs [#what-runs]

The engine builds the target node's full subgraph: every upstream ancestor (so the target gets proper inputs) plus every downstream descendant (so nodes that consume the target's output also run). Nodes outside this subgraph — unrelated branches — are skipped. The workflow's own trigger node is not fired.

Because descendants run too, a "Run this node" on a read-only node can still fire side-effecting nodes further down the chain.

Use cases [#use-cases]

* Test a single integration node without running the full workflow
* Debug a specific branch of a complex workflow
* Refresh input samples for Code or Transform previews
* Quickly check if a node's configuration is correct

Limitations [#limitations]

* The execution still counts toward your monthly run limit
* Only one execution can be active per workflow at a time
* Run Node can fire live side effects if the target's subgraph — ancestors or descendants — includes send, transfer, trade, or broadcast nodes; confirm the warning before running them

Next steps [#next-steps]

* [Running a Workflow](/docs/editor/running) - full workflow execution
* [Configuring Nodes](/docs/editor/configuring-nodes) - input/output panes and variables
* [Execution History](/docs/executions/history) - viewing past runs


---

# Running a Workflow (/docs/editor/running)

How to trigger and inspect executions from the editor.



Click the **Run** button in the top toolbar to execute your workflow.

What happens [#what-happens]

1. An execution record is created (status: pending)
2. The runtime picks it up and starts processing (status: running)
3. Nodes execute in graph order - each lights up as it runs
4. On completion, the execution shows success or failure

During execution [#during-execution]

* Nodes show real-time status: spinner (running), green check (success), red X (error)
* The executions panel shows progress and live node output as rows become available
* Only one execution runs at a time per workflow

Executions panel [#executions-panel]

The editor's executions panel is the fastest way to inspect recent runs without leaving the workflow.

Run list [#run-list]

The top section lists recent executions. Each row includes status, relative time, duration, node counts, source, and automatic labels when Solaris AI Flow can infer them from the trigger context. Runs involving AI nodes may also show a compact cost/token chip.

Use **Load older runs** to page back through history.

Run details [#run-details]

Selecting a run opens node-level details below the run list:

* Overall status, duration, source, and AI cost/token totals when available
* Optional user note. Click **Add note** or an existing note to edit it inline.
* Rerun controls for supported trigger contexts. Webhook reruns use the trigger's sample payload; they do not replay the original webhook delivery.
* A node mini-map for longer runs. Click a segment to select and scroll to that node run.
* **All** / **Failed** filters and node-run search for large workflows
* Node output, errors, timing, and copy/debug details

If a run fails, opening it auto-scrolls to the first failed node when possible.

After execution [#after-execution]

* Select a node run from the executions panel to see its output and errors
* Use the node mini-map, failed filter, or search to find relevant nodes in longer runs
* Open the full **Executions** page for historical filtering across workflows
* Failed nodes show their error message in the panel and on the canvas

Duplicate protection [#duplicate-protection]

If you click Run while a previous execution is still active, it is rejected. Wait for the current run to complete.

Preview and upstream runs [#preview-and-upstream-runs]

Node settings can run upstream samples for previewing Code and Transform nodes. These preview runs count as monthly runs and can execute side-effecting upstream nodes only after confirmation. They are hidden from the normal execution list but included in lifetime usage totals.

Next steps [#next-steps]

* [Execution History](/docs/executions/history) - view all past runs
* [Run Detail](/docs/executions/run-detail) - inspect node-level results
* [Configuring Nodes](/docs/editor/configuring-nodes) - input/output panes and live previews


---

# Saving (/docs/editor/saving)

How workflows are saved.



Workflows auto-save every minute when there are unsaved changes. You can also save manually at any time.

How to save [#how-to-save]

Click the **Save** button in the top toolbar. The save indicator shows:

* **Saved** - all changes persisted
* **Unsaved changes** - modifications not yet saved (will auto-save within a minute)

What's saved [#whats-saved]

* Node positions and configuration
* Edge connections
* Workflow name and description
* Viewport position and zoom level
* Trigger activation and cron configuration

Versioning [#versioning]

Workflows don't have version history for private use. However, marketplace-listed workflows track versions - each time you publish an update, a new version is created with a changelog.

Next steps [#next-steps]

* [Running a Workflow](/docs/editor/running) - execute your saved workflow


---

# Error Handling (/docs/executions/errors)

How errors surface and how to fix common ones.



When a node fails during execution, the workflow stops and the execution is marked as failed. Here's how to diagnose and fix issues.

Where errors appear [#where-errors-appear]

* **Execution history** - failed runs show a red status
* **Run detail** - the failed node is highlighted red with an error message
* **Workflow editor** - during live execution, failed nodes show a red indicator

Error types [#error-types]

| Type             | Cause                                 | Fix                                                               |
| ---------------- | ------------------------------------- | ----------------------------------------------------------------- |
| Credential error | Missing or invalid API key            | Check [Connections](/docs/credentials) page, re-enter the key     |
| API error        | External service returned an error    | Check the error message - often rate limits or invalid parameters |
| Timeout          | Node or execution exceeded time limit | Simplify the workflow or reduce data volume                       |
| Config error     | Missing required field on a node      | Open the node settings and fill in required fields                |
| SSRF blocked     | HTTP node tried to reach a private IP | Use a public URL instead                                          |
| Wallet error     | Transaction signing failed            | Check wallet balance and connection                               |

Reading error details [#reading-error-details]

In the Run Detail view, click the failed node. The error panel shows:

* **Error message** - human-readable description
* **Error hint** - suggested fix or context for the failure

Execution timeout [#execution-timeout]

All executions have a 10-minute hard limit. If your workflow approaches this limit:

* Remove unnecessary Delay nodes
* Reduce data volumes (lower `limit` parameters)
* Split into multiple smaller workflows

Stuck executions [#stuck-executions]

Executions stuck in "running" for more than 10 minutes are automatically marked as failed by a background cleanup job. If you see this, the underlying action crashed mid-flight.

Next steps [#next-steps]

* [Credentials](/docs/credentials) - managing API keys
* [Run Detail](/docs/executions/run-detail) - inspecting execution results


---

# Execution History (/docs/executions/history)

View and filter past workflow runs.



The Executions page lets you inspect past runs for each workflow. Navigate to **Executions** in the sidebar.

Layout [#layout]

* **Workflow selector** - pick a workflow from the sidebar on desktop or top pills on mobile
* **Status filter** - filter runs by: all, pending, success, or failed
* **Run list** - paginated list of executions for the selected workflow
* **Detail panel** - opens when you select a run, showing node-level results without leaving the page

Columns [#columns]

Each execution row shows:

| Column   | Description                                                |
| -------- | ---------------------------------------------------------- |
| Status   | pending, running, waiting, success, or failed              |
| Nodes    | Count of nodes executed, including success/failure summary |
| Started  | When the execution began                                   |
| Duration | How long the run took                                      |

The workflow editor's executions panel adds extra context on top of the history list, including automatic run labels, user notes, source badges, and AI cost/token chips when available.

Clicking a run [#clicking-a-run]

Click any execution row to open details. The panel shows node-level output, errors, timing, and copy/debug information without leaving the page.

From the workflow editor, the executions panel also includes recent-run notes, a node mini-map, node-run search, and failed-node filtering for faster debugging.

Deleting execution logs [#deleting-execution-logs]

Use the delete button on a finished execution to remove that execution log. Running, waiting, or pending executions cannot be deleted from the list. Deleting a log does not delete the workflow.

Retention window [#retention-window]

Execution logs are retained for **approximately 7 days from when the run started** - not from when it finished. A daily cleanup sweep removes terminal runs whose start time is older than that, along with their node-run details and any per-iteration ForEach data. Because the sweep runs on a 24-hour cycle, individual rows may persist up to roughly 8 days before being collected - but past the 7-day mark, expect the run to disappear at any time. The workflow itself is unaffected; only the historical run record is removed.

For a long-running workflow that takes hours or days (paused ForEach loops, scheduled retries), the retention window starts from the original start, not from completion. A run that started 6 days ago and finished today will be eligible for cleanup tomorrow.

Run-level loop credit and payment records are preserved indefinitely as part of the financial audit trail; only the operational history (node outputs, errors, ForEach iteration data) is subject to retention.

If you need a permanent record of a specific run, copy any details you need from the run-detail panel before the retention window expires.

Preview runs [#preview-runs]

Code and Transform preview runs are hidden from the main execution list. They still appear in lifetime usage counts so you can distinguish normal workflow runs from preview activity.

Pagination [#pagination]

Results are paginated. Scroll down or click **Load more** to see older runs.

Next steps [#next-steps]

* [Run Detail](/docs/executions/run-detail) - node-level run inspection
* [Error Handling](/docs/executions/errors) - diagnosing failures
* [Running a Workflow](/docs/editor/running) - editor-side run panel


---

# Executions (/docs/executions)

Viewing and inspecting workflow runs.



When a workflow runs, an execution record captures every node's status, output, errors, timing, source metadata, and AI usage when applicable.

Use execution records to:

* See which nodes ran, failed, or were skipped
* Inspect node output and errors
* Add notes to important runs from the editor executions panel
* Compare automatic run labels, trigger source, duration, and AI token/cost totals
* Re-run supported trigger contexts from the editor when available

Pages [#pages]

* [Execution History](/docs/executions/history) - browse past runs by workflow
* [Run Detail](/docs/executions/run-detail) - inspect node-level results
* [Error Handling](/docs/executions/errors) - diagnose and fix failures


---

# Run Detail (/docs/executions/run-detail)

Inspect node-by-node execution results and outputs.



Run details show a breakdown of a single workflow execution with node statuses, output, errors, timing, and debugging actions.

You can inspect run details from:

* The **Executions** page by selecting a run
* The workflow editor's **Executions** panel after a run starts or finishes

On the Executions page [#on-the-executions-page]

The standalone Executions page opens a detail sheet for the selected run. It includes:

* Workflow name
* Run status and timing
* Node runs with node type, status, duration, output, and errors
* Copy output or failure details for debugging

In the workflow editor [#in-the-workflow-editor]

The editor executions panel includes the same node-run inspection plus workflow-editor context:

* Live canvas status overlays for the selected execution
* Source badge, automatic run label, optional user note, and AI token/cost totals when available
* A **node mini-map** for longer runs. Click a segment to jump to that node run.
* **All** and **Failed** filters
* Node-run search by label, type, or response name
* Automatic scroll to the first failed node when opening a failed run
* Rerun controls for supported terminal runs

Node status indicators [#node-status-indicators]

Each node in the graph or node run list shows its execution status:

| Indicator   | Meaning                         |
| ----------- | ------------------------------- |
| Green check | Success                         |
| Red X       | Failed                          |
| Spinner     | Currently running or waiting    |
| Gray        | Queued, skipped, or not reached |

Inspecting a node [#inspecting-a-node]

Click a node run from the list to inspect:

* **Node type** - which node ran
* **Output** - the data produced by this node
* **Error** - error message and hint (if failed)
* **Duration** - how long the node took
* **Copy output** - copy JSON output or failure details for debugging

Run notes and labels [#run-notes-and-labels]

In the workflow editor, Solaris AI Flow can show an automatic label from trigger/run context so repeated runs are easier to recognize. You can also add your own note from the editor executions panel. Notes are saved on the execution record and are meant for debugging context, such as "before changing slippage" or "webhook test payload".

Rerunning [#rerunning]

From the editor executions panel, supported terminal runs can expose a rerun control:

* Manual-node runs can run that node again.
* Cron runs can fire the cron trigger now.
* Webhook runs can run with the trigger's configured sample payload.

Webhook reruns do **not** replay the original webhook delivery payload.

Next steps [#next-steps]

* [Error Handling](/docs/executions/errors) - common error patterns
* [Running a Workflow](/docs/editor/running) - triggering and inspecting new runs
* [Execution History](/docs/executions/history) - historical run list


---

# Adding Credentials (/docs/credentials/adding)

How to add, edit, and delete credentials.



Manage credentials from the **Connections** page in the sidebar.

Adding a credential [#adding-a-credential]

1. Open **Connections**
2. Expand the platform you want to connect, such as OpenRouter, Birdeye, Jupiter, Telegram, or Discord
3. Click **Add key**
4. Give it a descriptive label, such as "Birdeye Production Key"
5. Enter the required key, token, webhook URL, or plan metadata
6. Click **Save**

The credential is encrypted immediately and available to all your workflows.

Editing a credential [#editing-a-credential]

1. Expand the platform on the Connections page
2. Click the pencil icon beside the saved key
3. Secret fields are left blank for security. Non-secret metadata, such as CoinGecko plan type, is shown.
4. Enter a new value to update. Leave secret fields empty to keep the current value.
5. Click **Save**

Deleting a credential [#deleting-a-credential]

1. Expand the platform on the Connections page
2. Click the delete button beside the saved key
3. Confirm deletion

Note: If workflows reference this credential, those nodes will fail on next execution until you assign a new credential.

Returning from a template [#returning-from-a-template]

Some purchased or cloned workflows link you to Connections with the needed platform already expanded. Add the missing key, then use **Back to Template** to return to the workflow setup.

Naming conventions [#naming-conventions]

Use clear names that identify the service and environment:

* "Birdeye - Main"
* "Telegram - Alert Bot"
* "OpenRouter - GPT-4"

Next steps [#next-steps]

* [Per-Integration Guide](/docs/credentials/integrations) - where to find each API key


---

# Credentials (/docs/credentials)

How Solaris AI Flow stores and uses your API keys securely.



Credentials are encrypted API keys and tokens stored in your account. They let nodes authenticate with external services without hardcoding secrets into workflows.

Why credentials are separate [#why-credentials-are-separate]

* **Reusability** - one credential works across all workflows that need it
* **Security** - encrypted at rest, never exposed in workflow exports or marketplace clones
* **Rotation** - update a key once, all workflows pick up the change

Supported platforms [#supported-platforms]

| Platform    | Auth type        | Used by                                                                        |
| ----------- | ---------------- | ------------------------------------------------------------------------------ |
| OpenRouter  | API key          | AI nodes                                                                       |
| Birdeye     | API key          | Birdeye node                                                                   |
| Helius      | API key          | Helius node                                                                    |
| CoinGecko   | API key          | CoinGecko node                                                                 |
| Dune        | API key          | Dune node                                                                      |
| DefiTheOdds | API key          | DefiTheOdds node                                                               |
| Telegram    | Bot token        | Telegram node                                                                  |
| Discord     | Webhook URL      | Discord node                                                                   |
| Jupiter     | API key + wallet | API key required for all ops except transfer. Wallet needed for swap/transfer. |

**Phoenix Flight is intentionally absent.** Phoenix uses a one-time per-wallet invite activation, not a persistent credential. Activate via the inline panel inside any Phoenix node's config dialog. See the [Phoenix node page](/docs/nodes/defi/phoenix) and [Per-Integration Guide](/docs/credentials/integrations#phoenix-flight) for details.

Encryption [#encryption]

All credential values are encrypted server-side before storage. When you view a credential, values are masked (only last 4 characters shown). The decrypted key is only accessed by the execution engine at runtime.

Scope [#scope]

Credentials are per-account. Any workflow you own can use any credential you've added. Credentials are never included in marketplace clones - buyers must supply their own.

Connections page layout [#connections-page-layout]

Connections are grouped by platform category:

* **AI Models** - OpenRouter
* **Blockchain** - Birdeye, Helius, Jupiter, Dune, DefiTheOdds, CoinGecko
* **Messaging** - Telegram, Discord

Expand a platform to add, edit, or delete keys. A connected badge shows when at least one key exists for that platform.

Next steps [#next-steps]

* [Adding Credentials](/docs/credentials/adding) - step-by-step CRUD
* [Per-Integration Guide](/docs/credentials/integrations) - where to get each key


---

# Per-Integration Guide (/docs/credentials/integrations)

Where to get API keys for each supported integration.



Each integration requires specific credentials. Here's where to find them.

| Integration | Auth Type   | Where to get it                                        | Notes                                               |
| ----------- | ----------- | ------------------------------------------------------ | --------------------------------------------------- |
| OpenRouter  | API key     | [openrouter.ai/keys](https://openrouter.ai/keys)       | Create account, generate key. Supports 300+ models. |
| Birdeye     | API key     | [birdeye.so](https://birdeye.so)                       | Developer portal, free tier available               |
| Helius      | API key     | [helius.dev](https://helius.dev)                       | Create project, copy API key                        |
| CoinGecko   | API key     | [coingecko.com/api](https://www.coingecko.com/en/api)  | Free Demo plan or paid Pro plan                     |
| Dune        | API key     | [dune.com/settings/api](https://dune.com/settings/api) | Requires Plus plan for API access                   |
| DefiTheOdds | API key     | [defitheodds.xyz](https://defitheodds.xyz/#pricing)    | Free tier available                                 |
| Telegram    | Bot token   | [@BotFather on Telegram](https://t.me/BotFather)       | Send `/newbot`, copy the token                      |
| Discord     | Webhook URL | Server Settings → Integrations → Webhooks              | Copy the webhook URL                                |

Jupiter [#jupiter]

A Jupiter API key is required for all operations except SOL transfer. Add a Jupiter credential in the Connections page. Transfer uses your Privy wallet directly and does not need an API key.

Pump.fun [#pumpfun]

Pump.fun read operations (getCoin, getTopRunners, etc.) need no credential. On-chain operations - buy, sell, createToken (token launch), claimFees, and claimCashback - use your embedded Privy wallet for transaction signing. `createToken` additionally generates a one-shot mint keypair in-memory to co-sign the launch transaction (never persisted).

Phoenix Flight [#phoenix-flight]

Phoenix Flight is the Ellipsis Labs perps DEX where Solaris AI is a registered builder. The platform is in private beta, so every fresh Privy wallet must be whitelisted **once** with an invite code before any **Live** signed op (placing/cancelling orders or moving collateral). Paper-mode Phoenix signed ops do not require activation because they never sign or broadcast.

**Phoenix activation is NOT a credential.** Unlike API-key integrations, invite codes are single-use per wallet and don't belong in a persistent credential row. Activation is handled inline in the Phoenix node config dialog: open any Phoenix node, pick a signed op, flip it to Live, and an "Activate Phoenix Flight access" panel appears at the bottom. Enter your code, pick the code type (`access` or `referral`), click Activate. Once activated, the panel disappears forever across every Phoenix node in every workflow for that wallet, and no invite data is ever stored on the node, in workflow exports, or in a credential.

Read-only Phoenix ops and paper-mode signed ops don't require activation. See the [Phoenix node page](/docs/nodes/defi/phoenix) for the full per-op behavior.

Request a code at [x.com/PhoenixTrade](https://x.com/PhoenixTrade).

CoinGecko plan types [#coingecko-plan-types]

CoinGecko credentials include a `planType` field:

* **demo** - free tier, limited endpoints
* **analyst/lite/pro** - paid tiers with more data

Set this in the credential config so the node knows which endpoints are available.

Next steps [#next-steps]

* [Node Reference](/docs/nodes) - see what each node does with these credentials


---

# Beta Access (/docs/getting-started/beta-access)

How to get into Solaris AI Flow.



Solaris AI Flow is in open beta. There's no invite code or allowlist - anyone can sign in and start building.

Getting in [#getting-in]

1. Click **Enter App** from the [landing page](https://flow.solarisai.io) (top-right, or any "Enter App" button)
2. Sign in with Google or email through Privy
3. An embedded Solana wallet is created automatically for you
4. You're dropped straight into the dashboard

That's it - no code to enter, no waitlist.

Staying in the loop [#staying-in-the-loop]

Follow along and reach the team through:

* The [Solaris AI Flow Telegram community](https://t.me/Solaris_portal)
* [@SolarisAI\_fun on X](https://x.com/SolarisAI_fun)

Next steps [#next-steps]

* [Sign Up](/docs/getting-started/sign-up) - what happens when you create your account


---

# Dashboard Overview (/docs/getting-started/dashboard)

Navigate the main hub where you manage all your workflows.



The dashboard is your home base. It shows all your workflows with tools to search, filter, and create.

Sidebar [#sidebar]

The sidebar provides navigation to:

* **Workflows** - your workflow list (the dashboard home)
* **Marketplace** - browse and purchase workflows
* **Executions** - view past runs
* **Connections** - manage API keys and credentials
* **Wallet** - SOL balance and transaction history
* **Plans** - subscription management
* **Docs** - documentation

Workflow list [#workflow-list]

The main area shows your workflows in a grid or list view. Toggle between views with the view switcher.

Search and filtering [#search-and-filtering]

* **Search** - filter by workflow name
* **Status filter** - draft, active, running, failed
* **Trigger filter** - cron, webhook, manual
* **Sort** - by name, created date, last updated, or last run

Workflow actions [#workflow-actions]

Hover over a workflow card to see action buttons:

* **Open** - open in the workflow editor
* **Run** - open the workflow in the editor to execute it
* **Rename** - change the workflow name
* **Move to folder** - move the workflow into a folder or back to the root
* **Delete** - remove the workflow

Click a card to open it in the workflow editor.

Folders [#folders]

Organize workflows into nested folders. Use the **New Folder** button in the toolbar to create one, and click a folder card to enter it. Breadcrumb navigation at the top lets you jump back up the tree. See [Folders](/docs/editor/folders) for the full set of operations.

Creating a workflow [#creating-a-workflow]

Click the **Create Workflow** button to start a new workflow and open the editor. New workflows are created in your current folder.

Next steps [#next-steps]

* [Quickstart](/docs/getting-started/quickstart) - create and run your first workflow
* [Folders](/docs/editor/folders) - organize your workflows


---

# Getting Started (/docs/getting-started)

Set up your account and build your first workflow.



Get into the platform and run your first automation.

* [Beta Access](/docs/getting-started/beta-access) - getting into the platform
* [Sign Up](/docs/getting-started/sign-up) - creating your account
* [Dashboard Overview](/docs/getting-started/dashboard) - navigating the main hub
* [Quickstart](/docs/getting-started/quickstart) - your first workflow in 5 minutes


---

# Quickstart (/docs/getting-started/quickstart)

Build and run your first workflow in under 5 minutes.



This guide walks you through creating a simple workflow that fetches a token price and logs it.

This quickstart uses Birdeye, so add a Birdeye credential first. If you only want to test the editor without credentials, build a two-node workflow instead: **Manual Trigger** -> **Log**.

1\. Create a workflow [#1-create-a-workflow]

1. From the dashboard, click **Create Workflow**
2. Name it "My First Flow"
3. You're now in the visual editor

2\. Add a trigger [#2-add-a-trigger]

1. The node toolbar is on the left side of the canvas
2. Drag a **Manual Trigger** node onto the canvas
3. This lets you run the workflow by clicking a button

3\. Add a data node [#3-add-a-data-node]

1. Drag a **Birdeye** node onto the canvas (under Integrations)
2. Click the node to open its settings
3. Set the operation to **Get Price**
4. Enter a token address (e.g., SOL: `So11111111111111111111111111111111111111112`)
5. Select your Birdeye credential (add one in [Credentials](/docs/credentials) first)

4\. Connect the nodes [#4-connect-the-nodes]

1. Click the output port (right side) of the Manual Trigger
2. Drag to the input port (left side) of the Birdeye node
3. An edge appears connecting them

5\. Add a log node [#5-add-a-log-node]

1. Drag a **Log** node onto the canvas
2. Connect Birdeye's output to the Log input
3. In the Log config, set the message to: `Price: {birdeyeResponse.data.value}`

6\. Run it [#6-run-it]

1. Click the **Run** button in the top toolbar
2. Watch the nodes light up green as they execute
3. Open the executions panel to see the Log output, node timings, and run details

You just built and ran your first Solaris AI Flow workflow.

Next steps [#next-steps]

* [Core Concepts](/docs/concepts) - understand the building blocks
* [Node Reference](/docs/nodes) - explore the node library
* [Triggers](/docs/triggers) - automate with webhooks and cron


---

# Sign Up (/docs/getting-started/sign-up)

Create your Solaris AI Flow account with Google or email.



Solaris AI Flow uses [Privy](https://privy.io) for authentication. You can sign in with:

* **Google** - one-click sign in with your Google account
* **Email** - enter your email, verify with a code

What happens on sign-up [#what-happens-on-sign-up]

1. Privy verifies your identity
2. Solaris AI Flow creates your user profile with a unique display name
3. An embedded Solana wallet is created automatically for you
4. You're redirected to the dashboard

Embedded wallet [#embedded-wallet]

Every account gets an embedded Privy wallet on sign-up. This wallet is used for marketplace payments, subscriptions, and on-chain operations (swaps, transfers). No browser extension or external wallet needed.

Solaris AI Flow never stores or has access to your private keys. All wallet keys are secured and managed by Privy's infrastructure.

Data stored [#data-stored]

Solaris AI Flow stores:

* Your Privy identifier and linked email address
* Display name (editable, must be unique)
* Embedded wallet address

No seed phrases or private keys are ever stored by Solaris AI Flow.

Next steps [#next-steps]

* [Dashboard Overview](/docs/getting-started/dashboard) - orient yourself


---

# Browsing the Marketplace (/docs/marketplace/browsing)

How to discover and explore workflow listings.



The Marketplace is where you discover pre-built workflows created by other users. Navigate to **Marketplace** in the sidebar. Browsing requires a signed-in account.

Layout [#layout]

* **Search bar** - filters the currently loaded listings by title, description, and tags
* **Category filters** - filter by category (AI, Automation, Data, Finance, Notifications, Social, Utility, etc.)
* **My Listings** - appears when you have published listings, including inactive listings
* **Grid/List toggle** - switch between card and compact table views
* **Listing grid** - cards showing available workflows

Listing cards [#listing-cards]

Each card shows:

| Field       | Description                          |
| ----------- | ------------------------------------ |
| Title       | Workflow name                        |
| Description | Short summary                        |
| Seller      | Creator's display name               |
| Price       | Cost in SOL (0 = free)               |
| Category    | Classification tag                   |
| Tags        | Searchable labels                    |
| Sales count | Number of purchases (shown when > 0) |

Node count, version, integration logos, seller information, and version history are shown on the listing detail page when you click a card.

Categories [#categories]

Available categories: AI, Automation, Data, DevOps, Finance, Marketing, Notifications, Social, Utility, Other. The category filter shows active listing categories. If you have listings, **My Listings** also appears so you can manage yours.

Seller info [#seller-info]

Seller stats (active listings, total sales, and more listings by that creator) are shown on the listing detail page. The marketplace grid shows the seller's display name on each card.

Next steps [#next-steps]

* [Purchasing](/docs/marketplace/purchasing) - buying a workflow
* [Listing a Workflow](/docs/marketplace/listing) - selling your own


---

# Marketplace (/docs/marketplace)

Buy and sell pre-built workflows on the Solaris AI Flow marketplace.



The marketplace is where you discover, purchase, and sell workflow automations. Sellers list their workflows with a price in SOL. Buyers receive a permanent clone in their workspace.

Marketplace browsing and listing details are available inside the signed-in dashboard.

Key concepts [#key-concepts]

* **Listings** are published snapshots of a seller's workflow. Secrets are stripped automatically.
* **Purchases** clone the workflow into your workspace. You can edit it freely, subject to your account's workflow and trigger limits.
* **Updates** let sellers push new versions. Buyers can pull updates into their clone.
* **0% platform fees during beta.** Sellers keep 100% of revenue.

Sections [#sections]

* [Browsing](/docs/marketplace/browsing) - discovering workflows
* [Purchasing](/docs/marketplace/purchasing) - buying and cloning
* [Listing a Workflow](/docs/marketplace/listing) - selling your own
* [Version Updates](/docs/marketplace/version-updates) - pushing and pulling updates


---

# Listing a Workflow (/docs/marketplace/listing)

How to publish your workflow on the marketplace.



Any authenticated user can list workflows on the marketplace. There is no plan restriction for listing.

Creating a listing [#creating-a-listing]

1. Save your workflow first so the listing captures the latest version
2. Open the workflow you want to sell in the editor
3. Navigate to the marketplace listing option
4. Fill in the required fields:

| Field         | Required | Description                                                                                |
| ------------- | -------- | ------------------------------------------------------------------------------------------ |
| Title         | Yes      | Listing name (max 200 characters)                                                          |
| Description   | Yes      | What the workflow does (max 2000 characters)                                               |
| Price         | Yes      | Price in SOL (0 for free, min non-zero: 0.001, max: 100 SOL)                               |
| Category      | Yes      | AI, Automation, Data, DevOps, Finance, Marketing, Notifications, Social, Utility, or Other |
| Tags          | No       | Up to 10 searchable tags                                                                   |
| Preview image | No       | Uploaded cover image shown on listing cards and detail pages                               |

5. Click **List Workflow**

What happens on publish [#what-happens-on-publish]

* A snapshot of your workflow's nodes and edges is stored
* Your workflow is marked as public (visible in marketplace)
* Secrets are stripped from the snapshot (credentials, API keys, chat IDs, etc.)
* A version 1 is created

Managing your listing [#managing-your-listing]

From the listing detail page (as seller):

* **Edit** - update title, description, price, category, tags
* **Update cover image** - upload, replace, or remove the listing image
* **Unlist** - hide from marketplace (existing buyers keep their clones)
* **Reactivate** - re-list with current workflow content. This refreshes the snapshot for new buyers but does not create a new version or notify previous buyers. Use Publish Update for versioned buyer-facing changes.
* **Publish Update** - push a new version with changelog (see [Version Updates](/docs/marketplace/version-updates))
* **Preview as buyer** - clone your own listing to verify what buyers receive (see below)

Preview as buyer [#preview-as-buyer]

Worried something sensitive might leak in your published workflow? Use **Preview as buyer** on your listing's detail page to clone it yourself, exactly the way a paying buyer would.

The preview clone runs through the same code path as a real purchase:

* Same published snapshot (not your live, editable workflow)
* Same field allowlist - only operational fields survive (URLs, prompts, model settings, etc.)
* Same secret stripping - credentials, API keys, signed tokens, Telegram chat IDs, private webhook URLs are all removed
* Same sample-payload redaction - if you pasted real data into a Sample Payload field, the keys are kept but every value is zeroed (strings → `""`, numbers → `0`, booleans → `false`)
* Same annotation handling - Sticky Notes and Arrows clone with their content, styling, and size; invalid edges touching annotations are cleaned up

The only differences between a preview and a real purchase:

* No SOL charge, even on paid listings
* The clone is named `<title> (preview)` instead of `(purchased)`
* The sales counter on your listing does not increment
* The preview does not appear in any buyer's purchase history

If you delete the preview clone and click the button again, it re-clones from the same snapshot. Open the preview, scroll every node, and check that nothing private survived. If something did, fix it in the source workflow and Publish Update - existing buyers will see the leak too until they pull the update.

Sticky Note text is included for buyers. Do not put API keys, private webhook URLs, wallet-sensitive notes, or non-public trading instructions in annotations.

Inactive listings can still be opened if you already created a preview, but you cannot create a new preview while inactive - reactivate first.

Revenue [#revenue]

During beta: **0% platform fees**. You keep 100% of the SOL paid by buyers, sent directly to your connected wallet.

One workflow, one listing [#one-workflow-one-listing]

Each workflow can have at most one marketplace listing. Use Edit to change metadata, Publish Update for versioned workflow changes, or Unlist to hide it.

Next steps [#next-steps]

* [Version Updates](/docs/marketplace/version-updates) - pushing updates to buyers
* [Plans and Limits](/docs/billing/plans) - pricing tiers


---

# Purchasing a Workflow (/docs/marketplace/purchasing)

How to buy a marketplace workflow with SOL.



When you purchase a marketplace listing, a copy of the workflow is cloned into your workspace. You receive a permanent clone that you can edit and run, subject to your account's workflow and trigger limits.

Purchase flow [#purchase-flow]

1. Open a listing detail page from the marketplace
2. Review the description, integrations used, and price
3. Click **Purchase** (or **Clone** for free listings)
4. For paid listings, Solaris AI Flow checks your embedded wallet balance. If you need more SOL, the button changes to **Fund Wallet** before purchase.
5. Solaris AI Flow charges your embedded wallet automatically. If confirmation is delayed, the purchase completes once the transaction lands on-chain.
6. The workflow appears in your dashboard as "\[title] (purchased)"

You cannot purchase your own listing.

Paid listings also need a small SOL buffer for network fees. If Solaris AI Flow cannot read your balance because the public RPC is unavailable, you may still continue; the server does a final payment check before fulfillment.

Price verification [#price-verification]

The price you see is locked when you click Purchase. If the seller changes the price between you viewing and confirming, the transaction is rejected and you're asked to review the new price.

What you get [#what-you-get]

* A full copy of the workflow (nodes, edges, layout)
* Operational fields are preserved (prompts, addresses, config)
* Secrets are stripped. You must supply your own credentials and destination config (chat IDs, webhook URLs, etc.)

What you don't get [#what-you-dont-get]

* Access to the seller's credentials or API keys
* The seller's Telegram chat IDs or webhook URLs
* Automatic updates (you must pull them manually)

After purchase [#after-purchase]

1. Open the cloned workflow from your dashboard
2. Add your own credentials to each integration node
3. Configure destination fields (Telegram chatId, etc.)
4. Test with a manual run

Duplicate protection [#duplicate-protection]

You can only purchase a listing once. Re-purchasing returns the existing clone.

If you delete the cloned workflow from your dashboard and revisit the listing, clicking through again will re-clone it from the same snapshot at no extra charge. Your original payment covers any future restore.

Workflow limits [#workflow-limits]

Purchases count toward your plan's workflow limit. If you're at the limit, upgrade or delete workflows before purchasing.

Next steps [#next-steps]

* [Version Updates](/docs/marketplace/version-updates) - pulling seller updates
* [Credentials](/docs/credentials) - adding API keys for purchased workflows


---

# Version Updates (/docs/marketplace/version-updates)

How sellers push updates and buyers pull them.



Marketplace listings support versioning. Sellers can push updates, and buyers can pull them into their cloned workflows.

For sellers: publishing an update [#for-sellers-publishing-an-update]

1. Make changes to your source workflow in the editor
2. Save the workflow
3. Go to your listing and click **Publish Update**
4. Write a changelog describing the changes (required, max 2000 characters)
5. The version number increments and the snapshot is updated

The changelog is visible to buyers in the version history.

For buyers: pulling an update [#for-buyers-pulling-an-update]

When a seller publishes a new version, your purchased workflow shows an "Update available" indicator.

1. Open the purchased workflow or check the marketplace listing
2. Click **Pull Update**
3. Your workflow's nodes and edges are overwritten with the seller's latest version

**Warning**: Pulling an update replaces your current nodes and edges. If you've made local modifications, they will be lost. There is no rollback.

Owner preview clones can also show update status, but they are for testing what buyers receive. They do not count as sales.

Version history [#version-history]

Both sellers and buyers can view the version history for a listing, showing:

* Version number
* Publish date
* Changelog text

What updates include [#what-updates-include]

* Node additions, removals, and configuration changes
* Edge connection changes
* Layout/viewport changes
* Sticky Notes and Arrows

What updates don't include [#what-updates-dont-include]

Seller credentials and destination config are never included in updates. Pulling an update replaces your cloned workflow's nodes and edges, so you may need to re-enter your own credentials and destination settings (chat IDs, webhook URLs) afterward.

Next steps [#next-steps]

* [Purchasing](/docs/marketplace/purchasing) - initial purchase flow
* [Listing a Workflow](/docs/marketplace/listing) - managing your listings


---

# Node Reference (/docs/nodes)

Overview of the growing node library available in Solaris AI Flow.



Solaris AI Flow provides a growing library of node types. These docs organize nodes into five reference categories (AI, DeFi, Data, Messaging, Utility). The editor sidebar uses a different grouping: Triggers, Integrations, Logic, and Utilities.

Most integration nodes require a [credential](/docs/credentials) to authenticate with the external service.

Categories [#categories]

| Category      | Nodes                                                                                        | Use case                                                                                               |
| ------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **AI**        | OpenRouter                                                                                   | LLM inference with 300+ models                                                                         |
| **DeFi**      | Jupiter, Raydium, Pump.fun, Orca, Meteora, Kamino, Sanctum, DFlow, Drift, Phoenix            | Swaps, quotes, pools, lending, perps trading                                                           |
| **Data**      | Birdeye, Helius, Pyth, CoinGecko, Polymarket, Dune, DefiTheOdds                              | Prices, on-chain data, analytics                                                                       |
| **Messaging** | Telegram, Discord                                                                            | Alerts and notifications                                                                               |
| **Utility**   | HTTP, x402, Balance, Storage, Log, Delay, Transform, Merge, Code, Filter, Condition, ForEach | Requests, paid requests, wallet reads, storage, data shaping, merging, control flow, batched iteration |

Credential requirements [#credential-requirements]

| Credential needed                   | Nodes                                                                                                                                                                                                                                                        |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Yes                                 | OpenRouter, Birdeye, Helius, CoinGecko, Dune, DefiTheOdds, Telegram, Discord                                                                                                                                                                                 |
| API key + wallet                    | Jupiter (API key required for all ops except transfer, wallet for swap/transfer)                                                                                                                                                                             |
| Wallet only                         | Pump.fun (buy, sell, createToken, claimFees, claimCashback); Balance; x402 when an endpoint requires payment; Phoenix account read-only ops (they read the owner's address); Phoenix paper-mode signed ops                                                   |
| Wallet + one-time invite activation | Phoenix Live signed ops (private-beta gate; activate the wallet **once** via the inline panel in the Phoenix node config dialog; subsequent Live signed ops broadcast without further prompts; no invite code is ever stored on the node or in a credential) |
| None                                | HTTP, Log, Delay, Transform, Merge, Code, Filter, Condition, ForEach, Pyth, Polymarket, Orca, Meteora, Sanctum, Kamino, DFlow, Drift, Raydium, Phoenix market read-only ops                                                                                  |

Canvas-only annotations [#canvas-only-annotations]

Sticky Note and Arrow are available in the editor for documenting a workflow on the canvas. They do not run, do not connect to edges, and are ignored by execution counts. Marketplace clones keep annotation text, direction, color, and size so buyers can read the author's notes.

Next steps [#next-steps]

Browse by category:

* **AI** - [OpenRouter](/docs/nodes/ai/openrouter)
* **DeFi** - [Jupiter](/docs/nodes/defi/jupiter), [Raydium](/docs/nodes/defi/raydium), [Pump.fun](/docs/nodes/defi/pump-fun), [Orca](/docs/nodes/defi/orca), [Meteora](/docs/nodes/defi/meteora), [Kamino](/docs/nodes/defi/kamino), [Sanctum](/docs/nodes/defi/sanctum), [DFlow](/docs/nodes/defi/dflow), [Drift](/docs/nodes/defi/drift), [Phoenix](/docs/nodes/defi/phoenix)
* **Data** - [Birdeye](/docs/nodes/data/birdeye), [Helius](/docs/nodes/data/helius), [Pyth](/docs/nodes/data/pyth), [CoinGecko](/docs/nodes/data/coingecko), [Polymarket](/docs/nodes/data/polymarket), [Dune](/docs/nodes/data/dune), [DefiTheOdds](/docs/nodes/data/defi-the-odds)
* **Messaging** - [Telegram](/docs/nodes/messaging/telegram), [Discord](/docs/nodes/messaging/discord)
* **Utility** - [HTTP](/docs/nodes/utility/http), [x402](/docs/nodes/utility/x402), [Balance](/docs/nodes/utility/balance), [Storage](/docs/nodes/utility/storage), [Log](/docs/nodes/utility/log), [Delay](/docs/nodes/utility/delay), [Transform](/docs/nodes/utility/transform), [Merge](/docs/nodes/utility/merge), [Code](/docs/nodes/utility/code), [Filter](/docs/nodes/utility/filter), [Condition](/docs/nodes/utility/condition), [ForEach](/docs/nodes/utility/foreach)


---

# FAQ (/docs/reference/faq)

Frequently asked questions about Solaris AI Flow.



General [#general]

**What is Solaris AI Flow?**

A no-code automation platform for Solana and Web3 workflows. Build visual workflows that connect on-chain data, DeFi protocols, AI models, Telegram, Discord, webhooks, and scheduled jobs.

**Is Solaris AI Flow open source?**

No. The platform is closed-source.

**Is there a mobile app?**

No. Solaris AI Flow is a web application optimized for desktop browsers.

**Which Solana network does it use?**

Mainnet. All transactions (swaps, transfers, payments) happen on Solana mainnet.

Access [#access]

**How do I get beta access?**

Solaris AI Flow is in open beta - there's no invite code or waitlist. Click **Enter App** and sign in with Google or email to get started.

**Do I need a wallet extension?**

No. Solaris AI Flow creates an embedded Privy wallet automatically on sign-up. All payments and on-chain operations use this embedded wallet. No Phantom, Backpack, or other extension needed.

Workflows [#workflows]

**How many workflows can I create?**

<PlanName plan="free" />: <PlanLimit plan="free" field="maxWorkflows" />. <PlanName plan="pro" />: <PlanLimit plan="pro" field="maxWorkflows" />. <PlanName plan="ultra" />: <PlanLimit plan="ultra" field="maxWorkflows" />. See [Plans](/docs/billing/plans).

**Is there auto-save?**

Yes. Workflows auto-save every minute when there are unsaved changes. You can also save manually with the Save button in the toolbar.

**Can workflows run in parallel?**

Only one execution per workflow at a time. However, different workflows can run simultaneously (up to your plan's concurrent limit).

**What's the execution time limit?**

10 minutes. Executions exceeding this are automatically marked as failed.

Marketplace [#marketplace]

**Who can list on the marketplace?**

Any authenticated user can create marketplace listings. There is no plan restriction.

**What percentage does Solaris AI Flow take?**

0% during beta. You keep 100% of revenue.

**What do buyers get when they purchase?**

A permanent clone of the workflow in their own workspace (nodes, edges, config). Buyers can edit it freely, subject to their account's workflow and trigger limits. Secrets are stripped, so buyers must supply their own credentials and destination config.

**Can I update my listing after publishing?**

Yes. Use "Publish Update" to push a new version with a changelog. Buyers can pull the update into their clone.

Billing [#billing]

**How does billing work?**

Each paid plan is a one-time SOL payment for a <CycleDays />-day cycle. The USD price is fixed; the SOL amount adjusts to the live exchange rate at checkout. Plans do not auto-renew, and Solaris AI Flow does not keep a card on file. When the cycle ends, your account returns to Free unless you purchase another cycle.

**What happens when my subscription expires?**

Your account reverts to the <PlanName plan="free" /> plan. Workflows exceeding <PlanName plan="free" /> limits remain but excess automated triggers are disabled.

**Can I get a refund?**

SOL transactions on Solana are irreversible. Contact support for billing disputes.

Security [#security]

**Are my API keys safe?**

Credentials are encrypted at rest. Decrypted keys are only accessed by the execution engine at runtime. They are never exposed in marketplace clones, public workflow views, or API responses.

**Does Solaris AI Flow have access to my private keys or wallet funds?**

No. Solaris AI Flow never stores or has access to your private keys. All wallet keys are secured and managed by Privy's infrastructure. The platform can only initiate transactions through workflow execution or payment flows you confirm.

Next steps [#next-steps]

* [Quickstart](/docs/getting-started/quickstart) - get started
* [Troubleshooting](/docs/reference/troubleshooting) - fixing common issues


---

# Reference (/docs/reference)

Technical reference, troubleshooting, and FAQ.



Technical details and support resources.

* [Webhook API](/docs/reference/webhook-api) - endpoint specification
* [Keyboard Shortcuts](/docs/reference/shortcuts) - command palette and canvas shortcuts
* [Troubleshooting](/docs/reference/troubleshooting) - fixes for common issues
* [FAQ](/docs/reference/faq) - frequently asked questions


---

# Keyboard Shortcuts (/docs/reference/shortcuts)

Keyboard shortcuts and command palette reference.



Command palette [#command-palette]

Open with **Cmd+K** (Mac) or **Ctrl+K** (Windows/Linux).

| Shortcut     | Action                   |
| ------------ | ------------------------ |
| Cmd/Ctrl + K | Open command palette     |
| N            | Create a new workflow    |
| G W          | Go to Workflows          |
| G E          | Go to Executions         |
| G C          | Go to Connections        |
| G M          | Go to Marketplace        |
| G B          | Go to Wallet and Billing |

Canvas shortcuts [#canvas-shortcuts]

These work when the workflow editor canvas is focused.

| Shortcut             | Action                         |
| -------------------- | ------------------------------ |
| Cmd/Ctrl + Z         | Undo                           |
| Cmd/Ctrl + Shift + Z | Redo                           |
| Cmd/Ctrl + Y         | Redo (alternative)             |
| Cmd/Ctrl + A         | Select all nodes               |
| Cmd/Ctrl + D         | Duplicate selected             |
| Cmd/Ctrl + C         | Copy selected nodes            |
| Cmd/Ctrl + X         | Cut selected nodes             |
| Cmd/Ctrl + V         | Paste copied nodes             |
| Delete / Backspace   | Delete selected nodes or edges |
| Escape               | Clear selection                |
| Cmd/Ctrl + 0         | Zoom to 100%                   |
| Cmd/Ctrl + 1         | Fit view                       |
| Shift + Alt + T      | Tidy up / auto-layout          |
| V                    | Select / move tool             |
| H                    | Hand / pan tool                |
| Arrow keys           | Move selected nodes            |

Executions panel shortcuts [#executions-panel-shortcuts]

These work when focus is inside the workflow editor's executions panel and not inside an input, textarea, or editable note.

| Shortcut | Action                                          |
| -------- | ----------------------------------------------- |
| /        | Open node-run search for the selected execution |
| J        | Select the next execution in the run list       |
| K        | Select the previous execution in the run list   |

Canvas interactions [#canvas-interactions]

| Action       | How                                    |
| ------------ | -------------------------------------- |
| Pan          | Click and drag on empty canvas         |
| Zoom         | Scroll wheel or pinch                  |
| Select node  | Click a node                           |
| Multi-select | Shift + click, or drag a selection box |
| Context menu | Right-click on canvas or node          |

Next steps [#next-steps]

* [Canvas Overview](/docs/editor/canvas) - editor layout and controls
* [Running a Workflow](/docs/editor/running) - executions panel behavior


---

# Troubleshooting (/docs/reference/troubleshooting)

Solutions for the 10 most common issues.



1\. Workflow won't save [#1-workflow-wont-save]

**Cause**: Network connectivity issue or session expiration.

**Fix**: Check your internet connection. Refresh the page and try saving again. If the issue persists, copy your node configuration and reload.

2\. Node shows credential error after adding credentials [#2-node-shows-credential-error-after-adding-credentials]

**Cause**: The node isn't linked to the credential, or the key is invalid.

**Fix**: Click the node, open settings, and select your credential from the **Parameters** pane. If already selected, re-open the credential in [Credentials](/docs/credentials) and verify the key is correct.

3\. Cron trigger not firing [#3-cron-trigger-not-firing]

**Cause**: Schedule is disabled, or plan doesn't support the frequency.

**Fix**: Check the **Activate** / **Active** control in the editor toolbar. <PlanName plan="free" /> plan only supports daily/weekly crons. Verify the cron configuration in the trigger node.

4\. Webhook not receiving requests [#4-webhook-not-receiving-requests]

**Cause**: Workflow not enabled, or incorrect URL.

**Fix**: Click **Activate** in the editor toolbar. Verify the URL includes the correct `workflowId` and `token` parameters. Test with curl.

5\. DeFi node failing with API error [#5-defi-node-failing-with-api-error]

**Cause**: Rate limiting, invalid parameters, or service outage.

**Fix**: Check the error message in the executions panel or run detail sheet. Use the failed-node filter or node mini-map to jump to the failing node. Common causes: invalid token address, amount too small for swap, API rate limit. Try again after a few seconds.

6\. Marketplace purchase succeeded but workflow not in dashboard [#6-marketplace-purchase-succeeded-but-workflow-not-in-dashboard]

**Cause**: Transaction confirmed but clone not yet visible.

**Fix**: Purchases are normally fulfilled immediately after on-chain confirmation. If the confirmation timed out during checkout, a background recovery job will complete it within a few minutes. Refresh your dashboard. Check the Wallet page to confirm the payment status.

7\. SOL balance not updating [#7-sol-balance-not-updating]

**Cause**: Balance is fetched from the Solana network and may have a short cache.

**Fix**: Refresh the wallet page. Recent transactions may take a few seconds to reflect.

8\. Can't connect wallet [#8-cant-connect-wallet]

**Cause**: Embedded wallet not created yet or browser issue.

**Fix**: Solaris AI Flow creates an embedded Privy wallet automatically on sign-up. If your wallet isn't showing, try signing out and back in. Clear browser cache and retry. If the issue persists, check that third-party cookies are not blocked for the Privy auth domain.

9\. Execution stuck in "running" state [#9-execution-stuck-in-running-state]

**Cause**: The execution action crashed mid-flight.

**Fix**: Stuck executions are automatically cleaned up after 10 minutes and marked as failed. No manual intervention needed.

Next steps [#next-steps]

* [FAQ](/docs/reference/faq) - general questions
* [Error Handling](/docs/executions/errors) - execution-level errors


---

# Webhook API (/docs/reference/webhook-api)

Technical reference for the webhook trigger endpoint.



The webhook endpoint triggers a workflow execution from an external HTTP request.

Endpoint [#endpoint]

The full webhook URL is displayed in the Webhook Trigger node settings, in the **Parameters** pane. Copy it from there. The URL includes the workflow ID and a token generated for that trigger node.

Authentication [#authentication]

Authentication is via the `token` query parameter. This is a random secret generated per webhook trigger node. Treat it like a password - anyone with the URL can trigger your workflow.

Request [#request]

**Method**: POST only

**Headers**:

| Header                | Required    | Description                                 |
| --------------------- | ----------- | ------------------------------------------- |
| Content-Type          | Recommended | `application/json` for parsed body          |
| X-Webhook-Delivery-Id | No          | Unique delivery ID for replay protection    |
| X-GitHub-Delivery     | No          | GitHub-specific delivery ID (auto-detected) |

**Body**: Up to 100 KB. JSON is recommended. If `Content-Type: application/json` is set, the body is parsed as JSON. Other content types are accepted and stored as raw text in a `body` field.

```bash
curl -X POST "<your-webhook-url>" \
  -H "Content-Type: application/json" \
  -d '{"token": "SOL", "price": 150.23}'
```

Replace `<your-webhook-url>` with the URL from your Webhook Trigger node settings.

Response [#response]

**Success (200)**:

```json
{
  "success": true,
  "executionId": "k57abc123def..."
}
```

**Deduplicated (200)**:

```json
{
  "success": true,
  "executionId": "k57abc123def...",
  "deduplicated": true
}
```

**Errors**:

| Status | Cause                                                                                                                                               |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Missing workflowId or token, invalid JSON, failed to read request body                                                                              |
| 403    | Invalid token, workflow not active (returns executionId), plan limit exceeded, webhook triggers not available on current plan                       |
| 404    | Workflow not found                                                                                                                                  |
| 413    | Request body too large (>100 KB)                                                                                                                    |
| 429    | Per-workflow rate limit exceeded. Response includes a `Retry-After` header (seconds) and a `retryAfter` field in the JSON body. Back off and retry. |

Replay protection [#replay-protection]

If the sender includes `X-Webhook-Delivery-Id` or `X-GitHub-Delivery`, Solaris AI Flow deduplicates requests scoped to the workflow + trigger node + delivery ID. Duplicate deliveries return 200 with `deduplicated: true`.

Generic senders without a delivery ID are not replay-protected. Every request creates a new execution.

Editor reruns [#editor-reruns]

The editor executions panel can run a webhook trigger again with the trigger's configured sample payload. It does not replay the original request body.

Activation required [#activation-required]

The webhook only fires after the workflow is activated from the editor toolbar. Inactive workflows return 403.

Plan limits [#plan-limits]

Webhook triggers are subject to plan-based quotas. <PlanName plan="free" /> plan: <PlanLimit plan="free" field="maxWebhookWorkflows" noun="active webhook workflow" />. <PlanName plan="pro" />/<PlanName plan="ultra" />: unlimited.

Next steps [#next-steps]

* [Webhook Trigger](/docs/triggers/webhook) - setup guide
* [Troubleshooting](/docs/reference/troubleshooting) - common webhook issues


---

# Cron Trigger (/docs/triggers/cron)

Run workflows on a recurring schedule.



A cron trigger runs your workflow automatically on a recurring schedule.

Setup [#setup]

1. Add a **Cron Trigger** node to your canvas
2. Click the node to open settings and configure the schedule in the **Parameters** pane
3. Add a Sample Payload if downstream nodes need seeded data for testing
4. Click **Activate** in the editor toolbar

Schedule options [#schedule-options]

| Schedule        | Frequency                   | Config fields                   |
| --------------- | --------------------------- | ------------------------------- |
| Every 5 minutes | `*/5 * * * *`               | None                            |
| Hourly          | `{minute} * * * *`          | Minute (0-59)                   |
| Daily           | `{minute} {hour} * * *`     | Hour (0-23), Minute (0-59)      |
| Weekly          | `{minute} {hour} * * {day}` | Day of week (0-6), Hour, Minute |

The schedule is stored as a standard 5-field cron expression.

Sample payload [#sample-payload]

Cron triggers can emit optional JSON seed data. Top-level object fields are available directly, such as `{cron.symbol}`. If you paste an array or primitive, the editor stores it wrapped as `{ "body": <value> }` and you access it via `{cron.body}`.

The settings **Input** pane previews the sample payload while you edit it. If you rerun a cron execution from the editor's executions panel, Solaris AI Flow fires the cron trigger using its current configuration.

Output [#output]

Every cron fire emits at minimum:

```json
{
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "cron"
}
```

With a Sample Payload such as `{ "watchlist": ["SOL", "BONK"] }`, the trigger output becomes:

```json
{
  "watchlist": ["SOL", "BONK"],
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "cron"
}
```

Downstream nodes reference fields with the trigger response name (default `cron`): `{cron.watchlist}`, `{cron.triggeredAt}`. The base fields (`triggeredAt`, `source`) always win on key collision.

Day-of-week mapping [#day-of-week-mapping]

For weekly schedules, `dayOfWeek` follows the standard cron convention: `0` = Sunday, `1` = Monday, ..., `6` = Saturday.

Enabling and disabling [#enabling-and-disabling]

Toggle the workflow from:

* The editor toolbar (**Activate** / **Active**)
* The mobile actions menu (**Activate Schedule** / **Deactivate Schedule**)
* The `updateSchedule` action on the workflow

When disabled, the cron stops firing but the configuration is preserved.

Plan restrictions [#plan-restrictions]

| Plan  | Allowed frequencies                 |
| ----- | ----------------------------------- |
| Free  | Daily, Weekly only                  |
| Pro   | All (including every 5 min, hourly) |
| Ultra | All                                 |

If you downgrade from Pro to Free with an hourly cron active, it's automatically disabled with a deactivation reason shown in the editor.

Timezone [#timezone]

Cron schedules run in UTC. Configure the hour/minute relative to UTC.

Next steps [#next-steps]

* [Executions](/docs/concepts/executions) - what happens when a cron fires
* [Plans and Limits](/docs/billing/plans) - trigger quotas by plan


---

# Triggers (/docs/triggers)

The three trigger types that start workflow executions.



Every workflow needs a trigger to start. Solaris AI Flow supports three types.

* [Manual](/docs/triggers/manual) - click Run to execute
* [Webhook](/docs/triggers/webhook) - triggered by external HTTP POST
* [Cron](/docs/triggers/cron) - runs on a recurring schedule


---

# Manual Trigger (/docs/triggers/manual)

Run workflows on demand by clicking a button.



The simplest trigger. You click **Run** and the workflow executes immediately.

When to use [#when-to-use]

* Testing and debugging workflows during development
* One-off tasks that don't need automation
* Human-in-the-loop flows where you decide when to run

Configuration [#configuration]

The Manual Trigger node can be configured with:

| Field          | Description                                           |
| -------------- | ----------------------------------------------------- |
| Trigger Name   | Label shown on the canvas                             |
| Description    | Optional note for the trigger                         |
| Sample Payload | Optional JSON seed data emitted when you run manually |

Use Sample Payload when downstream nodes expect data before you have a webhook or scheduled source. Top-level object fields are available directly, such as `{trigger.token}`. If you paste an array or primitive, the editor stores it wrapped as `{ "body": <value> }` and you access it via `{trigger.body}`.

Output [#output]

Every manual run emits at minimum:

```json
{
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "manual"
}
```

With a Sample Payload such as `{ "token": "SOL", "amount": 1.5 }`, the trigger output becomes:

```json
{
  "token": "SOL",
  "amount": 1.5,
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "manual"
}
```

Downstream nodes reference fields with the trigger response name (default `trigger`): `{trigger.token}`, `{trigger.triggeredAt}`, `{trigger.source}`. The base fields (`triggeredAt`, `source`) always win on key collision, so naming a payload field `source` is shadowed.

Running [#running]

Two ways to trigger a manual run:

1. **From the editor** - click the Run button in the top toolbar
2. **From the dashboard** - use the Run action on a workflow card

In execution history [#in-execution-history]

Manual runs show `manual` as the trigger source. They count toward your monthly execution limit.

Next steps [#next-steps]

* [Webhook Trigger](/docs/triggers/webhook) - automate with external events
* [Cron Trigger](/docs/triggers/cron) - automate on a schedule


---

# Webhook Trigger (/docs/triggers/webhook)

Trigger workflows from external services via HTTP POST.



A webhook trigger generates a unique URL. When an external service sends a POST request to that URL, your workflow executes.

Setup [#setup]

1. Add a **Webhook Trigger** node to your canvas
2. A unique webhook token is generated automatically
3. Click the node to open settings, then copy the full webhook URL from the **Parameters** pane.
4. Paste the URL into your external service's webhook settings

Configuration [#configuration]

| Field          | Description                                                            |
| -------------- | ---------------------------------------------------------------------- |
| Trigger Label  | Display name shown on the canvas                                       |
| Route Key      | Optional helper key for routing payloads through downstream conditions |
| Webhook URL    | Generated URL to copy into the sending app                             |
| Sample Payload | Optional JSON example used for manual testing and previews             |

The settings **Input** pane previews the current sample payload. Downstream nodes can use that sample while you build and test the workflow without sending a real webhook request.

Activating [#activating]

The webhook only fires after you click **Activate** in the editor toolbar. Inactive workflows reject webhook deliveries.

Payload [#payload]

Send a JSON body with `Content-Type: application/json`. The payload is stored on the execution record.

```json
{
  "token": "SOL",
  "price": 150.23,
  "alert": "price_above_threshold"
}
```

Non-JSON content types are also accepted and stored as raw text.

The webhook trigger output includes the request data plus metadata. For object JSON bodies, fields are available directly:

```text
{webhook.token}
{webhook.price}
{webhook.alert}
{webhook.triggeredAt}
{webhook.source}
```

Arrays, primitive JSON values, and non-JSON bodies are wrapped under `body`, so use `{webhook.body}`.

For the JSON request above, the full trigger output is:

```json
{
  "token": "SOL",
  "price": 150.23,
  "alert": "price_above_threshold",
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "webhook"
}
```

For a non-object body such as `[1, 2, 3]` or `"hello"`, the output is:

```json
{
  "body": [1, 2, 3],
  "triggeredAt": "2026-05-14T10:00:00.000Z",
  "source": "webhook"
}
```

The base fields (`triggeredAt`, `source`) always win on key collision - a payload field named `source` will be shadowed by the trigger's own `"webhook"` value.

Testing and reruns [#testing-and-reruns]

Use the trigger's **Sample Payload** while configuring downstream nodes. In the editor's executions panel, webhook reruns use this configured sample payload; they do **not** replay the original webhook delivery body.

Request limits [#request-limits]

* **Max body size**: 100KB
* **Content type**: `application/json` (parsed) or any other (stored as raw string)
* **Method**: POST only

Replay protection [#replay-protection]

If the sender includes an `X-Webhook-Delivery-Id` or `X-GitHub-Delivery` header, Solaris AI Flow deduplicates requests with the same ID. Generic senders without a delivery ID are not replay-protected.

Plan limits [#plan-limits]

| Plan  | Webhook workflows |
| ----- | ----------------- |
| Free  | 1 active          |
| Pro   | Unlimited         |
| Ultra | Unlimited         |

Next steps [#next-steps]

* [Cron Trigger](/docs/triggers/cron) - schedule-based automation
* [Webhook API Reference](/docs/reference/webhook-api) - technical endpoint details


---

# Wallet (/docs/wallet)

Your embedded Solana wallet, balance, and transaction history.



The Wallet page shows your embedded Privy wallet details. Navigate to **Wallet** in the sidebar.

What's shown [#whats-shown]

* **Wallet address** - your Solana address (click to copy)
* **SOL balance** - live balance from the Solana network
* **USDC balance** - useful for x402 payments and other USDC-based flows
* **Plan and usage** - current plan, runs used, days remaining
* **Transaction history** - past payments (marketplace purchases, subscriptions)

Wallet actions [#wallet-actions]

* **Fund** - add SOL to your wallet
* **Export** - export your wallet for use elsewhere
* **Explorer** - view your wallet on a Solana block explorer
* **Copy address / QR** - share the wallet address for deposits

Embedded wallet [#embedded-wallet]

Your wallet is created automatically by Privy on sign-up. Solaris AI Flow never stores or has access to your private keys. All keys are secured by Privy's infrastructure.

Next steps [#next-steps]

* [Plans and Limits](/docs/billing/plans) - subscription tiers
* [Purchasing a Workflow](/docs/marketplace/purchasing) - using SOL to buy workflows


---

# Birdeye (/docs/nodes/data/birdeye)

Token prices, analytics, trades, and portfolio data via Birdeye.



Birdeye is Solana's leading token analytics platform. The Birdeye node provides real-time prices, token overviews, security audits, OHLCV data, trending tokens, wallet portfolio and PnL tracking, holder analytics (profile + raw list), and smart-money token discovery.

Prerequisites [#prerequisites]

* Birdeye API key ([birdeye.so](https://birdeye.so))
* Add as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

Each operation lists the minimum Birdeye package required.
See [Birdeye Data Accessibility by Packages](https://docs.birdeye.so/docs/data-accessibility-by-packages) for the full matrix.

| Operation                | Min. tier     | Description                                                                            | Key inputs                                             |
| ------------------------ | ------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| getPrice                 | Free/Standard | Get current price for a token                                                          | address                                                |
| getMultiPrice            | Lite/Starter  | Get prices for multiple tokens                                                         | listAddresses                                          |
| getTokenOverview         | Free/Standard | Get detailed token overview                                                            | address                                                |
| getTokenSecurity         | Lite/Starter  | Get token security audit data                                                          | address                                                |
| getOHLCV                 | Free/Standard | Get OHLCV candlestick data                                                             | address, timeframe                                     |
| getTrendingTokens        | Free/Standard | List trending tokens                                                                   | sortBy, sortType, limit                                |
| getNewListings           | Free/Standard | List newly listed tokens                                                               | limit                                                  |
| getTokenTrades           | Free/Standard | Get recent trades for a token                                                          | address, txType, limit                                 |
| getWalletPortfolio       | Lite/Starter  | Get wallet's token holdings (deprecated by Birdeye, prefer `getWalletCurrentNetWorth`) | walletAddress                                          |
| getWalletCurrentNetWorth | Free/Standard | Current portfolio with total value (v2 replacement for `getWalletPortfolio`)           | walletAddress                                          |
| getWalletNetWorthDetails | Free/Standard | Per-asset breakdown of a wallet's net worth                                            | walletAddress                                          |
| getWalletPnlSummary      | Free/Standard | Realized/unrealized PnL for a wallet over a duration window                            | walletAddress, duration                                |
| getHolderProfile         | Free/Standard | Holder breakdown by tag (bundler, sniper, insider, dev, smart\_trader)                 | address, uiAmountMode, includeZeroBalance              |
| getTokenHolder           | Free/Standard | Paginated raw list of token holders                                                    | address, limit, offset, uiAmountMode                   |
| getSmartMoneyTokenList   | Premium       | Tokens surfaced by smart-money traders, segmented by style                             | interval, traderStyle, sortBy, sortType, limit, offset |

Configuration [#configuration]

| Field              | Type    | Required                                                                          | Description                                                                     |
| ------------------ | ------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| address            | string  | For most ops                                                                      | Token contract address                                                          |
| listAddresses      | string  | For getMultiPrice                                                                 | Comma-separated addresses                                                       |
| walletAddress      | string  | For all wallet ops (Portfolio, Current Net Worth, Net Worth Details, PnL Summary) | Wallet to query                                                                 |
| timeframe          | string  | No                                                                                | OHLCV: `1m`, `5m`, `15m`, `1H`, `4H`, `1D` (default: `1H`)                      |
| timeFrom           | number  | No                                                                                | OHLCV start time (Unix seconds)                                                 |
| timeTo             | number  | No                                                                                | OHLCV end time (Unix seconds)                                                   |
| sortBy             | string  | No                                                                                | Sort field: `rank`, `volume24h`, etc.                                           |
| sortType           | string  | No                                                                                | `asc` or `desc`                                                                 |
| txType             | string  | No                                                                                | Trade type filter: `swap`, `all` (default: `swap`)                              |
| limit              | number  | No                                                                                | Results limit (default: 20; Smart Money max: 20; Token Holder max: 100)         |
| offset             | number  | No                                                                                | Pagination offset (max 10,000; Token Holder requires offset + limit ≤ 10,000)   |
| duration           | string  | No                                                                                | Wallet PnL window: `all`, `90d`, `30d`, `7d`, `24h` (default: `all`)            |
| interval           | string  | No                                                                                | Smart Money window: `1d`, `7d`, `30d` (default: `1d`)                           |
| traderStyle        | string  | No                                                                                | Smart Money filter: `all`, `risk_averse`, `risk_balancers`, `trenchers`         |
| uiAmountMode       | string  | No                                                                                | `raw` or `scaled`. Token Holder default `scaled`; Holder Profile default `raw`. |
| includeZeroBalance | boolean | No                                                                                | Holder Profile: include zero-balance holders (default: `true`)                  |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getPrice",
  "address": "So11111111111111111111111111111111111111112",
  "data": { /* raw Birdeye response */ }
}
```

Most ops echo the input identifier (`address`, `walletAddress`, etc.) at the top level so downstream nodes can reference what was queried without parsing the request. The upstream payload is always under `.data`. Reference fields downstream with the response name (default `birdeyeResponse`), for example `{birdeyeResponse.data.value}` for `getPrice` or `{birdeyeResponse.data.items[0].symbol}` for `getTrendingTokens`.

Common use cases [#common-use-cases]

* Monitor token prices and alert on thresholds
* Scan for trending tokens and analyze with AI
* Track wallet portfolio changes over time

Next steps [#next-steps]

* [Helius](/docs/nodes/data/helius) - on-chain asset and transaction data
* [CoinGecko](/docs/nodes/data/coingecko) - alternative market data


---

# CoinGecko (/docs/nodes/data/coingecko)

Token prices, pool data, OHLCV, trending tokens, and on-chain analytics via CoinGecko.



CoinGecko provides comprehensive crypto market data. The CoinGecko node supports 25+ operations covering token prices, pool analytics, OHLCV charts, trending data, holder analysis, and DEX information.

Prerequisites [#prerequisites]

* CoinGecko API key ([coingecko.com/api](https://www.coingecko.com/en/api))
* Add as a credential in [Credentials](/docs/credentials/adding)
* Set the `planType` field in your credential (`demo`, `analyst`, `lite`, or `pro`)

Operations [#operations]

| Operation            | Description                        | Key inputs                                 |
| -------------------- | ---------------------------------- | ------------------------------------------ |
| getTokenPrice        | Get price for a token by address   | address                                    |
| getBatchTokenPrice   | Get prices for multiple tokens     | addresses                                  |
| getTokenInfo         | Get token metadata and stats       | address                                    |
| getMultiTokenInfo    | Get info for multiple tokens       | addresses                                  |
| getPoolInfo          | Get pool details                   | poolAddress                                |
| getMultiPoolInfo     | Get multiple pool details          | poolAddresses                              |
| getTrendingPools     | List trending pools                | duration, page                             |
| getTopPools          | List top pools by volume           | page                                       |
| getNewPools          | List newly created pools           | page                                       |
| searchPools          | Search pools by keyword            | searchQuery, page                          |
| megafilterPools      | Advanced pool filtering            | dexId, minLiquidity, minVolume, sortBy     |
| getPoolOHLCV         | Get pool OHLCV candles             | poolAddress, timeframe, aggregate, limit   |
| getTokenOHLCV        | Get token OHLCV candles            | address, timeframe, aggregate, limit       |
| getRecentTrades      | Get recent trades for a pool       | poolAddress                                |
| listDexes            | List all tracked DEXes             | page                                       |
| getTopPoolsByDex     | List top pools on a specific DEX   | dexId, page                                |
| getPoolTokenInfo     | Get token info from pool context   | poolAddress                                |
| getTokenSecurityInfo | Get token security score           | address                                    |
| getTopPoolsByToken   | Get top pools for a token          | address, page                              |
| getTokenTrades       | Get trades for a token             | address                                    |
| getTokenHoldersChart | Get holder distribution chart      | address, holdersChartDays                  |
| getTopTokenHolders   | Get top token holders              | address, holdersCount                      |
| getTopTokenTraders   | Get top traders for a token        | address, tradersCount                      |
| getCoinOHLC          | Get coin OHLC data by CoinGecko ID | coinId, vsCurrency, ohlcDays, ohlcInterval |

Configuration [#configuration]

| Field            | Type   | Required                 | Description                                                                         |
| ---------------- | ------ | ------------------------ | ----------------------------------------------------------------------------------- |
| API Key          | select | Yes                      | Your CoinGecko credential from Connections                                          |
| Response Name    | text   | Yes                      | Name used by downstream nodes, such as `{coingeckoResponse.data}`                   |
| address          | string | For most ops             | Token contract address                                                              |
| addresses        | string | For batch ops            | Comma-separated token addresses. Demo supports fewer addresses than paid plans      |
| poolAddress      | string | For pool ops             | Pool contract address                                                               |
| poolAddresses    | string | For multi pool           | Comma-separated pool addresses                                                      |
| searchQuery      | string | For searchPools          | Search keyword                                                                      |
| coinId           | string | For getCoinOHLC          | CoinGecko coin ID (e.g., `solana`)                                                  |
| dexId            | string | No                       | DEX identifier                                                                      |
| vsCurrency       | string | No                       | Quote currency (default: `usd`)                                                     |
| timeframe        | string | No                       | Candle timeframe: `minute`, `hour`, `day`                                           |
| aggregate        | number | No                       | OHLCV aggregation. Minute supports 1, 5, 15; hour supports 1, 4, 12; day supports 1 |
| ohlcvCurrency    | select | No                       | OHLCV currency: USD or token                                                        |
| beforeTimestamp  | number | No                       | Unix timestamp used to page OHLCV candles backward                                  |
| duration         | select | No                       | Trending duration: 5m, 1h, 6h, or 24h                                               |
| page             | number | No                       | Pagination page. Demo plan supports up to page 10                                   |
| limit            | number | No                       | OHLCV candle limit, max 1000                                                        |
| sortBy           | string | No                       | Sort field for Megafilter Pools                                                     |
| topPoolsSort     | select | No                       | Top Pools sort: default, highest volume, or most transactions                       |
| minVolume        | number | No                       | Minimum volume filter                                                               |
| minLiquidity     | number | No                       | Minimum liquidity filter                                                            |
| holdersChartDays | select | For getTokenHoldersChart | 7 days, 30 days, or Max                                                             |
| holdersCount     | number | For getTopTokenHolders   | Number of holders to return, max 40                                                 |
| tradersCount     | number | For getTopTokenTraders   | Number of traders to return, max 50                                                 |
| ohlcDays         | select | For getCoinOHLC          | 1, 7, 14, 30, 90, 180, 365, or max depending on interval and plan                   |
| ohlcInterval     | select | No                       | Auto, daily, or hourly. Daily/hourly require a paid plan                            |

Include options [#include-options]

Several operations expose checkboxes that add richer data to the response:

| Option                           | Available on                      | Description                                   |
| -------------------------------- | --------------------------------- | --------------------------------------------- |
| Market Cap                       | getTokenPrice, getBatchTokenPrice | Adds market cap fields                        |
| 24h Volume                       | getTokenPrice, getBatchTokenPrice | Adds 24h volume                               |
| 24h Price Change %               | getTokenPrice, getBatchTokenPrice | Adds 24h price change                         |
| FDV Fallback for Market Cap      | getTokenPrice, getBatchTokenPrice | Uses FDV when market cap is unavailable       |
| Total Reserve (USD)              | getTokenPrice, getBatchTokenPrice | Adds reserve value                            |
| Pool Composition                 | getTokenInfo, getPoolInfo         | Adds related pool composition data            |
| Include Inactive Sources         | getTokenInfo                      | Includes inactive token sources               |
| Volume Breakdown                 | getPoolInfo                       | Adds detailed pool volume breakdown           |
| Include Pool Context             | getPoolTokenInfo                  | Adds pool context to token info               |
| Include Community Sentiment Data | getTrendingPools                  | Adds CoinGecko community sentiment data       |
| Include PnL Details              | getTopTokenHolders                | Adds holder profit/loss detail when available |

Plan limits matter on some CoinGecko operations. Demo credentials use the demo API endpoint, paid plans use the pro endpoint, and some fields such as OHLC interval, `max` OHLC days, holder charts, top holders, and top traders may require a paid CoinGecko plan.

Output [#output]

CoinGecko returns a JSON:API envelope of the form `{ data, included?, meta? }`. The node unwraps it so downstream references don't need to traverse `data.data`: the inner `data` value is exposed at `.data`, and `included` / `meta` are surfaced as siblings when present. A typical response:

```json
{
  "success": true,
  "operation": "getTokenPrice",
  "address": "So11111111111111111111111111111111111111112",
  "data": { /* inner JSON:API data (object or array) */ }
}
```

Operations that take an `address` echo it at the top level. Reference fields with the response name (default `coingeckoResponse`), for example `{coingeckoResponse.data.attributes.token_prices.<address>}` for `getTokenPrice` or `{coingeckoResponse.data.attributes.name}` for `getTokenInfo`.

Common use cases [#common-use-cases]

* Comprehensive market data for multi-protocol analysis
* Track new pool launches and trending tokens
* Analyze holder distribution and whale movements

Next steps [#next-steps]

* [Birdeye](/docs/nodes/data/birdeye) - Solana-specific token analytics
* [Dune](/docs/nodes/data/dune) - custom SQL queries on blockchain data


---

# DefiTheOdds (/docs/nodes/data/defi-the-odds)

Enriched OHLCV with 40+ indicators and ML market regime scores via DefiTheOdds.



DefiTheOdds provides enriched candle data for crypto assets - OHLCV augmented with 40+ technical indicators and ML-driven market regime scores. The node supports hourly and daily timeframes.

Prerequisites [#prerequisites]

* DefiTheOdds API key - [free tier available](https://defitheodds.xyz/#pricing)
* Add as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

| Operation | Description            | Key inputs      |
| --------- | ---------------------- | --------------- |
| getHourly | Get hourly candle data | ticker, candles |
| getDaily  | Get daily candle data  | ticker, candles |

Configuration [#configuration]

| Field   | Type   | Required | Description                                                   |
| ------- | ------ | -------- | ------------------------------------------------------------- |
| ticker  | string | Yes      | Asset ticker (e.g., `SOL`, `BTC`)                             |
| candles | number | No       | Number of candles to return (default: varies, capped at 1000) |

Output [#output]

```json
{
  "success": true,
  "operation": "getHourly",
  "ticker": "SOL",
  "data": [
    { "timestamp": 1712000000, "open": 149.5, "high": 151.2, "low": 148.8, "close": 150.3 }
  ]
}
```

Common use cases [#common-use-cases]

* Feed candle data into AI nodes for technical analysis
* Compare DefiTheOdds signals with Birdeye/CoinGecko prices
* Build alerts on candle patterns

Next steps [#next-steps]

* [Birdeye](/docs/nodes/data/birdeye) - token price analytics
* [OpenRouter](/docs/nodes/ai/openrouter) - AI-powered analysis


---

# Dune (/docs/nodes/data/dune)

Execute SQL queries and retrieve blockchain analytics via Dune.



Dune provides SQL-based blockchain analytics. The Dune node lets you execute existing queries, check execution status, and retrieve results programmatically.

Prerequisites [#prerequisites]

* Dune API key ([dune.com/settings/api](https://dune.com/settings/api))
* Requires Plus plan or higher for API access
* Add as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

| Operation           | Description                              | Key inputs                            |
| ------------------- | ---------------------------------------- | ------------------------------------- |
| getQueryResults     | Get cached results for a query           | queryId, limit                        |
| executeQuery        | Execute a query with optional parameters | queryId, queryParameters, performance |
| getExecutionStatus  | Check status of a running execution      | executionId                           |
| getExecutionResults | Get results from a completed execution   | executionId, limit                    |
| cancelExecution     | Cancel a running execution               | executionId                           |

Configuration [#configuration]

| Field           | Type   | Required          | Description                                   |
| --------------- | ------ | ----------------- | --------------------------------------------- |
| queryId         | string | For query ops     | Dune query ID (numeric)                       |
| executionId     | string | For execution ops | Execution ID from executeQuery                |
| queryParameters | string | No                | JSON string of query parameters               |
| performance     | string | No                | `medium` or `large` (default: `medium`)       |
| limit           | number | No                | Max rows to return (default: 100, max: 10000) |
| offset          | number | No                | Row offset for pagination                     |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getQueryResults",
  "queryId": "1234567",
  "data": { /* raw Dune response */ }
}
```

Query-result operations expose rows under `data.result.rows`. Reference fields downstream with the response name (default `duneResponse`), for example `{duneResponse.data.result.rows[0].volume_usd}`. Execution-state operations (`getExecutionStatus`, `getExecutionResults`, `cancelExecution`, `executeQuery`) echo `executionId` instead of `queryId`.

Common use cases [#common-use-cases]

* Run scheduled blockchain analytics queries
* Feed Dune data into AI for automated reporting
* Monitor specific on-chain metrics via custom SQL

Next steps [#next-steps]

* [CoinGecko](/docs/nodes/data/coingecko) - pre-built market data endpoints
* [Helius](/docs/nodes/data/helius) - real-time on-chain data


---

# Helius (/docs/nodes/data/helius)

On-chain asset data, transactions, balances, and DAS API via Helius.



Helius provides deep Solana on-chain data through the Digital Asset Standard (DAS) API and enhanced RPC methods. The Helius node covers asset lookups, token accounts, transaction parsing, and priority fee estimation.

Prerequisites [#prerequisites]

* Helius API key ([helius.dev](https://helius.dev))
* Add as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

| Operation               | Description                                                                                                                                                                | Key inputs                    |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| getAsset                | Get asset details by ID                                                                                                                                                    | mintAddress                   |
| getAssetsByOwner        | Get all assets owned by a wallet                                                                                                                                           | address                       |
| getAssetsByGroup        | Get assets by group (e.g., collection)                                                                                                                                     | groupKey, groupValue          |
| searchAssets            | Search assets with filters                                                                                                                                                 | address, tokenType            |
| getTokenAccounts        | Get token accounts for a wallet                                                                                                                                            | address                       |
| getTokenLargestAccounts | Get top-20 holders of a mint, aggregated by owner wallet (with raw token-account rows alongside). Subject to Helius's 5M-account cap - works for memecoins, not USDC/wSOL. | mintAddress                   |
| getBalance              | Get SOL balance                                                                                                                                                            | address                       |
| getAccountInfo          | Get raw account data                                                                                                                                                       | address                       |
| getSignaturesForAddress | Get transaction signatures                                                                                                                                                 | address                       |
| getBlock                | Get block data by slot                                                                                                                                                     | slot                          |
| getTokenAccountBalance  | Get token account balance                                                                                                                                                  | address                       |
| getFungibleTokens       | Get fungible token holdings                                                                                                                                                | address                       |
| getAssetBatch           | Get multiple assets at once                                                                                                                                                | mintAddress (comma-separated) |
| getAssetProof           | Get Merkle proof for compressed NFT                                                                                                                                        | mintAddress                   |
| getTransactionHistory   | Get parsed transaction history                                                                                                                                             | address                       |
| parseTransactions       | Parse raw transaction signatures                                                                                                                                           | transactionSignatures         |
| getPriorityFeeEstimate  | Get priority fee estimate                                                                                                                                                  | accountKeys                   |

Configuration [#configuration]

| Field                 | Type   | Required              | Description                                                                       |
| --------------------- | ------ | --------------------- | --------------------------------------------------------------------------------- |
| mintAddress           | string | Varies                | Asset mint address, comma-separated list, or JSON array for batch lookups         |
| address               | string | Varies                | Wallet or account address                                                         |
| groupKey              | string | For getAssetsByGroup  | Group key (e.g., `collection`)                                                    |
| groupValue            | string | For getAssetsByGroup  | Group value (collection address)                                                  |
| slot                  | number | For getBlock          | Solana slot number                                                                |
| transactionSignatures | string | For parseTransactions | Comma-separated signatures or JSON array                                          |
| accountKeys           | string | No                    | Optional comma-separated account keys for priority fee estimates                  |
| tokenType             | string | No                    | Search filter: `all`, `fungible`, `nonFungible`, `regularNft`, or `compressedNft` |
| limit                 | number | No                    | Results limit for list operations                                                 |

Output [#output]

Most operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getAsset",
  "data": { /* raw Helius response */ }
}
```

Operations that echo the input identifier add it at the top level (`address`, `mintAddress`, `slot`). Reference fields downstream with the response name (default `heliusResponse`), for example `{heliusResponse.data.id}` or `{heliusResponse.data.value}` for `getBalance`.

`getTokenLargestAccounts` is the one exception - it returns aggregated `holders` and the raw `tokenAccounts` rows alongside, not under `.data`:

```json
{
  "success": true,
  "operation": "getTokenLargestAccounts",
  "mint": "DezX...",
  "holders": [{ "owner": "Wallet1...", "amount": "1000000" }],
  "tokenAccounts": [{ "address": "Account1...", "amount": "1000000" }]
}
```

Common use cases [#common-use-cases]

* Track NFT collection holdings across wallets
* Parse and analyze historical transactions
* Estimate priority fees for time-sensitive workflows

Next steps [#next-steps]

* [Birdeye](/docs/nodes/data/birdeye) - token price analytics
* [Pyth](/docs/nodes/data/pyth) - oracle price feeds


---

# Polymarket (/docs/nodes/data/polymarket)

Prediction market data, orderbooks, prices, and trade history via Polymarket.



Polymarket is a leading prediction market platform. The Polymarket node provides market discovery, orderbook data, pricing, and trade history.

Prerequisites [#prerequisites]

No credential required. All operations use Polymarket's public APIs.

Operations [#operations]

| Operation        | Description                    | Key inputs                 |
| ---------------- | ------------------------------ | -------------------------- |
| getMarkets       | List prediction markets        | searchQuery, sortBy, limit |
| getEvents        | List prediction events         | searchQuery, limit         |
| getMarketBySlug  | Get a market by its URL slug   | marketSlug                 |
| getOrderBook     | Get orderbook for a token      | tokenId                    |
| getPrice         | Get current price for a token  | tokenId                    |
| getMidpoint      | Get midpoint price for a token | tokenId                    |
| getPriceHistory  | Get historical price data      | tokenId, interval          |
| getActiveMarkets | List currently active markets  | limit                      |

Configuration [#configuration]

| Field       | Type   | Required            | Description                                                                |
| ----------- | ------ | ------------------- | -------------------------------------------------------------------------- |
| tokenId     | string | For price/book ops  | Polymarket token ID                                                        |
| marketSlug  | string | For getMarketBySlug | Market URL slug                                                            |
| searchQuery | string | No                  | Search keyword (max 200 chars)                                             |
| sortBy      | string | No                  | Sort: Volume 24h, Volume 1w, Volume 1m, Liquidity, Start Date, or End Date |
| sortType    | string | No                  | Uses descending order for market search                                    |
| side        | string | No                  | Order side: `buy` or `sell`                                                |
| interval    | string | No                  | Price history interval: `1d`, `1w`, `1m`, `3m`, `6m`, `1y`, or `all`       |
| limit       | number | No                  | Results limit (default: 20, max: 100)                                      |

Price history uses a fixed 60-minute fidelity. Choose the interval in the node; there is no separate fidelity field in the UI.

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getPrice",
  "data": { /* raw Polymarket response */ }
}
```

Reference fields downstream with the response name (default `polymarketResponse`), for example `{polymarketResponse.data.price}` for `getPrice` or `{polymarketResponse.data[0].question}` for `getMarkets`. The upstream Polymarket payload is always under `.data`.

Common use cases [#common-use-cases]

* Monitor election and crypto event prediction odds
* Track price movements on specific outcome tokens
* Combine with AI to generate prediction market analysis

Next steps [#next-steps]

* [DFlow](/docs/nodes/defi/dflow) - Solana-native prediction markets
* [OpenRouter](/docs/nodes/ai/openrouter) - AI analysis of market data


---

# Pyth (/docs/nodes/data/pyth)

Oracle price feeds, search, and historical prices via Pyth Network.



Pyth Network provides high-frequency oracle price feeds used by Solana DeFi protocols. The Pyth node supports feed discovery, real-time prices, and historical price lookups.

Prerequisites [#prerequisites]

No credential required. All operations use Pyth's public Hermes API.

Operations [#operations]

| Operation          | Description                       | Key inputs             |
| ------------------ | --------------------------------- | ---------------------- |
| searchFeeds        | Search for price feeds by keyword | searchQuery, assetType |
| getLatestPrices    | Get latest prices for feed(s)     | feedIds                |
| getHistoricalPrice | Get price at a specific timestamp | feedIds, publishTime   |

Configuration [#configuration]

| Field       | Type   | Required               | Description                                                                      |
| ----------- | ------ | ---------------------- | -------------------------------------------------------------------------------- |
| feedIds     | string | For price ops          | Comma-separated Pyth feed IDs (hex format)                                       |
| searchQuery | string | For searchFeeds        | Search keyword (e.g., `SOL`, `BTC`)                                              |
| assetType   | string | No                     | Search filter: All, `crypto`, `fx`, `equity`, `metal`, `commodities`, or `rates` |
| publishTime | number | For getHistoricalPrice | Unix timestamp                                                                   |

Output [#output]

`searchFeeds` returns the raw Pyth feed list under `.data`:

```json
{
  "success": true,
  "operation": "searchFeeds",
  "data": [ { "id": "0xef0d...", "attributes": { "symbol": "Crypto.SOL/USD" } } ]
}
```

`getLatestPrices` and `getHistoricalPrice` enrich each feed with a `humanPrice` (and `humanEmaPrice` for latest), and surface the enriched list under `prices` instead of `data`. They echo back `feedIds` (and `timestamp` for historical):

```json
{
  "success": true,
  "operation": "getLatestPrices",
  "feedIds": ["ef0d8b6f..."],
  "prices": [
    {
      "id": "ef0d8b6f...",
      "price": { "price": "15023000000", "expo": -8, "publish_time": 1716000000 },
      "ema_price": { "price": "15012000000", "expo": -8 },
      "humanPrice": "150.23",
      "humanEmaPrice": "150.12"
    }
  ]
}
```

If the upstream response is not a parseable object, the node falls back to the standard `data` envelope. Reference fields downstream with the response name (default `pythResponse`), for example `{pythResponse.prices[0].humanPrice}`.

Common use cases [#common-use-cases]

* Get oracle prices for DeFi calculations (more reliable than DEX prices)
* Compare oracle vs. market prices for arbitrage detection
* Look up historical prices for backtesting strategies

Next steps [#next-steps]

* [Birdeye](/docs/nodes/data/birdeye) - market-based token prices
* [CoinGecko](/docs/nodes/data/coingecko) - aggregated market data


---

# OpenRouter (/docs/nodes/ai/openrouter)

LLM inference with 300+ models via OpenRouter.



A single AI node that talks to GPT, Claude, Gemini, and 300+ other models through OpenRouter. You bring one API key, pick a model from the dropdown (or type any model ID), and reference the result downstream as `{aiResponse.data}`.

Quick start [#quick-start]

1. Add an OpenRouter API key under [Credentials](/docs/credentials/adding).
2. Drop an AI node onto the canvas, pick the credential, choose a model.
3. Write a prompt - e.g. `Summarize this token: {json birdeyeResponse.data}`.
4. Wire the next node to read `{aiResponse.data}`.

That's the whole loop. Everything below is detail for when you need it.

Prerequisites [#prerequisites]

* OpenRouter API key ([get one here](https://openrouter.ai/keys))
* Add it as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

| Operation       | Description                   |
| --------------- | ----------------------------- |
| Chat completion | Send a prompt, get a response |

Input modes [#input-modes]

| Mode         | When to use                                                                                                                                                                        |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Prompt       | Default. Write a natural language prompt with template expressions like `{birdeyeResponse.data.value}`.                                                                            |
| JSON Request | When you need multi-turn messages, a `developer` role, or multimodal content parts. Send an OpenAI-compatible chat-completions body - see [JSON Request mode](#json-request-mode). |

Configuration [#configuration]

| Field              | Type              | Required          | Description                                                                                                                                                                                              |
| ------------------ | ----------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OpenRouter API Key | select            | Yes               | Your OpenRouter credential from Connections.                                                                                                                                                             |
| Response Name      | text              | Yes               | The variable name downstream nodes use to reference this node's output. Defaults to `aiResponse`, so you read the reply as `{aiResponse.data}`. Rename it if you have multiple AI nodes in one workflow. |
| model              | searchable select | Yes               | Pick from loaded OpenRouter models or type a full model ID. Defaults to `gpt-5-mini` if left blank in non-strict execution.                                                                              |
| prompt             | string            | Yes (prompt mode) | User message. Supports template expressions.                                                                                                                                                             |
| requestJson        | string            | Yes (JSON mode)   | OpenAI-compatible JSON body - see [JSON Request mode](#json-request-mode).                                                                                                                               |
| systemPrompt       | string            | No                | System instructions. Also templated - undefined paths fail the same way as in `prompt`.                                                                                                                  |
| temperature        | number            | No                | `0`–`2`, controls randomness.                                                                                                                                                                            |
| maxTokens          | number            | No                | Maximum response length.                                                                                                                                                                                 |
| responseFormat     | string            | No                | `text` (default) or `json_object`. See [Response format](#response-format).                                                                                                                              |

Select an OpenRouter credential first to load the live model list. You can also type a full model ID manually (e.g. `openai/gpt-5-mini`) if it doesn't appear in the suggestions.

The user prompt is hard-capped at 200,000 characters. Anything longer is truncated with a marker (`[Prompt truncated by Solaris to stay within model context limits]`) before the call goes out.

Advanced parameters [#advanced-parameters]

| Field            | Type   | Description                                                                                 |
| ---------------- | ------ | ------------------------------------------------------------------------------------------- |
| topP             | number | Nucleus sampling threshold. Range `(0, 1]` (0 is invalid; values above 1 are clamped to 1). |
| frequencyPenalty | number | Penalize repeated tokens. Range `-2` to `2`.                                                |
| presencePenalty  | number | Penalize tokens already present. Range `-2` to `2`.                                         |
| stop             | string | Comma-separated stop sequences. Up to 4 (extras are dropped).                               |
| seed             | number | Deterministic output seed (model-dependent). Must be an integer.                            |

Reasoning [#reasoning]

Some models support extended reasoning. When enabled, the model may return additional reasoning metadata alongside the reply.

| Field            | Type    | Description                                                                                                                                                              |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| reasoningEnabled | boolean | Enable extended thinking.                                                                                                                                                |
| reasoningEffort  | string  | `xhigh`, `high`, `medium`, `low`, `minimal`, or `none`. Roughly: `xhigh` ≈ 95% of token budget on reasoning, `high` ≈ 80%, `medium` ≈ 50%, `low` ≈ 20%, `minimal` ≈ 10%. |

Reasoning is model-dependent. Claude 4.6 uses adaptive thinking automatically (the effort setting is ignored). Models that don't support reasoning will reject the call.

JSON Request mode [#json-request-mode]

When `inputMode` is `json`, the body must be an object with an OpenAI-compatible `messages[]` array. This is the only shape the runtime forwards to OpenRouter.

Static JSON is checked in the editor before the workflow runs. If the JSON contains template expressions, the final shape is checked after those templates render at runtime. Legacy provider formats such as Gemini `contents[]` or Anthropic top-level `system` are rejected when the rendered request is parsed.

```json
{
  "messages": [
    { "role": "system", "content": "You are a concise assistant." },
    { "role": "user", "content": "Summarize: {json birdeyeResponse.data}" }
  ]
}
```

`role` must be one of `system`, `user`, `assistant`, `developer`. `content` is a non-empty string, or an OpenAI-format content-parts array (`[{ "type": "text", "text": "..." }, { "type": "image_url", "image_url": { "url": "..." } }]`) for multimodal models.

The body may also set:

| Field                               | Effect                                                   |
| ----------------------------------- | -------------------------------------------------------- |
| `temperature`                       | Overrides the node-level `temperature`.                  |
| `max_tokens` (or `maxOutputTokens`) | Overrides the node-level `maxTokens`. Capped at 128,000. |

All other fields in the body are ignored - advanced parameters (`topP`, `frequencyPenalty`, etc.) and `responseFormat` come from node settings, not the JSON body. Set them in the editor's [Advanced parameters](#advanced-parameters) panel or [Response format](#response-format) toggle.

Template expressions inside `requestJson` are resolved before the body is parsed as JSON, so a path like `{json codeResponse.data}` interpolates a JSON value at that position. Undefined paths fail loudly with `AI JSON input uses undefined variables: <path>` instead of producing invalid JSON or empty substitutions.

Response format [#response-format]

The `responseFormat` setting controls how the model's reply lands in the output envelope. **It must agree with what your prompt asks for** - instructing the model to respond in JSON without flipping this toggle leaves `.data` as a string that downstream nodes can't traverse with field paths.

| Value            | Behavior                                                                                                                                                                                                                                                                           |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text` (default) | The reply is returned verbatim. `.data` is a string. Use `{aiResponse.data}` to drop it into a downstream prompt or HTTP body.                                                                                                                                                     |
| `json_object`    | OpenRouter is asked for structured output and the reply is `JSON.parse`d before it lands in the envelope. `.data` is a parsed object you can address with field paths like `{json aiResponse.data.summary}`. If the model returns invalid JSON, the node fails with a clear error. |

Some models don't support structured outputs and will fail when `json_object` is set - pick a model that does, or fall back to `text` and parse downstream.

Output [#output]

In &#x2A;*`text`** mode (default):

```json
{
  "success": true,
  "data": "The current price of SOL is approximately $150.",
  "model": "openai/gpt-5-mini",
  "usage": { "promptTokens": 42, "completionTokens": 18 }
}
```

In &#x2A;*`json_object`** mode:

```json
{
  "success": true,
  "data": { "price": 150, "asset": "SOL", "confidence": "high" },
  "model": "openai/gpt-5-mini",
  "usage": { "promptTokens": 42, "completionTokens": 18 }
}
```

Reasoning models may add a `reasoning` field when reasoning is enabled.

Reference patterns:

* `{aiResponse.data}` - the model's reply as a string. In `text` mode this is the raw model output. In `json_object` mode `.data` is an object, so without the `json ` prefix the template substitutes `[object Object]` (string coercion). Use `{json aiResponse.data}` instead when `.data` is an object.
* `{aiResponse.data.field}` - a scalar field (string / number / boolean) from a `json_object` reply, dropped in unquoted. The right shape for prompts and most string fields in HTTP query strings.
* `{json aiResponse.data.field}` - the same field JSON-encoded (strings get quotes, objects/arrays get JSON syntax). Use this when interpolating into JSON HTTP bodies, or when the field itself is an object or array.
* `{json aiResponse}` - the full response envelope, useful for HTTP bodies or debugging.

Template variables [#template-variables]

The system prompt, prompt, and JSON-input fields all support template expressions like `{json codeResponse.data.field}`. Two things to know:

* **Use the variable picker** (the `{ }` button next to any text field). It walks upstream nodes and suggests valid paths, including nested field paths once the workflow has run once. Before the first run, it falls back to a static schema for known node types so you still get useful suggestions.
* **Undefined paths fail loudly.** If a path doesn't resolve (typo, wrong nesting, upstream node didn't emit that field), the node fails before calling the model - checked across all three template fields (`systemPrompt`, `prompt`, `requestJson`) so a single failure surfaces every offending path at once. The error message mentions the offending path: `Prompt template uses undefined variables: <path>` in prompt mode (covers system prompt + prompt), `AI JSON input uses undefined variables: <path>` in JSON Request mode (covers system prompt + JSON body). This is the most common cause of "I don't see any data" replies - fix the path instead of debugging the model. A path that resolves to `null` (e.g., `balanceResponse.mint` on a SOL balance) is treated as a present value, not a missing one.

Common use cases [#common-use-cases]

* Analyze on-chain data and generate trading signals
* Summarize token metrics into human-readable alerts
* Parse unstructured data into structured JSON with `json_object` response format
* Use reasoning models for complex multi-step analysis

Next steps [#next-steps]

* [Jupiter](/docs/nodes/defi/jupiter) - connect AI to on-chain swaps
* [Configuring Nodes](/docs/editor/configuring-nodes) - template expressions for dynamic prompts


---

# DFlow (/docs/nodes/defi/dflow)

Prediction market events, orderbooks, and trades via DFlow.



DFlow is a prediction market protocol on Solana. The DFlow node provides event discovery, market data, orderbooks, candlesticks, and trade history.

Prerequisites [#prerequisites]

No credential required. All operations use DFlow's public API.

Operations [#operations]

| Operation       | Description                       | Key inputs         |
| --------------- | --------------------------------- | ------------------ |
| getEvents       | List prediction events            | status, limit      |
| getEvent        | Get details for a specific event  | ticker             |
| searchEvents    | Search events by keyword          | searchQuery, limit |
| getMarkets      | List markets for an event         | ticker, limit      |
| getMarket       | Get details for a specific market | ticker             |
| getMarketByMint | Look up a market by token mint    | mintAddress        |
| getOrderbook    | Get orderbook depth for a market  | ticker             |
| getCandlesticks | Get OHLCV data for a market       | ticker             |
| getTrades       | Get recent trades for a market    | ticker, limit      |
| getCategories   | List event categories             | (none)             |

Configuration [#configuration]

| Field       | Type   | Required            | Description                          |
| ----------- | ------ | ------------------- | ------------------------------------ |
| ticker      | string | For most ops        | Event or market ticker               |
| mintAddress | string | For getMarketByMint | Token mint address                   |
| searchQuery | string | For searchEvents    | Search keyword (max 200 chars)       |
| status      | string | No                  | Event status filter                  |
| limit       | number | No                  | Results limit (default: 20, max: 50) |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getEvents",
  "data": { /* raw DFlow response */ }
}
```

Reference fields downstream with the response name (default `dflowResponse`), for example `{dflowResponse.data[0].ticker}` for a list of events or `{dflowResponse.data.outcomes[0].price}` for `getMarket`.

Common use cases [#common-use-cases]

* Monitor prediction market odds for crypto events
* Track orderbook depth and trade volume
* Combine with AI to analyze event sentiment

Next steps [#next-steps]

* [Polymarket](/docs/nodes/data/polymarket) - alternative prediction market data
* [Drift](/docs/nodes/defi/drift) - perpetuals trading data


---

# Drift (/docs/nodes/defi/drift)

Perpetuals markets, funding rates, candles, and positions via Drift.



Drift is Solana's leading perpetuals exchange. The Drift node provides market data, funding rates, candlestick charts, orderbooks, and user position tracking.

Prerequisites [#prerequisites]

No credential required. All operations use Drift's public data API.

Operations [#operations]

| Operation               | Description                    | Key inputs                      |
| ----------------------- | ------------------------------ | ------------------------------- |
| getMarkets              | List all markets               | (none)                          |
| getMarketPrices         | Get current market prices      | (none)                          |
| getMarketVolume         | Get market volume across Drift | interval                        |
| getFundingRates         | Get current funding rates      | (none)                          |
| getMarketFundingHistory | Get historical funding data    | marketSymbol, limit             |
| getMarketCandles        | Get OHLCV candlestick data     | marketSymbol, resolution, limit |
| getMarketTrades         | Get recent trades              | marketSymbol, limit             |
| getOrderBook            | Get orderbook depth            | marketSymbol, depth             |
| getUserPositions        | Get a user's open positions    | walletAddress                   |

Configuration [#configuration]

| Field         | Type   | Required                                            | Description                                                 |
| ------------- | ------ | --------------------------------------------------- | ----------------------------------------------------------- |
| marketSymbol  | string | For funding history, candles, trades, and orderbook | Market symbol (e.g., `SOL-PERP`, `BTC-PERP`, `SOL`)         |
| walletAddress | string | For getUserPositions                                | Wallet address to check                                     |
| resolution    | string | No                                                  | Candle resolution: `1`, `5`, `15`, `60`, `240`, `D`, or `W` |
| interval      | string | No                                                  | Volume interval: `1h`, `24h`, or `30d`                      |
| depth         | number | No                                                  | Orderbook depth (default: 10, max: 50)                      |
| limit         | number | No                                                  | Results limit for funding history, candles, and trades      |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getMarketPrices",
  "data": { /* raw Drift response */ }
}
```

Reference fields downstream with the response name (default `driftResponse`), for example `{driftResponse.data["SOL-PERP"].price}` for a price map or `{driftResponse.data.fundingRate}` for funding data.

Common use cases [#common-use-cases]

* Monitor funding rates to find arbitrage opportunities
* Track perpetual market prices alongside spot (via Birdeye/Jupiter)
* Alert on large trades or position changes

Next steps [#next-steps]

* [Jupiter](/docs/nodes/defi/jupiter) - spot trading and swaps
* [Pyth](/docs/nodes/data/pyth) - oracle price feeds for the same markets


---

# Jupiter (/docs/nodes/defi/jupiter)

Token swaps, quotes, search, and portfolio via Jupiter.



Jupiter is Solana's leading DEX aggregator. The Jupiter node supports quotes, swaps, token search, and SOL transfers.

Prerequisites [#prerequisites]

* **Jupiter API key** required for all operations except transfer. Add one in [Connections](/docs/credentials/adding).
* **Connected Privy wallet** required for swap and transfer operations.

Operations [#operations]

| Operation       | Description                            | Requires API key | Requires wallet |
| --------------- | -------------------------------------- | ---------------- | --------------- |
| getQuote        | Get a swap quote between two tokens    | Yes              | No              |
| getPrice        | Get current price of token(s)          | Yes              | No              |
| searchTokens    | Search tokens by name or symbol        | Yes              | No              |
| getTopTokens    | List top/trending/organic score tokens | Yes              | No              |
| getRecentTokens | List recently added tokens             | Yes              | No              |
| getPortfolio    | Get wallet token balances              | Yes              | No              |
| swap            | Execute a token swap                   | Yes              | Yes             |
| transfer        | Send SOL to an address                 | No               | Yes             |

Configuration [#configuration]

| Field          | Type     | Required                           | Description                                                     |
| -------------- | -------- | ---------------------------------- | --------------------------------------------------------------- |
| Credential     | select   | For all operations except transfer | Your Jupiter API key credential                                 |
| Response Name  | text     | Yes                                | Name used by downstream nodes, such as `{jupiterResponse.data}` |
| inputMint      | template | For getQuote and swap              | Token to sell, as a mint address                                |
| outputMint     | template | For getQuote and swap              | Token to buy, as a mint address                                 |
| amount         | template | For getQuote and swap              | Amount in the input token's smallest unit                       |
| slippageBps    | number   | No                                 | Optional manual slippage in basis points, 0-10000               |
| tokenQuery     | template | For getPrice and searchTokens      | Mint address list for price, or text query for search           |
| address        | template | For getPortfolio and transfer      | Wallet address for portfolio, or recipient address for transfer |
| transferAmount | template | For transfer                       | Amount of SOL to send, such as `0.1`                            |
| category       | select   | For getTopTokens                   | Top Traded, Top Trending, or Top Organic Score                  |
| interval       | select   | For getTopTokens                   | 5 minutes, 1 hour, 6 hours, or 24 hours                         |

For `getQuote` and `swap`, amount must be in the input token's smallest unit:

* `1000000000` for 1 SOL
* `1000000` for 1 USDC

You can use variables in `amount` and `transferAmount`, such as `{trigger.amountLamports}` or `{conditionResponse.value}`.

Leave Slippage empty to let Jupiter choose its routing defaults. Entering a slippage value switches to manual slippage mode.

Transfer [#transfer]

The `transfer` operation sends SOL from your connected Privy wallet. It does not require a Jupiter API key, but it does require:

* Recipient Address
* Amount (SOL)
* Enough SOL for the transfer and network fee

Use this for simple SOL payouts. Token transfers are not supported by the Jupiter transfer operation.

Swap fees [#swap-fees]

Swaps may include a platform fee deducted from the input amount before the swap executes.

* Base fee is 0.4% before SOLARIS holder discounts
* Fee is collected for SOL and supported SPL tokens (USDC, USDT, mSOL, jitoSOL, bSOL)
* Token-2022 mints with extensions (transfer hooks, transfer fees) are not fee-eligible
* Fee is non-refundable once collected, even if the swap itself fails after fee collection

SOLARIS token holder discounts [#solaris-token-holder-discounts]

Holding SOLARIS tokens in your executing Privy wallet reduces swap fees:

| SOLARIS holdings | Fee discount |
| ---------------- | ------------ |
| 5,000,000+       | 50% off      |
| 1,000,000+       | 25% off      |
| Below 1,000,000  | No discount  |

Tokens must be in your Privy embedded wallet (the same wallet executing the swap). Tokens in external wallets are not counted.

Output (getQuote) [#output-getquote]

```json
{
  "success": true,
  "operation": "getQuote",
  "data": {
    "inputMint": "So11111111111111111111111111111111111111112",
    "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "inAmount": "1000000000",
    "outAmount": "150230000",
    "priceImpactPct": "0.01"
  }
}
```

Next steps [#next-steps]

* [Raydium](/docs/nodes/defi/raydium) - alternative DEX
* [Credentials](/docs/credentials/adding) - adding your Jupiter API key


---

# Kamino (/docs/nodes/defi/kamino)

Lending markets, vaults, and user positions via Kamino.



Kamino is a Solana DeFi protocol offering lending, borrowing, and automated vaults. The Kamino node provides market data, user positions, and vault analytics.

Prerequisites [#prerequisites]

No credential required. All operations use Kamino's public API.

Operations [#operations]

| Operation          | Description                            | Key inputs                       |
| ------------------ | -------------------------------------- | -------------------------------- |
| getMarkets         | List lending markets                   | marketPubkey (optional filter)   |
| getReserveMetrics  | Get reserve metrics for a market       | marketPubkey                     |
| getMarketHistory   | Get historical market data             | marketPubkey, startDate, endDate |
| getUserObligations | Get user's lending/borrowing positions | walletAddress, marketPubkey      |
| getLeverageMetrics | Get leverage metrics for a market      | marketPubkey                     |
| getVaultMetrics    | Get vault performance metrics          | vaultPubkey                      |
| getVaultApyHistory | Get vault APY history                  | vaultPubkey, startDate, endDate  |

Configuration [#configuration]

| Field         | Type   | Required               | Description                       |
| ------------- | ------ | ---------------------- | --------------------------------- |
| marketPubkey  | string | Varies                 | Lending market address            |
| walletAddress | string | For getUserObligations | Wallet to check positions for     |
| vaultPubkey   | string | For vault ops          | Vault address                     |
| startDate     | string | No                     | Start date for historical queries |
| endDate       | string | No                     | End date for historical queries   |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getMarkets",
  "data": { /* raw Kamino response */ }
}
```

Reference fields downstream with the response name (default `kaminoResponse`), for example `{kaminoResponse.data[0].address}` for a market list or `{kaminoResponse.data.supplyApy}` for reserve metrics.

Common use cases [#common-use-cases]

* Monitor lending rates across Kamino markets
* Track vault performance and APY trends
* Check user positions for liquidation risk alerts

Next steps [#next-steps]

* [Drift](/docs/nodes/defi/drift) - perpetuals and margin trading
* [Sanctum](/docs/nodes/defi/sanctum) - liquid staking data


---

# Meteora (/docs/nodes/defi/meteora)

DLMM and DAMM pool data, OHLCV, and volume history via Meteora.



Meteora provides dynamic liquidity market making (DLMM) and dynamic AMM (DAMM) pools on Solana. The Meteora node covers pool discovery, analytics, and historical data.

Prerequisites [#prerequisites]

No credential required. All operations use Meteora's public API.

Operations [#operations]

| Operation            | Description                           | Key inputs                |
| -------------------- | ------------------------------------- | ------------------------- |
| getDLMMPools         | List DLMM pools with search and sort  | searchTerm, sortBy, limit |
| getDLMMPoolDetails   | Get details for a specific DLMM pool  | poolAddress               |
| getDLMMPoolGroups    | List DLMM pool groups                 | searchTerm, sortBy, limit |
| getDLMMOHLCV         | Get OHLCV candlestick data for a pool | poolAddress, timeframe    |
| getDLMMVolumeHistory | Get volume history for a pool         | poolAddress               |
| getDLMMStats         | Get DLMM protocol statistics          | (none)                    |
| getDAMMPools         | List DAMM pools                       | searchTerm, sortBy, limit |
| getDAMMPoolDetails   | Get details for a specific DAMM pool  | poolAddress               |
| getDAMMStats         | Get DAMM protocol statistics          | (none)                    |

Configuration [#configuration]

| Field       | Type   | Required             | Description                                          |
| ----------- | ------ | -------------------- | ---------------------------------------------------- |
| poolAddress | string | For detail/OHLCV ops | Pool address                                         |
| searchTerm  | string | No                   | Search keyword (max 200 chars)                       |
| sortBy      | string | No                   | Sort field (e.g., `liquidity`, `volume`, `feeApr`)   |
| sortType    | string | No                   | `asc` or `desc` (default: `desc`)                    |
| timeframe   | string | No                   | OHLCV timeframe: `1m`, `5m`, `15m`, `1h`, `4h`, `1d` |
| limit       | number | No                   | Results limit (default: 10, max: 50)                 |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getDLMMPools",
  "data": { /* raw Meteora response */ }
}
```

Reference fields downstream with the response name (default `meteoraResponse`). For pool detail operations the payload is the pool object under `.data`; for list operations the pools are typically under `.data` or a nested array such as `.data.pairs` depending on the Meteora endpoint.

Common use cases [#common-use-cases]

* Track DLMM pool performance and fee APRs
* Monitor volume trends across Meteora pools
* Analyze candlestick data for LP timing decisions

Next steps [#next-steps]

* [Orca](/docs/nodes/defi/orca) - concentrated liquidity pools
* [Kamino](/docs/nodes/defi/kamino) - lending and vaults


---

# Orca (/docs/nodes/defi/orca)

Pool data, token info, and protocol stats via Orca.



Orca is a concentrated liquidity DEX on Solana. The Orca node provides pool discovery, token lookups, and protocol statistics.

Prerequisites [#prerequisites]

No credential required. All operations use Orca's public API.

Operations [#operations]

| Operation        | Description                           | Key inputs                         |
| ---------------- | ------------------------------------- | ---------------------------------- |
| getPools         | List pools with filtering and sorting | sortBy, tokenFilter, minTvl, limit |
| getPoolDetails   | Get details for a specific pool       | poolAddress                        |
| searchPools      | Search pools by keyword               | searchQuery                        |
| getToken         | Get token details by mint             | tokenMint                          |
| searchTokens     | Search tokens by keyword              | searchQuery                        |
| getTokens        | List tokens with pagination           | limit                              |
| getProtocolStats | Get Orca protocol-wide statistics     | (none)                             |

Configuration [#configuration]

| Field       | Type   | Required           | Description                                       |
| ----------- | ------ | ------------------ | ------------------------------------------------- |
| poolAddress | string | For getPoolDetails | Pool address                                      |
| tokenMint   | string | For getToken       | Token mint address                                |
| searchQuery | string | For search ops     | Search keyword (max 200 chars)                    |
| tokenFilter | string | No                 | Filter pools by token                             |
| sortBy      | string | No                 | Sort: `tvl`, `volume`, `feeApr` (default: `tvl`)  |
| sortType    | string | No                 | `asc` or `desc` (default: `desc`)                 |
| statsPeriod | string | No                 | Stats window: `24h`, `7d`, `30d` (default: `24h`) |
| minTvl      | number | No                 | Minimum TVL filter                                |
| limit       | number | No                 | Results limit (default: 10, max: 50)              |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getPools",
  "data": { /* raw Orca response */ }
}
```

Reference fields downstream with the response name (default `orcaResponse`), for example `{orcaResponse.data[0].address}` for a pool list or `{orcaResponse.data.liquidity}` for `getPoolDetails`.

Common use cases [#common-use-cases]

* Monitor concentrated liquidity pool performance
* Discover high-yield pools for LP strategies
* Track protocol-wide volume and TVL trends

Next steps [#next-steps]

* [Meteora](/docs/nodes/defi/meteora) - alternative Solana DEX
* [Raydium](/docs/nodes/defi/raydium) - AMM pool data and swaps


---

# Phoenix (/docs/nodes/defi/phoenix)

Trade Solana perps on Phoenix (Ellipsis Labs) and read markets, orderbook, and trader state.



Phoenix is the Solana perpetuals DEX from Ellipsis Labs. The Phoenix node reads market and account data, places trades, and moves collateral from your connected Privy wallet.

Every trading and collateral operation runs in **paper mode by default**: it validates and simulates against the live market but broadcasts nothing until you explicitly flip the node to Live. So you can build and test a whole strategy before any real funds move.

Quick start (paper) [#quick-start-paper]

1. Add a Phoenix node and pick a trading op, e.g. **Place Market Order**.
2. Set `symbol` (e.g. `SOL`), `side` (Bid = buy, Ask = sell), `baseUnits` (size), and the required price cap `priceLimitUsd`.
3. Run it. The node stays in **paper** mode, estimates your fill against the live order book, and returns a simulated result (`simulated: true`, `signature: "PAPER"`). Nothing is signed or broadcast.
4. When you want real trades, flip the node's **PAPER / LIVE** switch and activate Phoenix Flight access once (see [Going live](#going-live-wallet-activation)).

You don't need wallet activation, funds, or a credential to paper-test.

What each operation needs [#what-each-operation-needs]

| Operation type                                                     | What it needs                                                                         |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| Market data reads (`getMarkets`, `getOrderbook`, candles, funding) | Nothing. Public data, no wallet or credential.                                        |
| Account reads (`getTraderState`, history, PnL)                     | Your connected wallet address (read-only, no signature, no activation).               |
| Trading & collateral in **paper** mode                             | Just be signed in. No activation, funds, or signature.                                |
| Trading & collateral in **Live** mode                              | A wallet with Phoenix Flight access (one-time activation below) plus USDC collateral. |

Per-op details are in [Operations](#operations).

Paper trading (default) [#paper-trading-default]

Signed Phoenix nodes show a **PAPER / LIVE** switch on the node card. Read-only ops don't show it, they never broadcast. New nodes start in paper.

Paper mode runs the **exact same input validation as live**, then stops before signing: no wallet is resolved, no Flight whitelist is checked, nothing is broadcast. A config error fails identically in both modes, so paper is a faithful dry run. What it returns depends on the op:

* **Limit / market orders** estimate your fill by walking the live L2 order book (VWAP across price levels, capped at your limit or price cap). This accounts for depth and slippage, so it is stricter than a top-of-book fill on large orders. When the relevant book side is empty it falls back to the mid.
* **Post-only orders** check whether the price would rest as a maker or be rejected for crossing the spread, exactly as Live would reject it on-chain. You never get a false "resting" result that dies the instant you go live.
* **Stop-loss / conditional / cancel ops** preview the trigger or cancellation they would submit.
* **Collateral / subaccount ops** preview the USDC movement.

Flipping a node to LIVE opens a confirmation dialog (real funds move). Flipping back to PAPER is instant. The first time a **Live** signed op runs in a workflow, you are prompted once to authorize wallet signing; paper ops never prompt.

Simulated outputs [#simulated-outputs]

Every paper result carries `simulated: true` and `signature: "PAPER"` instead of a real tx hash, plus an op-specific preview field:

| Op(s)                                                                  | Preview field                                          | Contents                                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| placeLimitOrder, placeMarketOrder                                      | `fill` (or `null`), plus optional `wouldRest` / `note` | Estimated `filledBaseUnits`, `avgPriceUsd`, `notionalUsd`, `feeUsd` (taker + builder fee), `partial`, `priceCapped`, `usedFallbackMid`. A limit order's unfilled remainder is reported under `wouldRest`; if nothing fills within the cap, `fill` is `null` and `note` explains why. |
| placePostOnlyOrder                                                     | `wouldRest`, **or the node fails**                     | If the maker price would NOT cross, returns `fill: null` + `wouldRest`. If it WOULD cross (a buy at/above the best ask, a sell at/below the best bid), the node **fails** with a cross-rejection error, exactly as a live post-only is rejected on-chain.                            |
| placeStopLoss, placePositionConditionalOrder                           | `wouldPlaceTrigger`                                    | The trigger(s) that would be placed (`kind`, side, prices, direction, order kind, and size for conditionals).                                                                                                                                                                        |
| cancelStopLoss, cancelOrdersById, cancelAll                            | `wouldCancel`                                          | What would be cancelled (`kind: "stopLoss"` \| `"ordersById"` \| `"all"`, plus the targets).                                                                                                                                                                                         |
| depositCollateral, withdrawCollateral                                  | `wouldMove`                                            | `{ direction: "deposit" \| "withdraw", amountUsdc }`.                                                                                                                                                                                                                                |
| transferCollateral, transferCollateralChildToParent, syncParentToChild | `wouldMove`                                            | `{ kind, srcSubaccountIndex, dstSubaccountIndex, amountUsdc? }` (no `amountUsdc` for sync).                                                                                                                                                                                          |

Example paper fill (`placeMarketOrder`, buy 10 SOL against a book whose best ask is 84.90):

```json
{
  "success": true,
  "operation": "placeMarketOrder",
  "simulated": true,
  "signature": "PAPER",
  "symbol": "SOL",
  "fill": {
    "side": "Bid",
    "requestedBaseUnits": "10",
    "filledBaseUnits": "10",
    "avgPriceUsd": "84.9",
    "notionalUsd": "849",
    "feeUsd": "0.38205",
    "partial": false,
    "priceCapped": false,
    "usedFallbackMid": false
  }
}
```

Going live: wallet activation [#going-live-wallet-activation]

Phoenix Flight is in private beta, so each wallet must be activated **once** before it can place Live trades or move collateral. Paper and read-only ops never need this, so a fresh wallet can paper-test freely.

Activation happens inline in the node config dialog, not in Connections and not on the node card:

1. Open a Phoenix node and pick a signed op (e.g. `placeLimitOrder`).
2. Flip it to **Live**. If the wallet isn't activated yet, an **Activate Phoenix Flight access** panel appears at the bottom.
3. Choose your code type (access or referral), paste the code, and click **Activate**.

You can use **either** an access code **or** a referral code; a valid referral code works on its own, you don't need an allowlist invite first. Request a code at [x.com/PhoenixTrade](https://x.com/PhoenixTrade).

Once activated, the panel disappears for good across every Phoenix node and workflow for that wallet, and live signed ops broadcast without prompting for a code again. No code is ever stored on the node, in workflow exports, or in a credential.

You also need USDC collateral on Phoenix before placing orders. Use `depositCollateral` (or fund directly at phoenix.trade); both deposit and withdraw run a balance precheck and fail fast with a clear message if you're short.

Operations [#operations]

Market data (read-only) [#market-data-read-only]

| Operation             | Description                                           | Key inputs                                              | Signs? |
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------- | ------ |
| getMarkets            | List all available Phoenix perp markets               | (none)                                                  | No     |
| getMarket             | Get a single market's fees, risk, and funding cadence | symbol                                                  | No     |
| getOrderbook          | Get L2 orderbook depth                                | symbol                                                  | No     |
| getCandles            | OHLCV candle history for a market                     | symbol, **timeframe**, optional startTime/endTime/limit | No     |
| getMarketFills        | Public trade tape for a market                        | symbol, optional limit/cursor                           | No     |
| getMarketStatsHistory | Open interest, fees, mark/spot price over time        | symbol, optional timeframe/startTime/endTime/limit      | No     |
| getFundingOverview    | Funding-rate series across all markets                | optional startTime/endTime/perMarketLimit               | No     |
| getFundingRateHistory | Funding-rate series for one market                    | symbol, optional startTime/endTime/limit                | No     |

**getMarkets**: Lists every perp market live on Phoenix in a single call, each with its fee schedule, risk/leverage tiers, and funding cadence. Takes no inputs. Use it to discover what's tradable, populate a market picker, or loop a workflow over every symbol. The raw markets array comes back under `data`.

**getMarket**: The same per-market detail as one row of `getMarkets`, but scoped to a single `symbol` you already know. Cheaper than pulling the whole list when you only care about one market. Returns that market's taker/maker fees, risk parameters, and funding info under `data`, with `symbol` echoed back.

**getOrderbook**: The current L2 order-book depth for `symbol`: aggregated bid and ask levels (price + size) plus the mid price. This is the exact book the paper-trading simulator walks to estimate fills. Use it to check liquidity and spread before trading, or to feed a pricing decision into an upstream AI/logic node.

**getCandles**: OHLCV candle history for `symbol` at a **required** `timeframe` (`1m`, `5m`, `15m`, `1h`, `4h`, `1d`, etc.), with an optional `startTime`/`endTime` window and `limit`. Use it for charting, technical indicators, or backtesting logic. Times go on the wire as Unix seconds; the executor converts whatever format you supply.

**getMarketFills**: The public trade tape for `symbol`: the most recent fills that printed on the market, from all traders, newest first. Paginates by `limit`/`cursor` (no time window). Use it to watch live order flow or compute recent traded volume.

**getMarketStatsHistory**: A time series of market-level statistics for `symbol`: open interest, fees, and mark/spot price over time. Optional `timeframe`, time window, and `limit`. Use it to track how a market's open interest or basis evolves, e.g. to detect crowding.

**getFundingOverview**: Funding-rate history for **all** markets at once, returning one series per market so you don't have to loop symbol-by-symbol. Takes no `symbol`. `perMarketLimit` caps the number of points in each series (this is deliberately a different field from `limit`). Use it to scan funding across the whole venue, e.g. to find the richest carry.

**getFundingRateHistory**: Funding-rate history for a **single** `symbol` over an optional time window. Use it when you only care about one market and want finer control than the venue-wide overview gives.

Account (read-only, uses your connected wallet) [#account-read-only-uses-your-connected-wallet]

| Operation                  | Description                                                                          | Key inputs                                                | Signs? |
| -------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------ |
| getTraderState             | Collateral, positions, open orders, triggers (one snapshot)                          | (none)                                                    | No     |
| getTraderOrderHistory      | Paginated order history (V2: includes intended/placed/currentState/fills aggregates) | optional symbol/limit/cursor/startTime/endTime            | No     |
| getTraderTradeHistory      | Paginated fills history (V2 endpoint)                                                | optional symbol/limit/cursor                              | No     |
| getTraderCollateralHistory | Paginated deposits/withdrawals/transfers                                             | optional limit/cursor                                     | No     |
| getTraderFundingHistory    | Paginated funding payments                                                           | optional symbol/startTime/endTime/limit/cursor/resolution | No     |
| getTraderPnl               | Realized + unrealized PnL series over time                                           | **resolution**, optional startTime/endTime/limit          | No     |
| getTraderPortfolioValues   | Portfolio value series over time                                                     | **resolution**, optional startTime/endTime/limit          | No     |
| getTraderMarketPnl         | PnL per market                                                                       | **resolution**, optional symbol/startTime/endTime/limit   | No     |
| getNotifications           | Phoenix-side fill / liquidation / SL execution notifications                         | optional limit/cursor                                     | No     |

These read your own account using the workflow owner's Privy wallet address (derived server-side, never client input). No signature and no whitelist are needed.

**getTraderState**: A single live snapshot of your Phoenix account: collateral, open positions, resting orders, and active triggers across all subaccounts. If the wallet has never opened a Phoenix account it returns `data: null` and `hasTraderAccount: false` instead of erroring, so downstream branches can test `{phoenixResponse.hasTraderAccount}` without try/catch. This is the canonical "what do I hold right now" read, and the source you pull `orderSequenceNumber` / `priceTicks` from when cancelling orders by id.

**getTraderOrderHistory**: Paginated history of your orders (the V2 endpoint), including intended vs placed vs current state and per-order fill aggregates. Optional `symbol` filter, time window, and `limit`/`cursor`. Use it to audit what you submitted and how each order resolved.

**getTraderTradeHistory**: Paginated history of your executed fills (the V2 endpoint). Paginates by `limit`/`cursor` with an optional `symbol` filter and no time window. Use it for realized-trade accounting or to compute your average entry on a position.

**getTraderCollateralHistory**: Paginated record of your collateral movements: deposits, withdrawals, and inter-subaccount transfers. `limit`/`cursor` only. Use it to reconcile balances or build a funding ledger for a strategy.

**getTraderFundingHistory**: Paginated history of the funding payments you've paid or received, with optional `symbol`, time window, and a `resolution` bucket. Use it to attribute funding cost (or income) to a position over time.

**getTraderPnl**: Your combined realized + unrealized PnL as a time series. Requires a `resolution` (bucket size like `1h` or `1d`); optional time window and `limit`. Use it to chart account performance over time.

**getTraderPortfolioValues**: Your total portfolio value (account equity) as a time series; also requires `resolution`. Like `getTraderPnl` but tracks equity rather than PnL, so it's what you'd plot as an equity curve.

**getTraderMarketPnl**: PnL broken down per market, requiring `resolution`, with optional `symbol` to scope to one market. Use it to see which markets are making or losing you money.

**getNotifications**: Phoenix-side notifications for your account: fills, liquidations, and stop-loss executions. `limit`/`cursor` only. Use it to react to events (e.g. send an alert on liquidation) without polling positions yourself.

Trading (signed) [#trading-signed]

| Operation                     | Description                                            | Key inputs                                                                | Signs? |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- | ------ |
| placeLimitOrder               | Resting limit order                                    | symbol, side, priceUsd, baseUnits                                         | Yes    |
| placeMarketOrder              | Take liquidity up to a price cap                       | symbol, side, priceLimitUsd, baseUnits                                    | Yes    |
| placePostOnlyOrder            | Maker-only limit (rejects on cross)                    | symbol, side, priceUsd, baseUnits                                         | Yes    |
| placeStopLoss                 | Bare stop-loss / take-profit tied to a market          | symbol, side, triggerPrice, executionPrice, executionDirection, orderKind | Yes    |
| cancelStopLoss                | Cancel an existing stop-loss / TP                      | symbol, executionDirection                                                | Yes    |
| placePositionConditionalOrder | Up to two greater/less triggers attached to a position | symbol, conditionalTriggers (JSON), optional baseUnits                    | Yes    |
| cancelOrdersById              | Cancel specific resting orders                         | symbol, orderIds                                                          | Yes    |
| cancelAll                     | Cancel every resting order on the symbol               | symbol                                                                    | Yes    |

**placeLimitOrder**: Posts a standard limit order from your `side` (Bid = buy/long, Ask = sell/short), at `priceUsd`, for `baseUnits` of the base asset. It immediately fills against any resting orders priced better than yours, then rests the remainder at your price as a maker. This is the default order type for most strategies. In paper mode the fill is estimated from the live book and any unfilled remainder is reported under `wouldRest`.

**placeMarketOrder**: Takes liquidity immediately, walking the book from the best price until your `baseUnits` is filled, but never past the **required** `priceLimitUsd` worst-price cap. The cap is mandatory: the node refuses to submit without it, so a thin or fast-moving book can't fill you at a runaway price. Use it when getting filled matters more than the spread you pay.

**placePostOnlyOrder**: A maker-only limit order. Same inputs as `placeLimitOrder`, but it will **never** take liquidity: if your price would cross the spread (a buy at or above the best ask, a sell at or below the best bid) Phoenix rejects it on-chain instead of filling. Use it to guarantee you post as a maker and never pay taker fees. In paper mode a crossing post-only fails the node, exactly mirroring the live rejection, so you don't get a false "resting" result.

**placeStopLoss**: Places a standalone stop-loss or take-profit tied to a `symbol` (not attached to a specific order). `triggerPrice` arms it; `executionDirection` (`GreaterThan` / `LessThan`) sets whether it fires when price rises above or falls below the trigger; when it fires it submits at `executionPrice` as either `IOC` (fill what it can immediately at that worst-case price, cancel the rest) or `Limit` (rest at that price as a maker). `side` is the direction of the protective order. Use it to auto-exit a position at a threshold.

**cancelStopLoss**: Removes an existing stop-loss / take-profit on `symbol`, identified by the `executionDirection` you placed it with. Use it to clear a trigger before replacing it, or once the position it protected is closed.

**placePositionConditionalOrder**: Attaches up to two price triggers to a position in one instruction via the `conditionalTriggers` JSON: a `greater` bucket (fires when price rises through a level) and/or a `less` bucket (fires when price falls through one). Each entry carries its own `tradeSide`, `orderKind` (`IOC`/`Limit`), `triggerPrice`, and `executionPrice`. Optional `baseUnits` sizes the order; omit it to size to the full position. Use it for bracket-style exits, e.g. a take-profit above plus a stop below, set together.

**cancelOrdersById**: Cancels specific resting orders identified by `orderIds`, a JSON array of `{ price | priceTicks, orderSequenceNumber }` entries pulled from a prior `getTraderState` snapshot. Use it for surgical cancellation when you don't want to wipe the whole book. See [Cancel-by-id price precision](#cancel-by-id-price-precision) for when to use `price` vs the tick-perfect `priceTicks`.

**cancelAll**: Cancels every resting order you have on `symbol` in a single instruction. Use it as a fast reset before re-quoting, or as a kill-switch step in a rebalance flow.

Collateral & subaccount management (signed) [#collateral--subaccount-management-signed]

| Operation                       | Description                                                    | Key inputs                                         | Signs? |
| ------------------------------- | -------------------------------------------------------------- | -------------------------------------------------- | ------ |
| depositCollateral               | Deposit USDC into your Phoenix trader account                  | amountUsdc                                         | Yes    |
| withdrawCollateral              | Withdraw USDC from your Phoenix trader account                 | amountUsdc                                         | Yes    |
| transferCollateral              | Move USDC between two subaccounts under the same wallet        | srcSubaccountIndex, dstSubaccountIndex, amountUsdc | Yes    |
| transferCollateralChildToParent | Sweep an isolated subaccount's full balance back to cross main | traderSubaccountIndex (the child)                  | Yes    |
| syncParentToChild               | Refresh state on an isolated subaccount from the parent        | traderSubaccountIndex (the child)                  | Yes    |

No Flight builder fee applies to any collateral op, even live: moving USDC never has Solaris AI take a cut.

**depositCollateral**: Moves `amountUsdc` of USDC from your wallet into your Phoenix trader account at the resolved `(marginType, traderSubaccountIndex)`. Before broadcasting, a precheck reads your wallet's USDC balance and fails fast (reporting actual vs required) if you're short. The first deposit to a fresh subaccount auto-registers it. You need collateral here before you can place any orders.

**withdrawCollateral**: Pulls `amountUsdc` of USDC out of your Phoenix account back to your wallet. A precheck reads `effectiveCollateralForWithdrawals` (your withdrawable balance after open positions, unrealized PnL, funding, and margin) and refuses if it's below the request, so you can branch on that error instead of hitting a cryptic on-chain failure. Use it to take profit or free up capital.

**transferCollateral**: Moves `amountUsdc` between two subaccounts under the **same** wallet, from `srcSubaccountIndex` to `dstSubaccountIndex` (both 0-100, must differ). The source must already hold collateral; a missing destination is auto-registered (Cross for subaccount 0, Isolated otherwise). Use it to rebalance capital between strategies without round-tripping through your wallet.

**transferCollateralChildToParent**: Sweeps an Isolated subaccount's **entire** balance back to the Cross main account (subaccount 0); `traderSubaccountIndex` is the child being emptied. Use it to wind down an isolated strategy and reclaim its collateral. If the parent (subaccount 0) doesn't exist yet, it's auto-registered as part of the same transaction.

**syncParentToChild**: Refreshes an Isolated child subaccount's view of state from the Cross parent; `traderSubaccountIndex` is the child. It's idempotent (re-syncing identical state is a no-op revert). Use it to reconcile a child after parent-side changes.

The &#x2A;*Signs?** column above describes **Live** mode. In **Paper** mode (the default for every op marked "Yes") nothing is signed or broadcast; the op is simulated instead.

All trading + collateral signed operations (except transfer/sync, which have their own subaccount routing) also accept `marginType` (default `Cross`) and, for Isolated margin, `traderSubaccountIndex`. See [Margin and subaccount routing](#margin-and-subaccount-routing).

Configuration [#configuration]

| Field                                  | Type                        | Required for                                                                                                                                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| symbol                                 | string                      | `getMarket`, `getOrderbook`, and all trading / cancel ops                                                                                                                                                             | Base asset symbol (e.g., `SOL`, `BTC`, `ETH`). A legacy `-PERP` suffix is stripped automatically for backwards compatibility. Also accepted as an **optional** market filter on `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderFundingHistory`, and `getTraderMarketPnl` (leave blank for all markets). Not used by `getMarkets`, `getTraderState`, `depositCollateral`, or `withdrawCollateral`.                                                                                                                                                                                                                                                                                                                                                                                             |
| side                                   | `Bid` \| `Ask`              | placeLimitOrder, placeMarketOrder, placePostOnlyOrder, placeStopLoss                                                                                                                                                  | `Bid` = buy / long, `Ask` = sell / short. Runtime also accepts templated/imported aliases `buy` / `long` and `sell` / `short`. (`placePositionConditionalOrder` instead sets `tradeSide` per entry inside `conditionalTriggers`.)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| priceUsd                               | string                      | placeLimitOrder, placePostOnlyOrder                                                                                                                                                                                   | Limit price in USD (e.g. `135.87`). For post-only, this is the maker price the order rests at; it rejects rather than crossing if it would match a resting order on the opposite side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| priceLimitUsd                          | string                      | placeMarketOrder                                                                                                                                                                                                      | **Required price cap.** Worst price you accept; market orders stop matching past this. Prevents slippage on thin books.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| baseUnits                              | string                      | placeLimitOrder, placeMarketOrder, placePostOnlyOrder (optional for placePositionConditionalOrder)                                                                                                                    | Order size in the base asset (e.g. `0.25` for a 0.25 SOL position). Required for the three plain place ops; on `placePositionConditionalOrder` it's optional, omit it to size to the full position.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| orderIds                               | JSON string                 | cancelOrdersById                                                                                                                                                                                                      | Array of `{ "price": <decimal USD>, "orderSequenceNumber": "<string>" }`, e.g. `[{"price": 135.87, "orderSequenceNumber": "1234"}]`. `price` may be a JSON number or a base-10 decimal string (`"135.87"`); scientific notation and hex strings are rejected so a mis-typed `"1e2"` can't silently target tick 100. Pull from a prior `getTraderState` at `data.snapshot.subaccounts[].orders[].orders[]` (each row exposes `priceUsd` and `orderSequenceNumber`). &#x2A;*Precision option:** each entry may use `priceTicks` (positive integer **string**, e.g. `"12345"`) instead of `price`. Pull from the snapshot's `priceTicks` field. Setting both `price` AND `priceTicks` on the same entry is rejected. See [Cancel-by-id price precision](#cancel-by-id-price-precision) for when to use which. |
| amountUsdc                             | string                      | depositCollateral, withdrawCollateral, transferCollateral                                                                                                                                                             | USDC amount as a base-10 decimal with at most 6 fractional digits (e.g. `"1.5"`, `"100"`, `"0.000001"`). `"1.5"` means 1.5 USDC (=1,500,000 base units), not 1,500,000 USDC. Scientific notation, hex, leading dots, and negatives are rejected; the parser converts string to bigint with no `Number()` so there's no float drift on large amounts. Supports template variables.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| marginType                             | `Cross` \| `Isolated`       | Trading / cancel / deposit / withdraw signed ops (not transfer/sync)                                                                                                                                                  | Margin mode for the trader account this op targets. Defaults to `Cross`. &#x2A;*Locks irreversibly at first signed op for the (subaccount) pair.** Phoenix doesn't allow changing margin type on an existing on-chain trader account. `Cross` is always routed to subaccount 0; `Isolated` requires `traderSubaccountIndex` in `[1, 100]`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| traderSubaccountIndex                  | number (1-100)              | Trading / cancel / deposit / withdraw with Isolated margin; ALSO transferCollateralChildToParent and syncParentToChild (as the child subaccount)                                                                      | Subaccount slot. For single-subaccount ops: required when `marginType: "Isolated"`, must be omitted or 0 when `Cross`. For childToParent / sync: this IS the child subaccount, range 1-100. Subaccount 0 is reserved for Cross by SDK contract. Supports template variables. Phoenix supports up to 101 trader accounts per wallet: Cross at subaccount 0 plus Isolated at any of 1-100.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| timeframe                              | string                      | `getCandles` (required), `getMarketStatsHistory` (optional)                                                                                                                                                           | Candle / stats resolution like `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Forwarded verbatim to Phoenix; whatever the upstream API accepts works.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| startTime, endTime                     | string                      | `getCandles`, `getMarketStatsHistory`, `getFundingOverview`, `getFundingRateHistory`, `getTraderOrderHistory`, `getTraderFundingHistory`, `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl` (optional) | ISO-8601, Unix seconds (10 digits), or Unix millis (13 digits). The executor normalizes per-op. Not accepted by `getMarketFills`, `getTraderTradeHistory`, `getTraderCollateralHistory`, or `getNotifications` (those endpoints paginate by `limit` / `cursor` only).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| limit                                  | number/string               | All paginated read ops (optional)                                                                                                                                                                                     | Max items per page. Phoenix enforces per-endpoint caps server-side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| cursor                                 | string                      | `getMarketFills`, `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderCollateralHistory`, `getTraderFundingHistory`, `getNotifications` (optional)                                                            | Pagination cursor from a prior response's `nextCursor`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| perMarketLimit                         | number/string               | `getFundingOverview` (optional)                                                                                                                                                                                       | Caps points per series (overview returns one series per market). Distinct from `limit` because `FundingOverviewRequest` uses a different key.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| resolution                             | string                      | `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl` (required), `getTraderFundingHistory` (optional)                                                                                                     | Aggregation bucket for time-series outputs (e.g. `1h`, `1d`). PnL / portfolio endpoints reject empty resolution.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| triggerPrice, executionPrice           | string (USD decimal)        | `placeStopLoss`                                                                                                                                                                                                       | Trigger price fires the SL; execution price is the resting limit (Limit kind) or worst acceptable fill (IOC kind). Converted to ticks per the market's tick size.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| executionDirection                     | `GreaterThan` \| `LessThan` | `placeStopLoss`, `cancelStopLoss`                                                                                                                                                                                     | Trigger comparison direction. Runtime also accepts aliases `above`/`below`/`gt`/`lt`/`>`/`<` to play nicely with template-driven AI workflows.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| orderKind                              | `IOC` \| `Limit`            | `placeStopLoss`                                                                                                                                                                                                       | `IOC` cancels any unfilled remainder immediately at the execution price (worst case); `Limit` rests at the execution price as a maker order.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| conditionalTriggers                    | JSON string                 | `placePositionConditionalOrder`                                                                                                                                                                                       | Array of 1-2 entries `{"bucket":"greater"\|"less", "tradeSide":"Bid"\|"Ask", "orderKind":"IOC"\|"Limit", "triggerPrice":"<USD>", "executionPrice":"<USD>"}`. At most one per bucket; setting `bucket: "greater"` twice is rejected.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| srcSubaccountIndex, dstSubaccountIndex | number (0-100)              | `transferCollateral`                                                                                                                                                                                                  | Source and destination subaccounts. Both required, must differ, both in \[0, 100]. Source must already hold collateral; destination is auto-registered (Isolated for non-zero, Cross for 0) if it doesn't exist yet.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

Invite codes are never configured as fields on the Phoenix node. Activation is a one-time wallet-level action handled via the inline panel described in [Going live](#going-live-wallet-activation).

Using template variables [#using-template-variables]

Most fields accept template variables (e.g. `{aiResponse.data.targetPrice}`): `symbol`, `side`, `priceUsd`, `priceLimitUsd`, `baseUnits`, `orderIds`, `amountUsdc`, `marginType`, `traderSubaccountIndex`, `timeframe`, `startTime`, `endTime`, `limit`, `cursor`, `perMarketLimit`, `resolution`, `triggerPrice`, `executionPrice`, `executionDirection`, `orderKind`, `conditionalTriggers`, `srcSubaccountIndex`, and `dstSubaccountIndex`.

A few of these (`side`, `marginType`, `orderKind`, `executionDirection`) are shown as fixed dropdowns in the canvas config rather than free-text boxes, so on the canvas you pick a value. To drive one from an upstream node, template it from a JSON-edited workflow instead (e.g. `side` as `{aiResponse.suggestedSide}` resolving to `"Bid"` or `"Ask"`).

Numeric fields (`priceUsd`, `priceLimitUsd`, `baseUnits`) must be base-10 decimal strings (e.g. `"135.87"`, `"0.25"`); scientific notation, hex, and leading dots are rejected. `amountUsdc` follows the same shape but caps fractional digits at 6 (USDC's on-chain decimal precision).

Time window inputs [#time-window-inputs]

Read ops that take a time window (`getCandles`, `getMarketStatsHistory`, `getFundingOverview`, `getFundingRateHistory`, `getTraderOrderHistory`, `getTraderFundingHistory`, `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl`) accept any of these formats for `startTime` / `endTime`:

* ISO-8601 string, e.g. `2026-01-01T00:00:00Z`
* Unix seconds, 10-digit integer string, e.g. `1700000000`
* Unix millis, 13-digit integer string, e.g. `1700000000000` (floored to seconds)

The executor normalizes the format per endpoint, so you don't have to remember which one each uses. `getMarketFills`, `getTraderTradeHistory`, `getTraderCollateralHistory`, and `getNotifications` paginate by `limit` / `cursor` only and expose no time window.

Pagination [#pagination]

For ops with a `cursor` field, Phoenix returns `nextCursor` on each page. Template the value back in via `{phoenixResponse.data.nextCursor}` and re-run the node to fetch the next page. `cursor` is empty/absent on the first call. Same field name and shape across `getMarketFills`, `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderCollateralHistory`, `getTraderFundingHistory`, and `getNotifications`.

Output [#output]

Symbol-scoped market reads (`getMarket`, `getOrderbook`, `getCandles`, `getMarketFills`, `getMarketStatsHistory`, `getFundingRateHistory`) echo `symbol`; `getMarkets` and `getFundingOverview` omit it (they take no symbol):

```json
{
  "success": true,
  "operation": "getOrderbook",
  "symbol": "SOL",
  "data": { /* raw Phoenix response */ }
}
```

`getTraderState` adds the wallet authority and `hasTraderAccount`:

```json
{
  "success": true,
  "operation": "getTraderState",
  "authority": "7abc...",
  "data": null,
  "hasTraderAccount": false
}
```

Live signed place/cancel ops return a real signature plus status flags (in Paper mode the shape is different: see [Simulated outputs](#simulated-outputs)):

```json
{
  "success": true,
  "operation": "placeLimitOrder",
  "symbol": "SOL",
  "signature": "5JR...",
  "registeredThisCall": false,
  "nowWhitelisted": true
}
```

* `registeredThisCall: true` means this broadcast bundled the register-trader instruction (the first signed op on a fresh subaccount). It's distinct from `getTraderState.hasTraderAccount`, which describes current on-chain state independent of this call.
* `nowWhitelisted: true` is always true on success. Useful as a downstream gate like `{phoenixResponse.nowWhitelisted}`. Reference any field with the response name (default `phoenixResponse`), e.g. `{phoenixResponse.signature}` to forward the tx hash to a notification node.

Collateral ops return the same shape with `amountUsdc` in place of `symbol`. `transferCollateral` echoes both subaccount indices; `transferCollateralChildToParent` and `syncParentToChild` echo `childSubaccountIndex`.

Account-history reads use a nested envelope: outer `data` is our wrapper, inner `data` is Phoenix's paginated response, so you template the next page as `{phoenixResponse.data.nextCursor}`:

```json
{
  "success": true,
  "operation": "getTraderOrderHistory",
  "authority": "7abc...",
  "data": { "data": [/* orders */], "nextCursor": "...", "hasMore": true }
}
```

Margin and subaccount routing [#margin-and-subaccount-routing]

Phoenix supports two margin modes and up to 101 trader accounts per wallet:

* **Cross** is always routed to subaccount 0 (one shared collateral pool backs every position in that subaccount). Picking `marginType: "Cross"` and omitting `traderSubaccountIndex` covers the typical single-strategy case.
* **Isolated** silos each strategy in its own subaccount (1-100), with its own collateral pool, positions, and margin accounting. Pick `marginType: "Isolated"` and set `traderSubaccountIndex` to any number in 1-100. Each `(margin, subaccount)` combo is its own on-chain account that auto-registers on its first signed op.

You can mix them on the same wallet, e.g. a Cross BTC strategy on subaccount 0 plus an Isolated SOL trial on subaccount 5. Cross at a non-zero subaccount, or Isolated at subaccount 0, are rejected at publish time. Margin mode locks irreversibly at the first signed op for a subaccount: once registered as Cross or Isolated it stays that way, so switch by routing the next strategy to a different subaccount.

`depositCollateral` and `withdrawCollateral` target the resolved `(margin, subaccount)` pair. To fund an Isolated strategy on subaccount 5, set `marginType: "Isolated", traderSubaccountIndex: 5` on the deposit node. The first deposit auto-registers subaccount 5 as Isolated and credits the USDC there, independent of subaccount 0.

Cancel-by-id price precision [#cancel-by-id-price-precision]

When cancelling specific orders, you identify each one by price. Phoenix stores prices as integer "ticks," and converting a decimal USD price back to a tick uses floor math that can occasionally land on the tick next to your actual order, so the cancel misses. You have two ways to cancel:

* **`price` (USD)**: the simple path. Hand the node a decimal USD value (e.g. `135.87`). Works cleanly for prices that aren't on a precision-fragile boundary.
* **`priceTicks` (string)**: the precision-safe path. Pull the order's exact on-chain tick from a `getTraderState` snapshot at `data.snapshot.subaccounts[].orders[].orders[].priceTicks`. The node converts it back to a price that is guaranteed to target the right tick, and refuses to submit if it can't, so you never cancel the wrong order.

Pick `priceTicks` for deterministic, tick-perfect cancellation (high-frequency or replace-on-cancel flows). Pick `price` for the simpler path when the order's price is whole-cents-flat. Each entry must use one or the other; setting both is rejected.

Common use cases [#common-use-cases]

* AI sentiment to a conditional `placeLimitOrder` on Phoenix
* Cross-venue arbitrage: read Drift orderbook, place a hedge on Phoenix
* Scheduled rebalancing: `getTraderState` to `cancelAll` to fresh limit orders
* Scheduled collateral top-up: cron trigger to `depositCollateral` with a templated `amountUsdc`
* Automated profit-take: run `withdrawCollateral` with your threshold amount and let the precheck gate it. The precheck reads `effectiveCollateralForWithdrawals` (which accounts for open positions, unrealized PnL, funding, and margin) and rejects with a clean "insufficient withdrawable" error, so you can route the workflow on that error. Note it's best-effort: state can move between precheck and broadcast, so a precheck-pass withdraw can still fail on-chain. Don't gate on `getTraderState.data.snapshot.subaccounts[].collateral`: it's raw per-subaccount collateral and does NOT account for positions/PnL/funding/margin.

Under the hood [#under-the-hood]

You don't need any of this to use the node. It's here for the curious and for anyone debugging unexpected behavior.

Builder fees [#builder-fees]

Solaris AI is a registered Phoenix Flight builder, so order-placing instructions (`placeLimitOrder`, `placeMarketOrder`, `placePostOnlyOrder`, `placePositionConditionalOrder`) carry Solaris AI's builder code and earn a builder fee on the resulting **taker** fills (rate set server-side by Phoenix, currently 1 bp). Maker fills earn no builder fee, so a post-only order (maker-only) never generates one even though its instruction is still wrapped. Stop-loss, cancel, and collateral instructions are not order-placing, so they're never wrapped and never carry a fee. A test asserts the builder authority stays wired up so a future refactor can't silently drop it.

Reliability [#reliability]

* `placeMarketOrder` will not submit without `priceLimitUsd` set, even though the SDK accepts uncapped market packets. The node treats that as user error and fails before broadcasting.
* Each signed transaction is prepended with compute-budget instructions and a priority fee. The fee is estimated per broadcast from a live Helius `getPriorityFeeEstimate` (High level, keyed off the transaction's writable accounts) and clamped to a floor/ceiling (currently 40,000 to 1,000,000 micro-lamports/CU), so time-sensitive trades and cancels stay competitive under congestion without a fee spike draining the wallet. Estimating per build means a blockhash-expiry retry also re-prices instead of resubmitting the same fee. If the estimate is unavailable (RPC without the method, timeout, error), it falls back to the floor. Most ops use a 400k CU budget; deposit and withdraw chain more instructions and use 800k.
* If a Live signed op throws, the engine does **not** automatically re-run the node, because a fresh attempt could duplicate an order or fund movement that may have actually landed (a thrown op is ambiguous about whether its transaction landed). The one exception is `syncParentToChild`, which is idempotent (re-syncing identical state is a no-op) and stays retryable. Paper ops are always retryable since they never broadcast; a transient order-book read just runs again.
* Deposits and withdrawals run a balance precheck (wallet USDC for deposit, `effectiveCollateralForWithdrawals` for withdraw) and fail fast with a clear message rather than a cryptic on-chain error. The withdraw precheck is best-effort: on-chain state can move between precheck and broadcast.

How activation is checked [#how-activation-is-checked]

Activation is verified, never stored. The activation panel calls a server action that resolves your wallet via Privy, checks Phoenix's whitelist, and only consumes a code if you actually need one (an already-whitelisted wallet, or one that already has a Phoenix trader account from a prior referral activation, short-circuits without burning a code). At runtime the executor only **reads** activation state before a Live broadcast: it checks the whitelist, falls back to probing for an existing default trader account, and throws a clear "open the Phoenix node and click Activate" error if neither proves access. No transaction is broadcast in that case, and the executor never sees invite codes.

SDK coverage [#sdk-coverage]

The node wraps the Ellipsis Labs Rise SDK. These SDK surfaces are intentionally **not** exposed as node operations:

* `placeMultiLimitOrder`, `placeAttachedConditionalOrder`, `placeLimitOrderWithConditionals`, `cancelUpTo`, `cancelConditionalOrder`: only practical via low-level instruction builders that need manual account resolution.
* `placeIsolatedLimitOrder` / `placeIsolatedMarketOrder`: superseded by our two-step `transferCollateral` then `placeLimitOrder` flow, which has clearer node-level intent.
* `delegateTrader`: multi-signer / copy-trading delegation, out of scope for typical workflows.
* Escrow operations (create / accept / cancel request, grant / revoke permission): niche inter-trader transfers, not in current scope.
* Notification ack endpoints: write ops on the notification feed; would need their own signing-op slot.
* WebSocket / streaming surfaces: workflows run as one-shot HTTP calls, so long-lived subscriptions don't fit.

Next steps [#next-steps]

* [Drift](/docs/nodes/defi/drift) - compare perps surface
* [Jupiter](/docs/nodes/defi/jupiter) - spot routing for hedging


---

# Pump.fun (/docs/nodes/defi/pump-fun)

Memecoin discovery, trading, launch, and fee claims on Pump.fun.



Pump.fun is Solana's memecoin launchpad. The Pump.fun node supports token discovery, price checks, on-chain buy/sell, new token launches, and creator-fee / cashback claims.

Prerequisites [#prerequisites]

* **Read operations**: No credential required
* **All on-chain operations** (buy, sell, createToken, claimFees, claimCashback): Connected Privy wallet (transactions are signed through your embedded wallet)

Operations [#operations]

| Operation        | Description                                                                                                                                           | Requires wallet |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| getCoin          | Frontend API metadata (name, image, socials, market cap)                                                                                              | No              |
| getTokenInfo     | On-chain protocol state (bonding progress, graduated flag, creator, mayhem/cashback, PumpSwap pool info when graduated)                               | No              |
| getTopRunners    | Raw top-runners list, flattened + enriched with `priceSol`/`bondingProgressPct`                                                                       | No              |
| getTrending      | Top-runners padded from the recommended pool, deduped by mint                                                                                         | No              |
| getRecommended   | Pump.fun-curated recommended tokens                                                                                                                   | No              |
| getNew           | Recently launched, sorted by `created_timestamp` desc                                                                                                 | No              |
| getGraduating    | Still on the bonding curve, sorted by `real_sol_reserves` desc (closest to graduation first)                                                          | No              |
| getCurrentlyLive | Tokens currently in the bonding curve phase                                                                                                           | No              |
| search           | Free-text filter against name/symbol/description across the top-300 recommended                                                                       | No              |
| getSolPrice      | Current SOL price in USD                                                                                                                              | No              |
| buy              | Buy a token with SOL                                                                                                                                  | Yes             |
| sell             | Sell tokens for SOL                                                                                                                                   | Yes             |
| createToken      | Launch a new token via `create_v2` (uploads metadata to pump.fun IPFS or accepts a pre-hosted URI, optionally includes an initial buy in the same tx) | Yes             |
| claimFees        | Collect accrued creator fees from both the pump.fun bonding-curve vault (SOL) and the PumpSwap AMM vault (WSOL)                                       | Yes             |
| claimCashback    | Claim accrued volume-based cashback from the pump.fun bonding-curve program                                                                           | Yes             |

All discovery ops return a **normalized token list** - each entry has at least
`mint`, `name`, `symbol`, `priceSol`, and `bondingProgressPct` so downstream
nodes can reference `{pumpfunResponse.data[0].name}` regardless of which op
produced the list. `priceSol` / `bondingProgressPct` may be `null` when the
upstream endpoint doesn't include reserve data.

Automatic routing [#automatic-routing]

Buy and sell operations transparently route based on the token's state:

* **Bonding curve** - while the token is still on the Pump.fun bonding curve
* **PumpSwap AMM** - once the token has graduated from the bonding curve

You don't need to configure the route; the node detects it at run time.

Configuration [#configuration]

Every numeric and boolean field below accepts either a literal value or a
`{varName.path}` template resolved at run time against upstream node
outputs - e.g. `solAmount: "{trigger.amount}"`,
`slippageBps: "{aiDecision.bps}"`, or `dryRun: "{gate.simulate}"`.
Boolean templates resolve `true`/`false`, `1`/`0`, and `yes`/`no`
(case-insensitive) after substitution. Each boolean field in the config UI
has a "Use {"{template}"}" toggle that swaps the checkbox for a template
input. Literal values are bounds-checked in the config UI; templates are
validated at run time after substitution.

| Field                          | Type                | Required                          | Description                                                                                                                                                                                             |
| ------------------------------ | ------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mintAddress                    | string              | For getCoin/getTokenInfo/buy/sell | Token mint address (template-aware)                                                                                                                                                                     |
| query                          | string              | For search                        | Case-insensitive substring match against name/symbol/description                                                                                                                                        |
| solAmount                      | number \| template  | For buy                           | SOL amount to spend                                                                                                                                                                                     |
| tokenAmount                    | number \| template  | For sell (unless sellAll)         | Token amount to sell (whole tokens)                                                                                                                                                                     |
| sellAll                        | boolean \| template | No                                | Sell the entire wallet balance; overrides tokenAmount. When templated, `tokenAmount` must also be set as a fallback for the case where the template resolves `false`                                    |
| dryRun                         | boolean             | No                                | Simulate the transaction against the RPC without broadcasting (no funds spent; for `createToken`, also skips the IPFS upload)                                                                           |
| slippageBps                    | number \| template  | No                                | Slippage in basis points (default: 1500, range: 0-10000; 0 enforces the exact min-out, 10000 = 100%). Values above 5000 are useful for illiquid pools but may fill far from the quoted price.           |
| priorityFee                    | number \| template  | No                                | microLamports/CU tip (0-10,000,000). Blank = venue default (buy: 200k · PumpSwap: 10k · launch: 500k · claim: 200k)                                                                                     |
| computeUnits                   | number \| template  | No                                | CU limit for the tx (10,000-1,400,000). Blank = venue default                                                                                                                                           |
| confirm                        | boolean             | No                                | Wait for on-chain confirmation (default: true)                                                                                                                                                          |
| limit                          | number \| template  | No                                | Results limit, 1-300 (default: 20)                                                                                                                                                                      |
| offset                         | number \| template  | No                                | Pagination offset (>= 0)                                                                                                                                                                                |
| includeNsfw                    | boolean             | No                                | Include NSFW tokens (default: false)                                                                                                                                                                    |
| name                           | string              | For createToken                   | Token display name (≤32 UTF-8 bytes, Metaplex cap)                                                                                                                                                      |
| symbol                         | string              | For createToken                   | Token ticker (≤10 UTF-8 bytes)                                                                                                                                                                          |
| description                    | string              | For createToken upload mode       | Description shown on pump.fun. Required unless `metadataUri` is supplied                                                                                                                                |
| imageUrl                       | string              | No                                | Remote image URL - fetched server-side and streamed into the pump.fun IPFS upload. Upload mode only                                                                                                     |
| metadataUri                    | string              | For createToken URI mode          | Pre-hosted metadata JSON URL (http/https, ≤200 bytes). When set, skips IPFS upload; `description`/`imageUrl`/socials are ignored. `name` + `symbol` are still required (stored on-chain by `create_v2`) |
| twitter \| telegram \| website | string              | No                                | Optional socials echoed into the IPFS form. Upload mode only                                                                                                                                            |
| initialBuySol                  | number \| template  | No                                | Optional SOL amount to spend immediately after creation in the same tx. No slippage control on a fresh curve - matches reference CLI semantics                                                          |
| isMayhem                       | boolean \| template | No                                | Launch in mayhem mode (affects fee recipient and cashback)                                                                                                                                              |
| isCashback                     | boolean \| template | No                                | Enable volume-based cashback on this token - buyers accrue claimable rewards                                                                                                                            |

Output (getCoin) [#output-getcoin]

```json
{
  "success": true,
  "operation": "getCoin",
  "mint": "TokenMintAddress123...",
  "data": {
    "mint": "TokenMintAddress123...",
    "name": "Example Token",
    "symbol": "EXT",
    "market_cap": 125000,
    "usd_market_cap": 125000
  }
}
```

Output (getTokenInfo) [#output-gettokeninfo]

On-chain protocol state. When the token has graduated, `priceSol` switches from
bonding-curve spot to PumpSwap pool spot, and pool fields are populated. If the
PumpSwap pool lookup fails on a graduated token, the bonding-curve snapshot is
returned with `pumpswapWarning` / `pumpswapError` attached.

```json
{
  "success": true,
  "operation": "getTokenInfo",
  "mint": "TokenMintAddress123...",
  "data": {
    "mint": "TokenMintAddress123...",
    "bondingCurve": "BondingCurvePda...",
    "priceSol": 0.00003,
    "bondingProgressPct": 23.5,
    "graduated": false,
    "realSolReserves": 20.0,
    "virtualSolReserves": 30.0,
    "virtualTokenReserves": "1000000000000",
    "creator": "CreatorPubkey...",
    "isMayhemMode": false,
    "isCashbackEnabled": false
  }
}
```

Output shape - all operations [#output-shape---all-operations]

Every Pump.fun operation (reads and trades) returns the same outer envelope:

```
{ success, operation, mint?, data }
```

Downstream nodes always reference fields via `{responseName.data.<field>}` -
e.g. `{pumpfunResponse.data.txHash}` on a trade, `{pumpfunResponse.data.name}`
on `getCoin`. `mint` is omitted on operations that don't take one
(`getTopRunners`, `getSolPrice`, `getRecommended`, `getCurrentlyLive`).

Output (buy) [#output-buy]

```json
{
  "success": true,
  "operation": "buy",
  "mint": "TokenMintAddress123...",
  "data": {
    "venue": "bonding_curve",
    "walletAddress": "UserWallet...",
    "solAmount": 0.1,
    "estimatedTokens": 3250.4,
    "minTokens": 2762.9,
    "slippageBps": 1500,
    "priceSol": 0.00003,
    "bondingProgressPct": 23.5,
    "confirmed": true,
    "txHash": "5Lbm..."
  }
}
```

For a graduated token, `venue` becomes `"pumpswap"` and `bondingProgressPct` is
replaced by `poolPrice`. `bondingProgressPct` is a 0–100 percent (one decimal),
matching `getTokenInfo`. Dry-run results additionally include `dryRun: true`,
`simulated: true`, `unitsConsumed`, `logs`, `error`, and `balanceWarning`.

Output (sell) [#output-sell]

```json
{
  "success": true,
  "operation": "sell",
  "mint": "TokenMintAddress123...",
  "data": {
    "venue": "pumpswap",
    "walletAddress": "UserWallet...",
    "tokensSold": 1000,
    "estimatedSol": 0.0312,
    "minSol": 0.0265,
    "slippageBps": 1500,
    "confirmed": true,
    "txHash": "5Lbm..."
  }
}
```

Preflight failures [#preflight-failures]

Both buy and sell preflight the wallet's SOL balance before broadcasting. A live
trade with insufficient SOL throws before spending any funds - `Insufficient SOL
for sell fees. Have X SOL, need ~Y SOL.` (or the `buy` equivalent). A dry-run
attaches the same information as `data.balanceWarning` instead of throwing so
the workflow can inspect it.

Output (createToken) [#output-createtoken]

```json
{
  "success": true,
  "operation": "createToken",
  "mint": "NewMintAddress...",
  "data": {
    "mint": "NewMintAddress...",
    "bondingCurve": "BondingCurvePda...",
    "name": "Example Coin",
    "symbol": "EXM",
    "metadataUri": "https://...",
    "uploadedMetadata": true,
    "isMayhem": false,
    "isCashback": false,
    "initialBuySol": 0.01,
    "walletAddress": "UserWallet...",
    "templateHint": "Use {pumpfunResponse.mint} in downstream nodes. Explorer: https://solscan.io/token/...",
    "pumpUrl": "https://pump.fun/coin/...",
    "explorer": "https://solscan.io/token/...",
    "confirmed": true,
    "bondingCurveVerified": true,
    "txHash": "5Lbm...",
    "txExplorer": "https://solscan.io/tx/..."
  }
}
```

`mint` is surfaced at the top level so downstream nodes can reference it as
`{pumpfunResponse.mint}` - the canonical way to chain follow-up work after a
launch (e.g. post to Telegram, set an alert, auto-buy more). `uploadedMetadata`
is `true` when we uploaded to pump.fun IPFS and `false` when you supplied a
pre-hosted `metadataUri`. On a dry run, `metadataUriPlaceholder: true` and a
`metadataNote` indicate a synthetic URI was used - **IPFS is not touched during
dry-run**, and balance/validation failures in live mode also skip the upload.

Signing + integrity [#signing--integrity]

`createToken` requires two signers: your Privy wallet (fee payer) and a fresh
mint keypair. The mint keypair is generated in-memory for one transaction and
never persisted. To avoid undocumented mutations to the transaction message,
the node calls Privy's `signTransaction` (not `signAndSendTransaction`), then
performs a byte-level integrity check on Privy's response before broadcasting:
serialized message bytes must be unchanged, the mint signature must survive the
round-trip, and Privy must have added at least one user signature. Any
discrepancy aborts before broadcast.

Output (claimFees) [#output-claimfees]

Collects accrued creator fees from both vaults (skips either when empty). When
neither vault has a balance, returns `success: false` with `error: "no_fees"`
so downstream conditions can branch cleanly.

```json
{
  "success": true,
  "operation": "claimFees",
  "data": {
    "walletAddress": "UserWallet...",
    "pumpRecoveredSol": 0.123,
    "pumpswapRecoveredSol": 0.045,
    "totalRecoveredSol": 0.168,
    "wsolNote": "PumpSwap fees land as WSOL in your Wrapped SOL ATA. Unwrap manually or via Jupiter to get native SOL.",
    "createdWsolAta": true,
    "confirmed": true,
    "txHash": "5Lbm...",
    "txExplorer": "https://solscan.io/tx/..."
  }
}
```

PumpSwap fees land as WSOL in your creator WSOL ATA - the node does not auto-unwrap
because that would require closing the ATA and could nuke any pre-existing WSOL
balance. Use the Jupiter node or any Solana wallet to unwrap when you're ready.

Output (claimCashback) [#output-claimcashback]

Claims accrued volume-based cashback. When the user's volume-accumulator PDA
doesn't exist, returns `success: false` with `error: "no_accumulator"`. Note:
an existing accumulator with zero claimable cashback is NOT detected
client-side - the on-chain program is the authoritative source and the tx
will revert at runtime if there's nothing to claim.

```json
{
  "success": true,
  "operation": "claimCashback",
  "data": {
    "walletAddress": "UserWallet...",
    "accumulator": "UserVolAccumulatorPda...",
    "confirmed": true,
    "txHash": "5Lbm...",
    "txExplorer": "https://solscan.io/tx/..."
  }
}
```

Common use cases [#common-use-cases]

* Monitor new token launches and filter by market cap
* Automate memecoin buys when conditions are met (combine with AI or Condition node)
* Track top runners and send alerts via Telegram

Next steps [#next-steps]

* [Jupiter](/docs/nodes/defi/jupiter) - swap on established tokens
* [Birdeye](/docs/nodes/data/birdeye) - deeper token analytics


---

# Raydium (/docs/nodes/defi/raydium)

Token prices, pool data, swap quotes, and LaunchLab via Raydium.



Raydium is a leading Solana AMM. The Raydium node provides token prices, pool lookups, swap quotes, and LaunchLab token discovery.

Prerequisites [#prerequisites]

No credential required. All operations use Raydium's public API.

Operations [#operations]

| Operation         | Description                          | Key inputs                                 |
| ----------------- | ------------------------------------ | ------------------------------------------ |
| getTokenPrice     | Get price for one or more tokens     | mintAddress                                |
| getPoolsByToken   | Find pools containing a token        | mintAddress, mintAddress2 (optional)       |
| getPoolDetails    | Get details for specific pool(s)     | poolId                                     |
| getTopPools       | List top pools by liquidity/volume   | poolType, sortField, limit                 |
| getSwapQuote      | Get a swap quote between two tokens  | inputMint, outputMint, amount, slippageBps |
| getPlatformStats  | Get Raydium platform-wide statistics | (none)                                     |
| getLaunchLabList  | List LaunchLab tokens                | sortField, limit                           |
| getLaunchLabToken | Get LaunchLab token details by mint  | mintAddress                                |

Configuration [#configuration]

| Field        | Type   | Required           | Description                                           |
| ------------ | ------ | ------------------ | ----------------------------------------------------- |
| mintAddress  | string | Varies             | Token mint address                                    |
| mintAddress2 | string | No                 | Second token for pool pair lookups                    |
| poolId       | string | For getPoolDetails | Pool ID(s)                                            |
| inputMint    | string | For swap           | Token to sell                                         |
| outputMint   | string | For swap           | Token to buy                                          |
| amount       | string | For swap           | Amount in smallest units                              |
| slippageBps  | number | No                 | Slippage tolerance in basis points (default: 50)      |
| poolType     | string | No                 | Filter: `all`, `standard`, `concentrated`             |
| sortField    | string | No                 | Sort by: `liquidity`, `volume24h`, `fee24h`, `apr24h` |
| sortType     | string | No                 | `asc` or `desc` (default: `desc`)                     |
| limit        | number | No                 | Results per page (default: 10)                        |

Output (getSwapQuote) [#output-getswapquote]

```json
{
  "success": true,
  "operation": "getSwapQuote",
  "inputMint": "So11111111111111111111111111111111111111112",
  "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "data": { "swapType": "BaseIn", "outputAmount": "150230000" }
}
```

Common use cases [#common-use-cases]

* Compare swap quotes between Raydium and Jupiter
* Monitor pool liquidity changes over time
* Discover new LaunchLab token launches

Next steps [#next-steps]

* [Jupiter](/docs/nodes/defi/jupiter) - alternative DEX aggregator with swap execution
* [Pump.fun](/docs/nodes/defi/pump-fun) - memecoin trading


---

# Sanctum (/docs/nodes/defi/sanctum)

Liquid staking token (LST) data, APY, TVL, and metadata via Sanctum.



Sanctum is Solana's liquid staking hub. The Sanctum node provides LST pricing, APY data, TVL tracking, and metadata for all supported liquid staking tokens.

Prerequisites [#prerequisites]

No credential required. All operations use Sanctum's public API.

Operations [#operations]

| Operation             | Description                     | Key inputs             |
| --------------------- | ------------------------------- | ---------------------- |
| getSolValue           | Get SOL value of LST tokens     | lstSymbols             |
| getApy                | Get APY for LST tokens          | lstSymbols             |
| getTvl                | Get TVL for LST tokens          | lstSymbols             |
| getHolders            | Get holder count for LST tokens | lstSymbols             |
| getLstMeta            | Get metadata for LST tokens     | lstSymbols             |
| getApyHistory         | Get historical APY data         | lstSymbols, epochCount |
| getLstList            | List all available LSTs         | (none)                 |
| getFeaturedLsts       | Get featured/promoted LSTs      | (none)                 |
| getInfinityAllocation | Get Infinity pool allocation    | (none)                 |
| getLstCategories      | Get LST category groupings      | (none)                 |

Configuration [#configuration]

| Field      | Type   | Required     | Description                                             |
| ---------- | ------ | ------------ | ------------------------------------------------------- |
| lstSymbols | string | For most ops | Comma-separated LST symbols (e.g., `mSOL,jitoSOL,bSOL`) |
| epochCount | number | No           | Number of epochs for APY history (default: 10)          |

Output [#output]

All operations return the standard envelope:

```json
{
  "success": true,
  "operation": "getApy",
  "data": { /* raw Sanctum response */ }
}
```

Reference fields downstream with the response name (default `sanctumResponse`), for example `{sanctumResponse.data.apys.mSOL}`. Per-LST operations (`getSolValue`, `getApy`, `getTvl`, `getHolders`, `getLstMeta`) typically return values keyed by symbol; list operations return an array under `.data`.

Common use cases [#common-use-cases]

* Compare APY across liquid staking options
* Monitor TVL shifts between LST providers
* Alert when an LST's APY drops below a threshold

Next steps [#next-steps]

* [Kamino](/docs/nodes/defi/kamino) - lending markets and vaults
* [Pyth](/docs/nodes/data/pyth) - oracle price feeds


---

# Discord (/docs/nodes/messaging/discord)

Send messages to Discord channels via webhooks.



The Discord node sends plain-text messages to a Discord channel through a webhook URL. Embed objects, attachments, and components are not supported - use the message field's Markdown for formatting.

Prerequisites [#prerequisites]

* Discord Webhook URL (Server Settings → Integrations → Webhooks)
* Add as a credential in [Credentials](/docs/credentials/adding)

Operations [#operations]

The Discord node sends a message to the configured webhook. There's no operation selector - it always sends a message.

Configuration [#configuration]

| Field    | Type   | Required | Description                                     |
| -------- | ------ | -------- | ----------------------------------------------- |
| content  | string | Yes      | Message content (supports template expressions) |
| message  | string | Alt      | Alternative to content (same behavior)          |
| username | string | No       | Override the webhook's display name             |

Template expressions [#template-expressions]

Use upstream data in your messages:

```
🚀 New token detected!
Name: {pumpfunResponse.data.name}
Market Cap: ${pumpfunResponse.data.market_cap}
```

Output [#output]

A successful send returns:

```json
{
  "success": true,
  "message": "Discord message sent",
  "renderedMessage": "🚀 New token: BONK",
  "webhook": "https://discord.com/api/webhooks/123.../redacted",
  "discordMessageId": "1234567890",
  "discordChannelId": "9876543210",
  "discordGuildId": "1122334455",
  "messageLink": "https://discord.com/channels/1122334455/9876543210/1234567890",
  "verifiedByFetch": true,
  "postedToDifferentChannel": false
}
```

Webhook discovery fields (`discordMessageId`, `discordChannelId`, `discordGuildId`, `messageLink`) are present when Discord returns them in the webhook response; long messages may also include `truncated: true` and `originalLength`. Reference these downstream with your response name (default `discordResponse`):

```text
{discordResponse.messageLink}
{discordResponse.discordMessageId}
```

Common use cases [#common-use-cases]

* Alert a Discord channel on DeFi events
* Post daily portfolio summaries
* Notify team channels on workflow failures

Next steps [#next-steps]

* [Telegram](/docs/nodes/messaging/telegram) - alternative with richer bot features
* [Webhook Trigger](/docs/triggers/webhook) - receive Discord events


---

# Telegram (/docs/nodes/messaging/telegram)

Send messages, photos, and check bot status via Telegram Bot API.



The Telegram node sends messages and photos through the Telegram Bot API. Use it for alerts, notifications, and automated reporting.

Prerequisites [#prerequisites]

* Telegram Bot Token (create via [@BotFather](https://t.me/BotFather))
* Add as a credential in [Credentials](/docs/credentials/adding)
* Your bot must be added to the target chat/group

Operations [#operations]

| Operation   | Description                        | Key inputs                |
| ----------- | ---------------------------------- | ------------------------- |
| sendMessage | Send a text message                | chatId, message           |
| sendPhoto   | Send a photo with optional caption | chatId, photoUrl, caption |
| getMe       | Get bot info (test connectivity)   | (none)                    |
| getUpdates  | Get recent messages to the bot     | (none)                    |

Configuration [#configuration]

| Field               | Type    | Required        | Description                                  |
| ------------------- | ------- | --------------- | -------------------------------------------- |
| chatId              | string  | For send ops    | Telegram chat/group/channel ID               |
| message             | string  | For sendMessage | Message text (supports template expressions) |
| photoUrl            | string  | For sendPhoto   | URL of the photo to send                     |
| caption             | string  | No              | Photo caption text                           |
| parseMode           | string  | No              | `Markdown` or `HTML` for formatting          |
| disableNotification | boolean | No              | Send silently (default: false)               |

Getting your chat ID [#getting-your-chat-id]

1. Start a conversation with your bot
2. Send any message
3. Use the `getUpdates` operation to see recent messages
4. Find the `chat.id` field in the response

Template expressions in messages [#template-expressions-in-messages]

Use upstream node data in your messages:

```
SOL Price Alert: {birdeyeResponse.data.value} USD
24h Change: {birdeyeResponse.data.priceChange24h}%
```

Output [#output]

`sendMessage`:

```json
{
  "success": true,
  "operation": "sendMessage",
  "chatId": "-1001234567890",
  "renderedMessage": "SOL Price Alert: 150.23 USD",
  "data": { /* raw Telegram Bot API response */ }
}
```

`sendPhoto` returns the same shape minus `renderedMessage`. `getMe` and `getUpdates` return `{ success, operation, data }`, where `data` is the raw Bot API response; `getUpdates` is a snapshot of recent updates with no offset tracking, so re-runs may include previously-seen messages.

Downstream references use the response name (default `telegramResponse`): `{telegramResponse.data.result.message_id}` for the sent-message ID, `{telegramResponse.chatId}` for the chat the message was sent to.

Common use cases [#common-use-cases]

* Price alerts when tokens hit thresholds
* Portfolio summary reports on a daily cron
* New token launch notifications from Pump.fun

Next steps [#next-steps]

* [Discord](/docs/nodes/messaging/discord) - alternative notification channel
* [Cron Trigger](/docs/triggers/cron) - schedule recurring alerts


---

# Balance (/docs/nodes/utility/balance)

Read the connected wallet's SOL or SPL token balance.



The Balance node reads the embedded Privy wallet attached to the signed-in user. Use it before trading, paying, or branching so the workflow can check available funds.

Prerequisites [#prerequisites]

* A signed-in Solaris AI Flow account
* An embedded Privy Solana wallet
* No API key required

Configuration [#configuration]

| Field      | Type   | Required | Description                                                                 |
| ---------- | ------ | -------- | --------------------------------------------------------------------------- |
| Node Label | string | No       | Display name shown on the canvas                                            |
| Token mint | string | No       | Leave empty to read SOL. Paste an SPL token mint to read that token instead |

The token mint field supports template expressions. For example:

```text
{webhook.body.mint}
```

Use a template when the token to inspect comes from a webhook, API response, or previous workflow step.

Output for SOL [#output-for-sol]

When **Token mint** is empty, the node reads native SOL:

```json
{
  "walletAddress": "YourWallet...",
  "mint": null,
  "symbol": "SOL",
  "decimals": 9,
  "balance": 1.25,
  "balanceRaw": "1250000000"
}
```

Output for SPL tokens [#output-for-spl-tokens]

When **Token mint** is set, the node reads the wallet's associated token account:

```json
{
  "walletAddress": "YourWallet...",
  "mint": "TokenMint...",
  "symbol": null,
  "decimals": 6,
  "balance": 42.5,
  "balanceRaw": "42500000",
  "accountExists": true
}
```

If the token account does not exist yet, the node returns a zero balance with `accountExists: false` instead of failing. That is useful before buys or swaps where the wallet may not have received that token before.

Common use cases [#common-use-cases]

* Check SOL before a Jupiter transfer, Pump.fun trade, marketplace purchase, or x402 request
* Check a token balance before deciding whether to sell
* Branch with a [Condition](/docs/nodes/utility/condition) node when a balance is above or below a threshold
* Use a webhook-provided mint address to inspect arbitrary SPL token balances

Example: only continue if SOL is available [#example-only-continue-if-sol-is-available]

Workflow:

```text
Manual Trigger -> Balance -> Condition -> Jupiter
```

Leave **Token mint** empty. Then configure Condition:

```text
expression: {balanceResponse.balance}
operator: greater_than
value: 0.05
```

Next steps [#next-steps]

* [Condition](/docs/nodes/utility/condition) - branch based on the balance
* [Jupiter](/docs/nodes/defi/jupiter) - transfer or swap tokens
* [x402](/docs/nodes/utility/x402) - pay x402-enabled endpoints


---

# Code (/docs/nodes/utility/code)

Run custom JavaScript against upstream workflow data.



The Code node runs small JavaScript transformations in a sandbox and passes the returned value downstream. Use it when the Transform node's visual pipeline is not expressive enough for custom math, grouping, reducing arrays, or normalizing inconsistent nested data.

Code nodes are synchronous. They can read upstream data through `$input` and must `return` a JSON-serializable value.

Configuration [#configuration]

| Field           | Type   | Required | Description                                                                               |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| JavaScript      | string | Yes      | Code to run. Use `$input` to read upstream node outputs and `return` the downstream value |
| Timeout (ms)    | number | No       | Execution deadline. Values are clamped between `100` and `10000` ms                       |
| Output Variable | string | No       | Name used by downstream nodes. Defaults to `codeResponse`                                 |
| Node Label      | string | No       | Display name shown on the canvas                                                          |

How it works [#how-it-works]

1. Upstream node outputs are collected into `$input`
2. Your JavaScript runs in a sandboxed QuickJS runtime
3. The returned value is saved as this node's output
4. Downstream nodes reference it through the configured output variable

The sandbox does not provide host APIs such as `fetch`, `process`, filesystem access, npm imports, or timers. Use integration nodes for network calls and the Code node for data shaping.

Working with `$input` [#working-with-input]

The node settings **Input** pane shows the upstream sample passed to `$input`. Click a value in the Input pane while the JavaScript editor is focused to insert the matching `$input` access at the cursor.

Examples:

```js
$input.pumpfunResponse.data[0].symbol
$input["price-feed"].data.value
```

The Input pane also includes JSON, Schema, and Table views when you need exact inspection instead of the default Variables browser.

Example: rank Pump.fun top tokens [#example-rank-pumpfun-top-tokens]

Workflow:

```text
Manual Trigger → Pump.fun getTopRunners → Code → Telegram
```

Set the Pump.fun node output variable to `pumpfunResponse`. Then add this Code node:

```js
const tokens = Array.isArray($input.pumpfunResponse?.data)
  ? $input.pumpfunResponse.data
  : [];

const ranked = tokens
  .filter((token) => {
    return (
      token.mint &&
      token.name &&
      token.symbol &&
      token.priceSol != null &&
      token.bondingProgressPct != null
    );
  })
  .map((token) => ({
    mint: token.mint,
    name: token.name,
    symbol: token.symbol,
    priceSol: token.priceSol,
    bondingProgressPct: token.bondingProgressPct,
    score:
      Number(token.bondingProgressPct) * 2 + Number(token.priceSol) * 100000,
  }))
  .sort((a, b) => b.score - a.score)
  .slice(0, 5);

return {
  count: ranked.length,
  tokens: ranked,
};
```

If the Code node output variable is `rankedTokens`, a downstream Telegram or Discord node can use:

```text
Top Pump.fun runner: {rankedTokens.data.tokens[0].symbol}
Mint: {rankedTokens.data.tokens[0].mint}
Progress: {rankedTokens.data.tokens[0].bondingProgressPct}%
```

Output [#output]

A successful Code node returns:

```json
{
  "success": true,
  "data": {
    "count": 5,
    "tokens": []
  }
}
```

Downstream nodes reference the returned value under `.data`:

```text
{codeResponse.data}
{codeResponse.data.tokens[0].symbol}
```

If you set **Output Variable** to `rankedTokens`, use:

```text
{rankedTokens.data.tokens[0].symbol}
```

Previewing [#previewing]

Code settings use the shared node-settings layout:

* **Input** shows upstream sample data available as `$input`.
* **Parameters** contains the JavaScript editor, timeout, output variable, and node label.
* **Output** shows a **Live** preview for the current draft and a **Last** tab for the most recent persisted run output.

The Live preview runs your draft code in the same QuickJS sandbox used by workflow execution, using the current upstream sample. It is for inspection while editing; execute the node or workflow to persist a last-run output.

If the Code node has no input sample, use the Input pane button to execute previous nodes. This runs previous nodes and uses their latest outputs as preview data. Preview runs count as monthly runs and are hidden from normal execution history. If upstream nodes can send messages, transfer funds, trade, or broadcast transactions, the editor asks for confirmation before firing them.

Limits [#limits]

| Limit       | Value        |
| ----------- | ------------ |
| Timeout     | 100-10000 ms |
| Memory      | 16 MB        |
| Stack       | 1 MB         |
| Output size | 5 MB         |

The returned value must be JSON-serializable. Returning functions, symbols, circular objects, or unsupported values causes the node to fail.

When to use Code vs Transform [#when-to-use-code-vs-transform]

Use **Transform** when your data change fits the visual pipeline:

* Filter, sort, and slice arrays
* Keep, drop, or rename fields
* Add fixed fields
* Add template-derived fields
* Compute age from timestamps

Use **Code** when you need arbitrary JavaScript logic:

* Custom scoring or math
* Grouping or reducing arrays
* Normalizing inconsistent API shapes
* Handling branching data structures that do not fit the visual steps
* Building a custom object for alerts or downstream trades

Next steps [#next-steps]

* [Transform](/docs/nodes/utility/transform) - visual pipeline-based reshaping
* [Configuring Nodes](/docs/editor/configuring-nodes) - input/output panes and variables
* [Filter](/docs/nodes/utility/filter) - conditionally pass or block data
* [Pump.fun](/docs/nodes/defi/pump-fun) - discover and trade Pump.fun tokens


---

# Condition (/docs/nodes/utility/condition)

Multi-path branching based on rules and named routes.



The Condition node evaluates one expression against a list of rules, then sends the run down the matching route. Unlike Filter, which only passes or blocks execution, Condition can create multiple named output handles such as `high`, `low`, `buy`, `sell`, or `other`.

Configuration [#configuration]

| Field          | Type           | Required | Description                                                            |
| -------------- | -------------- | -------- | ---------------------------------------------------------------------- |
| Node Label     | text           | No       | Display name shown on the canvas                                       |
| Response Name  | text           | Yes      | Name used by downstream nodes, such as `{conditionResponse.route}`     |
| Expression     | template       | Yes      | The upstream value to evaluate, such as `{birdeyeResponse.data.value}` |
| Rules          | visual builder | Yes      | One or more operator, value, and route rows                            |
| Fallback Route | text           | No       | Route to use when no rule matches                                      |

Rule format [#rule-format]

Each rule compares the node's Expression to a value and directs the run to a route:

```json
[
  { "operator": "greater_than", "value": "150", "route": "high" },
  { "operator": "less_than", "value": "100", "route": "low" }
]
```

If the Expression is above 150, the `high` route executes. If it is below 100, the `low` route executes. If no rule matches, the Fallback Route executes. If no fallback route is set, unmatched runs use `default`.

The rule value can also use a variable, such as `{settingsResponse.maxPrice}`, if you want to compare one upstream value against another upstream value.

Operators [#operators]

| Operator                | Use it for                                        |
| ----------------------- | ------------------------------------------------- |
| `equals`                | Exact matches                                     |
| `not_equals`            | Anything except one exact value                   |
| `contains`              | Text or list-like values that include a substring |
| `greater_than`          | Numeric value is higher than the rule value       |
| `less_than`             | Numeric value is lower than the rule value        |
| `greater_than_or_equal` | Numeric value is at least the rule value          |
| `less_than_or_equal`    | Numeric value is at most the rule value           |

Connecting routes [#connecting-routes]

Each complete rule creates a named output handle on the node. The fallback route also creates a handle when you enter one. Connect each handle to the downstream node that should run for that branch.

For example:

* Connect `high` to a Telegram alert.
* Connect `low` to a buy workflow.
* Connect `other` to a Log node.

Output [#output]

When a rule matches:

```json
{
  "route": "high",
  "value": "175.32",
  "matchedRule": {
    "route": "high",
    "operator": "greater_than",
    "value": "150"
  },
  "evaluatedAt": "2026-05-14T10:00:00.000Z"
}
```

If the rule value itself was a template (for example `{settingsResponse.maxPrice}`), the resolved comparison value also appears under `matchedRule.resolvedValue`.

When no rule matches, `matchedRule` is omitted and `route` is the Fallback Route (or `"default"` if no fallback is set):

```json
{
  "route": "default",
  "value": "120.5",
  "evaluatedAt": "2026-05-14T10:00:00.000Z"
}
```

Downstream nodes read the Condition result with the response name:

```text
{conditionResponse.route}
{conditionResponse.value}
{conditionResponse.matchedRule.operator}
```

Common use cases [#common-use-cases]

* Route different price ranges to different actions
* Branch based on AI sentiment analysis results
* Handle success vs. error paths from upstream nodes

Next steps [#next-steps]

* [Filter](/docs/nodes/utility/filter) - simple pass/block filtering
* [Edges](/docs/concepts/edges) - how branching connections work


---

# Delay (/docs/nodes/utility/delay)

Pause workflow execution for a specified duration.



The Delay node pauses execution for a set amount of time before passing data to the next node.

Configuration [#configuration]

| Field      | Type   | Required | Description                      |
| ---------- | ------ | -------- | -------------------------------- |
| Node Label | text   | No       | Display name shown on the canvas |
| durationMs | number | Yes      | Delay in milliseconds            |

Limits [#limits]

Delay values are capped at 300,000 ms, or 5 minutes. If you enter a larger number, the run waits for 5 minutes and then continues.

Use `60000` for 1 minute, `300000` for 5 minutes.

Common use cases [#common-use-cases]

* Rate limiting between API calls
* Waiting for on-chain confirmation before checking results
* Pacing notifications to avoid spam

Next steps [#next-steps]

* [Transform](/docs/nodes/utility/transform) - process data between steps
* [Log](/docs/nodes/utility/log) - record timing info


---

# Filter (/docs/nodes/utility/filter)

Conditionally pass or block data based on an expression.



The Filter node evaluates a condition and either passes data downstream or stops execution at that branch.

Configuration [#configuration]

| Field                | Type   | Required                        | Description                                              |
| -------------------- | ------ | ------------------------------- | -------------------------------------------------------- |
| Node Label           | text   | No                              | Display name shown on the canvas                         |
| expression           | string | Yes                             | Value to evaluate (supports template expressions)        |
| operator             | string | Yes                             | Comparison: `equals`, `not_equals`, `contains`, `exists` |
| value                | string | For equals/not\_equals/contains | Comparison value                                         |
| Output Variable Name | string | No                              | Optional response name for downstream references         |

How it works [#how-it-works]

1. The `expression` is rendered using upstream node data
2. The `operator` compares the rendered value against `value`
3. If true, data passes through. If false, the branch stops.

If you set an Output Variable Name, downstream nodes can reference the filter result by that name. Leave it empty if you only need pass/block behavior.

Examples [#examples]

Only continue if a value exists (non-empty):

```
expression: {birdeyeResponse.data.value}
operator: exists
```

Check if a field matches a specific value:

```
expression: {birdeyeResponse.data.symbol}
operator: equals
value: SOL
```

Check if a field contains a keyword:

```
expression: {aiResponse.data}
operator: contains
value: bullish
```

For numeric comparisons (greater than, less than), use the [Condition](/docs/nodes/utility/condition) node instead.

Output [#output]

When the expression passes the operator check:

```json
{ "filtered": false, "passed": true, "value": "SOL" }
```

When it does not pass, downstream nodes on this branch are skipped and the node records:

```json
{
  "filtered": true,
  "passed": false,
  "reason": "Expression \"{birdeyeResponse.data.symbol}\" resolved to \"BONK\" which did not pass equals check"
}
```

An empty expression short-circuits to `{ "filtered": true, "reason": "Filter expression is empty" }`.

If you set an Output Variable Name, downstream nodes can read the result, for example `{filterResponse.passed}` or `{filterResponse.value}`. The `value` field is the rendered expression string, not the original template.

Next steps [#next-steps]

* [Condition](/docs/nodes/utility/condition) - multi-path branching with routes
* [Transform](/docs/nodes/utility/transform) - reshape data before filtering


---

# For Each (/docs/nodes/utility/foreach)

Run a body subgraph once per item in an array, then aggregate the results.



For Each runs the same mini-workflow once for every item in a list. Use it when you have 10 tokens, wallets, rows, or alerts and want Solaris AI Flow to process each one without manually duplicating nodes.

For Each is a paired node:

* `For Each Start` opens the loop and chooses the array.
* Body nodes run once per array item.
* `For Each End` closes the loop and collects the results.

```text
[Trigger] -> [For Each Start] -> [Birdeye] -> [Transform] -> [For Each End]
                    |                                      ^
                    +------- body runs once per item -------+
```

Quick start [#quick-start]

1. Add a trigger or upstream node that outputs an array.
2. Add **For Each Start** and set **Array to iterate** to a single array path, such as `{trigger.tokens}`.
3. Keep the default item variable, or rename it to something clearer like `token`.
4. Add one or more body nodes after For Each Start.
5. Reference the current item inside body nodes, for example `{token.address}` or `{item}`.
6. Add **For Each End** after the body tail.
7. Downstream nodes read the collected results from `{forEachResponse.result}`.

Use **sequential** mode when each iteration needs the previous results. Use **parallel** mode when items can run independently and you want faster completion.

Common pattern [#common-pattern]

The most common pattern is "fan out, enrich, fan back in":

```text
[Manual Trigger]
  samplePayload: {
    "tokens": [
      { "address": "So111...", "label": "SOL" },
      { "address": "DezX...", "label": "BONK" }
    ]
  }

[For Each Start]
  Array to iterate: {trigger.tokens}
  Item variable: token

[Birdeye Token Overview]
  Mint: {token.address}

[For Each End]
  Response name: enriched
```

After the run, downstream nodes can reference:

```text
{enriched.result}
{enriched.result[0].data.value}
```

If you need to preserve the original token labels next to the enriched response, add a Code or Transform node inside the body and return the shape you want:

```js
return {
  address: $input.token.address,
  label: $input.token.label,
  price: $input.birdeyeResponse.data.value,
};
```

That Code or Transform node becomes the body tail, so each element in `{enriched.result}` is the full tail envelope:

```json
{ "success": true, "data": { "address": "So111...", "label": "SOL", "price": 150.23 } }
```

For a flatter output across multiple branches, pair For Each with [Merge](/docs/nodes/utility/merge). Merge unwraps the top-level For Each End envelope from `{ success, result: [...] }` to the inner array, but it does not unwrap each element inside that array. For a join that matches the original token list, make both branches have the same row shape first:

* Flatten `{enriched.result}` to an array like `[{ address, label, price }, ...]` with Transform or Code, then Merge by `address`.
* Or keep envelopes on both branches and match against a shared nested key path when each branch has the same envelope shape.

Configuration [#configuration]

For Each Start [#for-each-start]

| Field                   | Required | Description                                                                                                                     |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Array to iterate        | Yes      | A single template expression resolving to an array, such as `{trigger.tokens}`, or a JSON array literal like `["SOL", "BONK"]`. |
| Item variable           | No       | Variable name body nodes use for the current item. Default: `item`.                                                             |
| Index variable          | No       | Variable name for the zero-based item index. Default: `index`.                                                                  |
| Total variable          | No       | Variable name for the total item count. Default: `total`.                                                                       |
| Iteration mode          | No       | `sequential` runs one item at a time. `parallel` runs multiple items at once.                                                   |
| Max parallel iterations | No       | Parallel-mode concurrency. During beta, this is capped at 5 across all plans.                                                   |
| Prior results variable  | No       | Sequential-mode variable for previous iteration outputs. Default: `priorResults`.                                               |
| On iteration error      | No       | `fail-fast` stops on the first failure. `continue` records failures and keeps going.                                            |

For Each End [#for-each-end]

| Field         | Required | Description                                                                               |
| ------------- | -------- | ----------------------------------------------------------------------------------------- |
| Response name | No       | Variable name downstream nodes use for the aggregated result. Default: `forEachResponse`. |

Array input rules [#array-input-rules]

The **Array to iterate** field accepts exactly two shapes:

* A single template path, such as `{trigger.tokens}` or `{birdeyeResponse.data.items}`.
* A JSON array literal, such as `["a", "b", "c"]` or `[1, 2, 3]`.

Mixed templates such as `prefix-{x}`, `{a}{b}`, or `[{a}, {b}]` are not supported because they are string templates, not array values.

Each item is capped at 256 KB. Oversized items fail before the loop starts, so Solaris AI Flow does not create partial iteration rows.

Body variables [#body-variables]

Inside the body, these variables are available:

```text
{item}          // current item, or your renamed item variable
{index}         // zero-based index
{total}         // total item count
{priorResults}  // sequential mode only
```

You can rename these in For Each Start. Variable names can use letters, digits, underscores, and hyphens. They cannot contain dots or spaces.

Variable names must be unique. The names `__proto__`, `constructor`, and `prototype` are blocked. In sequential mode, the prior-results variable also cannot start with `_` or collide with executor envelope keys such as `data`, `success`, `result`, `error`, `operation`, `usage`, or `model`.

Output [#output]

For Each End emits:

```json
{
  "success": true,
  "result": [
    { "success": true, "data": { "address": "So111...", "price": 150.23 } },
    { "success": true, "data": { "address": "DezX...", "price": 0.00002 } }
  ],
  "meta": {
    "total": 2,
    "completed": 2,
    "failed": 0
  }
}
```

The `result` array follows input order, not completion order. If **On iteration error** is set to `continue`, failed iterations appear at their original index with an error marker.

Use these downstream paths:

```text
{forEachResponse.result}
{forEachResponse.result[0].data.address}
{forEachResponse.meta.completed}
```

Loop credits [#loop-credits]

Each iteration consumes loop credits. Loop credits are separate from your plan's monthly run quota.

Credits are reserved before the loop starts and settled after the run. Unused credits are refunded if the loop ends early or uses less than estimated. If your balance is too low, the loop fails before any iteration runs.

You can top up from the For Each Start node. The top-up modal offers four packs: 10,000, 50,000, 250,000, and 1,000,000 credits. Credits are priced at 10,000 credits per $1 USD, paid in SOL at live rates. Credits never expire.

Limits [#limits]

For Each is in beta. Current limits are:

| Limit                            | Current value                                  |
| -------------------------------- | ---------------------------------------------- |
| Items per For Each               | 50                                             |
| Body nodes between Start and End | 5                                              |
| Parallel iterations              | 5                                              |
| Nested loops                     | Not supported yet                              |
| Body node output                 | About 35 KB per body-node output per iteration |
| Aggregated visible results       | Most recent 100 iterations                     |

The result visibility cap is 100 outputs. This is above the current 50-item beta limit, but it will matter when higher item limits are enabled. If you need every output after those limits increase, split work across smaller loops, reduce or flatten each iteration's output, or send results to an external API/database until storage-backed iteration outputs ship.

Body subgraph rules [#body-subgraph-rules]

The body subgraph must be self-contained:

* Body nodes can connect to other body nodes.
* Body nodes can connect to For Each End.
* Body nodes cannot connect to anything outside the loop.
* Body nodes cannot receive input from outside the loop except For Each Start.
* Trigger nodes cannot appear inside the body.

For Each End accepts one body tail. If multiple body branches need to converge, add a [Merge](/docs/nodes/utility/merge) node before For Each End.

Advanced behavior [#advanced-behavior]

Sequential mode [#sequential-mode]

Sequential mode runs items in input order. Use it when ordering matters, when an API does not tolerate parallel calls, or when each item needs context from previous items.

In sequential mode, `{priorResults}` is an array of previous iteration tail outputs. Iteration 0 receives an empty array.

```js
const summaries = $input.priorResults
  .filter((result) => !result.__solaris_iterationFailed)
  .map((result) => result.data.summary);

return {
  context: summaries.join("\n"),
  current: $input.item,
};
```

Read primitive fields from prior results and return a fresh object. Avoid spreading or forwarding previous result objects, since that can create recursive output.

```js
// Prefer this.
const last = $input.priorResults.at(-1)?.data;

return {
  total: (last?.total ?? 0) + $input.item.value,
  count: (last?.count ?? 0) + 1,
};
```

```js
// Avoid this.
const last = $input.priorResults.at(-1);

return { ...last, total: (last?.total ?? 0) + $input.item.value };
```

Prior Code and Transform outputs are under `.data` because those nodes return standard envelopes like `{ success: true, data: output }`.

Result visibility cap [#result-visibility-cap]

For Each End exposes at most the most recent 100 iteration outputs in `result`. When earlier entries are omitted, Solaris AI Flow inserts a truncation marker at the start of the result array.

Prior result trimming [#prior-result-trimming]

In sequential mode, Solaris AI Flow keeps prior results small enough to pass through the workflow runtime safely. If the prior-results blob grows too large, older entries are removed first and a truncation marker is inserted. The newest entries are preserved.

Runtime internals [#runtime-internals]

Solaris AI Flow marks prior-result entries internally so a loop body cannot accidentally re-embed all previous outputs into each new iteration. You may see fields such as `__solaris_iterEntry`, `__solaris_priorResultsTruncated`, `__solaris_iterationFailed`, or `__solaris_resultTruncated` in advanced debugging output.

Do not depend on those marker fields in normal workflow logic. They exist to keep loop state bounded and to make truncation explicit when it happens.

What's coming next [#whats-coming-next]

* Nested loops inside a body.
* Higher per-plan item counts.
* Storage-backed iteration outputs so larger result sets can be surfaced downstream.

See also [#see-also]

* [Merge](/docs/nodes/utility/merge): combine outputs from multiple branches.
* [Storage](/docs/nodes/utility/storage): track processed items across runs.


---

# HTTP (/docs/nodes/utility/http)

Make HTTP requests to any API endpoint.



The HTTP node makes arbitrary HTTP requests to external APIs. Use it for integrations not covered by dedicated nodes.

Prerequisites [#prerequisites]

No credential required. Authentication (if needed) is configured via headers.

Configuration [#configuration]

| Field   | Type          | Required | Description                                 |
| ------- | ------------- | -------- | ------------------------------------------- |
| url     | string        | Yes      | Request URL (supports template expressions) |
| method  | string        | Yes      | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`     |
| headers | array         | No       | Key-value header pairs                      |
| body    | string/object | No       | Request body (for POST/PUT/PATCH)           |
| timeout | number        | No       | Timeout in ms (max: 120000)                 |

Security [#security]

* SSRF protection: requests to private/internal IPs are blocked
* On cross-origin redirects, auth headers are automatically stripped
* Maximum response size: 10 MB
* Response text is truncated at 100,000 characters

Template expressions [#template-expressions]

Use upstream data in the URL, headers, or body:

```
https://api.example.com/tokens/{birdeyeResponse.data.address}
```

Authentication [#authentication]

The node has no built-in auth - add the header your API expects.

Bearer token (most APIs):

```text
Header key:   Authorization
Header value: Bearer {credentialResponse.apiKey}
```

API key in a custom header:

```text
Header key:   X-API-Key
Header value: {trigger.apiKey}
```

On a cross-origin redirect, all auth-looking headers (`Authorization`, `Cookie`, `Proxy-*`, etc.) are stripped before the next hop, so a redirect to a third-party host cannot exfiltrate the credential.

POST body [#post-body]

For `POST`, `PUT`, and `PATCH`, provide the body as JSON. Templates inside the body are resolved before the request fires.

```json
{
  "token": "{webhook.token}",
  "price": "{birdeyeResponse.data.value}",
  "alert": "above_threshold"
}
```

Use the `{json …}` prefix when you need to interpolate an object or array as JSON (so `{json codeResponse.data}` produces valid JSON, not `[object Object]`).

If you set a non-JSON `Content-Type` such as `application/x-www-form-urlencoded`, the body is sent as the resolved string verbatim.

Output [#output]

```json
{
  "status": 200,
  "statusText": "OK",
  "headers": { "content-type": "application/json" },
  "data": { "result": "..." }
}
```

When the response is JSON, `data` is the parsed object. For non-JSON responses, `data` is the response text, truncated at 100,000 characters (the full payload is still capped at 10 MB at the network layer).

Reference fields downstream with the response name, for example `{httpResponse.data.result}` or `{httpResponse.status}`.

SSRF protection [#ssrf-protection]

Requests to private, loopback, and link-local destinations are rejected before any network call. This includes `localhost`, `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, and the IPv6 equivalents. Use a public DNS name; a private intranet endpoint will not work even if reachable from your browser.

Common use cases [#common-use-cases]

* Call APIs without a dedicated Solaris AI Flow node
* Post data to custom webhooks
* Fetch external configuration or feature flags

Next steps [#next-steps]

* [Transform](/docs/nodes/utility/transform) - reshape HTTP response data
* [Condition](/docs/nodes/utility/condition) - branch based on response values


---

# Log (/docs/nodes/utility/log)

Log a message during workflow execution for debugging.



The Log node records a message in the execution output. Use it for debugging or audit trails.

Configuration [#configuration]

| Field   | Type   | Required | Description                                                   |
| ------- | ------ | -------- | ------------------------------------------------------------- |
| message | string | Yes      | Message template (supports expressions)                       |
| level   | string | No       | Log level: `debug`, `info`, `warn`, `error` (default: `info`) |

Template expressions [#template-expressions]

```
Token price: {birdeyeResponse.data.value}
Swap result: {jupiterResponse.data.outAmount}
```

Output [#output]

A successful Log node returns:

```json
{
  "success": true,
  "level": "info",
  "message": "Token price: 150.23"
}
```

If the rendered message is empty, the node short-circuits with:

```json
{ "success": true, "skipped": true, "reason": "Log message is empty" }
```

The rendered text is stored on the node run and shown in the execution detail view. Downstream nodes can reference the rendered message with the node's response name, for example `{logResponse.message}`.

Next steps [#next-steps]

* [Delay](/docs/nodes/utility/delay) - pause between nodes
* [Filter](/docs/nodes/utility/filter) - conditionally pass data


---

# Merge (/docs/nodes/utility/merge)

Combine outputs from incoming workflow branches into one payload.



The Merge node combines the outputs from incoming workflow branches and passes one merged payload downstream. It is most useful with two or more branches; a single incoming branch is allowed, but the output still uses the selected Merge mode and the `{ success, mode, inputCount, result, meta }` envelope. Use it when parallel nodes produce related data and the next node needs to read everything from one place.

Common uses:

* Keep multiple API responses together without overwriting fields
* Combine several lists into one list
* Pair list items by position
* Join list items by a shared field such as `address`, `mint`, or `symbol`

Merge is **edge-driven**: it reads the nodes connected directly into it. You do not write template expressions in the Merge node itself.

Configuration [#configuration]

| Field            | Type   | Required                               | Description                                                                            |
| ---------------- | ------ | -------------------------------------- | -------------------------------------------------------------------------------------- |
| Node Label       | string | No                                     | Display name shown on the canvas                                                       |
| Response Name    | string | Recommended                            | Variable name used by downstream nodes. Defaults to `mergeResponse`                    |
| Mode             | select | Recommended                            | How incoming branches are combined. Defaults to **Keep each branch separate** if unset |
| Match Field      | string | Only for **Match list items by field** | Field path used to join list items, for example `address` or `data.mint`               |
| Per-Item Combine | select | For list matching modes                | Whether matched items are flattened together or kept under each branch name            |

Downstream nodes read the merged value under `.result`:

```text
{mergeResponse.result}
```

If you change **Response Name** to `mergedPrices`, downstream nodes use:

```text
{mergedPrices.result}
```

Modes [#modes]

| Mode in the editor               | Best for                                  | What it does                                                                                                        |
| -------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Keep each branch separate**    | Safest default; multiple API responses    | Stores each branch under its response name                                                                          |
| **Combine matching fields**      | Object outputs with different field names | Spreads all incoming object fields into one object. Later branches overwrite earlier branches on key conflicts      |
| **Stack lists together**         | Multiple arrays                           | Concatenates arrays end-to-end                                                                                      |
| **Match list items by position** | Arrays already in the same order          | Combines item 1 with item 1, item 2 with item 2, and so on. Output length is the shortest input list                |
| **Match list items by field**    | Arrays that share an ID field             | Inner-joins items where every branch has the same value at the match field. Output follows the first branch's order |

Recommended default: keep each branch separate [#recommended-default-keep-each-branch-separate]

For most users, especially when connecting integration nodes like Birdeye, use **Keep each branch separate**.

Example workflow:

```text
Manual Trigger → Birdeye SOL price ┐
                                   ├→ Merge → Telegram
Manual Trigger → Birdeye BONK price┘
```

Set the Birdeye nodes' response names to something readable:

```text
Birdeye SOL price  → solPrice
Birdeye BONK price → bonkPrice
Merge              → mergeResponse
```

With **Keep each branch separate**, Merge outputs:

```json
{
  "success": true,
  "mode": "namespacedObject",
  "inputCount": 2,
  "result": {
    "solPrice": {
      "success": true,
      "operation": "getPrice",
      "address": "So111...",
      "data": { "value": 142.31 }
    },
    "bonkPrice": {
      "success": true,
      "operation": "getPrice",
      "address": "DezX...",
      "data": { "value": 0.000018 }
    }
  },
  "meta": {
    "inputs": [
      { "responseName": "solPrice", "type": "object" },
      { "responseName": "bonkPrice", "type": "object" }
    ]
  }
}
```

A downstream Telegram, Discord, AI, Transform, Code, or Condition node can reference:

```text
SOL: {mergeResponse.result.solPrice.data.value}
BONK: {mergeResponse.result.bonkPrice.data.value}
```

If two upstream nodes have the same response name, Merge keeps both by adding suffixes such as `birdeyeResponse` and `birdeyeResponse_2`. That works, but readable response names such as `solPrice` and `bonkPrice` are easier to use.

Combine matching fields [#combine-matching-fields]

**Combine matching fields** requires every incoming branch to output an object. It spreads the fields into one object.

Example inputs:

```json
{ "symbol": "SOL", "price": 142.31 }
{ "volume24h": 1000000, "liquidity": 500000 }
```

Merged value inside `result`:

```json
{
  "symbol": "SOL",
  "price": 142.31,
  "volume24h": 1000000,
  "liquidity": 500000
}
```

Be careful with raw integration outputs. Many integration nodes return similar wrapper fields such as `success`, `operation`, and `data`. If two branches both have `data`, the later branch overwrites the earlier branch's `data`. Use **Keep each branch separate** when you need to preserve both full responses.

Stack lists together [#stack-lists-together]

**Stack lists together** requires every incoming branch to output an array. It appends all items into one list.

Example inputs:

```json
[
  { "symbol": "SOL" },
  { "symbol": "BONK" }
]
```

```json
[
  { "symbol": "JUP" }
]
```

Merged value inside `result`:

```json
[
  { "symbol": "SOL" },
  { "symbol": "BONK" },
  { "symbol": "JUP" }
]
```

Reference the first item downstream with:

```text
{mergeResponse.result[0].symbol}
```

Envelope auto-unwrap [#envelope-auto-unwrap]

The three list modes - **Stack lists together**, **Match list items by position**, and **Match list items by field** - accept the standard executor envelopes around an array directly:

* `{ success: true, data: [...] }` (Transform, Code, AI nodes, most integrations)
* `{ success: true, result: [...] }` (For Each End, Merge itself)

Merge sees through the wrapper to the inner array, so a Transform or Code node in between is no longer required. The `meta.unwrappedFromEnvelope` field on Merge's output records which incoming branches were auto-unwrapped, so the behavior stays visible.

You still need a [Transform](/docs/nodes/utility/transform) step when the array is nested deeper, for example to pull `data.tokens` out of `{ success, data: { tokens: [...] } }`. Auto-unwrap is intentionally one level - anything further is a real reshape that belongs in Transform.

Match list items by position [#match-list-items-by-position]

**Match list items by position** requires every incoming branch to output an array. It combines items with the same index.

Example inputs:

```json
[
  { "address": "A", "symbol": "AAA" },
  { "address": "B", "symbol": "BBB" }
]
```

```json
[
  { "price": 1.25 },
  { "price": 2.5 }
]
```

Merged value inside `result` with **Per-Item Combine** set to **Combine fields**:

```json
[
  { "address": "A", "symbol": "AAA", "price": 1.25 },
  { "address": "B", "symbol": "BBB", "price": 2.5 }
]
```

The output length is the shortest input list. If one list has 10 items and another has 8, the merged result has 8 items.

Use this only when the lists are already in the same order.

Match list items by field [#match-list-items-by-field]

**Match list items by field** requires every incoming branch to output an array of objects. It joins items where all branches share the same value at the configured **Match Field**.

Example configuration:

```text
Mode: Match list items by field
Match Field: address
Per-Item Combine: Combine fields
```

Example inputs:

```json
[
  { "address": "A", "symbol": "AAA" },
  { "address": "B", "symbol": "BBB" }
]
```

```json
[
  { "address": "B", "price": 2.5 },
  { "address": "A", "price": 1.25 }
]
```

Merged value inside `result`:

```json
[
  { "address": "A", "symbol": "AAA", "price": 1.25 },
  { "address": "B", "symbol": "BBB", "price": 2.5 }
]
```

The output follows the order of the first incoming branch. Items without a match in every branch are dropped and counted in `meta.droppedFromInputs` (one count per incoming branch). Duplicate join keys are counted in `meta.duplicateKeysPerInput` so you can debug unexpected drops.

Match fields can use dotted paths:

```text
address
data.mint
token.id
```

Field paths cannot be empty, start or end with `.`, contain `..`, or use reserved segments such as `__proto__`, `constructor`, or `prototype`.

Per-item combine [#per-item-combine]

For **Match list items by position** and **Match list items by field**, choose how matched items become one row:

| Option             | Output shape                               | Use when                                                                           |
| ------------------ | ------------------------------------------ | ---------------------------------------------------------------------------------- |
| **Combine fields** | `{ "symbol": "SOL", "price": 142 }`        | Fields do not conflict, or you are okay with later fields overwriting earlier ones |
| **Keep separate**  | `{ "tokens": { ... }, "prices": { ... } }` | You need to preserve each branch exactly                                           |

If you choose **Keep separate**, each matched item is nested under the upstream branch's response name. If **Match list items by position** is set to **Combine fields** but a row contains a non-object item, that row automatically falls back to the same nested shape so the run does not silently drop the value. The fallback row indices are recorded in `meta.autoNamespacedIndices`.

**Match list items by field** does not fall back: if any array element is not an object, the node fails with a clear error so you can fix the upstream shape.

Output [#output]

A successful Merge node returns:

```json
{
  "success": true,
  "mode": "namespacedObject",
  "inputCount": 2,
  "result": {},
  "meta": {
    "inputs": [
      { "responseName": "branchA", "type": "object" },
      { "responseName": "branchB", "type": "array", "length": 12 }
    ]
  }
}
```

Downstream nodes normally read from `.result`:

```text
{mergeResponse.result}
{mergeResponse.result.solPrice.data.value}
{mergeResponse.result[0].address}
```

The `meta` object is for debugging. The exact fields depend on the mode:

| Field                   | Modes                                          | What it tells you                                                                                                                                                          |
| ----------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inputs`                | All                                            | One entry per incoming branch: `{ responseName, type, length? }`                                                                                                           |
| `outputLength`          | Stack lists, Match by position, Match by field | Number of items in the merged list                                                                                                                                         |
| `lengthMismatch`        | Match list items by position                   | `true` when input arrays were not all the same length                                                                                                                      |
| `autoNamespacedIndices` | Match list items by position                   | Row indices that auto-fell-back to nested shape because a row had a non-object item                                                                                        |
| `droppedFromInputs`     | Match list items by field                      | One count per branch - items that did not survive to the merged output (missing key, no match, or a sibling branch lacked the key)                                         |
| `duplicateKeysPerInput` | Match list items by field                      | One count per branch - items skipped because their join key was already seen in that branch (first-write-wins)                                                             |
| `key`                   | Match list items by field                      | Echo of the resolved match field                                                                                                                                           |
| `unwrappedFromEnvelope` | Stack lists, Match by position, Match by field | One entry per branch whose `{ success, data \| result: [...] }` envelope was auto-unwrapped to the inner array - `{ responseName, from }`. Omitted when no unwrap occurred |

Limits and safety [#limits-and-safety]

* Merge needs at least one incoming branch. One branch is valid, but two or more branches are recommended for real merging.
* Duplicate edges from the same source node count as one input.
* Routing-skipped branches are not merged.
* Mode-specific type checks are strict. Array modes fail if an incoming branch is neither an array nor a `{ success: true, data | result: [...] }` envelope around one. Unknown values for **Mode** or **Per-Item Combine** fail the run rather than falling back silently.
* **Keep each branch separate** and namespaced per-item modes require safe response names: start with a letter or digit and contain only letters, digits, `_`, or `-`.
* Avoid response names that start with `_` or `$`, contain `.`, or equal `__proto__`, `constructor`, or `prototype`.
* Merge errors are treated as failed node runs, not soft-success responses.

When to use Merge vs Transform vs Code [#when-to-use-merge-vs-transform-vs-code]

Use **Merge** when you have incoming branches and want one downstream node to read their outputs together. It is designed for two or more branches; with one branch, the output still follows the selected Merge mode.

Use **Transform** before Merge when each branch needs to extract or reshape data first, for example turning `{ success, data: { tokens: [...] } }` into a bare token array.

Use **Code** when the combine logic needs custom JavaScript, complex grouping, custom scoring, or behavior that does not fit the Merge modes.

Next steps [#next-steps]

* [Transform](/docs/nodes/utility/transform) - reshape branch outputs before merging
* [Code](/docs/nodes/utility/code) - write custom combine logic
* [Condition](/docs/nodes/utility/condition) - route branches before they merge


---

# Storage (/docs/nodes/utility/storage)

Persistent key/value and list storage scoped to a workflow, for state that survives across runs.



The Storage node lets a workflow remember values between runs. State is scoped to a single workflow, so when a Cron Trigger fires every 5 minutes, each run sees the same persisted store.

The most common use is dedup. A sniper checks whether a mint has already been processed, acts only on new ones, and appends each handled mint to a list. The list survives across cron ticks without needing an external database.

Prerequisites [#prerequisites]

* A signed-in Solaris AI Flow account
* No API key required

Operations [#operations]

| Operation        | Purpose                                                        | Required fields                                                           |
| ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Get              | Read the value at a key                                        | key                                                                       |
| Set              | Write a value (creating or overwriting)                        | key, value (any JSON)                                                     |
| Has              | Check whether a key exists, or whether a list contains a value | key (membership value optional)                                           |
| Append Unique    | Append to an array, no-op if already present                   | key, value (must be a string, number, or boolean, not an array or object) |
| Remove From List | Remove a value from an array                                   | key, value (string, number, or boolean)                                   |
| Delete           | Delete the entire key                                          | key                                                                       |

Configuration [#configuration]

| Field                 | Type               | Required | Description                                                                                                                                                                                                                                                                                                                                                 |
| --------------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Node Label            | string             | No       | Display name shown on the canvas                                                                                                                                                                                                                                                                                                                            |
| Operation             | enum               | Yes      | One of the operations above                                                                                                                                                                                                                                                                                                                                 |
| Key                   | string             | Yes      | Identifier for the stored value (max 128 chars). Allowed characters: letters, digits, `_ - : .`                                                                                                                                                                                                                                                             |
| Value                 | string             | Varies   | Required for Set, Append Unique, Remove From List                                                                                                                                                                                                                                                                                                           |
| Value Shape           | `scalar` \| `json` | No       | Controls how `Value` and `List Membership Value` are decoded. `scalar` (default) keeps the literal string. `json` parses the input. With `json`, Set stores any JSON value (arrays, numbers, booleans, objects), but Append Unique, Remove From List, and Has-with-membership only accept primitives (string, number, boolean) and reject arrays or objects |
| List Membership Value | string             | No       | Only for Has. When set, checks whether the array at the key contains this value                                                                                                                                                                                                                                                                             |
| TTL Seconds           | number             | No       | Only for Set and Append Unique. Overrides the default expiry. Must be a positive number, capped by your plan max (see Plan limits below)                                                                                                                                                                                                                    |

`Key`, `Value`, and `List Membership Value` all accept template expressions. For example:

```text
seenMints
{webhook.payload.mint}
{token.address}
```

Keep Value Shape consistent across reads and writes [#keep-value-shape-consistent-across-reads-and-writes]

If you store a value with `Value Shape: json` and the input is a number like `42`, it is persisted as the number `42`. A later Has check with `Value Shape: scalar` and the same `42` compares the **string** `"42"` against the **number** `42`. The check returns `false`, and your dedup silently misses.

Rule of thumb: pick one shape (most workflows want `scalar`, since mint addresses and IDs are strings) and use the same shape for every Storage node touching that key. Switch to `json` only when you actually need numbers, booleans, or (for Set only) arrays and objects.

Plan limits [#plan-limits]

Storage is available on every plan. Caps differ by tier and are enforced server-side:

| Cap                   | Free (beta)                     | Pro / Ultra |
| --------------------- | ------------------------------- | ----------- |
| Keys per workflow     | 5                               | 50          |
| Array entries per key | 250                             | 1,000       |
| Value size            | 5 KB                            | 10 KB       |
| Default TTL           | 48 hours                        | 48 hours    |
| Max TTL               | 48 hours (no permanent storage) | 7 days      |

Free-tier values always expire. There is no permanent storage on the free plan. An hourly background sweep deletes expired entries.

Templating against the response [#templating-against-the-response]

Each Storage operation writes its result to `storageResponse` (override via the Response Name field in the config dialog):

| Operation        | Output shape                                                |
| ---------------- | ----------------------------------------------------------- |
| Get              | `{ found: boolean, value: any, expiresAt: number \| null }` |
| Set              | `{ ok: true, expiresAt: number }`                           |
| Has              | `{ exists: boolean }`                                       |
| Append Unique    | `{ added: boolean, count: number, expiresAt: number }`      |
| Remove From List | `{ removed: boolean, count: number }`                       |
| Delete           | `{ deleted: boolean }`                                      |

All outputs include `success: true`, `operation`, and `key` alongside the operation-specific fields above.

Operation details [#operation-details]

Get [#get]

Reads the value at `key`. Returns `found: false` when the key is missing or expired. Expiration is lazy: the row is treated as absent on read regardless of whether the cron has swept it yet.

Set [#set]

Writes `value` at `key`, creating the row or overwriting it. The `expiresAt` resets on every write. If `Value Shape` is `json`, the value is parsed before storage so `[1,2,3]` becomes a real array, not the literal string.

Has [#has]

Two modes:

* Empty `List Membership Value`: checks whether the key exists (and is not expired).
* Non-empty `List Membership Value`: checks whether the array at the key contains the value. If the stored value is not an array, falls back to scalar equality.

The List Membership Value's shape is also controlled by `Value Shape`. Pick `json` if the list contains numbers or booleans you want to match exactly. Whitespace-only resolved values (for example, a template that resolved to `""`) are treated as the empty case, so Has becomes a key-presence check.

Append Unique [#append-unique]

Appends a primitive (string, number, or boolean) to the array at `key`. Arrays and objects are rejected at runtime. If you need those, use Set with `Value Shape: json`.

On a real append, the entry's TTL is reset to `now + ttlSeconds` (your override, or the 48h default), so a sliding dedup window stays alive as long as new items keep being appended.

If the value is already present, returns `added: false` and does not refresh the TTL. This prevents repeated Append Unique calls of the same value from keeping an entry alive past its dedup window.

If the key is missing or expired, a new array is created. If the key holds a non-array value (for example, you previously called Set with a string), the operation throws.

Remove From List [#remove-from-list]

Removes a value from the array at `key`. No-op when the key is missing, expired, or the value is not present. Throws if the stored value is not an array. The TTL is **not** refreshed.

Delete [#delete]

Deletes the entire row. No-op when the key is missing. Unlike Get and Has, which treat expired rows as absent, Delete operates on any row that still exists. This is useful for reclaiming a key slot before the hourly sweep runs.

Marketplace and ownership [#marketplace-and-ownership]

Storage is scoped to `(owner, workflowId)`. Marketplace clones receive a fresh, empty store, never the original author's data, so a buyer who imports a sniper template starts with zero "seen" mints. Storage entries are never shared between two workflows even if owned by the same user.

Common use cases [#common-use-cases]

* Sniper or trading dedup: track mints you have already evaluated so a 1-minute cron does not re-process the same token forever
* "Already notified" lists for alerts and webhooks
* Per-workflow counters or recently-processed IDs

Example: dedup before buying a token [#example-dedup-before-buying-a-token]

```text
Cron Trigger -> Pump.fun (Get New) -> ForEach -> Storage (Has) -> Condition -> Pump.fun (Buy) -> Storage (Append Unique) -> ForEach End
```

Inside the loop body:

1. **Storage (Has)**: `key: seenMints`, `List Membership Value: {token.mint}`. Output: `{exists: true | false}`.
2. **Condition**: branch on `{storageResponse.exists}`. Continue only when `false`.
3. **Pump.fun (Buy)**: execute the trade.
4. **Storage (Append Unique)**: `key: seenMints`, `value: {token.mint}`, optional `TTL Seconds: 172800`. The mint is now ignored on the next 48 hours of cron ticks (free default; lower it explicitly if you want a shorter dedup window).

For the inverse, marking a mint as eligible again after a sale, wire a &#x2A;*Storage (Remove From List)** node into your sell branch with the same `key` and `value`.

Next steps [#next-steps]

* [Condition](/docs/nodes/utility/condition): branch on `{storageResponse.exists}` or `{storageResponse.found}`
* [ForEach](/docs/nodes/utility/foreach): iterate over a candidate list and dedup per item
* [Code](/docs/nodes/utility/code): shape the value before storing or compute against the retrieved list


---

# Transform (/docs/nodes/utility/transform)

Reshape upstream data with visual pipeline steps or a template expression.



The Transform node reshapes data from earlier workflow nodes and passes the result downstream. Use it when you need to extract fields, clean up API responses, filter or sort lists, keep only the fields another node needs, or prepare a payload for AI, Telegram, Discord, trading, or condition nodes.

Transform has two modes:

* **Pipeline** - the recommended visual editor. Pick an input and add ordered steps such as Filter, Sort, Slice, Project, Computed field, Literal field, and Age since.
* **Template** - a raw template expression for simple one-off reshaping or older workflows.

Configuration [#configuration]

| Field               | Type                     | Required      | Description                                                                                                  |
| ------------------- | ------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------ |
| Node Label          | string                   | No            | Display name shown on the canvas                                                                             |
| Name this result    | string                   | Recommended   | Stable variable name for downstream nodes, for example `topTokens`. Downstream nodes read `{topTokens.data}` |
| Mode                | `Pipeline` or `Template` | Yes           | Chooses which saved draft runs. Switching modes keeps the other draft until you clear it                     |
| Input               | template path            | Pipeline mode | Field to start from, for example `{pumpfunResponse.data}`. Leave blank to use all upstream outputs           |
| Steps               | list                     | Pipeline mode | Ordered visual operations that transform the input                                                           |
| Template Expression | string                   | Template mode | Raw template string, optionally JSON, evaluated against upstream outputs                                     |

Pipeline mode [#pipeline-mode]

Pipeline mode starts with the **Input** value, then runs each step in order. If Input is blank, the pipeline starts with the full upstream metadata object. If Input is `{pumpfunResponse.data}`, the pipeline starts with that field's value.

Most list-oriented steps expect the current value to be an array. A common pipeline is:

```text
Input: {pumpfunResponse.data}
1. Filter
2. Sort
3. Slice
4. Project
```

Available steps [#available-steps]

| Step           | Use it to                               | Notes                                                                                                                                     |
| -------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Filter         | Keep array items that match rules       | Supports AND/OR, nested groups, numeric comparison, text matching, regex, membership, existence checks, empty checks, and age comparisons |
| Sort           | Reorder an array by one or more fields  | Earlier sort keys take precedence. Missing values sort last                                                                               |
| Slice          | Keep first N, last N, or skip then take | Use after Sort to keep top results                                                                                                        |
| Project        | Keep, drop, or rename fields            | Can apply to the root object or to each item in an array                                                                                  |
| Computed field | Add a field from a template expression  | A lone `{path}` keeps the original value type; mixed text becomes a string                                                                |
| Literal field  | Add a fixed JSON value                  | Supports string, number, boolean, null, array, or object values                                                                           |
| Age since      | Add elapsed time from a timestamp       | Accepts epoch milliseconds, epoch seconds, or ISO date strings; output unit can be seconds, minutes, hours, or days                       |

Filter operators [#filter-operators]

Filter rules can use:

* Comparison: `=`, `≠`, `<`, `≤`, `>`, `≥`
* Text: `contains`, `starts with`, `ends with`, `matches regex`
* Membership: `in list`, `not in list`
* Existence: `exists`, `is empty`
* Age: younger or older than N seconds, minutes, hours, or days

Use nested groups for logic such as:

```text
(status = live AND bondingProgressPct >= 80)
OR
(source in ["pumpfun", "birdeye"] AND age_hours < 6)
```

Example: select top Pump.fun tokens [#example-select-top-pumpfun-tokens]

Workflow:

```text
Manual Trigger → Pump.fun → Transform → Telegram
```

Set the Pump.fun node result name to `pumpfunResponse`. In the Transform node:

1. Set **Name this result** to `topTokens`
2. Choose **Pipeline** mode
3. Set **Input** to:

```text
{pumpfunResponse.data}
```

Add these steps:

| Step           | Configuration                                                                                   |
| -------------- | ----------------------------------------------------------------------------------------------- |
| Filter         | `bondingProgressPct >= 80`                                                                      |
| Sort           | `bondingProgressPct` descending                                                                 |
| Slice          | First `5` items                                                                                 |
| Project        | Apply to each item. Keep `mint`, `name`, `symbol`, `priceSol`, `bondingProgressPct`             |
| Computed field | Apply to each item. Target field `summary`, expression `{symbol}: {bondingProgressPct}% bonded` |

A downstream Telegram node can then use:

```text
Top token: {topTokens.data[0].symbol}
Mint: {topTokens.data[0].mint}
Progress: {topTokens.data[0].bondingProgressPct}%
Summary: {topTokens.data[0].summary}
```

Template mode [#template-mode]

Template mode evaluates a raw expression against upstream node outputs. Use it for simple extraction or small string/JSON payloads.

Extract one field:

```text
{birdeyeResponse.data.value}
```

Build a message string:

```text
{jupiterResponse.data.inputMint} → {jupiterResponse.data.outputMint}: {jupiterResponse.data.outAmount}
```

Build a JSON object:

```json
{
  "symbol": "{pumpfunResponse.data[0].symbol}",
  "mint": "{pumpfunResponse.data[0].mint}",
  "progress": "{pumpfunResponse.data[0].bondingProgressPct}"
}
```

If the entire expression is a single `{path}` that resolves to an array or object, Transform returns the native value directly - no `String(value)` coercion that would otherwise turn an array into `"[object Object],..."`. For mixed templates and primitive paths, the rendered template is parsed as JSON when it's valid (so `"42"` becomes the number `42`); otherwise it stays a string.

Output [#output]

A successful Transform node returns:

```json
{
  "success": true,
  "data": "<transformed result>"
}
```

Downstream nodes read the transformed value under `.data`.

If **Name this result** is `topTokens`:

```text
{topTokens.data}
{topTokens.data[0].symbol}
```

Set a result name whenever another node needs to reference this Transform. If no friendly name is set, use the variable hints in downstream text fields to insert the available node-id path.

Previewing [#previewing]

Transform settings use the shared node-settings layout:

* **Input** shows the upstream sample available to the Transform. The default **Variables** view is best for finding fields; switch to **JSON**, **Schema**, or **Table** for exact inspection.
* **Parameters** contains the Transform mode, input field, and pipeline/template editor.
* **Output** shows a **Live** preview for the current draft and a **Last** tab for the most recent persisted run output.

When upstream sample data is available, the Live preview runs the same pipeline/template interpreter used by production workflow runs, so the preview should match runtime behavior. It updates as you edit the Transform draft.

If there is no sample yet, use the Input pane button to execute previous nodes. This runs the nodes before the Transform and uses their latest outputs as preview data. Preview runs count as one monthly run and are hidden from normal execution history. If upstream nodes can send messages, transfer funds, trade, or broadcast transactions, the editor asks for confirmation before firing them.

Clicking values in the Input pane inserts template paths into Transform fields when possible. Use `{json ...}` when the value is an object or array and the receiving field expects valid JSON text.

Limits and safety [#limits-and-safety]

* Pipeline output is capped at **5 MB**.
* Array-processing steps are capped at **100,000 items**.
* Field paths use dot notation such as `token.holders.count`.
* Field paths cannot be empty, start/end with `.`, contain doubled dots, or use reserved prototype segments such as `__proto__`, `constructor`, or `prototype`.
* Filter, Sort, Slice, and per-item Project/Computed/Literal/Age steps require the current pipeline value to be an array.
* Literal field values must be JSON-serializable.
* Age filters and Age since read the current time; other pipeline steps are deterministic for the same input.

When to use Transform vs Code [#when-to-use-transform-vs-code]

Use **Transform** when your data change fits the visual steps: filter, sort, slice, project, rename, add fixed fields, add template-derived fields, or compute ages.

Use **Code** when you need arbitrary JavaScript, such as custom math, grouping, complex scoring, reducing an array into a summary object, or handling inconsistent data shapes that do not fit the visual pipeline.

Next steps [#next-steps]

* [Code](/docs/nodes/utility/code) - run custom JavaScript when the visual pipeline is not expressive enough
* [Configuring Nodes](/docs/editor/configuring-nodes) - input/output panes and variables
* [Filter](/docs/nodes/utility/filter) - conditionally pass or block a workflow path
* [Condition](/docs/nodes/utility/condition) - multi-path branching


---

# x402 (/docs/nodes/utility/x402)

Call paid x402 endpoints with automatic Solana USDC payment.



The x402 node calls an x402-enabled HTTP endpoint. If the endpoint returns HTTP 402 with payment terms, Solaris AI Flow signs and sends the payment from your embedded Privy wallet, then retries the request.

Use it when an API charges per request through x402 instead of a normal API key.

Prerequisites [#prerequisites]

* A signed-in Solaris AI Flow account with an embedded Privy wallet
* SOL for gas
* USDC in the embedded wallet if the endpoint requires payment
* An x402 endpoint that supports Solana mainnet USDC

Solaris AI Flow only settles x402 payments in USDC on Solana mainnet. Endpoints advertising another network or asset are refused.

Configuration [#configuration]

| Field             | Type        | Required    | Description                                                                |
| ----------------- | ----------- | ----------- | -------------------------------------------------------------------------- |
| Node Label        | string      | No          | Display name shown on the canvas                                           |
| Response Name     | string      | Recommended | Variable name used by downstream nodes. Defaults to `x402Response`         |
| HTTP Method       | select      | Yes         | `GET` or `POST`                                                            |
| Endpoint URL      | string      | Yes         | x402-enabled URL. Supports template expressions                            |
| Request Body      | string      | For POST    | JSON body. Supports template expressions                                   |
| Headers           | JSON object | No          | Custom request headers. Header values must be strings                      |
| Max USDC per call | number      | No          | Optional spend cap. Payments above this amount are rejected before signing |

Reserved x402 transport headers are managed by Solaris AI Flow and cannot be supplied manually.

Checking an endpoint [#checking-an-endpoint]

Click **Check URL reachability** before saving or running. The check:

* Sends an unauthenticated GET request
* Detects whether the endpoint returns HTTP 402
* Shows advertised payment terms when available
* Checks whether your wallet appears to have enough USDC and SOL gas
* Warns when the endpoint uses an unsupported network, malformed payment terms, or a price above your Max USDC cap

The check is GET-only. POST-only endpoints may show a status that differs from the actual configured POST call.

Output [#output]

Downstream nodes usually read:

```text
{x402Response.response.body}
{x402Response.response.headersLowercase}
```

A successful paid or unpaid request returns the HTTP response body, response headers, status information, and payment metadata when payment was attempted.

Safety notes [#safety-notes]

* Running the workflow may spend USDC
* Payment happens automatically during execution after the endpoint advertises valid terms
* Use **Max USDC per call** for dynamic or unfamiliar endpoints
* If a network error happens after signing, inspect your wallet before retrying to avoid paying twice
* For async jobs, pair x402 with HTTP, Delay, and Condition nodes to poll the returned job URL or status field

Common use cases [#common-use-cases]

* Pay-per-call data APIs
* Paid AI or analytics endpoints
* Workflows that purchase a single API response only when upstream conditions are met

Next steps [#next-steps]

* [Balance](/docs/nodes/utility/balance) - check SOL or USDC before calling x402
* [HTTP](/docs/nodes/utility/http) - call unpaid APIs or poll async jobs
* [Condition](/docs/nodes/utility/condition) - gate paid requests behind a rule
