# Build an AI Trading Agent Source: https://docs.lasersell.io/ai-agents/build-a-trading-agent Step-by-step guide to building an AI trading agent on Solana using LaserSell's Exit Intelligence engine for automated buy execution, position monitoring, and exit strategy management. This guide walks through building an AI agent that can trade Solana tokens autonomously using LaserSell as its execution layer. The agent handles the decision-making (when to buy, what strategy to use), and LaserSell handles everything else: protocol routing, position monitoring, PnL tracking, and automated exit execution. This pattern works regardless of how your agent is built. Whether you are extending a personal AI assistant like [OpenClaw](https://openclaw.ai/) with trading skills, building a standalone trading bot, integrating into a Telegram bot framework, or wiring up an agent built with LangChain, CrewAI, or any other framework, the LaserSell integration is the same. Your agent calls the API, connects to the stream, and signs transactions. The rest is up to you. ## What the Agent Will Do 1. **Connect** to the Exit Intelligence Stream to begin monitoring. 2. **Buy** a token by building and submitting a transaction through the REST API. 3. **Monitor** the position automatically via the stream (PnL updates, price tracking). 4. **Exit** when strategy conditions are met (target profit, stop loss, trailing stop, or deadline). The agent does not need to know which DEX or launchpad a token is on. LaserSell resolves the protocol, builds the transaction, and delivers exit signals in real time. ## Prerequisites * A LaserSell API key ([get one here](https://app.lasersell.io)). * A Solana keypair (JSON byte array file). * Python 3.10+ with the LaserSell SDK installed. ```bash theme={null} pip install lasersell-sdk[tx,stream] ``` The examples below use Python, but the same flow applies with the [TypeScript](/api/sdk/typescript), [Rust](/api/sdk/rust), or [Go](/api/sdk/go) SDKs. ## Architecture ```mermaid theme={null} sequenceDiagram participant Agent as Your Agent participant LS as LaserSell participant Sol as Solana Agent->>LS: 1. Connect Exit Intelligence Stream LS-->>Agent: Stream ready Agent->>LS: 2. Build buy transaction LS-->>Agent: Unsigned tx Agent->>Sol: Sign + submit buy LS-->>Agent: position_opened LS-->>Agent: pnl_update (continuous) LS-->>Agent: exit_signal_with_tx Agent->>Sol: Sign + submit exit ``` Your agent owns the decisions. LaserSell owns the execution. The boundary between them is clean: the agent sends requests and receives events. All transactions are unsigned and signed locally by the agent. ## Step 1: Connect the Exit Intelligence Stream The stream must be connected **before** the agent buys. The stream detects positions by watching for on-chain token arrivals in real time. If the buy lands before the stream is connected, the position will not be tracked. ```python theme={null} import asyncio import json import os from pathlib import Path from solders.keypair import Keypair from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession api_key = os.environ["LASERSELL_API_KEY"] keypair_bytes = json.loads(Path("./keypair.json").read_text()) signer = Keypair.from_bytes(bytes(keypair_bytes)) wallet_pubkey = str(signer.pubkey()) # Connect and configure the stream stream_client = StreamClient(api_key) session = await StreamSession.connect( stream_client, StreamConfigure( wallet_pubkeys=[wallet_pubkey], strategy={ "target_profit_pct": 10.0, "stop_loss_pct": 5.0, "trailing_stop_pct": 3.0, "sell_on_graduation": True, }, deadline_timeout_sec=120, send_mode="helius_sender", tip_lamports=1000, ), ) ``` The strategy configuration tells LaserSell when to generate exit signals: | Parameter | Value | Meaning | | ---------------------- | ------ | --------------------------------------------------------- | | `target_profit_pct` | `10.0` | Sell when profit reaches 10%. | | `stop_loss_pct` | `5.0` | Sell when loss reaches 5%. | | `trailing_stop_pct` | `3.0` | Sell when profit drops 3% from its peak. | | `sell_on_graduation` | `true` | Sell when the token migrates from bonding curve to AMM. | | `deadline_timeout_sec` | `120` | Force sell after 120 seconds if no other condition fires. | Your agent can adjust these dynamically based on its own logic. See [Strategy Configuration](/api/stream/strategy-configuration). ## Step 2: Build and Submit a Buy Once the stream is connected, the agent can buy a token. The REST API builds an unsigned transaction that the agent signs locally and submits. ```python theme={null} from lasersell_sdk.exit_api import ExitApiClient, BuildBuyTxRequest from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx api_client = ExitApiClient.with_api_key(api_key) # Build the unsigned buy transaction buy_request = BuildBuyTxRequest( mint="TOKEN_MINT_ADDRESS", user_pubkey=wallet_pubkey, amount=0.1, # 0.1 SOL slippage_bps=2_000, # 20% slippage tolerance ) response = await api_client.build_buy_tx(buy_request) # Sign locally and submit signed_tx = sign_unsigned_tx(response.tx, signer) signature = await send_transaction(SendTargetHeliusSender(), signed_tx) print(f"Buy submitted: {signature}") ``` The agent never sends its private key anywhere. LaserSell returns an unsigned transaction, the agent signs it locally, and submits it directly to the Solana network via Helius Sender. ## Step 3: Monitor and Exit Automatically After the buy lands on-chain, the Exit Intelligence Stream detects the new token balance and begins tracking the position. The agent listens for events and acts on exit signals. ```python theme={null} from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx while True: event = await session.recv() if event is None: break # Stream disconnected if event.type == "position_opened": handle = event.handle print(f"Position opened: {handle.mint}") print(f" Token account: {handle.token_account}") elif event.type == "pnl_update": msg = event.message pnl_pct = msg["pnl_pct"] print(f"PnL update: {pnl_pct:.2f}%") elif event.type == "exit_signal_with_tx": msg = event.message # TypedDict, use dict access reason = msg["reason"] print(f"Exit signal fired: {reason}") # Sign and submit the pre-built exit transaction signed_tx = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed_tx) print(f"Exit submitted: {sig}") elif event.type == "position_closed": msg = event.message print(f"Position closed: {msg['reason']}") ``` The key events: | Event | What It Means | | --------------------- | ------------------------------------------------------------------------------------------------------ | | `position_opened` | A new token arrived in the wallet. Tracking has started. | | `pnl_update` | Periodic profit/loss snapshot for the position. | | `exit_signal_with_tx` | A strategy condition was met. Contains a pre-built unsigned exit transaction ready to sign and submit. | | `position_closed` | The position is no longer tracked (sold, transferred, or manually closed). | ## Step 4: Update Strategy Mid-Session Your agent can adjust strategy parameters at any time based on its own logic. For example, tightening the trailing stop after a position is profitable, or disabling the deadline if the agent decides to hold longer. ```python theme={null} # Tighten trailing stop after detecting strong momentum session.sender().update_strategy({ "target_profit_pct": 15.0, "stop_loss_pct": 3.0, "trailing_stop_pct": 2.0, }) ``` The update takes effect immediately for all tracked positions. No reconnection needed. ## Full Working Example Here is the complete agent loop combining all steps: ```python theme={null} import asyncio import json import os from pathlib import Path from solders.keypair import Keypair from lasersell_sdk.exit_api import ExitApiClient, BuildBuyTxRequest from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx async def run_agent(mint: str, amount_sol: float): api_key = os.environ["LASERSELL_API_KEY"] signer = Keypair.from_bytes( bytes(json.loads(Path("./keypair.json").read_text())) ) wallet_pubkey = str(signer.pubkey()) # --- 1. Connect the Exit Intelligence Stream --- stream_client = StreamClient(api_key) session = await StreamSession.connect( stream_client, StreamConfigure( wallet_pubkeys=[wallet_pubkey], strategy={ "target_profit_pct": 10.0, "stop_loss_pct": 5.0, "trailing_stop_pct": 3.0, "sell_on_graduation": True, }, deadline_timeout_sec=120, ), ) # --- 2. Build and submit the buy --- api_client = ExitApiClient.with_api_key(api_key) buy_request = BuildBuyTxRequest( mint=mint, user_pubkey=wallet_pubkey, amount=amount_sol, slippage_bps=2_000, ) response = await api_client.build_buy_tx(buy_request) signed_tx = sign_unsigned_tx(response.tx, signer) buy_sig = await send_transaction(SendTargetHeliusSender(), signed_tx) print(f"Buy submitted: {buy_sig}") # --- 3. Listen for events and handle exits --- while True: event = await session.recv() if event is None: print("Stream disconnected") break if event.type == "position_opened": print(f"Tracking position: {event.handle.mint}") elif event.type == "exit_signal_with_tx": msg = event.message print(f"Exit signal: {msg['reason']}") signed_tx = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed_tx) print(f"Exit submitted: {sig}") break # Position exited, agent is done elif event.type == "position_closed": print(f"Position closed: {event.message['reason']}") break asyncio.run(run_agent( mint="TOKEN_MINT_ADDRESS", amount_sol=0.1, # 0.1 SOL )) ``` ## Extending This Pattern This guide shows a single buy-and-exit cycle. A production agent would build on this foundation: **Signal integration.** The agent receives buy signals from any source: user prompts, on-chain analysis, social feeds, copy-trading leaders, or another AI model. The signal determines when to call `build_buy_tx`. **Multi-position management.** The stream tracks multiple positions simultaneously across one or more wallets. An agent can manage a portfolio of active positions, each with its own entry logic, while LaserSell evaluates exit conditions on all of them in parallel. **Dynamic strategy.** Use `update_strategy` to adjust parameters based on market conditions, position performance, or agent confidence. An agent that detects high volatility might tighten stops. One that detects a strong trend might widen them. **Risk controls.** Enforce position sizing, maximum concurrent positions, daily loss limits, or any other risk rules in your agent's decision layer before calling the API. **MCP integration.** If your agent runs inside an MCP-compatible client like [OpenClaw](https://openclaw.ai/), Claude, Cursor, or another AI assistant, it can use the [LaserSell MCP server](/ai-agents/mcp-server) to look up documentation, API schemas, and code examples in real time while building or debugging the integration. ## Next Steps * [API Overview](/api/overview) for the full API surface. * [Exit Intelligence Stream](/api/stream/overview) for the stream protocol deep dive. * [Strategy Configuration](/api/stream/strategy-configuration) for all strategy parameters. * [Transaction Signing](/api/transactions/signing) for signing and submission details. * [MCP Server](/ai-agents/mcp-server) to give your AI agent access to LaserSell documentation. # Coding Skills Source: https://docs.lasersell.io/ai-agents/coding-skills Install pre-packaged SDK knowledge into your AI coding assistant so it can write LaserSell integrations with full context on patterns, method signatures, examples, and troubleshooting. Coding skills give your AI coding assistant deep, embedded knowledge of the LaserSell SDK. Instead of searching documentation on demand, the assistant loads SDK patterns, method signatures, working examples, strategy configurations, and troubleshooting guidance directly into its context window. This means it can write correct LaserSell code from the start, without needing to look anything up. ## Skills vs MCP LaserSell offers two AI integration paths. They serve different purposes and work best together. | | MCP Server | Coding Skills | | ------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | **How it works** | Searches LaserSell documentation on demand when the assistant needs to look something up | Loads deep SDK knowledge directly into the assistant's context before it starts writing code | | **Best for** | Looking up API schemas, finding specific docs pages, answering questions about LaserSell | Writing SDK integrations, building transactions, connecting to the stream, handling errors | | **When knowledge is available** | On demand (the assistant must ask for it) | Immediately (the assistant already has it in context) | | **Setup** | Add MCP server config to your editor | Install the skill package | The MCP server is great for discovery and reference lookups. Coding skills are great for writing code that integrates with the SDK. Use both for the best experience. ## What's Included The LaserSell coding skill ships a structured knowledge tree that the assistant loads when working on LaserSell code: * **SKILL.md** — root skill file with an overview of the SDK, key concepts, and pointers to detailed references * **Working examples in 4 languages** — TypeScript, Python, Rust, and Go examples covering buy/sell transactions, stream connections, strategy configuration, and transaction submission * **Strategy guide** — detailed reference for target profit, stop loss, trailing stop, sell on graduation, and deadline timeout configuration * **Troubleshooting guide** — common errors, their causes, and fixes * **API and type references** — request/response schemas, enum values, and field descriptions for every endpoint * **Stream protocol reference** — the full Exit Intelligence Stream message format, event types, and connection lifecycle * **Templates** — starter code templates for common integration patterns ## Install ### Option 1: npx (Recommended) ```bash theme={null} npx skills add lasersell/skills ``` This downloads and installs the LaserSell coding skill into your project. The assistant picks it up automatically on the next session. ### Option 2: Claude Code CLI ```bash theme={null} claude install-skill https://github.com/lasersell/skills/tree/main/skills/lasersell ``` ### Option 3: Manual Copy the `skills/lasersell/` directory from the [LaserSell skills repository](https://github.com/lasersell/skills) into your project's `.claude/skills/` folder: ```bash theme={null} mkdir -p .claude/skills cp -r path/to/skills/lasersell .claude/skills/ ``` The assistant will detect the skill files and load them into context when you work on LaserSell code. ## What the Skill Covers Once installed, your AI assistant has full context on these topics: **Building buy and sell transactions.** The assistant knows how to construct `BuildBuyTxRequest` and `BuildSellTxRequest` objects, call the LaserSell API, and handle the response across all four SDK languages. It understands output types (SOL vs USD1), slippage configuration, and partial sells. **Exit Intelligence Stream.** The assistant can write stream connection code, configure strategy parameters, handle all server event types (`pnl_update`, `exit_signal_with_tx`, `position_opened`, `position_closed`, `liquidity_snapshot`), and process exit signals end to end. **Partial and multi-leg sells.** The assistant understands how to query slippage bands, determine the maximum sellable amount at a given slippage threshold, and build partial sell transactions. It can also construct multi-leg sell sequences for large positions. **Liquidity snapshots.** The assistant knows how to read slippage bands, interpret liquidity trends (`growing`, `stable`, `draining`), and use snapshot data to size sells optimally. **Transaction signing and submission.** The assistant can write code for local transaction signing, and understands all send targets: default RPC, custom RPC, and Helius Sender. It knows when to use each and how to configure them. **Error handling.** The assistant has context on common error codes, retry patterns, slippage failures, and connection recovery for both the REST API and the stream. ## Combining Skills and MCP For the best AI coding experience, use both the coding skill and the MCP server together: 1. **Install the coding skill** so your assistant has deep SDK knowledge loaded into context whenever you write LaserSell code. 2. **Connect the MCP server** so your assistant can search the full documentation when it needs to look up something outside the skill's scope, like partner fee details, rate limit tiers, or changelog entries. With both active, the assistant can write correct SDK code immediately (from the skill) and look up supplementary information on demand (from MCP). See [MCP Server](/ai-agents/mcp-server) for MCP setup instructions. # MCP Server Source: https://docs.lasersell.io/ai-agents/mcp-server Connect AI agents and MCP-compatible clients to the LaserSell documentation via the Model Context Protocol. The LaserSell MCP server lets any MCP-compatible AI client search and retrieve LaserSell documentation in real time. This gives AI agents access to API references, SDK guides, code examples, and integration details without leaving their workflow. ## Server URL ``` https://docs.lasersell.io/mcp ``` ## Available Tools | Tool | Description | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SearchLaserSellDocumentation` | Search across the LaserSell documentation knowledge base to find relevant information, code examples, API references, and guides. Returns contextual content with titles and direct links to documentation pages. | ## Setup ### Claude Desktop Add the following to your Claude Desktop MCP configuration file: * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={null} { "mcpServers": { "lasersell-docs": { "command": "npx", "args": [ "@mintlify/mcp", "--url", "https://docs.lasersell.io/mcp" ] } } } ``` Requires Node.js 18+ installed on your machine. Restart Claude Desktop after saving the configuration. ### Claude Code ```bash theme={null} claude mcp add lasersell-docs -- npx @mintlify/mcp --url https://docs.lasersell.io/mcp ``` ### Cursor Open Cursor Settings, navigate to **MCP**, click **Add new MCP server**, and enter: * **Name:** `lasersell-docs` * **Type:** `command` * **Command:** `npx @mintlify/mcp --url https://docs.lasersell.io/mcp` ### Windsurf Add to your Windsurf MCP configuration: ```json theme={null} { "mcpServers": { "lasersell-docs": { "command": "npx", "args": [ "@mintlify/mcp", "--url", "https://docs.lasersell.io/mcp" ] } } } ``` ### Other MCP Clients Any client that supports the Model Context Protocol can connect using the server URL: ``` https://docs.lasersell.io/mcp ``` The transport is Streamable HTTP. Pass the URL to your MCP client's remote server configuration. ## What Agents Can Search Once connected, the `SearchLaserSellDocumentation` tool gives agents access to the full LaserSell documentation, including: * **API reference** for `/v1/buy`, `/v1/sell`, and the Exit Intelligence Stream WebSocket. * **SDK guides** for TypeScript, Python, Rust, and Go with code examples. * **Strategy configuration** for target profit, stop loss, trailing stop, sell on graduation, and deadline timeout. * **Transaction signing and submission** across Helius Sender, Astralane, and custom RPC. * **Partner fee configuration** for integrators monetizing through LaserSell. * **Market context schemas** for all supported protocols (Pump.fun, PumpSwap, Meteora, Raydium). ## Example Queries Once the MCP server is connected, you can ask your AI client questions like: * "How do I build a sell transaction with the LaserSell Python SDK?" * "What are the fields in a BuildBuyTxRequest?" * "Show me how to connect to the Exit Intelligence Stream in TypeScript." * "What send targets are available for transaction submission?" * "How do partner fees work?" # AI Agents and LaserSell Source: https://docs.lasersell.io/ai-agents/overview Connect AI agents to LaserSell's Exit Intelligence engine for automated Solana token trading, strategy management, and real-time position monitoring. AI agents that interact with Solana tokens can use LaserSell as their execution and exit management layer. Instead of building swap routing, protocol handling, and exit logic from scratch, an agent connects to LaserSell and gets pre-built transactions, real-time PnL tracking, and automated exits out of the box. ## What Agents Can Do with LaserSell **Build and execute trades.** The [REST API](/api/overview) returns unsigned swap transactions for any supported Solana DEX or launchpad. An agent sends a mint and amount, gets back a transaction, signs it, and submits. No protocol-specific logic required. **Automate exits with strategy.** The [Exit Intelligence Stream](/api/stream/overview) watches wallets in real time and delivers pre-built exit transactions when configurable thresholds are met: target profit, stop loss, trailing stop, sell on graduation, and deadline timeout. An agent connects once and receives exit signals automatically. **Monitor positions.** The stream provides continuous PnL updates, balance changes, position opens, and position closes. Agents can use this data to make decisions, surface information to users, or feed into other systems. **Cover every major Solana protocol.** A single integration covers Pump.fun, PumpSwap, Meteora DBC, Meteora DAMM V2, Raydium Launchpad, and Raydium CPMM. The agent does not need to know which protocol a token is on. ## How to Connect There are three ways for AI agents to work with LaserSell: ### 1. MCP Server (Documentation Access) The LaserSell MCP server gives any MCP-compatible AI client (Claude, Cursor, Windsurf, and others) the ability to search and retrieve LaserSell documentation in real time. This is useful for agents that need to look up API schemas, SDK patterns, code examples, or integration details while working. See [MCP Server](/ai-agents/mcp-server) for setup instructions. ### 2. Coding Skills (Embedded SDK Knowledge) Install the LaserSell coding skill to give your AI assistant deep knowledge of SDK patterns, method signatures, examples, and troubleshooting loaded directly into context when writing LaserSell code. See [Coding Skills](/ai-agents/coding-skills) for install instructions. ### 3. SDK and API (Direct Integration) For agents that need to build transactions, manage positions, or execute trades programmatically, integrate using the LaserSell SDK or API directly. * [API Overview](/api/overview) for the full API surface. * [Quickstart](/api/quickstart) to build your first transaction. * SDKs available in [TypeScript](/api/sdk/typescript), [Python](/api/sdk/python), [Rust](/api/sdk/rust), and [Go](/api/sdk/go). ## Why Agents Should Use LaserSell ### The agent integration problem An AI agent that wants to trade Solana tokens today has to solve a stack of hard problems: identify which DEX protocol a token is on, deserialize on-chain pool state, compute swap amounts using protocol-specific math, construct the right instruction set, manage priority fees, handle slippage retries, and poll for price changes to know when to exit. Each protocol (Pump.fun, PumpSwap, Raydium, Meteora) has a different program, different account layouts, and different swap logic. This is hundreds of hours of integration work, and it breaks every time a new launchpad ships. LaserSell collapses all of that into two API calls: one to build a transaction, one WebSocket to receive exit signals. ### Technical advantages | Capability | Without LaserSell | With LaserSell | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | **Protocol routing** | Agent must detect which DEX a token is on, deserialize pool state, and build protocol-specific instructions for each of 6+ programs | Single API call. Send a mint address, get back a signed-ready transaction. LaserSell resolves the protocol, pool, and route server-side | | **Exit logic** | Agent must poll RPC for price data, calculate PnL, track peak profit for trailing stops, manage timers for deadlines, and build sell transactions manually | Connect once to the Exit Intelligence Stream. Receive `exit_signal_with_tx` events with pre-built unsigned transactions when strategy conditions fire | | **PnL tracking** | Agent must fetch token balances, resolve decimals, query pool reserves, compute spot price, and derive PnL at regular intervals | Stream delivers `pnl_update` events continuously. No polling, no RPC calls, no price math | | **New position detection** | Agent must monitor wallet transaction history or subscribe to on-chain logs and parse each transaction to identify buys | Stream fires `position_opened` with full context (cost basis, mint, market context) the moment a purchase lands | | **Multi-protocol coverage** | Separate integration for Pump.fun bonding curves, PumpSwap AMM, Raydium Launchpad, Raydium CPMM, Meteora DBC, Meteora DAMM V2 | One integration covers all six. Agent code does not change when a new protocol is added | | **Transaction signing** | N/A | All transactions returned unsigned. Agent signs locally. No private key ever leaves the agent's environment | | **Slippage handling** | Agent must estimate slippage, retry on failure, and bump tolerance incrementally | Server computes slippage. Client retries are automatic in the SDK with configurable bump logic | | **New launchpad support** | Agent must reverse-engineer each new program, build instructions from scratch, and maintain ongoing compatibility | LaserSell adds protocol support server-side. Agent gets coverage with zero code changes | ### What this means in practice **Fewer RPC calls.** An agent without LaserSell needs to poll price data, fetch balances, and read pool state constantly. With the Exit Intelligence Stream, the server pushes everything. The agent's RPC usage drops to transaction submission only. **Deterministic exit execution.** Strategy conditions (TP, SL, trailing stop, deadline, graduation) are evaluated server-side on every price tick. The agent does not need to stay online continuously or worry about missing a price spike. When a threshold is met, the exit transaction is already built and waiting. **No protocol-specific code.** The agent sends `{ mint, user_pubkey, amount }` to `/v1/sell` or `/v1/buy`. Whether that token is on a Pump.fun bonding curve, a PumpSwap AMM, or a Raydium CPMM pool, the returned transaction just works. **Event-driven architecture.** The stream is a natural fit for agents. Instead of running poll loops, the agent subscribes and reacts: `position_opened` to log a new trade, `pnl_update` to surface data, `exit_signal_with_tx` to sign and submit. Clean async patterns with no wasted compute. **SDK parity across 4 languages.** TypeScript, Python, Rust, and Go SDKs expose identical schemas, method names, and behavior. An agent built in Python works the same way as one built in Rust. No SDK is a second-class citizen. # Authentication Source: https://docs.lasersell.io/api/authentication Obtain your API key from the LaserSell dashboard and authenticate requests across all four SDKs. ## Getting Your API Key 1. Sign in at [app.lasersell.io](https://app.lasersell.io). 2. Navigate to **Settings > API Keys**. 3. Click **Create API Key**, give it a name, and copy the value. Store the key securely. It cannot be displayed again after creation. ## Authenticating Requests Every request to the LaserSell API and every WebSocket connection to the Exit Intelligence Stream requires an API key passed via the `x-api-key` HTTP header. ### curl ```bash theme={null} curl -X POST https://api.lasersell.io/v1/sell \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"mint":"TOKEN_MINT","user_pubkey":"WALLET","amount_tokens":1000000}' ``` ### SDK Examples ```typescript TypeScript theme={null} import { ExitApiClient } from "@lasersell/lasersell-sdk"; const client = ExitApiClient.withApiKey("YOUR_API_KEY"); ``` ```python Python theme={null} from lasersell_sdk.exit_api import ExitApiClient client = ExitApiClient.with_api_key("YOUR_API_KEY") ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::ExitApiClient; let client = ExitApiClient::with_api_key("YOUR_API_KEY"); ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" client := lasersell.NewExitAPIClientWithAPIKey("YOUR_API_KEY") ``` ### Exit Intelligence Stream Authentication The Exit Intelligence Stream WebSocket also uses `x-api-key`. The SDK handles this automatically when you construct a `StreamClient`: ```typescript TypeScript theme={null} import { StreamClient } from "@lasersell/lasersell-sdk"; const client = new StreamClient("YOUR_API_KEY"); ``` ```python Python theme={null} from lasersell_sdk.stream.client import StreamClient client = StreamClient("YOUR_API_KEY") ``` ```rust Rust theme={null} use lasersell_sdk::stream::client::StreamClient; use secrecy::SecretString; let client = StreamClient::new(SecretString::new("YOUR_API_KEY".into())); ``` ```go Go theme={null} import "github.com/lasersell/lasersell-sdk/go/stream" client := stream.NewStreamClient("YOUR_API_KEY") ``` ## Security Best Practices * **Never commit your API key** to version control. Use environment variables or a secrets manager. * **Rotate keys** periodically through the dashboard. * **Scope access** by creating separate keys for different bots or environments. * If a key is compromised, revoke it immediately from the dashboard and create a replacement. # Get Account Source: https://docs.lasersell.io/api/exit-api/account GET /v1/account Retrieve your account tier, plan name, and usage limits. # Build Buy Transaction Source: https://docs.lasersell.io/api/exit-api/buy POST /v1/buy Build an unsigned buy transaction to swap SOL or USD1 for tokens via one of the supported DEXs. The response contains a base64-encoded unsigned `VersionedTransaction`. Sign it locally with your keypair and submit it to the network. If you plan to track this buy with the Exit Intelligence Stream, you **must** connect the stream **before** submitting the buy transaction. The stream detects new positions by observing on-chain token arrivals in real time. If the stream is not connected when the buy lands, the position will not be tracked and no exit signals will be generated for it. Exactly one of `amount` or `amount_in_total` must be provided. `amount` is automatically converted to atomic units based on the `input` asset (SOL: 10^9, USD1: 10^6). ## SDK Examples ```typescript TypeScript theme={null} import { ExitApiClient, type BuildBuyTxRequest } from "@lasersell/lasersell-sdk"; const client = ExitApiClient.withApiKey("YOUR_API_KEY"); const request: BuildBuyTxRequest = { mint: "TOKEN_MINT_ADDRESS", user_pubkey: "YOUR_WALLET_PUBKEY", amount: 0.1, // 0.1 SOL slippage_bps: 2_000, }; const response = await client.buildBuyTx(request); console.log("Unsigned tx (base64):", response.tx); ``` ```python Python theme={null} from lasersell_sdk.exit_api import ExitApiClient, BuildBuyTxRequest client = ExitApiClient.with_api_key("YOUR_API_KEY") request = BuildBuyTxRequest( mint="TOKEN_MINT_ADDRESS", user_pubkey="YOUR_WALLET_PUBKEY", amount=0.1, # 0.1 SOL slippage_bps=2_000, ) response = await client.build_buy_tx(request) print("Unsigned tx (base64):", response.tx) ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::{ExitApiClient, BuildBuyTxRequest}; let client = ExitApiClient::with_api_key("YOUR_API_KEY"); let request = BuildBuyTxRequest { mint: "TOKEN_MINT_ADDRESS".into(), user_pubkey: "YOUR_WALLET_PUBKEY".into(), amount: Some(0.1), // 0.1 SOL slippage_bps: 2_000, ..Default::default() }; let response = client.build_buy_tx(&request).await?; println!("Unsigned tx (base64): {}", response.tx); ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" client := lasersell.NewExitAPIClientWithAPIKey("YOUR_API_KEY") amount := 0.1 // 0.1 SOL resp, err := client.BuildBuyTx(ctx, lasersell.BuildBuyTxRequest{ Mint: "TOKEN_MINT_ADDRESS", UserPubkey: "YOUR_WALLET_PUBKEY", Amount: &amount, SlippageBps: 2_000, }) if err != nil { log.Fatal(err) } fmt.Println("Unsigned tx (base64):", resp.Tx) ``` ## Error Responses See [Error Handling](/api/exit-api/error-handling) for the full error envelope specification and retryable error logic. # Error Handling Source: https://docs.lasersell.io/api/exit-api/error-handling SDK error types, retryability, and retry configuration for the LaserSell API. ## `ExitApiError` Kinds Every SDK exposes an `ExitApiError` (or equivalent) with a `kind` discriminator: | Kind | Trigger | Retryable | | ----------------- | ----------------------------------------- | --------------------------- | | `transport` | Network failure, DNS error, timeout | Yes | | `http_status` | Non 2xx HTTP response | Yes if status >= 500 or 429 | | `envelope_status` | Server returned `{ "status": "error" }` | No | | `parse` | Response body could not be parsed as JSON | No | ### Checking Retryability ```typescript TypeScript theme={null} import { ExitApiError } from "@lasersell/lasersell-sdk"; try { const response = await client.buildSellTx(request); } catch (error) { if (error instanceof ExitApiError) { console.log("Kind:", error.kind); console.log("Retryable:", error.isRetryable()); console.log("HTTP status:", error.status); console.log("Body:", error.body); } } ``` ```python Python theme={null} from lasersell_sdk.exit_api import ExitApiError try: response = await client.build_sell_tx(request) except ExitApiError as error: print("Kind:", error.kind) print("Retryable:", error.is_retryable()) print("HTTP status:", error.status) print("Body:", error.body) ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::ExitApiError; match client.build_sell_tx(&request).await { Ok(response) => { /* use response */ } Err(error) => { eprintln!("Kind: {:?}", error.kind()); eprintln!("Retryable: {}", error.is_retryable()); } } ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" resp, err := client.BuildSellTx(ctx, request) if err != nil { var apiErr *lasersell.ExitAPIError if errors.As(err, &apiErr) { fmt.Println("Kind:", apiErr.Kind) fmt.Println("Retryable:", apiErr.IsRetryable()) fmt.Println("HTTP status:", apiErr.Status) } } ``` ## Built in Retry Behavior All SDKs include automatic retry with these defaults: | Setting | Default Value | | --------------- | ------------- | | Max attempts | 2 | | Initial backoff | 25 ms | | Max backoff | 25 ms | | Jitter | 25 ms | | Connect timeout | 200 ms | | Attempt timeout | 900 ms | Retries only trigger for errors where `isRetryable()` returns `true` (transport failures, 5xx responses, and 429 rate limits). ### Customizing Retry Policy ```typescript TypeScript theme={null} const client = ExitApiClient.withOptions("YOUR_API_KEY", { attempt_timeout_ms: 2000, retry_policy: { max_attempts: 3, initial_backoff_ms: 50, max_backoff_ms: 200, jitter_ms: 50, }, }); ``` ```python Python theme={null} from lasersell_sdk.exit_api import ExitApiClient, ExitApiClientOptions from lasersell_sdk.retry import RetryPolicy client = ExitApiClient.with_options( "YOUR_API_KEY", ExitApiClientOptions( attempt_timeout_s=2.0, retry_policy=RetryPolicy( max_attempts=3, initial_backoff_ms=50, max_backoff_ms=200, jitter_ms=50, ), ), ) ``` ```rust Rust theme={null} use std::time::Duration; use lasersell_sdk::exit_api::{ExitApiClient, ExitApiClientOptions}; use lasersell_sdk::retry::RetryPolicy; use secrecy::SecretString; let client = ExitApiClient::with_options( Some(SecretString::new("YOUR_API_KEY".into())), ExitApiClientOptions { connect_timeout: Duration::from_millis(200), attempt_timeout: Duration::from_millis(2000), retry_policy: RetryPolicy { max_attempts: 3, initial_backoff: Duration::from_millis(50), max_backoff: Duration::from_millis(200), jitter: Duration::from_millis(50), }, }, )?; ``` ```go Go theme={null} import ( "time" lasersell "github.com/lasersell/lasersell-sdk/go" ) client := lasersell.NewExitAPIClientWithOptions("YOUR_API_KEY", lasersell.ExitAPIClientOptions{ ConnectTimeout: 200 * time.Millisecond, AttemptTimeout: 2000 * time.Millisecond, RetryPolicy: lasersell.RetryPolicy{ MaxAttempts: 3, InitialBackoff: 50 * time.Millisecond, MaxBackoff: 200 * time.Millisecond, Jitter: 50 * time.Millisecond, }, }) ``` # Get Trade History Source: https://docs.lasersell.io/api/exit-api/history GET /v1/history Fetch paginated trade history for your account, ordered by most recent first. # Build Sell Transaction Source: https://docs.lasersell.io/api/exit-api/sell POST /v1/sell Build an unsigned sell transaction to swap tokens for SOL or USD1 via one of the supported DEXs. The response contains a base64-encoded unsigned `VersionedTransaction`. Sign it locally with your keypair and submit it to the network. ## SDK Examples ```typescript TypeScript theme={null} import { ExitApiClient, type BuildSellTxRequest } from "@lasersell/lasersell-sdk"; const client = ExitApiClient.withApiKey("YOUR_API_KEY"); const request: BuildSellTxRequest = { mint: "TOKEN_MINT_ADDRESS", user_pubkey: "YOUR_WALLET_PUBKEY", amount_tokens: 1_000_000, slippage_bps: 2_000, output: "SOL", }; const response = await client.buildSellTx(request); console.log("Unsigned tx (base64):", response.tx); // Or get just the base64 string: const txB64 = await client.buildSellTxB64(request); ``` ```python Python theme={null} from lasersell_sdk.exit_api import ExitApiClient, BuildSellTxRequest, SellOutput client = ExitApiClient.with_api_key("YOUR_API_KEY") request = BuildSellTxRequest( mint="TOKEN_MINT_ADDRESS", user_pubkey="YOUR_WALLET_PUBKEY", amount_tokens=1_000_000, slippage_bps=2_000, output=SellOutput.SOL, ) response = await client.build_sell_tx(request) print("Unsigned tx (base64):", response.tx) ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::{ExitApiClient, BuildSellTxRequest, SellOutput}; let client = ExitApiClient::with_api_key("YOUR_API_KEY"); let request = BuildSellTxRequest { mint: "TOKEN_MINT_ADDRESS".into(), user_pubkey: "YOUR_WALLET_PUBKEY".into(), amount_tokens: 1_000_000, output: SellOutput::Sol, slippage_bps: 2_000, ..Default::default() }; let response = client.build_sell_tx(&request).await?; println!("Unsigned tx (base64): {}", response.tx); ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" client := lasersell.NewExitAPIClientWithAPIKey("YOUR_API_KEY") resp, err := client.BuildSellTx(ctx, lasersell.BuildSellTxRequest{ Mint: "TOKEN_MINT_ADDRESS", UserPubkey: "YOUR_WALLET_PUBKEY", AmountTokens: 1_000_000, Output: lasersell.SellOutputSOL, SlippageBps: 2_000, }) if err != nil { log.Fatal(err) } fmt.Println("Unsigned tx (base64):", resp.Tx) ``` ## Error Responses See [Error Handling](/api/exit-api/error-handling) for the full error envelope specification and retryable error logic. # Register Wallet Source: https://docs.lasersell.io/api/exit-api/wallets-register POST /v1/wallets/register Register a wallet by proving ownership with an Ed25519 signature. Registration is required before connecting wallets to the Exit Intelligence Stream. No on-chain transaction is needed. Generate the proof locally using your keypair and the SDK's `proveOwnership` helper. ## SDK Usage ### Generate Proof Use the SDK's `proveOwnership` helper to generate a `WalletProof` locally. This is a pure cryptographic operation with no network call. ```typescript TypeScript theme={null} import { proveOwnership } from "@lasersell/lasersell-sdk"; const proof = proveOwnership(keypair); // proof = { walletPubkey, signature, message } ``` ```python Python theme={null} from lasersell_sdk.exit_api import prove_ownership proof = prove_ownership(keypair) # proof = WalletProof(wallet_pubkey, signature, message) ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::prove_ownership; let proof = prove_ownership(&keypair); // proof = WalletProof { wallet_pubkey, signature, message } ``` ```go Go theme={null} proof := lasersell.ProveOwnership(privateKey) // proof = WalletProof{WalletPubkey, Signature, Message} ``` ### Register Send the proof to the API to register the wallet. ```typescript TypeScript theme={null} const client = ExitApiClient.withApiKey(apiKey); await client.registerWallet(proof, "My Wallet"); ``` ```python Python theme={null} client = ExitApiClient.with_api_key(api_key) await client.register_wallet(proof, label="My Wallet") ``` ```rust Rust theme={null} client.register_wallet(&proof, Some("My Wallet")).await?; ``` ```go Go theme={null} client.RegisterWallet(ctx, proof, lasersell.Ptr("My Wallet")) ``` ### Connect with Wallets (one step) Convenience method that registers wallets and connects to the stream in one call. ```typescript TypeScript theme={null} import { StreamClient, proveOwnership } from "@lasersell/lasersell-sdk"; const proof = proveOwnership(keypair); const client = new StreamClient(apiKey); const session = await client.connectWithWallets( [proof], { target_profit_pct: 50, stop_loss_pct: 10 }, 120, // deadline_timeout_sec ); ``` ```python Python theme={null} from lasersell_sdk.exit_api import prove_ownership from lasersell_sdk.stream.client import StreamClient proof = prove_ownership(keypair) client = StreamClient(api_key) session = await client.connect_with_wallets( [proof], {"target_profit_pct": 50, "stop_loss_pct": 10}, deadline_timeout_sec=120, ) ``` ```rust Rust theme={null} let proof = prove_ownership(&keypair); let client = StreamClient::new(api_key); let session = client.connect_with_wallets( &[proof], strategy, 120, ).await?; ``` ```go Go theme={null} // Go SDK: register separately, then connect proof := lasersell.ProveOwnership(privateKey) client.RegisterWallet(ctx, proof, nil) session, _ := stream.ConnectSession(ctx, streamClient, configure) ``` # Unregister Wallet Source: https://docs.lasersell.io/api/exit-api/wallets-unregister DELETE /v1/wallets Remove a wallet registration from your account. # API and SDK Overview Source: https://docs.lasersell.io/api/overview High level introduction to the LaserSell API, Exit Intelligence Stream, and SDKs for programmatic token trading on Solana. ## What is the LaserSell API? The LaserSell API lets you build, sign, and submit Solana swap transactions programmatically. It exposes two surfaces: * **LaserSell API** (REST): Build unsigned buy and sell transactions on demand via `POST /v1/sell` and `POST /v1/buy`. Retrieve your account details with `GET /v1/account` and query your trade history with `GET /v1/history`. * **Exit Intelligence Stream** (WebSocket): Connect a persistent session that watches your wallets, tracks positions, evaluates your strategy in real time, and delivers pre built exit transactions when thresholds are met. Both surfaces return **unsigned transactions**. Your private key never leaves your machine. You sign locally, then submit through the send target of your choice. ## Non Custodial Model LaserSell is fully non custodial. The server constructs optimized swap instructions but cannot execute them without your signature. This means: 1. You hold the keypair at all times. 2. The API returns a base64 encoded unsigned transaction. 3. You sign with your local keypair. 4. You submit via RPC, Helius Sender, or Astralane. No funds, tokens, or keys are ever stored or accessed by LaserSell infrastructure. ## Architecture at a Glance ```mermaid theme={null} flowchart LR A["Your Code
(SDK)"] -->|REST| B["LaserSell API
api.lasersell.io"] A -->|WebSocket| C["Exit Intelligence Stream
stream.lasersell.io"] B -->|unsigned tx| A C -->|unsigned tx| A A -->|"sign locally,
then submit"| D["Solana RPC
or Sender"] ``` ## SDK Languages Official SDKs are available in four languages, each providing the same capabilities: | Language | Package | Modules | | ---------- | --------------------------------------- | -------------------------------------------------------------------------- | | TypeScript | `@lasersell/lasersell-sdk` | `ExitApiClient`, `StreamClient`, `StreamSession`, tx helpers | | Python | `lasersell-sdk` | `ExitApiClient`, `StreamClient`, `StreamSession`, tx helpers | | Rust | `lasersell-sdk` | `exit_api`, `stream`, `tx` | | Go | `github.com/lasersell/lasersell-sdk/go` | `ExitAPIClient`, `stream.StreamClient`, `stream.StreamSession`, tx helpers | All SDKs share the same request and response schemas, error types, and retry behavior. Pick the language that fits your stack and follow the corresponding SDK guide. ## What to Read Next * [Authentication](/api/authentication): Get your API key and start making requests. * [Quickstart](/api/quickstart): Build your first sell transaction in under five minutes. * [Exit Intelligence Stream](/api/stream/overview): Learn when to use the WebSocket stream instead of REST. # Quickstart Source: https://docs.lasersell.io/api/quickstart Build, sign, and submit your first sell transaction in under five minutes, then set up an auto sell stream. ## Prerequisites * A LaserSell API key ([get one here](/api/authentication)). * A Solana keypair file (JSON array of bytes). * Node.js 18+ (for the TypeScript example) or the runtime for your preferred language. **Using AI to code?** Add the [LaserSell MCP server](/ai-agents/mcp-server) to your editor so your AI assistant can search LaserSell documentation in real time. Works with Claude Code, Claude Desktop, Cursor, and Windsurf. ## Example 1: Build, Sign, and Send a Sell Transaction This example calls the LaserSell API to build an unsigned sell transaction, signs it locally, and submits through Helius Sender. ```typescript TypeScript theme={null} import { readFile } from "node:fs/promises"; import { Keypair } from "@solana/web3.js"; import { ExitApiClient, sendTransaction, sendTargetHeliusSender, signUnsignedTx, type BuildSellTxRequest, } from "@lasersell/lasersell-sdk"; const apiKey = process.env.LASERSELL_API_KEY!; const keypairPath = "./keypair.json"; // Load keypair const raw = await readFile(keypairPath, "utf8"); const keypair = Keypair.fromSecretKey( Uint8Array.from(JSON.parse(raw)) ); // Build unsigned tx const client = ExitApiClient.withApiKey(apiKey); const request: BuildSellTxRequest = { mint: "So11111111111111111111111111111111111111112", user_pubkey: keypair.publicKey.toBase58(), amount_tokens: 1_000_000, slippage_bps: 2_000, output: "SOL", }; const unsignedTxB64 = await client.buildSellTxB64(request); // Sign locally const signedTx = signUnsignedTx(unsignedTxB64, keypair); // Submit const signature = await sendTransaction( sendTargetHeliusSender(), signedTx ); console.log("Signature:", signature); ``` ```python Python theme={null} import asyncio, json from pathlib import Path from solders.keypair import Keypair from lasersell_sdk.exit_api import ExitApiClient, BuildSellTxRequest, SellOutput from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx async def main(): api_key = "YOUR_API_KEY" keypair_bytes = json.loads(Path("./keypair.json").read_text()) signer = Keypair.from_bytes(bytes(keypair_bytes)) client = ExitApiClient.with_api_key(api_key) request = BuildSellTxRequest( mint="So11111111111111111111111111111111111111112", user_pubkey=str(signer.pubkey()), amount_tokens=1_000_000, slippage_bps=2_000, output=SellOutput.SOL, ) unsigned_tx_b64 = await client.build_sell_tx_b64(request) signed_tx = sign_unsigned_tx(unsigned_tx_b64, signer) signature = await send_transaction(SendTargetHeliusSender(), signed_tx) print("Signature:", signature) asyncio.run(main()) ``` ```rust Rust theme={null} use lasersell_sdk::exit_api::{ExitApiClient, BuildSellTxRequest}; use lasersell_sdk::tx::{SendTarget, sign_unsigned_tx, send_transaction}; use solana_sdk::signature::read_keypair_file; #[tokio::main] async fn main() -> anyhow::Result<()> { let api_key = std::env::var("LASERSELL_API_KEY")?; let keypair = read_keypair_file("./keypair.json")?; let http = reqwest::Client::new(); let client = ExitApiClient::with_api_key(&api_key); let request = BuildSellTxRequest { mint: "So11111111111111111111111111111111111111112".into(), user_pubkey: keypair.pubkey().to_string(), amount_tokens: 1_000_000, output: SellOutput::Sol, slippage_bps: 2_000, ..Default::default() }; let unsigned_tx_b64 = client.build_sell_tx_b64(&request).await?; let signed_tx = sign_unsigned_tx(&unsigned_tx_b64, &keypair)?; let signature = send_transaction(&http, &SendTarget::HeliusSender, &signed_tx).await?; println!("Signature: {signature}"); Ok(()) } ``` ```go Go theme={null} package main import ( "context" "fmt" "log" "github.com/gagliardetto/solana-go" lasersell "github.com/lasersell/lasersell-sdk/go" ) func main() { ctx := context.Background() apiKey := "YOUR_API_KEY" privateKey, err := solana.PrivateKeyFromSolanaKeygenFile("./keypair.json") if err != nil { log.Fatal(err) } client := lasersell.NewExitAPIClientWithAPIKey(apiKey) resp, err := client.BuildSellTx(ctx, lasersell.BuildSellTxRequest{ Mint: "So11111111111111111111111111111111111111112", UserPubkey: privateKey.PublicKey().String(), AmountTokens: 1_000_000, Output: lasersell.SellOutputSOL, SlippageBps: 2_000, }) if err != nil { log.Fatal(err) } signedTx, err := lasersell.SignUnsignedTx(resp.Tx, privateKey) if err != nil { log.Fatal(err) } sig, err := lasersell.SendTransaction(ctx, nil, lasersell.SendTargetHeliusSender(), signedTx) if err != nil { log.Fatal(err) } fmt.Println("Signature:", sig) } func intPtr(v int) *int { return &v } func strPtr(v string) *string { return &v } ``` ## Example 2: Auto Sell with the Exit Intelligence Stream Connect the Exit Intelligence Stream to monitor wallets and automatically execute exits when your strategy thresholds are reached. **Connect the stream before you buy.** The stream detects positions by watching for on chain token arrivals. If you call `/v1/buy` before the stream is connected and configured, the position will not be tracked. Always start the stream first, then submit your buy transaction. ```typescript TypeScript theme={null} import { readFile } from "node:fs/promises"; import { Keypair } from "@solana/web3.js"; import { StreamClient, StreamSession, sendTransaction, sendTargetHeliusSender, signUnsignedTx, } from "@lasersell/lasersell-sdk"; const apiKey = process.env.LASERSELL_API_KEY!; const raw = await readFile("./keypair.json", "utf8"); const signer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(raw))); const client = new StreamClient(apiKey); const session = await StreamSession.connect(client, { wallet_pubkeys: [signer.publicKey.toBase58()], strategy: { target_profit_pct: 5, stop_loss_pct: 1.5, }, deadline_timeout_sec: 45, send_mode: "helius_sender", tip_lamports: 1000, }); while (true) { const event = await session.recv(); if (event === null) break; if (event.type === "exit_signal_with_tx") { const signed = signUnsignedTx(event.message.unsigned_tx_b64, signer); const sig = await sendTransaction(sendTargetHeliusSender(), signed); console.log("Exit submitted:", sig); } } ``` ```python Python theme={null} import asyncio, json from pathlib import Path from solders.keypair import Keypair from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx async def main(): signer = Keypair.from_bytes(bytes(json.loads(Path("./keypair.json").read_text()))) client = StreamClient("YOUR_API_KEY") session = await StreamSession.connect( client, StreamConfigure( wallet_pubkeys=[str(signer.pubkey())], strategy={"target_profit_pct": 5.0, "stop_loss_pct": 1.5}, deadline_timeout_sec=45, send_mode="helius_sender", tip_lamports=1000, ), ) while True: event = await session.recv() if event is None: break if event.type == "exit_signal_with_tx": msg = event.message signed = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed) print("Exit submitted:", sig) asyncio.run(main()) ``` ```rust Rust theme={null} use lasersell_sdk::stream::client::{StreamClient, StreamConfigure, strategy_config_from_optional}; use lasersell_sdk::stream::session::{StreamSession, StreamEvent}; use lasersell_sdk::stream::proto::ServerMessage; use lasersell_sdk::tx::{SendTarget, sign_unsigned_tx, send_transaction}; use secrecy::SecretString; use solana_sdk::signature::read_keypair_file; #[tokio::main] async fn main() -> anyhow::Result<()> { let api_key = std::env::var("LASERSELL_API_KEY")?; let keypair = read_keypair_file("./keypair.json")?; let http = reqwest::Client::new(); let client = StreamClient::new(SecretString::new(api_key)); let configure = StreamConfigure { wallet_pubkeys: vec![keypair.pubkey().to_string()], strategy: strategy_config_from_optional( Some(5.0), Some(1.5), None, None, ), deadline_timeout_sec: 45, send_mode: Some("helius_sender".to_string()), tip_lamports: Some(1000), }; let mut session = StreamSession::connect(&client, configure).await?; while let Some(event) = session.recv().await { if let StreamEvent::ExitSignalWithTx { message: ServerMessage::ExitSignalWithTx { unsigned_tx_b64, .. }, .. } = event { let signed = sign_unsigned_tx(&unsigned_tx_b64, &keypair)?; let sig = send_transaction(&http, &SendTarget::HeliusSender, &signed).await?; println!("Exit submitted: {sig}"); } } Ok(()) } ``` ```go Go theme={null} package main import ( "context" "errors" "fmt" "io" "log" "github.com/gagliardetto/solana-go" lasersell "github.com/lasersell/lasersell-sdk/go" "github.com/lasersell/lasersell-sdk/go/stream" ) func main() { ctx := context.Background() apiKey := "YOUR_API_KEY" privateKey, err := solana.PrivateKeyFromSolanaKeygenFile("./keypair.json") if err != nil { log.Fatal(err) } client := stream.NewStreamClient(apiKey) sendMode := "helius_sender" tipLamports := uint64(1000) session, err := stream.ConnectSession(ctx, client, stream.StreamConfigure{ WalletPubkeys: []string{privateKey.PublicKey().String()}, Strategy: stream.StrategyConfigMsg{ TargetProfitPct: 5.0, StopLossPct: 1.5, }, DeadlineTimeoutSec: 45, SendMode: &sendMode, TipLamports: &tipLamports, }) if err != nil { log.Fatal(err) } for { event, err := session.Recv(ctx) if errors.Is(err, io.EOF) { break } if err != nil { log.Fatal(err) } if msg, ok := event.Message.(stream.ExitSignalWithTxServerMessage); ok { signedTx, err := lasersell.SignUnsignedTx(msg.UnsignedTxB64, privateKey) if err != nil { log.Printf("sign failed: %v", err) continue } sig, err := lasersell.SendTransaction(ctx, nil, lasersell.SendTargetHeliusSender(), signedTx) if err != nil { log.Printf("send failed: %v", err) continue } fmt.Println("Exit submitted:", sig) } } } ``` ## Next Steps * [POST /v1/sell](/api/exit-api/sell): Full request and response schema for sell transactions. * [Exit Intelligence Stream](/api/stream/overview): Deep dive into the WebSocket stream. * [Transaction Signing](/api/transactions/signing): Understand the non custodial signing flow. # Market Context Source: https://docs.lasersell.io/api/reference/market-context Full MarketContextMsg schema with nested objects for each supported DEX protocol. ## Overview `MarketContextMsg` describes the DEX pool and protocol used for a token's market. It appears in: * `position_opened` server events * `exit_signal_with_tx` server events * `BuildSellTxRequest` (optional, to skip server side resolution) If omitted from a build request, the server resolves the context automatically. ## `MarketContextMsg` Schema | Field | Type | Required | Description | | ------------------- | -------- | -------- | ---------------------------------------------- | | `market_type` | `string` | Yes | One of the supported market types (see below). | | `pumpfun` | `object` | No | Pump.fun bonding curve context. | | `pumpswap` | `object` | No | PumpSwap AMM context. | | `meteora_dbc` | `object` | No | Meteora DBC context. | | `meteora_damm_v2` | `object` | No | Meteora DAMM V2 context. | | `raydium_launchpad` | `object` | No | Raydium Launchpad context. | | `raydium_cpmm` | `object` | No | Raydium CPMM context. | Exactly one of the nested objects should be present, matching the `market_type`. The `market_type` value uses underscores (e.g. `pump_fun`) while the nested object key does not (e.g. `pumpfun`). Match the exact casing shown in the tables below. ## `market_type` Values | Value | Protocol | | ------------------- | ----------------- | | `pump_fun` | Pump.fun | | `pump_swap` | PumpSwap | | `meteora_dbc` | Meteora DBC | | `meteora_damm_v2` | Meteora DAMM V2 | | `raydium_launchpad` | Raydium Launchpad | | `raydium_cpmm` | Raydium CPMM | ## Nested Context Objects ### `PumpFunContextMsg` Empty object. Pump.fun bonding curve tokens do not require additional pool context. ```json theme={null} { "market_type": "pump_fun", "pumpfun": {} } ``` ### `PumpSwapContextMsg` | Field | Type | Required | Description | | --------------- | -------- | -------- | ------------------------------- | | `pool` | `string` | Yes | PumpSwap pool address. | | `global_config` | `string` | No | PumpSwap global config address. | ```json theme={null} { "market_type": "pump_swap", "pumpswap": { "pool": "PoolAddr...", "global_config": "ConfigAddr..." } } ``` ### `MeteoraDbcContextMsg` | Field | Type | Required | Description | | ------------ | -------- | -------- | -------------------------------- | | `pool` | `string` | Yes | Meteora DBC pool address. | | `config` | `string` | Yes | Pool configuration address. | | `quote_mint` | `string` | Yes | Quote asset mint (SOL or other). | ```json theme={null} { "market_type": "meteora_dbc", "meteora_dbc": { "pool": "PoolAddr...", "config": "ConfigAddr...", "quote_mint": "So111..." } } ``` ### `MeteoraDammV2ContextMsg` | Field | Type | Required | Description | | ------ | -------- | -------- | ----------------------------- | | `pool` | `string` | Yes | Meteora DAMM V2 pool address. | ```json theme={null} { "market_type": "meteora_damm_v2", "meteora_damm_v2": { "pool": "PoolAddr..." } } ``` ### `RaydiumLaunchpadContextMsg` | Field | Type | Required | Description | | -------------------- | -------- | -------- | ------------------------------- | | `pool` | `string` | Yes | Raydium Launchpad pool address. | | `config` | `string` | Yes | Pool configuration address. | | `platform` | `string` | Yes | Platform address. | | `quote_mint` | `string` | Yes | Quote asset mint. | | `user_quote_account` | `string` | Yes | User's quote token account. | ```json theme={null} { "market_type": "raydium_launchpad", "raydium_launchpad": { "pool": "PoolAddr...", "config": "ConfigAddr...", "platform": "PlatformAddr...", "quote_mint": "So111...", "user_quote_account": "QuoteATA..." } } ``` ### `RaydiumCpmmContextMsg` | Field | Type | Required | Description | | -------------------- | -------- | -------- | --------------------------- | | `pool` | `string` | Yes | Raydium CPMM pool address. | | `config` | `string` | Yes | Pool configuration address. | | `quote_mint` | `string` | Yes | Quote asset mint. | | `user_quote_account` | `string` | Yes | User's quote token account. | ```json theme={null} { "market_type": "raydium_cpmm", "raydium_cpmm": { "pool": "PoolAddr...", "config": "ConfigAddr...", "quote_mint": "So111...", "user_quote_account": "QuoteATA..." } } ``` ## Passing Market Context in Build Requests You can include `market_context` in a `BuildSellTxRequest` to skip server side pool resolution. This is useful when you already have the context from a `position_opened` or `exit_signal_with_tx` event. ```typescript TypeScript theme={null} const request: BuildSellTxRequest = { mint: "TOKEN_MINT", user_pubkey: "WALLET", amount_tokens: 1_000_000, slippage_bps: 2_000, market_context: { market_type: "pump_swap", pumpswap: { pool: "PoolAddr..." }, }, }; ``` ```python Python theme={null} request = BuildSellTxRequest( mint="TOKEN_MINT", user_pubkey="WALLET", amount_tokens=1_000_000, slippage_bps=2_000, market_context={ "market_type": "pump_swap", "pumpswap": {"pool": "PoolAddr..."}, }, ) ``` ```rust Rust theme={null} let request = BuildSellTxRequest { mint: "TOKEN_MINT".into(), user_pubkey: "WALLET".into(), amount_tokens: 1_000_000, output: SellOutput::Sol, slippage_bps: 2_000, market_context: Some(MarketContextMsg { market_type: "pump_swap".into(), pumpswap: Some(PumpSwapContextMsg { pool: "PoolAddr...".into(), ..Default::default() }), ..Default::default() }), ..Default::default() }; ``` ```go Go theme={null} resp, err := client.BuildSellTx(ctx, lasersell.BuildSellTxRequest{ Mint: "TOKEN_MINT", UserPubkey: "WALLET", AmountTokens: 1_000_000, Output: lasersell.SellOutputSOL, SlippageBps: 2_000, MarketContext: &stream.MarketContextMsg{ MarketType: "pump_swap", PumpSwap: &stream.PumpSwapContextMsg{Pool: "PoolAddr..."}, }, }) ``` ## TypeScript Type Reference ```typescript theme={null} type MarketTypeMsg = | "pump_fun" | "pump_swap" | "meteora_dbc" | "meteora_damm_v2" | "raydium_launchpad" | "raydium_cpmm"; interface MarketContextMsg { market_type: MarketTypeMsg; pumpfun?: PumpFunContextMsg; pumpswap?: PumpSwapContextMsg; meteora_dbc?: MeteoraDbcContextMsg; meteora_damm_v2?: MeteoraDammV2ContextMsg; raydium_launchpad?: RaydiumLaunchpadContextMsg; raydium_cpmm?: RaydiumCpmmContextMsg; } ``` # Partner Fees Source: https://docs.lasersell.io/api/reference/partner-fees Configure partner fees on buy and sell transactions using basis points or flat lamport amounts. ## Overview The LaserSell API supports adding a partner fee to any buy or sell transaction. This lets integrators collect a fee routed to a specified wallet as part of the swap instruction. Partner fees are optional and configured per request. They are **not** related to LaserSell's own protocol fee. ## Fee Modes You can specify the fee in one of two ways. These modes are mutually exclusive; do not set both on the same request. ### Basis Points Mode Set `partner_fee_bps` to charge a percentage of the swap value. | Field | Type | Description | | ----------------------- | -------- | ----------------------------------------- | | `partner_fee_recipient` | `string` | Recipient wallet address (base58 pubkey). | | `partner_fee_bps` | `number` | Fee in basis points. Max `50` (0.5%). | **Example**: A 25 bps (0.25%) fee on a 1 SOL swap routes 0.0025 SOL to the recipient. ### Flat Lamports Mode Set `partner_fee_lamports` to charge a fixed amount in lamports. | Field | Type | Description | | ----------------------- | -------- | --------------------------------------------- | | `partner_fee_recipient` | `string` | Recipient wallet address (base58 pubkey). | | `partner_fee_lamports` | `number` | Fee in lamports. Max `50,000,000` (0.05 SOL). | ## Limits | Mode | Maximum Value | Equivalent | | -------- | ------------------- | ---------- | | bps | 50 bps | 0.5% | | lamports | 50,000,000 lamports | 0.05 SOL | Requests exceeding these limits will be rejected with a `400` error. ## Code Examples ### Sell with BPS Fee ```typescript TypeScript theme={null} const request: BuildSellTxRequest = { mint: "TOKEN_MINT", user_pubkey: "WALLET", amount_tokens: 1_000_000, slippage_bps: 2_000, output: "SOL", partner_fee_recipient: "FEE_WALLET_PUBKEY", partner_fee_bps: 25, // 0.25% }; const response = await client.buildSellTx(request); ``` ```python Python theme={null} request = BuildSellTxRequest( mint="TOKEN_MINT", user_pubkey="WALLET", amount_tokens=1_000_000, slippage_bps=2_000, output=SellOutput.SOL, partner_fee_recipient="FEE_WALLET_PUBKEY", partner_fee_bps=25, ) response = await client.build_sell_tx(request) ``` ```rust Rust theme={null} let request = BuildSellTxRequest { mint: "TOKEN_MINT".into(), user_pubkey: "WALLET".into(), amount_tokens: 1_000_000, output: SellOutput::Sol, slippage_bps: 2_000, partner_fee_recipient: Some("FEE_WALLET_PUBKEY".into()), partner_fee_bps: Some(25), ..Default::default() }; let response = client.build_sell_tx(&request).await?; ``` ```go Go theme={null} resp, err := client.BuildSellTx(ctx, lasersell.BuildSellTxRequest{ Mint: "TOKEN_MINT", UserPubkey: "WALLET", AmountTokens: 1_000_000, Output: lasersell.SellOutputSOL, SlippageBps: 2_000, PartnerFeeRecipient: lasersell.Ptr("FEE_WALLET_PUBKEY"), PartnerFeeBps: lasersell.Ptr(uint16(25)), }) ``` ### Buy with Flat Lamports Fee ```typescript TypeScript theme={null} const request: BuildBuyTxRequest = { mint: "TOKEN_MINT", user_pubkey: "WALLET", amount: 0.1, // 0.1 SOL slippage_bps: 2_000, partner_fee_recipient: "FEE_WALLET_PUBKEY", partner_fee_lamports: 5_000_000, // 0.005 SOL }; const response = await client.buildBuyTx(request); ``` ```python Python theme={null} request = BuildBuyTxRequest( mint="TOKEN_MINT", user_pubkey="WALLET", amount=0.1, # 0.1 SOL slippage_bps=2_000, partner_fee_recipient="FEE_WALLET_PUBKEY", partner_fee_lamports=5_000_000, ) response = await client.build_buy_tx(request) ``` ```rust Rust theme={null} let request = BuildBuyTxRequest { mint: "TOKEN_MINT".into(), user_pubkey: "WALLET".into(), amount: Some(0.1), // 0.1 SOL slippage_bps: 2_000, partner_fee_recipient: Some("FEE_WALLET_PUBKEY".into()), partner_fee_lamports: Some(5_000_000), ..Default::default() }; let response = client.build_buy_tx(&request).await?; ``` ```go Go theme={null} resp, err := client.BuildBuyTx(ctx, lasersell.BuildBuyTxRequest{ Mint: "TOKEN_MINT", UserPubkey: "WALLET", Amount: lasersell.Ptr(0.1), // 0.1 SOL SlippageBps: 2_000, PartnerFeeRecipient: lasersell.Ptr("FEE_WALLET_PUBKEY"), PartnerFeeLamports: lasersell.Ptr(uint64(5_000_000)), }) ``` ## Enterprise: Custom Monetization For Enterprise partners processing significant volume, we offer custom monetization arrangements that go beyond the standard partner fee system. These are structured on a per-partner basis and can include: * **Custom revenue sharing** routed on-chain as part of every transaction your users execute through LaserSell. * **Custom split configurations** tailored to your business model and volume. All enterprise monetization is enforced atomically on-chain, so payouts are transparent, verifiable, and require no manual reconciliation. To discuss a custom arrangement, contact [sales@lasersell.io](mailto:sales@lasersell.io). ## Important Notes * Partner fees are available on the **Advanced** and **Enterprise** tiers. See [Rate Limits and Tiers](/api/reference/rate-limits). * The `partner_fee_recipient` wallet must be a valid base58 Solana public key. * The recipient does not need to have an existing account; the fee instruction will create one if necessary. * Setting both `partner_fee_bps` and `partner_fee_lamports` on the same request will result in a `400` error. * Partner fees are deducted from the swap output and routed atomically within the same transaction. # Rate Limits and Tiers Source: https://docs.lasersell.io/api/reference/rate-limits Understand tier capacity, the limits object from hello_ok, and how to handle 429 responses. ## Pricing Tiers LaserSell offers four tiers. Each tier determines your rate limits for both the REST API and the Exit Intelligence Stream. | Tier | Name | Price | | ---- | -------------------- | -------- | | 0 | Personal | Free | | 1 | Professional | \$99/mo | | 2 | Advanced Exit Engine | \$249/mo | | 3 | Enterprise | Custom | ## Exit Intelligence Stream Limits by Tier | Limit | Personal (Free) | Professional | Advanced | Enterprise | | ------------------------------------------------------------------- | --------------- | ------------ | -------- | ---------- | | Wallets per session | 1 | 5 | 200 | Custom | | Positions per wallet | 2 | 100 | 100 | Custom | | Positions per session | 2 | 500 | 20,000 | Custom | | Sessions per API key | 1 | 1 | 1 | Custom | | High priority buffer (`hi_capacity`) | 256 | 256 | 256 | Custom | | PnL flush interval (`pnl_flush_ms`) | 100 ms | 100 ms | 100 ms | Custom | | Inactivity timeout | 45 min | None | None | None | | [Liquidity snapshots](/api/stream/server-events#liquidity_snapshot) | No | Yes | Yes | Yes | | Partner fees | No | No | Yes | Yes | ## REST API Rate Limits The REST API (`/v1/sell`, `/v1/buy`) enforces a per key request limit using a sliding window. | Tier | Requests per minute | | --------------- | ------------------- | | Personal (Free) | 60 | | Professional | 60 | | Advanced | 60 | | Enterprise | Custom | ## Reading Your Limits on Connect When you connect to the Exit Intelligence Stream, the `hello_ok` message includes a `limits` object that reflects your API key's tier: ```json theme={null} { "type": "hello_ok", "session_id": 42, "server_time_ms": 1706000000000, "limits": { "hi_capacity": 256, "pnl_flush_ms": 100, "max_positions_per_session": 500, "max_wallets_per_session": 5, "max_positions_per_wallet": 100, "max_sessions_per_api_key": 1 } } ``` ## `LimitsMsg` Schema | Field | Type | Description | | --------------------------- | -------- | ---------------------------------------------------------- | | `hi_capacity` | `number` | Maximum high priority messages the server will buffer. | | `pnl_flush_ms` | `number` | Interval in milliseconds between `pnl_update` flushes. | | `max_positions_per_session` | `number` | Maximum positions tracked in a single session. | | `max_wallets_per_session` | `number` | Maximum wallets that can be monitored in a single session. | | `max_positions_per_wallet` | `number` | Maximum positions tracked per wallet. | | `max_sessions_per_api_key` | `number` | Maximum concurrent stream sessions per API key. | ## Reading Limits in Code `StreamSession.connect()` consumes the `hello_ok` message internally during the handshake. To read the limits, use the low-level `StreamClient` directly: ```typescript TypeScript theme={null} const client = new StreamClient(apiKey); const connection = await client.connect(configure); const [sender, receiver] = connection.split(); const helloOk = await receiver.recv(); if (helloOk?.type === "hello_ok") { const limits = helloOk.limits; console.log("Max positions:", limits.max_positions_per_session); console.log("Max wallets:", limits.max_wallets_per_session); console.log("PnL interval:", limits.pnl_flush_ms, "ms"); } ``` ```python Python theme={null} client = StreamClient("YOUR_API_KEY") connection = await client.connect(configure) sender, receiver = connection.split() msg = await receiver.recv() if msg and msg.get("type") == "hello_ok": limits = msg["limits"] print("Max positions:", limits["max_positions_per_session"]) print("Max wallets:", limits["max_wallets_per_session"]) ``` ```rust Rust theme={null} let client = StreamClient::new(SecretString::new(api_key)); let connection = client.connect(configure).await?; let (sender, mut receiver) = connection.split(); if let Some(ServerMessage::HelloOk { limits, .. }) = receiver.recv().await { println!("Max positions: {}", limits.max_positions_per_session); println!("Max wallets: {}", limits.max_wallets_per_session); println!("PnL interval: {} ms", limits.pnl_flush_ms); } ``` ```go Go theme={null} client := stream.NewStreamClient(apiKey) connection, err := client.Connect(ctx, configure) sender, receiver := connection.Split() msg, err := receiver.Recv(ctx) if err == nil { if helloOk, ok := msg.(stream.HelloOkServerMessage); ok { fmt.Println("Max positions:", helloOk.Limits.MaxPositionsPerSession) fmt.Println("Max wallets:", helloOk.Limits.MaxWalletsPerSession) fmt.Println("PnL interval:", helloOk.Limits.PnlFlushMs, "ms") } } ``` ## Handling 429 Responses When you exceed the REST API rate limit, the server responds with HTTP `429 Too Many Requests`. The SDKs automatically retry `429` responses using the built in retry policy. If you see persistent 429 errors: 1. **Check your request frequency.** Reduce the rate of API calls. 2. **Batch requests.** If building multiple transactions, space them out. 3. **Increase retry backoff.** Configure a longer backoff to give the server time to recover. See [Error Handling](/api/exit-api/error-handling) for details on customizing the retry policy. ## Exit Intelligence Stream Capacity If you exceed the Exit Intelligence Stream's capacity limits (e.g., too many positions or wallets), the server sends an `error` message: ```json theme={null} { "type": "error", "code": "capacity_exceeded", "message": "max_positions_per_session limit reached" } ``` The stream connection remains open. Remove positions with `close_position` or reduce wallets with `update_wallets` to free capacity. ## Best Practices * **Read limits on connect.** Use the `limits` object to validate your configuration before sending positions. * **Monitor for capacity errors.** Log and alert on `error` messages with capacity related codes. * **Use lane splitting (TypeScript).** For high position counts, use `connectLanes()` to prevent `pnl_update` messages from blocking time sensitive events. * **Respect 429 responses.** Do not retry immediately; use exponential backoff. # Supported DEXs Source: https://docs.lasersell.io/api/reference/supported-dexs Protocols, market types, graduation support, and launchpad coverage for LaserSell swap routing. ## Supported Protocols LaserSell routes swaps through multiple Solana DEX protocols. The `market_type` field in `MarketContextMsg` identifies which protocol is being used for a given token. | `market_type` | Protocol | Type | Description | | ------------------- | ----------------- | --------------- | ---------------------------------------------- | | `pump_fun` | Pump.fun | Bonding curve | Original Pump.fun bonding curve. | | `pump_swap` | PumpSwap | AMM | Graduated Pump.fun tokens on the PumpSwap AMM. | | `meteora_dbc` | Meteora DBC | Dynamic bonding | Meteora dynamic bonding curve pools. | | `meteora_damm_v2` | Meteora DAMM V2 | AMM | Meteora dynamic AMM V2 pools. | | `raydium_launchpad` | Raydium Launchpad | Bonding curve | Raydium launchpad bonding curve tokens. | | `raydium_cpmm` | Raydium CPMM | AMM | Raydium constant product market maker pools. | ## Bonding Curves vs AMM Pools **Bonding curve** protocols (`pump_fun`, `meteora_dbc`, `raydium_launchpad`) use a mathematical curve to determine price. Tokens on bonding curves may later **graduate** to a full AMM pool. **AMM** protocols (`pump_swap`, `meteora_damm_v2`, `raydium_cpmm`) use constant product or dynamic liquidity pools. These offer deeper liquidity and standard swap mechanics. ## Graduation Some tokens launch on a bonding curve and later "graduate" to an AMM when certain conditions are met (usually a market cap threshold). LaserSell detects graduation events and can automatically sell on graduation if `sell_on_graduation: true` is set in your strategy. | Source Curve | Graduates To | | ------------------- | ----------------- | | `pump_fun` | `pump_swap` | | `meteora_dbc` | `meteora_damm_v2` | | `raydium_launchpad` | `raydium_cpmm` | When graduation occurs, the Exit Intelligence Stream updates the `market_context` and continues tracking seamlessly. ## Launchpad Coverage LaserSell monitors all major Solana token launchpads: * **Pump.fun**: The most popular memecoin launchpad. Tokens start on a bonding curve and graduate to PumpSwap. * **Meteora**: Dynamic bonding curves that graduate to DAMM V2 pools. * **Raydium Launchpad**: Raydium's native launchpad with graduation to CPMM pools. New protocols are added as they gain adoption on Solana. The `market_type` enum will expand accordingly. ## Using Market Context The `market_context` is included in `position_opened` and `exit_signal_with_tx` events. You can also pass it in a `BuildSellTxRequest` to skip server side resolution: ```json theme={null} { "market_type": "pump_swap", "pumpswap": { "pool": "PoolAddress..." } } ``` See [Market Context](/api/reference/market-context) for the full nested schema per protocol. # Go SDK Source: https://docs.lasersell.io/api/sdk/go Install, configure, and use the LaserSell Go SDK with context.Context patterns for building transactions and streaming positions. ## Installation ```bash theme={null} go get github.com/lasersell/lasersell-sdk/go ``` ## Packages | Package | Purpose | | ---------------------------------------------- | ------------------------------------------------------------------ | | `github.com/lasersell/lasersell-sdk/go` | `ExitAPIClient`, `SendTarget`, `SignUnsignedTx`, `SendTransaction` | | `github.com/lasersell/lasersell-sdk/go/stream` | `StreamClient`, `StreamSession`, `StreamConfigure`, message types | ## API Client All methods accept a `context.Context` for cancellation and timeouts. ```go theme={null} package main import ( "context" "fmt" "log" lasersell "github.com/lasersell/lasersell-sdk/go" ) func main() { ctx := context.Background() client := lasersell.NewExitAPIClientWithAPIKey("YOUR_API_KEY") // Build sell sellResp, err := client.BuildSellTx(ctx, lasersell.BuildSellTxRequest{ Mint: "TOKEN_MINT", UserPubkey: "WALLET", AmountTokens: 1_000_000, Output: lasersell.SellOutputSOL, SlippageBps: 2_000, }) if err != nil { log.Fatal(err) } fmt.Println("Sell tx:", sellResp.Tx) // Build buy buyResp, err := client.BuildBuyTx(ctx, lasersell.BuildBuyTxRequest{ Mint: "TOKEN_MINT", UserPubkey: "WALLET", AmountInTotal: 100_000_000, SlippageBps: 2_000, }) if err != nil { log.Fatal(err) } fmt.Println("Buy tx:", buyResp.Tx) } // Helper functions for pointer fields (not part of the SDK) func intPtr(v int) *int { return &v } func strPtr(v string) *string { return &v } ``` Optional fields in the Go SDK use pointer types (`*int`, `*string`). The `intPtr` and `strPtr` helpers above are not part of the SDK. Define them in your own code or use inline expressions like `&[]int{2000}[0]`. ### Custom Options ```go theme={null} opts := lasersell.ExitAPIClientOptions{ ConnectTimeout: 500 * time.Millisecond, AttemptTimeout: 2 * time.Second, RetryPolicy: lasersell.RetryPolicy{ MaxAttempts: 3, InitialBackoff: 50 * time.Millisecond, MaxBackoff: 200 * time.Millisecond, Jitter: 50 * time.Millisecond, }, } client := lasersell.NewExitAPIClientWithOptions("YOUR_API_KEY", opts) ``` ## Exit Intelligence Stream Session ```go theme={null} package main import ( "context" "errors" "fmt" "io" "log" "github.com/gagliardetto/solana-go" lasersell "github.com/lasersell/lasersell-sdk/go" "github.com/lasersell/lasersell-sdk/go/stream" ) func main() { ctx := context.Background() privateKey, err := solana.PrivateKeyFromSolanaKeygenFile("./keypair.json") if err != nil { log.Fatal(err) } client := stream.NewStreamClient("YOUR_API_KEY") sendMode := "helius_sender" tipLamports := uint64(1000) session, err := stream.ConnectSession(ctx, client, stream.StreamConfigure{ WalletPubkeys: []string{"WALLET_PUBKEY"}, Strategy: stream.StrategyConfigMsg{ TargetProfitPct: 5.0, StopLossPct: 1.5, }, DeadlineTimeoutSec: 45, SendMode: &sendMode, TipLamports: &tipLamports, }) if err != nil { log.Fatal(err) } for { event, err := session.Recv(ctx) if errors.Is(err, io.EOF) { break } if err != nil { log.Fatal(err) } switch msg := event.Message.(type) { case stream.PositionOpenedServerMessage: fmt.Printf("New position: %s\n", msg.Mint) case stream.ExitSignalWithTxServerMessage: signedTx, err := lasersell.SignUnsignedTx(msg.UnsignedTxB64, privateKey) if err != nil { log.Printf("Sign error: %v", err) continue } sig, err := lasersell.SendTransaction( ctx, nil, lasersell.SendTargetHeliusSender(), signedTx, ) if err != nil { log.Printf("Send error: %v", err) continue } fmt.Printf("Exit submitted: %s\n", sig) case stream.ErrorServerMessage: log.Printf("Stream error: code=%s message=%s", msg.Code, msg.Message) } } } ``` ## Liquidity Snapshots and Partial Sells **Tier 1+ only.** Requires a Professional or Advanced subscription. See the [announcement](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for full details. `StreamSession` caches the latest liquidity snapshot per position. Query slippage bands, maximum sellable amounts, and liquidity trends: ```go theme={null} bands := session.GetSlippageBands(positionID) maxTokens := session.GetMaxSellAtSlippage(positionID, 500) // 5% slippage trend := session.GetLiquidityTrend(positionID) // "growing" | "stable" | "draining" ``` Use `BuildPartialSellTx()` to sell a portion of a position based on slippage data: ```go theme={null} maxTokens := session.GetMaxSellAtSlippage(positionID, 500) if maxTokens != nil { response, err := client.BuildPartialSellTx(ctx, handle, *maxTokens, 500, lasersell.SellOutputSOL) } ``` ## Mirror Trading Configure watch wallets to mirror trades from other wallets. When a watched wallet opens a position on a supported market, the stream sends a `MirrorBuySignalServerMessage` with an unsigned transaction for your wallet to execute. ```go theme={null} package main import ( "context" "errors" "fmt" "io" "log" "github.com/gagliardetto/solana-go" lasersell "github.com/lasersell/lasersell-sdk/go" "github.com/lasersell/lasersell-sdk/go/stream" ) func main() { ctx := context.Background() privateKey, err := solana.PrivateKeyFromSolanaKeygenFile("./keypair.json") if err != nil { log.Fatal(err) } walletPubkey := "YOUR_WALLET_PUBKEY" sendMode := "helius_sender" tipLamports := uint64(1000) session, err := stream.ConnectSession(ctx, stream.NewStreamClient("YOUR_API_KEY"), stream.StreamConfigure{ WalletPubkeys: []string{walletPubkey}, Strategy: stream.StrategyConfigMsg{ TargetProfitPct: 50.0, StopLossPct: 25.0, }, DeadlineTimeoutSec: 45, SendMode: &sendMode, TipLamports: &tipLamports, WatchWallets: []stream.WatchWalletConfig{ { Pubkey: "WatchedWalletPubkey...", AutoBuy: &stream.AutoBuyConfig{ WalletPubkey: walletPubkey, AmountQuoteUnits: intPtr(100_000_000), }, MirrorSell: false, }, }, MirrorConfig: &stream.MirrorConfig{ MaxPositionsPerWallet: 1, CooldownSec: 30, MaxActiveSOL: 5.0, BuySlippageBps: 2500, }, }) if err != nil { log.Fatal(err) } for { event, err := session.Recv(ctx) if errors.Is(err, io.EOF) { break } if err != nil { log.Fatal(err) } switch msg := event.Message.(type) { case stream.MirrorBuySignalServerMessage: signedTx, err := lasersell.SignUnsignedTx(msg.UnsignedTxB64, privateKey) if err != nil { log.Printf("Sign error: %v", err) continue } sig, err := lasersell.SendTransaction( ctx, nil, lasersell.SendTargetHeliusSender(), signedTx, ) if err != nil { log.Printf("Send error: %v", err) continue } session.Sender().MirrorBuyResult(msg.Mint, true) fmt.Printf("Mirror buy submitted: %s\n", sig) case stream.MirrorBuyFailedServerMessage: log.Printf("Mirror buy failed: %s", msg.Reason) case stream.ExitSignalWithTxServerMessage: signedTx, err := lasersell.SignUnsignedTx(msg.UnsignedTxB64, privateKey) if err != nil { log.Printf("Sign error: %v", err) continue } sig, err := lasersell.SendTransaction( ctx, nil, lasersell.SendTargetHeliusSender(), signedTx, ) if err != nil { log.Printf("Send error: %v", err) continue } fmt.Printf("Exit submitted: %s\n", sig) } } } func intPtr(v int) *int { return &v } ``` The `WatchWallets` slice specifies which wallets to mirror. Each `WatchWalletConfig` includes an `AutoBuy` configuration that controls how much to spend when mirroring a trade, and a `MirrorSell` flag that determines whether to also mirror sell transactions. The `MirrorConfig` struct sets global limits: `MaxPositionsPerWallet` caps concurrent mirrored positions, `CooldownSec` enforces a delay between mirror buys, `MaxActiveSOL` limits total SOL exposure, and `BuySlippageBps` sets the slippage tolerance for mirror buy transactions. ## Transaction Helpers The second parameter to `SendTransaction` is an `*http.Client`. Pass `nil` to use the SDK's default HTTP client. ```go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" // Sign signedTx, err := lasersell.SignUnsignedTx(unsignedTxB64, privateKey) // Send via Helius Sender (nil = use default HTTP client) sig, err := lasersell.SendTransaction(ctx, nil, lasersell.SendTargetHeliusSender(), signedTx) // Send via custom RPC sig, err = lasersell.SendTransaction(ctx, nil, lasersell.SendTargetRpc("https://rpc.example.com"), signedTx) // Send via Astralane sig, err = lasersell.SendTransaction(ctx, nil, lasersell.SendTargetAstralane("KEY", "fr"), signedTx) // With a custom HTTP client customClient := &http.Client{Timeout: 5 * time.Second} sig, err = lasersell.SendTransaction(ctx, customClient, lasersell.SendTargetHeliusSender(), signedTx) ``` ## Error Handling ```go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" resp, err := client.BuildSellTx(ctx, request) if err != nil { var apiErr *lasersell.ExitAPIError if errors.As(err, &apiErr) { fmt.Println("Kind:", apiErr.Kind) fmt.Println("Retryable:", apiErr.IsRetryable()) fmt.Println("Status:", apiErr.Status) } } ``` ## `context.Context` Patterns All Go SDK methods accept `context.Context` as their first argument. Use this to control: * **Timeouts**: `context.WithTimeout(ctx, 5*time.Second)` * **Cancellation**: `context.WithCancel(ctx)` for graceful shutdown * **Deadlines**: `context.WithDeadline(ctx, time.Now().Add(30*time.Second))` ```go theme={null} ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() resp, err := client.BuildSellTx(ctx, request) ``` ## Complete Example See the [Quickstart](/api/quickstart) for a full working example. # Python SDK Source: https://docs.lasersell.io/api/sdk/python Install, configure, and use the LaserSell Python SDK with async patterns for building transactions and streaming positions. ## Installation ```bash theme={null} pip install lasersell-sdk ``` Optional extras for additional features: ```bash theme={null} # Transaction signing (requires solders) pip install lasersell-sdk[tx] # WebSocket stream (requires websockets) pip install lasersell-sdk[stream] # Both pip install lasersell-sdk[tx,stream] ``` ## Modules | Import Path | Purpose | | ------------------------------ | --------------------------------------------------------------------- | | `lasersell_sdk.exit_api` | `ExitApiClient`, request/response types, `ExitApiError` | | `lasersell_sdk.tx` | `sign_unsigned_tx`, `send_transaction`, `SendTarget*` classes | | `lasersell_sdk.stream.client` | `StreamClient`, `StreamConfigure`, `StreamSender` | | `lasersell_sdk.stream.session` | `StreamSession`, `StreamEvent`, `PositionHandle` | | `lasersell_sdk.stream.proto` | Message types (`ServerMessage`, `ClientMessage`, `StrategyConfigMsg`) | ## API Client All API client methods are `async` and must be awaited. ```python theme={null} import asyncio from lasersell_sdk.exit_api import ExitApiClient, BuildSellTxRequest, SellOutput async def main(): client = ExitApiClient.with_api_key("YOUR_API_KEY") request = BuildSellTxRequest( mint="TOKEN_MINT", user_pubkey="WALLET", amount_tokens=1_000_000, slippage_bps=2_000, output=SellOutput.SOL, ) response = await client.build_sell_tx(request) print("Unsigned tx:", response.tx) asyncio.run(main()) ``` ### Buy Transactions ```python theme={null} from lasersell_sdk.exit_api import BuildBuyTxRequest request = BuildBuyTxRequest( mint="TOKEN_MINT", user_pubkey="WALLET", amount=0.1, # 0.1 SOL slippage_bps=2_000, ) response = await client.build_buy_tx(request) ``` ### Custom Options ```python theme={null} from lasersell_sdk.exit_api import ExitApiClient, ExitApiClientOptions from lasersell_sdk.retry import RetryPolicy client = ExitApiClient.with_options( "YOUR_API_KEY", ExitApiClientOptions( attempt_timeout_s=2.0, retry_policy=RetryPolicy( max_attempts=3, initial_backoff_ms=50, max_backoff_ms=200, jitter_ms=50, ), ), ) ``` ## Exit Intelligence Stream Session `event.message` is a `TypedDict` union. At runtime, access fields using dict syntax (e.g. `msg["unsigned_tx_b64"]`). ```python theme={null} import asyncio from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx async def main(): client = StreamClient("YOUR_API_KEY") session = await StreamSession.connect( client, StreamConfigure( wallet_pubkeys=["WALLET_PUBKEY"], strategy={ "target_profit_pct": 5.0, "stop_loss_pct": 1.5, }, deadline_timeout_sec=45, send_mode="helius_sender", tip_lamports=1000, ), ) while True: event = await session.recv() if event is None: break if event.type == "position_opened" and event.handle is not None: print(f"New position: {event.handle.mint}") if event.type == "exit_signal_with_tx": msg = event.message # TypedDict, use dict access signed = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed) print(f"Exit submitted: {sig}") asyncio.run(main()) ``` ## Liquidity Snapshots and Partial Sells **Tier 1+ only.** Requires a Professional or Advanced subscription. See the [announcement](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for full details. `StreamSession` caches the latest liquidity snapshot per position. Query slippage bands, maximum sellable amounts, and liquidity trends: ```python theme={null} bands = session.get_slippage_bands(position_id) max_tokens = session.get_max_sell_at_slippage(position_id, 500) # 5% slippage trend = session.get_liquidity_trend(position_id) # "growing" | "stable" | "draining" ``` Use `build_partial_sell_tx()` to sell a portion of a position based on slippage data: ```python theme={null} max_tokens = session.get_max_sell_at_slippage(position_id, 500) if max_tokens is not None: response = await client.build_partial_sell_tx(handle, max_tokens, 500, "SOL") ``` ## Mirror Trading Configure watch wallets to mirror trades from other wallets. When a watched wallet opens a position on a supported market, the stream sends a `mirror_buy_signal` with an unsigned transaction for your wallet to execute. ```python theme={null} import asyncio from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession from lasersell_sdk.tx import SendTargetHeliusSender, send_transaction, sign_unsigned_tx async def main(): wallet_pubkey = "YOUR_WALLET_PUBKEY" client = StreamClient("YOUR_API_KEY") session = await StreamSession.connect( client, StreamConfigure( wallet_pubkeys=[wallet_pubkey], strategy={ "target_profit_pct": 50.0, "stop_loss_pct": 25.0, }, watch_wallets=[ { "pubkey": "WatchedWalletPubkey...", "auto_buy": { "wallet_pubkey": wallet_pubkey, "amount_quote_units": 100_000_000, "amount_usd1_units": None, }, "mirror_sell": False, }, ], mirror_config={ "max_positions_per_wallet": 1, "cooldown_sec": 30, "max_active_sol": 5.0, "buy_slippage_bps": 2500, }, ), ) while True: event = await session.recv() if event is None: break if event.type == "mirror_buy_signal": msg = event.message signed = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed) session.sender().mirror_buy_result(str(msg["mint"]), True) print(f"Mirror buy submitted: {sig}") if event.type == "mirror_buy_failed": msg = event.message print(f"Mirror buy failed: {msg['reason']}") if event.type == "exit_signal_with_tx": msg = event.message signed = sign_unsigned_tx(str(msg["unsigned_tx_b64"]), signer) sig = await send_transaction(SendTargetHeliusSender(), signed) print(f"Exit submitted: {sig}") asyncio.run(main()) ``` The `watch_wallets` list specifies which wallets to mirror. Each entry includes an `auto_buy` configuration that controls how much to spend when mirroring a trade, and a `mirror_sell` flag that determines whether to also mirror sell transactions. The `mirror_config` dict sets global limits: `max_positions_per_wallet` caps concurrent mirrored positions, `cooldown_sec` enforces a delay between mirror buys, `max_active_sol` limits total SOL exposure, and `buy_slippage_bps` sets the slippage tolerance for mirror buy transactions. ## Transaction Helpers ```python theme={null} from lasersell_sdk.tx import ( sign_unsigned_tx, send_transaction, send_transaction_b64_to, encode_signed_tx, SendTargetRpc, SendTargetHeliusSender, SendTargetAstralane, ) # Sign signed_tx = sign_unsigned_tx(unsigned_tx_b64, keypair) # Send signature = await send_transaction(SendTargetHeliusSender(), signed_tx) # Send with custom RPC signature = await send_transaction( SendTargetRpc(url="https://rpc.example.com"), signed_tx, ) # Astralane signature = await send_transaction( SendTargetAstralane(api_key="KEY", region="fr"), signed_tx, ) ``` ## Error Handling ```python theme={null} from lasersell_sdk.exit_api import ExitApiError from lasersell_sdk.tx import TxSubmitError from lasersell_sdk.stream.client import StreamClientError try: response = await client.build_sell_tx(request) except ExitApiError as e: print(f"Kind: {e.kind}, Retryable: {e.is_retryable()}") print(f"Status: {e.status}, Body: {e.body}") except TxSubmitError as e: print(f"Kind: {e.kind}, Target: {e.target}") except StreamClientError as e: print(f"Kind: {e.kind}") ``` ## Loading a Keypair ```python theme={null} import json from pathlib import Path from solders.keypair import Keypair raw = json.loads(Path("./keypair.json").read_text()) keypair = Keypair.from_bytes(bytes(raw)) ``` Requires the `solders` package, which is included in the `[tx]` extra. ## Complete Example See the [Quickstart](/api/quickstart) for a complete build, sign, and submit workflow. # Rust SDK Source: https://docs.lasersell.io/api/sdk/rust Install, configure, and use the LaserSell Rust SDK with async/.await for building transactions and streaming positions. ## Installation ```bash theme={null} cargo add lasersell-sdk cargo add tokio --features full ``` ## Modules | Module | Purpose | | -------------------------------- | --------------------------------------------------------------------- | | `lasersell_sdk::exit_api` | `ExitApiClient`, request/response types, `ExitApiError` | | `lasersell_sdk::tx` | `SendTarget`, `sign_unsigned_tx`, `send_transaction` | | `lasersell_sdk::stream::client` | `StreamClient`, `StreamConfigure`, `StreamConnection`, `StreamSender` | | `lasersell_sdk::stream::session` | `StreamSession`, position tracking | | `lasersell_sdk::stream::proto` | Message types (`ServerMessage`, `ClientMessage`, `StrategyConfigMsg`) | | `lasersell_sdk::retry` | `RetryPolicy` configuration | ## API Client All methods are `async` and return `Result`. ```rust theme={null} use lasersell_sdk::exit_api::{ExitApiClient, BuildSellTxRequest, BuildBuyTxRequest}; #[tokio::main] async fn main() -> anyhow::Result<()> { let client = ExitApiClient::with_api_key("YOUR_API_KEY"); // Build sell transaction let sell_request = BuildSellTxRequest { mint: "TOKEN_MINT".into(), user_pubkey: "WALLET".into(), amount_tokens: 1_000_000, output: SellOutput::Sol, slippage_bps: 2_000, ..Default::default() }; let sell_response = client.build_sell_tx(&sell_request).await?; println!("Sell tx: {}", sell_response.tx); // Build buy transaction let buy_request = BuildBuyTxRequest { mint: "TOKEN_MINT".into(), user_pubkey: "WALLET".into(), amount: Some(0.1), // 0.1 SOL slippage_bps: 2_000, ..Default::default() }; let buy_response = client.build_buy_tx(&buy_request).await?; println!("Buy tx: {}", buy_response.tx); Ok(()) } ``` ### Custom Options ```rust theme={null} use lasersell_sdk::exit_api::{ExitApiClient, ExitApiClientOptions}; use lasersell_sdk::retry::RetryPolicy; use std::time::Duration; let options = ExitApiClientOptions { connect_timeout: Duration::from_millis(500), attempt_timeout: Duration::from_secs(2), retry_policy: RetryPolicy { max_attempts: 3, initial_backoff: Duration::from_millis(50), max_backoff: Duration::from_millis(200), jitter: Duration::from_millis(50), }, }; let client = ExitApiClient::with_options(Some("YOUR_API_KEY"), options); ``` ## Exit Intelligence Stream Session ```rust theme={null} use lasersell_sdk::stream::client::{StreamClient, StreamConfigure}; use lasersell_sdk::stream::session::{StreamSession, StreamEvent}; use lasersell_sdk::stream::proto::StrategyConfigMsg; use lasersell_sdk::tx::{SendTarget, sign_unsigned_tx, send_transaction}; use secrecy::SecretString; use solana_sdk::signature::read_keypair_file; #[tokio::main] async fn main() -> anyhow::Result<()> { let keypair = read_keypair_file("./keypair.json")?; let client = StreamClient::new(SecretString::new(std::env::var("LASERSELL_API_KEY")?)); let session = StreamSession::connect(&client, StreamConfigure { wallet_pubkeys: vec!["WALLET_PUBKEY".into()], strategy: StrategyConfigMsg { target_profit_pct: 5.0, stop_loss_pct: 1.5, trailing_stop_pct: Some(3.0), sell_on_graduation: None, }, deadline_timeout_sec: Some(45), send_mode: Some("helius_sender".to_string()), tip_lamports: Some(1000), }).await?; loop { let event = match session.recv().await { Some(event) => event, None => break, }; match &event { StreamEvent::ExitSignalWithTx { message, .. } => { let http = reqwest::Client::new(); let signed = sign_unsigned_tx(&message.unsigned_tx_b64, &keypair)?; let sig = send_transaction(&http, &SendTarget::HeliusSender, &signed).await?; println!("Exit submitted: {sig}"); } StreamEvent::PositionOpened { handle, .. } => { println!("New position: {}", handle.mint); } _ => {} } } Ok(()) } ``` ## Liquidity Snapshots and Partial Sells **Tier 1+ only.** Requires a Professional or Advanced subscription. See the [announcement](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for full details. `StreamSession` caches the latest liquidity snapshot per position. Query slippage bands, maximum sellable amounts, and liquidity trends: ```rust theme={null} let bands = session.get_slippage_bands(position_id); let max_tokens = session.get_max_sell_at_slippage(position_id, 500); // 5% slippage let trend = session.get_liquidity_trend(position_id); // "growing" | "stable" | "draining" ``` Use `build_partial_sell_tx()` to sell a portion of a position based on slippage data: ```rust theme={null} if let Some(max_tokens) = session.get_max_sell_at_slippage(position_id, 500) { let response = client.build_partial_sell_tx(&handle, max_tokens, 500, "SOL").await?; } ``` ## Mirror Trading Configure watch wallets to mirror trades from other wallets. When a watched wallet opens a position on a supported market, the stream sends a `MirrorBuySignal` event with an unsigned transaction for your wallet to execute. ```rust theme={null} use lasersell_sdk::stream::client::{StreamClient, StreamConfigure}; use lasersell_sdk::stream::session::{StreamSession, StreamEvent}; use lasersell_sdk::stream::proto::{ StrategyConfigMsg, WatchWalletConfig, AutoBuyConfig, MirrorConfig, }; use lasersell_sdk::tx::{SendTarget, sign_unsigned_tx, send_transaction}; use secrecy::SecretString; use solana_sdk::signature::read_keypair_file; #[tokio::main] async fn main() -> anyhow::Result<()> { let keypair = read_keypair_file("./keypair.json")?; let wallet_pubkey = "YOUR_WALLET_PUBKEY".to_string(); let client = StreamClient::new(SecretString::new(std::env::var("LASERSELL_API_KEY")?)); let session = StreamSession::connect(&client, StreamConfigure { wallet_pubkeys: vec![wallet_pubkey.clone()], strategy: StrategyConfigMsg { target_profit_pct: 50.0, stop_loss_pct: 25.0, trailing_stop_pct: None, sell_on_graduation: None, }, deadline_timeout_sec: Some(45), send_mode: Some("helius_sender".to_string()), tip_lamports: Some(1000), watch_wallets: Some(vec![ WatchWalletConfig { pubkey: "WatchedWalletPubkey...".into(), auto_buy: Some(AutoBuyConfig { wallet_pubkey: wallet_pubkey.clone(), amount_quote_units: Some(100_000_000), amount_usd1_units: None, }), mirror_sell: false, }, ]), mirror_config: Some(MirrorConfig { max_positions_per_wallet: 1, cooldown_sec: 30, max_active_sol: 5.0, buy_slippage_bps: 2500, }), }).await?; let http = reqwest::Client::new(); loop { let event = match session.recv().await { Some(event) => event, None => break, }; match &event { StreamEvent::MirrorBuySignal { message, .. } => { let signed = sign_unsigned_tx(&message.unsigned_tx_b64, &keypair)?; let sig = send_transaction(&http, &SendTarget::HeliusSender, &signed).await?; session.sender().mirror_buy_result(&message.mint, true); println!("Mirror buy submitted: {sig}"); } StreamEvent::MirrorBuyFailed { message, .. } => { eprintln!("Mirror buy failed: {}", message.reason); } StreamEvent::ExitSignalWithTx { message, .. } => { let signed = sign_unsigned_tx(&message.unsigned_tx_b64, &keypair)?; let sig = send_transaction(&http, &SendTarget::HeliusSender, &signed).await?; println!("Exit submitted: {sig}"); } _ => {} } } Ok(()) } ``` The `watch_wallets` field specifies which wallets to mirror. Each `WatchWalletConfig` includes an `auto_buy` configuration that controls how much to spend when mirroring a trade, and a `mirror_sell` flag that determines whether to also mirror sell transactions. The `MirrorConfig` struct sets global limits: `max_positions_per_wallet` caps concurrent mirrored positions, `cooldown_sec` enforces a delay between mirror buys, `max_active_sol` limits total SOL exposure, and `buy_slippage_bps` sets the slippage tolerance for mirror buy transactions. ## Transaction Helpers ```rust theme={null} use lasersell_sdk::tx::{ SendTarget, sign_unsigned_tx, send_transaction, astralane_iris_url, MAINNET_BETA_RPC_URL, }; // Sign let signed_tx = sign_unsigned_tx(&unsigned_tx_b64, &keypair)?; // Create an HTTP client (reuse across calls) let http = reqwest::Client::new(); // Send via Helius Sender let sig = send_transaction(&http, &SendTarget::HeliusSender, &signed_tx).await?; // Send via custom RPC let sig = send_transaction( &http, &SendTarget::Rpc { url: "https://rpc.example.com".into() }, &signed_tx, ).await?; // Send via Astralane let sig = send_transaction( &http, &SendTarget::Astralane { api_key: "KEY".into(), region: Some("fr".into()), }, &signed_tx, ).await?; ``` ## Error Handling ```rust theme={null} use lasersell_sdk::exit_api::ExitApiError; use lasersell_sdk::tx::TxSubmitError; match client.build_sell_tx(&request).await { Ok(response) => println!("tx: {}", response.tx), Err(e) => { eprintln!("Error kind: {:?}", e.kind()); eprintln!("Retryable: {}", e.is_retryable()); } } ``` ## Complete Example See the [Quickstart](/api/quickstart) for a full working example. # TypeScript SDK Source: https://docs.lasersell.io/api/sdk/typescript Install, configure, and use the LaserSell TypeScript SDK for building transactions, streaming positions, and submitting trades. ## Installation ```bash theme={null} npm install @lasersell/lasersell-sdk ``` The package ships with TypeScript declarations and supports both ESM and CommonJS. ## Modules | Import | Purpose | | ---------------------------------------------------------------- | -------------------------------------------- | | `ExitApiClient` | Build unsigned buy/sell transactions. | | `StreamClient` | Low level WebSocket stream client. | | `StreamSession` | High level stream with position tracking. | | `signUnsignedTx` | Sign an unsigned base64 transaction. | | `signUnsignedTxB64Fast` | Fast path in place signing (returns base64). | | `sendTransaction` | Submit a signed transaction. | | `sendTargetRpc`, `sendTargetHeliusSender`, `sendTargetAstralane` | Send target constructors. | ## API Client ```typescript theme={null} import { ExitApiClient, type BuildSellTxRequest, type BuildBuyTxRequest, } from "@lasersell/lasersell-sdk"; const client = ExitApiClient.withApiKey("YOUR_API_KEY"); // Build sell const sellRequest: BuildSellTxRequest = { mint: "TOKEN_MINT", user_pubkey: "WALLET", amount_tokens: 1_000_000, slippage_bps: 2_000, output: "SOL", }; const sellResponse = await client.buildSellTx(sellRequest); const txB64 = sellResponse.tx; // Build buy const buyRequest: BuildBuyTxRequest = { mint: "TOKEN_MINT", user_pubkey: "WALLET", amount: 0.1, // 0.1 SOL slippage_bps: 2_000, }; const buyResponse = await client.buildBuyTx(buyRequest); ``` ### Client Options ```typescript theme={null} const client = ExitApiClient.withOptions("YOUR_API_KEY", { connect_timeout_ms: 500, attempt_timeout_ms: 2000, retry_policy: { max_attempts: 3, initial_backoff_ms: 50, max_backoff_ms: 200, jitter_ms: 50, }, }); ``` ## Exit Intelligence Stream Session ```typescript theme={null} import { StreamClient, StreamSession, sendTransaction, sendTargetHeliusSender, signUnsignedTx, } from "@lasersell/lasersell-sdk"; const client = new StreamClient("YOUR_API_KEY"); const session = await StreamSession.connect(client, { wallet_pubkeys: ["WALLET_PUBKEY"], strategy: { target_profit_pct: 5, stop_loss_pct: 1.5, trailing_stop_pct: 3, }, deadline_timeout_sec: 45, send_mode: "helius_sender", tip_lamports: 1000, }); while (true) { const event = await session.recv(); if (event === null) break; switch (event.type) { case "position_opened": console.log("New position:", event.handle.mint); break; case "exit_signal_with_tx": if (event.message.type === "exit_signal_with_tx") { const signed = signUnsignedTx(event.message.unsigned_tx_b64, keypair); const sig = await sendTransaction(sendTargetHeliusSender(), signed); console.log("Exit submitted:", sig); } break; case "pnl_update": if (event.message.type === "pnl_update") { console.log( `PnL position=${event.message.position_id} profit=${event.message.profit_units}` ); } break; } } ``` ### Lane Splitting For high throughput scenarios, split the Exit Intelligence Stream into priority lanes: ```typescript theme={null} const connection = await client.connectLanes(configure, { lowPriorityCapacity: 512, }); const [sender, highRx, lowRx] = connection.split(); // High priority: exit signals, position events for await (const msg of highRx) { // Handle critical messages } ``` ## Liquidity Snapshots and Partial Sells **Tier 1+ only.** Requires a Professional or Advanced subscription. See the [announcement](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for full details. `StreamSession` caches the latest liquidity snapshot per position. Query slippage bands, maximum sellable amounts, and liquidity trends: ```typescript theme={null} const bands = session.getSlippageBands(positionId); const maxTokens = session.getMaxSellAtSlippage(positionId, 500); // 5% slippage const trend = session.getLiquidityTrend(positionId); // "growing" | "stable" | "draining" ``` Use `buildPartialSellTx()` to sell a portion of a position based on slippage data: ```typescript theme={null} const maxTokens = session.getMaxSellAtSlippage(positionId, 500); if (maxTokens !== undefined) { const response = await client.buildPartialSellTx(handle, maxTokens, 500, "SOL"); } ``` ## Mirror Trading Configure watch wallets to mirror trades from other wallets. When a watched wallet opens a position on a supported market, the stream sends a `mirror_buy_signal` with an unsigned transaction for your wallet to execute. ```typescript theme={null} import { StreamClient, StreamSession, sendTransaction, sendTargetHeliusSender, signUnsignedTx, } from "@lasersell/lasersell-sdk"; const walletPubkey = "YOUR_WALLET_PUBKEY"; const client = new StreamClient("YOUR_API_KEY"); const session = await StreamSession.connect(client, { wallet_pubkeys: [walletPubkey], strategy: { target_profit_pct: 50, stop_loss_pct: 25, }, watch_wallets: [ { pubkey: "WatchedWalletPubkey...", auto_buy: { wallet_pubkey: walletPubkey, amount_quote_units: 100_000_000, amount_usd1_units: null, }, mirror_sell: false, }, ], mirror_config: { max_positions_per_wallet: 1, cooldown_sec: 30, max_active_sol: 5.0, buy_slippage_bps: 2500, }, }); while (true) { const event = await session.recv(); if (event === null) break; switch (event.type) { case "mirror_buy_signal": { const { mint, unsigned_tx_b64 } = event.message; const signed = signUnsignedTx(unsigned_tx_b64, keypair); const sig = await sendTransaction(sendTargetHeliusSender(), signed); session.sender().mirrorBuyResult(mint, true); console.log("Mirror buy submitted:", sig); break; } case "mirror_buy_failed": console.log(`Mirror buy failed: ${event.message.reason}`); break; case "exit_signal_with_tx": { if (event.message.type === "exit_signal_with_tx") { const signed = signUnsignedTx(event.message.unsigned_tx_b64, keypair); const sig = await sendTransaction(sendTargetHeliusSender(), signed); console.log("Exit submitted:", sig); } break; } } } ``` The `watch_wallets` array specifies which wallets to mirror. Each entry includes an `auto_buy` configuration that controls how much to spend when mirroring a trade, and a `mirror_sell` flag that determines whether to also mirror sell transactions. The `mirror_config` object sets global limits: `max_positions_per_wallet` caps concurrent mirrored positions, `cooldown_sec` enforces a delay between mirror buys, `max_active_sol` limits total SOL exposure, and `buy_slippage_bps` sets the slippage tolerance for mirror buy transactions. ## Transaction Helpers ```typescript theme={null} import { signUnsignedTx, signUnsignedTxB64Fast, signAndSendUnsignedTxB64, sendTransaction, sendTransactionB64To, encodeSignedTx, sendTargetRpc, sendTargetHeliusSender, sendTargetAstralane, sendTargetDefaultRpc, } from "@lasersell/lasersell-sdk"; // Standard sign const signedTx = signUnsignedTx(unsignedTxB64, keypair); // Fast sign (returns base64 string) const signedB64 = signUnsignedTxB64Fast(unsignedTxB64, keypair); // Sign + send in one step const sig = await signAndSendUnsignedTxB64( sendTargetHeliusSender(), unsignedTxB64, keypair, ); // Send a pre-signed transaction const sig2 = await sendTransaction(sendTargetRpc("https://rpc.example.com"), signedTx); // Send a pre-signed base64 string const sig3 = await sendTransactionB64To(sendTargetHeliusSender(), signedB64); ``` ## Error Handling ```typescript theme={null} import { ExitApiError, TxSubmitError, StreamClientError } from "@lasersell/lasersell-sdk"; try { await client.buildSellTx(request); } catch (error) { if (error instanceof ExitApiError) { // error.kind, error.status, error.body, error.isRetryable() } if (error instanceof TxSubmitError) { // error.kind, error.target, error.status } if (error instanceof StreamClientError) { // error.kind } } ``` ## Complete Example See the [Quickstart](/api/quickstart) for a full working example that builds, signs, and submits a sell transaction. # Client Messages Source: https://docs.lasersell.io/api/stream/client-messages Complete schema and JSON examples for all 9 client message types sent to the Exit Intelligence Stream. ## Message Types | Type | Description | | -------------------------- | ------------------------------------------------ | | `ping` | Latency probe; server responds with `pong`. | | `configure` | Initial session setup with wallets and strategy. | | `update_strategy` | Change strategy parameters mid session. | | `close_position` | Remove a position from tracking. | | `request_exit_signal` | Manually request an exit transaction. | | `update_wallets` | Add or remove wallets mid session. | | `update_position_strategy` | Override strategy for a single position. | | `update_watch_wallets` | Add or update watch wallets for copy trading. | | `mirror_buy_result` | Report outcome of a mirror buy transaction. | ## `ping` ```json theme={null} { "type": "ping", "client_time_ms": 1706000000000 } ``` | Field | Type | Required | Description | | ---------------- | -------- | -------- | ---------------------------------------------------------------------------------------- | | `client_time_ms` | `number` | Yes | Client timestamp in milliseconds. Compare with `pong.server_time_ms` to measure latency. | ## `configure` Sent once after receiving `hello_ok`. Sets the wallets to watch and the strategy to evaluate. ```json theme={null} { "type": "configure", "wallet_pubkeys": [ "WalletPubkey1...", "WalletPubkey2..." ], "strategy": { "target_profit_pct": 5.0, "stop_loss_pct": 1.5, "trailing_stop_pct": 3.0, "sell_on_graduation": false }, "deadline_timeout_sec": 45, "send_mode": "helius_sender", "tip_lamports": 1000, "watch_wallets": [ { "pubkey": "WatchWalletPubkey1..." }, { "pubkey": "WatchWalletPubkey2...", "auto_buy": { "wallet_pubkey": "YourBuyWallet...", "amount_quote_units": 100000000 } } ], "mirror_config": { "max_positions_per_wallet": 1, "cooldown_sec": 30, "skip_creator_tokens": false, "max_active_sol": 5.0, "buy_slippage_bps": 2500, "min_liquidity_sol": null, "max_entry_drift_pct": null, "max_consecutive_losses": null } } ``` | Field | Type | Required | Description | | ---------------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `wallet_pubkeys` | `string[]` | Yes | One or more wallet public keys to monitor. | | `strategy` | `object` | Yes | Strategy configuration. See [Strategy Configuration](/api/stream/strategy-configuration). | | `deadline_timeout_sec` | `number` | No | Seconds of inactivity before the server auto-generates an exit signal. Omit or set to `0` to disable. | | `send_mode` | `string` | No | Transaction submission mode: `"rpc"`, `"helius_sender"`, or `"astralane"`. Default: `"rpc"`. | | `tip_lamports` | `number` | No | Priority fee tip in lamports. Default: `1000` (0.001 SOL). | | `watch_wallets` | `WatchWalletEntryMsg[]` | No | Optional list of external wallets to mirror for copy trading. Each entry has `pubkey` (string) and optional `auto_buy` (AutoBuyConfigMsg). | | `mirror_config` | `MirrorConfigMsg` | No | Optional hardening settings for mirror trading. See [Mirror Config](#mirror-config) below. | ### Mirror Config The `mirror_config` object controls safety limits and behavior for mirror (copy) trading. All fields are optional; the server uses sensible defaults when omitted. | Field | Type | Default | Description | | -------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `max_positions_per_wallet` | `number` | `1` | Maximum concurrent mirrored positions per watched wallet. Additional signals are ignored until an existing position closes. | | `cooldown_sec` | `number` | `30` | Minimum seconds between mirror buys from the same watched wallet. | | `skip_creator_tokens` | `boolean` | `false` | When `true`, ignore buy signals where the watched wallet is the token creator. | | `max_active_sol` | `number` | `5.0` | Maximum total SOL exposure across all active mirrored positions. New signals are skipped when this cap would be exceeded. | | `buy_slippage_bps` | `number` | `2500` | Slippage tolerance in basis points for mirror buy transactions (2500 = 25%). | | `min_liquidity_sol` | `number` | `null` | Minimum pool liquidity in SOL required to execute a mirror buy. `null` to disable. | | `max_entry_drift_pct` | `number` | `null` | Maximum allowed price drift (%) from the watched wallet's entry price. `null` to disable. | | `max_consecutive_losses` | `number` | `null` | After this many consecutive losing trades from a watched wallet, the wallet is auto-disabled and a `mirror_wallet_auto_disabled` event is sent. `null` to disable. | The SDKs send `configure` automatically when you call `StreamClient.connect()` or `StreamSession.connect()`. All wallets in `wallet_pubkeys` must be [registered](/api/exit-api/wallets) to your account before connecting. The stream will reject unregistered wallets with error code `wallet_not_registered`. ## `update_strategy` Update the strategy without reconnecting. Takes effect immediately for all tracked positions. ```json theme={null} { "type": "update_strategy", "strategy": { "target_profit_pct": 15.0, "stop_loss_pct": 3.0, "trailing_stop_pct": 7.0, "sell_on_graduation": true } } ``` | Field | Type | Required | Description | | ---------- | -------- | -------- | ---------------------- | | `strategy` | `object` | Yes | New strategy to apply. | ```typescript TypeScript theme={null} session.updateStrategy({ target_profit_pct: 15, stop_loss_pct: 3, trailing_stop_pct: 7, }); ``` ```python Python theme={null} session.sender().update_strategy({ "target_profit_pct": 15.0, "stop_loss_pct": 3.0, "trailing_stop_pct": 7.0, }) ``` ```rust Rust theme={null} session.update_strategy(StrategyConfigMsg { target_profit_pct: 15.0, stop_loss_pct: 3.0, trailing_stop_pct: Some(7.0), sell_on_graduation: None, }, None); ``` ```go Go theme={null} session.Sender().UpdateStrategy(stream.StrategyConfigMsg{ TargetProfitPct: 15.0, StopLossPct: 3.0, TrailingStopPct: 7.0, }) ``` ## `close_position` Remove a position from tracking. The server will stop monitoring PnL and will not generate exit signals for it. Identify the position by either `position_id` or `token_account`. ```json theme={null} { "type": "close_position", "position_id": 1 } ``` Or by token account: ```json theme={null} { "type": "close_position", "token_account": "ATA..." } ``` | Field | Type | Required | Description | | --------------- | -------- | -------- | -------------------------------------------------------------- | | `position_id` | `number` | No\* | Position ID to close. Mutually exclusive with `token_account`. | | `token_account` | `string` | No\* | Token account address. Mutually exclusive with `position_id`. | \*One of `position_id` or `token_account` is required. ```typescript TypeScript theme={null} // By handle (from StreamSession) session.close(handle); // By position ID (via sender) session.sender().closeById(1); // By token account session.sender().closePosition("ATA..."); ``` ```python Python theme={null} # By position ID session.sender().close_by_id(1) # By token account session.sender().close_position("ATA...") ``` ```rust Rust theme={null} // By position handle session.close(handle); // By position ID session.sender().close_by_id(1); // By token account session.sender().close_position("ATA..."); ``` ```go Go theme={null} // By position ID sender.CloseByID(1) // By token account sender.ClosePosition("ATA...") ``` ## `request_exit_signal` Manually request the server to generate an exit transaction for a position, regardless of whether the strategy thresholds have been met. ```json theme={null} { "type": "request_exit_signal", "position_id": 1, "slippage_bps": 3000 } ``` Or by token account: ```json theme={null} { "type": "request_exit_signal", "token_account": "ATA...", "slippage_bps": 3000 } ``` | Field | Type | Required | Description | | --------------- | -------- | -------- | ----------------------------------------------------- | | `position_id` | `number` | No\* | Position ID. Mutually exclusive with `token_account`. | | `token_account` | `string` | No\* | Token account. Mutually exclusive with `position_id`. | | `slippage_bps` | `number` | No | Override slippage for this exit transaction. | \*One of `position_id` or `token_account` is required. The server responds with an `exit_signal_with_tx` containing the unsigned transaction. ```typescript TypeScript theme={null} // Using StreamSession session.requestExitSignal(handle, 3000); // Using sender directly session.sender().requestExitSignal("ATA...", 3000); session.sender().requestExitSignalById(1, 3000); ``` ```python Python theme={null} session.sender().request_exit_signal("ATA...", slippage_bps=3000) session.sender().request_exit_signal_by_id(1, slippage_bps=3000) ``` ```rust Rust theme={null} session.request_exit_signal(handle, Some(3000)); // Using sender directly session.sender().request_exit_signal("ATA...", Some(3000)); session.sender().request_exit_signal_by_id(1, Some(3000)); ``` ```go Go theme={null} sender.RequestExitSignal("ATA...", intPtr(3000)) sender.RequestExitSignalByID(1, intPtr(3000)) ``` ## `update_wallets` Add or remove wallets from the session without reconnecting. The server will start monitoring the new set and stop tracking wallets that are no longer listed. ```json theme={null} { "type": "update_wallets", "wallet_pubkeys": [ "WalletPubkey1...", "WalletPubkey3..." ] } ``` | Field | Type | Required | Description | | ---------------- | ---------- | -------- | ----------------------------------- | | `wallet_pubkeys` | `string[]` | Yes | Updated list of wallets to monitor. | ```typescript TypeScript theme={null} session.sender().updateWallets([ "WalletPubkey1...", "WalletPubkey3...", ]); ``` ```python Python theme={null} session.sender().update_wallets([ "WalletPubkey1...", "WalletPubkey3...", ]) ``` ```rust Rust theme={null} session.sender().update_wallets(vec![ "WalletPubkey1...".into(), "WalletPubkey3...".into(), ]); ``` ```go Go theme={null} sender.UpdateWallets([]string{ "WalletPubkey1...", "WalletPubkey3...", }) ``` ## `update_position_strategy` Override the strategy for a single position without affecting other tracked positions. ```json theme={null} { "type": "update_position_strategy", "position_id": 1, "strategy": { "target_profit_pct": 200.0, "stop_loss_pct": 5.0, "trailing_stop_pct": 10.0 } } ``` | Field | Type | Required | Description | | ------------- | -------- | -------- | ----------------------------------------- | | `position_id` | `number` | Yes | Position to override. | | `strategy` | `object` | Yes | Strategy override for this position only. | ```typescript TypeScript theme={null} session.sender().updatePositionStrategy(positionId, { target_profit_pct: 200, stop_loss_pct: 5, trailing_stop_pct: 10, }); ``` ```python Python theme={null} session.sender().update_position_strategy(position_id, { "target_profit_pct": 200.0, "stop_loss_pct": 5.0, "trailing_stop_pct": 10.0, }) ``` ```rust Rust theme={null} session.sender().update_position_strategy(position_id, StrategyConfigMsg { target_profit_pct: 200.0, stop_loss_pct: 5.0, trailing_stop_pct: 10.0, ..Default::default() })?; ``` ```go Go theme={null} sender.UpdatePositionStrategy(positionID, stream.StrategyConfigMsg{ TargetProfitPct: 200.0, StopLossPct: 5.0, TrailingStopPct: 10.0, }) ``` ## `update_watch_wallets` Add or update watch wallets mid-session for copy trading. Replaces the current watch wallet list. ```json theme={null} { "type": "update_watch_wallets", "watch_wallets": [ { "pubkey": "WatchWalletPubkey1..." }, { "pubkey": "WatchWalletPubkey2...", "auto_buy": { "wallet_pubkey": "YourBuyWallet...", "amount_quote_units": 100000000 } } ] } ``` | Field | Type | Required | Description | | --------------- | ----------------------- | -------- | ---------------------------------- | | `watch_wallets` | `WatchWalletEntryMsg[]` | Yes | Updated list of wallets to mirror. | Each `WatchWalletEntryMsg` has: | Field | Type | Required | Description | | ------------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- | | `pubkey` | `string` | Yes | Public key of the wallet to watch. | | `auto_buy` | `AutoBuyConfigMsg` | No | If set, automatically mirror buys from this wallet. | | `mirror_sell` | `boolean` | No | Reserved for future use. When `true`, mirror sells from this wallet in addition to buys. Default: `false`. Currently unused. | `AutoBuyConfigMsg`: | Field | Type | Required | Description | | -------------------- | -------- | -------- | ------------------------------------------- | | `wallet_pubkey` | `string` | Yes | Your wallet that will execute the buy. | | `amount_quote_units` | `number` | Yes | Amount in quote asset atomic units per buy. | | `amount_usd1_units` | `number` | No | Amount in USD1 atomic units (alternative). | ```typescript TypeScript theme={null} session.sender().updateWatchWallets([ { pubkey: "WatchWalletPubkey1..." }, { pubkey: "WatchWalletPubkey2...", auto_buy: { wallet_pubkey: "YourBuyWallet...", amount_quote_units: 100000000 } }, ]); ``` ```python Python theme={null} session.sender().update_watch_wallets([ {"pubkey": "WatchWalletPubkey1..."}, {"pubkey": "WatchWalletPubkey2...", "auto_buy": {"wallet_pubkey": "YourBuyWallet...", "amount_quote_units": 100000000}}, ]) ``` ```rust Rust theme={null} session.sender().update_watch_wallets(vec![ WatchWalletEntryMsg { pubkey: "WatchWalletPubkey1...".into(), auto_buy: None }, WatchWalletEntryMsg { pubkey: "WatchWalletPubkey2...".into(), auto_buy: Some(AutoBuyConfigMsg { wallet_pubkey: "YourBuyWallet...".into(), amount_quote_units: 100000000, amount_usd1_units: None, }), }, ])?; ``` ```go Go theme={null} sender.UpdateWatchWallets([]stream.WatchWalletEntryMsg{ {Pubkey: "WatchWalletPubkey1..."}, {Pubkey: "WatchWalletPubkey2...", AutoBuy: &stream.AutoBuyConfigMsg{ WalletPubkey: "YourBuyWallet...", AmountQuoteUnits: 100000000, }}, }) ``` ## `mirror_buy_result` Reports the outcome of a mirror buy transaction back to the stream. Send this after you sign and submit (or fail to submit) the transaction from a `mirror_buy_signal` event. The stream uses this feedback to update internal state, track consecutive losses, and manage cooldown timers. ```json theme={null} { "type": "mirror_buy_result", "mint": "TokenMint...", "success": true } ``` | Field | Type | Required | Description | | --------- | --------- | -------- | ----------------------------------------------------------------------------------- | | `mint` | `string` | Yes | Token mint address from the original `mirror_buy_signal`. | | `success` | `boolean` | Yes | `true` if the transaction was signed and submitted successfully, `false` otherwise. | ```typescript TypeScript theme={null} session.sender().mirrorBuyResult("TokenMint...", true); ``` ```python Python theme={null} session.sender().mirror_buy_result("TokenMint...", True) ``` ```rust Rust theme={null} session.sender().mirror_buy_result("TokenMint...", true); ``` ```go Go theme={null} sender.MirrorBuyResult("TokenMint...", true) ``` # Connection Lifecycle Source: https://docs.lasersell.io/api/stream/connection-lifecycle WebSocket handshake, hello_ok, limits, reconnection behavior, ping/pong, and the StreamClient vs StreamSession abstraction for the Exit Intelligence Stream. ## Connection Steps ### 1. Open WebSocket Connect to the Exit Intelligence Stream endpoint with your API key in the `x-api-key` header: ``` wss://stream.lasersell.io/v1/ws ``` ### 2. Receive `hello_ok` Immediately after the connection opens, the server sends a `hello_ok` message: ```json theme={null} { "type": "hello_ok", "session_id": 42, "server_time_ms": 1706000000000, "limits": { "hi_capacity": 1024, "pnl_flush_ms": 1000, "max_positions_per_session": 100, "max_wallets_per_session": 10, "max_positions_per_wallet": 20, "max_sessions_per_api_key": 5 } } ``` The `limits` object reflects your tier's capacity. See [Rate Limits and Tiers](/api/reference/rate-limits) for details. ### 3. Send `configure` After receiving `hello_ok`, send a `configure` message with your wallets and strategy: ```json theme={null} { "type": "configure", "wallet_pubkeys": ["WALLET_PUBKEY_1", "WALLET_PUBKEY_2"], "strategy": { "target_profit_pct": 5.0, "stop_loss_pct": 1.5, "trailing_stop_pct": 3.0 }, "send_mode": "helius_sender", "tip_lamports": 1000 } ``` ### 4. Receive Acknowledgement The server responds with initial `balance_update` messages for tokens already held in the configured wallets. If a position is detected, you will also receive `position_opened`. ## The `limits` Object | Field | Type | Description | | --------------------------- | -------- | ----------------------------------------------- | | `hi_capacity` | `number` | Maximum high priority messages buffered. | | `pnl_flush_ms` | `number` | Interval at which PnL updates are flushed (ms). | | `max_positions_per_session` | `number` | Max tracked positions per session. | | `max_wallets_per_session` | `number` | Max wallets per session. | | `max_positions_per_wallet` | `number` | Max positions tracked per wallet. | | `max_sessions_per_api_key` | `number` | Max concurrent sessions per API key. | ## Reconnection The SDKs handle reconnection automatically. When the WebSocket disconnects: 1. The client waits with exponential backoff starting at 100 ms. 2. Backoff doubles on each attempt up to a maximum of 2,000 ms. 3. On successful reconnect, the client re sends the `configure` message. 4. The server will re emit `balance_update` and `position_opened` events for existing holdings. You do not need to implement reconnection logic yourself. ## Ping and Pong ### Client Ping Send a `ping` to measure round trip latency: ```json theme={null} { "type": "ping", "client_time_ms": 1706000000000 } ``` The server responds with `pong`: ```json theme={null} { "type": "pong", "server_time_ms": 1706000000001 } ``` ### Server Ping The server may also send WebSocket protocol level pings. The SDKs respond with pong frames automatically. ## StreamClient vs StreamSession ### `StreamClient` The low level client manages the raw WebSocket connection: * Handles connect, reconnect, and message framing. * Returns raw `ServerMessage` objects from `recv()`. * Provides a `StreamSender` for sending client messages. * Supports **lane splitting** via `connectLanes()` which separates high priority messages (exit signals, position events) from low priority messages (`pnl_update`). ### `StreamSession` The high level session wraps `StreamClient` with: * **Position tracking**: Automatically maintains a map of open positions by ID and token account. * **Typed events**: Returns `StreamEvent` objects with a `PositionHandle` attached. * **Deadline timers**: Automatically requests exit signals after `deadline_timeout_sec`. * **Strategy updates**: `updateStrategy()` and `updateStrategyOptional()` methods. For most integrations, use `StreamSession`. ## Lanes Lane splitting separates the message stream into two receivers: * **High priority lane**: `hello_ok`, `error`, `balance_update`, `position_opened`, `position_closed`, `exit_signal_with_tx` * **Low priority lane**: `pnl_update` This prevents high frequency PnL updates from blocking time sensitive exit signals. Lane splitting is currently available in the **TypeScript SDK only** via `connectLanes()`: ```typescript theme={null} const client = new StreamClient("YOUR_API_KEY"); const connection = await client.connectLanes(configure, { lowPriorityCapacity: 512, }); const [sender, highRx, lowRx] = connection.split(); // Process high priority in main loop for await (const msg of highRx) { // exit signals, position events... } // Process low priority in background for await (const msg of lowRx) { // pnl_update... } ``` When the low priority buffer is full, the oldest message is dropped to keep the Exit Intelligence Stream responsive. # Exit Intelligence Stream Overview Source: https://docs.lasersell.io/api/stream/overview Introduction to the LaserSell Exit Intelligence Stream for real time position tracking and automated exit execution. ## What is the Exit Intelligence Stream? The Exit Intelligence Stream is a persistent WebSocket connection that monitors your wallets on chain, tracks token positions, evaluates your profit and loss strategy in real time, and delivers pre built unsigned exit transactions when your thresholds are met. **Professional and Advanced tier** subscribers also receive real time [liquidity snapshots](/api/stream/server-events#liquidity_snapshot) with slippage bands and liquidity trend data, giving you visibility into how much of a position can be sold at a given price impact and whether pool liquidity is growing, stable, or draining. Read the [full announcement](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for details. ## Endpoint ``` wss://stream.lasersell.io/v1/ws ``` Authentication is handled via the `x-api-key` header, which the SDKs set automatically. ## When to Use the Exit Intelligence Stream vs REST | Scenario | Use | | --------------------------------------------- | ------------------------ | | Automated sell when profit/loss target is hit | Exit Intelligence Stream | | One off buy or sell transaction | REST (LaserSell API) | | Continuous position monitoring | Exit Intelligence Stream | | Building a transaction for user confirmation | REST (LaserSell API) | | Bot that reacts to wallet activity | Exit Intelligence Stream | Use the **Exit Intelligence Stream** when you want the server to watch your positions and deliver exit transactions automatically. Use the **REST API** when you need a single transaction built on demand. **Connect the stream before you buy.** The Exit Intelligence Stream detects new positions by observing on chain token arrivals. If you call `/v1/buy` before the stream is connected and configured, the resulting position will not be tracked and no exit signals will fire. Always connect and configure the stream first, then submit your buy. ## High Level Flow 1. **Connect** to `wss://stream.lasersell.io/v1/ws` with your API key. 2. Receive `hello_ok` from the server (includes session ID and rate limits). 3. **Send `configure`** with wallet public keys and your strategy parameters. 4. Receive initial `balance_update` messages for existing token holdings. 5. **The stream monitors** your wallets for new token arrivals and tracks profit and loss. 6. When a position hits your target profit, stop loss, trailing stop, or deadline, the server sends an `exit_signal_with_tx`. 7. **Sign locally** and submit the unsigned transaction. ```mermaid theme={null} sequenceDiagram participant C as Client participant S as Server participant Sol as Solana C->>S: connect (x-api-key) S->>C: hello_ok C->>S: configure S->>C: balance_update S->>C: position_opened S->>C: pnl_update S->>C: exit_signal_with_tx C->>Sol: sign + submit ``` ## SDK Entry Points The SDKs provide two abstraction levels: * **`StreamClient`**: Low level client. Manages the WebSocket connection, reconnection, and message framing. Returns raw `ServerMessage` objects. * **`StreamSession`**: High level wrapper. Wraps `StreamClient` with position tracking, deadline timers, liquidity snapshot caching, and typed `StreamEvent` objects that include a `PositionHandle`. For most use cases, start with `StreamSession`. ```typescript TypeScript theme={null} import { StreamClient, StreamSession } from "@lasersell/lasersell-sdk"; const client = new StreamClient("YOUR_API_KEY"); const session = await StreamSession.connect(client, { wallet_pubkeys: ["WALLET_PUBKEY"], strategy: { target_profit_pct: 5, stop_loss_pct: 1.5 }, deadline_timeout_sec: 45, send_mode: "helius_sender", tip_lamports: 1000, }); while (true) { const event = await session.recv(); if (event === null) break; // Handle event... } ``` ```python Python theme={null} from lasersell_sdk.stream.client import StreamClient, StreamConfigure from lasersell_sdk.stream.session import StreamSession client = StreamClient("YOUR_API_KEY") session = await StreamSession.connect( client, StreamConfigure( wallet_pubkeys=["WALLET_PUBKEY"], strategy={"target_profit_pct": 5.0, "stop_loss_pct": 1.5}, deadline_timeout_sec=45, ), ) while True: event = await session.recv() if event is None: break # Handle event... ``` ```rust Rust theme={null} use lasersell_sdk::stream::client::{StreamClient, StreamConfigure}; use lasersell_sdk::stream::session::StreamSession; use lasersell_sdk::stream::proto::StrategyConfigMsg; use secrecy::SecretString; let client = StreamClient::new(SecretString::new(std::env::var("LASERSELL_API_KEY")?)); let session = StreamSession::connect(&client, StreamConfigure { wallet_pubkeys: vec!["WALLET_PUBKEY".into()], strategy: StrategyConfigMsg { target_profit_pct: 5.0, stop_loss_pct: 1.5, ..Default::default() }, deadline_timeout_sec: Some(45), }).await?; loop { let event = match session.recv().await { Some(event) => event, None => break, }; // Handle event... } ``` ```go Go theme={null} import "github.com/lasersell/lasersell-sdk/go/stream" client := stream.NewStreamClient("YOUR_API_KEY") session, err := stream.ConnectSession(ctx, client, stream.StreamConfigure{ WalletPubkeys: []string{"WALLET_PUBKEY"}, Strategy: stream.StrategyConfigMsg{ TargetProfitPct: 5.0, StopLossPct: 1.5, }, DeadlineTimeoutSec: 45, }) if err != nil { log.Fatal(err) } for { event, err := session.Recv(ctx) if errors.Is(err, io.EOF) { break } // Handle event... } ``` ## Next Steps * [Connection Lifecycle](/api/stream/connection-lifecycle): Detailed handshake, reconnection, and lane splitting. * [Strategy Configuration](/api/stream/strategy-configuration): Configure profit targets, stop losses, and trailing stops. * [Server Events](/api/stream/server-events): Complete schema for all 9 server message types, including liquidity snapshots. * [Client Messages](/api/stream/client-messages): All 6 client message types and their schemas. # Server Events Source: https://docs.lasersell.io/api/stream/server-events Complete schema and JSON examples for all 13 server message types sent by the Exit Intelligence Stream. ## Message Types The server sends messages with a `type` discriminator. All SDKs parse these into typed objects. | Type | Priority | Description | | ----------------------------- | -------- | ------------------------------------------------------- | | `hello_ok` | High | Connection accepted, session started. | | `pong` | High | Response to client ping. | | `error` | High | Server error notification. | | `balance_update` | High | Token balance changed for a tracked wallet. | | `position_opened` | High | New position detected and tracking started. | | `position_closed` | High | Position tracking ended. | | `exit_signal_with_tx` | High | Exit threshold met; unsigned tx included. | | `mirror_buy_signal` | High | Watched wallet bought; unsigned buy tx for your wallet. | | `mirror_buy_failed` | High | Mirror buy could not be executed. | | `mirror_wallet_auto_disabled` | High | Watch wallet disabled by consecutive loss breaker. | | `pnl_update` | Low | Periodic profit and loss snapshot. | | `liquidity_snapshot` | Low | Slippage bands and liquidity trend (Tier 1+). | | `trade_tick` | Low | Real-time trade notification for a tracked position. | ## `hello_ok` Sent once immediately after the WebSocket opens. Contains the session ID and tier limits. ```json theme={null} { "type": "hello_ok", "session_id": 42, "server_time_ms": 1706000000000, "limits": { "hi_capacity": 1024, "pnl_flush_ms": 1000, "max_positions_per_session": 100, "max_wallets_per_session": 10, "max_positions_per_wallet": 20, "max_sessions_per_api_key": 5 } } ``` | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------------- | | `session_id` | `number` | Unique session identifier. | | `server_time_ms` | `number` | Server timestamp in milliseconds. | | `limits` | `object` | Tier capacity and rate limits. See [Rate Limits](/api/reference/rate-limits). | ## `pong` Response to a client `ping`. Use for latency measurement. ```json theme={null} { "type": "pong", "server_time_ms": 1706000000001 } ``` | Field | Type | Description | | ---------------- | -------- | --------------------------------- | | `server_time_ms` | `number` | Server timestamp in milliseconds. | ## `error` Server side error notification. Check the `code` to determine severity. ```json theme={null} { "type": "error", "code": "invalid_strategy", "message": "target_profit_pct must be >= 0" } ``` | Field | Type | Description | | --------- | -------- | ---------------------------- | | `code` | `string` | Machine readable error code. | | `message` | `string` | Human readable error detail. | ## `balance_update` Emitted when a token balance changes in a tracked wallet (new arrivals, transfers, sells). ```json theme={null} { "type": "balance_update", "wallet_pubkey": "Abc123...", "mint": "TokenMint...", "token_account": "ATA...", "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "tokens": 5000000, "slot": 280000000 } ``` | Field | Type | Description | | --------------- | -------- | ---------------------------------------------------------- | | `wallet_pubkey` | `string` | Wallet that owns the token account. | | `mint` | `string` | Token mint address. | | `token_account` | `string` | Associated token account address. May be absent. | | `token_program` | `string` | Token program ID (SPL Token or Token 2022). May be absent. | | `tokens` | `number` | Current balance in atomic units. | | `slot` | `number` | Solana slot at which the balance was observed. | ## `position_opened` A new position has been detected and tracking has begun. ```json theme={null} { "type": "position_opened", "position_id": 1, "wallet_pubkey": "Abc123...", "mint": "TokenMint...", "token_account": "ATA...", "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "tokens": 5000000, "entry_quote_units": 100000000, "market_context": { "market_type": "pump_swap", "pumpswap": { "pool": "PoolAddress..." } }, "slot": 280000000 } ``` | Field | Type | Description | | ------------------- | -------- | ---------------------------------------------------------------------------------------- | | `position_id` | `number` | Server assigned position identifier. | | `wallet_pubkey` | `string` | Wallet holding the position. | | `mint` | `string` | Token mint address. | | `token_account` | `string` | Associated token account. | | `token_program` | `string` | Token program ID. May be absent. | | `tokens` | `number` | Token balance at entry. | | `entry_quote_units` | `number` | Entry value in quote asset atomic units. | | `market_context` | `object` | Market/pool context. May be absent. See [Market Context](/api/reference/market-context). | | `slot` | `number` | Solana slot at detection. | ## `position_closed` A position has been removed from tracking. ```json theme={null} { "type": "position_closed", "position_id": 1, "wallet_pubkey": "Abc123...", "mint": "TokenMint...", "token_account": "ATA...", "reason": "stop_loss", "slot": 280000100 } ``` | Field | Type | Description | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `position_id` | `number` | Position identifier. | | `wallet_pubkey` | `string` | Wallet that held the position. | | `mint` | `string` | Token mint address. | | `token_account` | `string` | Associated token account. May be absent. | | `reason` | `string` | Why the position was closed (e.g., `"stop_loss"`, `"target_profit"`, `"trailing_stop"`, `"deadline"`, `"manual"`). | | `slot` | `number` | Solana slot at close. | ## `exit_signal_with_tx` The most important event. Sent when a position meets your exit criteria. Includes a pre built unsigned transaction ready for signing. ```json theme={null} { "type": "exit_signal_with_tx", "session_id": 42, "position_id": 1, "wallet_pubkey": "Abc123...", "mint": "TokenMint...", "token_account": "ATA...", "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "position_tokens": 5000000, "profit_units": 50000000, "reason": "target_profit", "triggered_at_ms": 1706000005000, "market_context": { "market_type": "pump_swap", "pumpswap": { "pool": "PoolAddress..." } }, "unsigned_tx_b64": "AQAAAA..." } ``` | Field | Type | Description | | ----------------- | -------- | ----------------------------------------------------------------------------------------- | | `session_id` | `number` | Session that generated this signal. | | `position_id` | `number` | Position identifier. | | `wallet_pubkey` | `string` | Wallet holding the position. | | `mint` | `string` | Token mint address. | | `token_account` | `string` | Associated token account. May be absent. | | `token_program` | `string` | Token program ID. May be absent. | | `position_tokens` | `number` | Tokens held at signal time. | | `profit_units` | `number` | Estimated profit in quote asset atomic units. | | `reason` | `string` | Trigger reason (e.g., `"target_profit"`, `"stop_loss"`, `"trailing_stop"`, `"deadline"`). | | `triggered_at_ms` | `number` | Millisecond timestamp when the signal was generated. | | `market_context` | `object` | Market/pool context for the sell route. May be absent. | | `unsigned_tx_b64` | `string` | Base64 encoded unsigned `VersionedTransaction`. | ## `mirror_buy_signal` Sent when a watched wallet opens a position on a supported market and the stream builds an unsigned buy transaction for your wallet. Sign and submit this transaction to mirror the trade. ```json theme={null} { "type": "mirror_buy_signal", "session_id": 42, "watched_wallet": "WatchedWalletPubkey...", "mint": "TokenMint...", "user_wallet": "YourWalletPubkey...", "amount_quote_units": 100000000, "input": "SOL", "unsigned_tx_b64": "AQAAAA...", "slippage_bps": 2500, "send_mode": "helius_sender", "tip_lamports": 1000, "market_context": { "market_type": "pump_swap", "pumpswap": { "pool": "PoolAddress..." } } } ``` | Field | Type | Description | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------- | | `session_id` | `number` | Session that generated this signal. | | `watched_wallet` | `string` | Public key of the watched wallet that triggered the buy. | | `mint` | `string` | Token mint address being purchased. | | `user_wallet` | `string` | Your wallet that will execute the buy transaction. | | `amount_quote_units` | `number` | Buy amount in quote asset atomic units. | | `input` | `string` | Quote asset used for the buy: `"SOL"` or `"USD1"`. | | `unsigned_tx_b64` | `string` | Base64 encoded unsigned `VersionedTransaction`. | | `slippage_bps` | `number` | Slippage tolerance in basis points. | | `send_mode` | `string` | Transaction submission mode. May be absent. | | `tip_lamports` | `number` | Priority fee tip in lamports. May be absent. | | `market_context` | `object` | Market/pool context for the buy route. May be absent. See [Market Context](/api/reference/market-context). | ## `mirror_buy_failed` Sent when a mirror buy cannot be executed. Common reasons include insufficient balance, no route found, or pool liquidity below the minimum threshold. ```json theme={null} { "type": "mirror_buy_failed", "watched_wallet": "WatchedWalletPubkey...", "mint": "TokenMint...", "reason": "insufficient_balance" } ``` | Field | Type | Description | | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `watched_wallet` | `string` | Public key of the watched wallet that triggered the buy. | | `mint` | `string` | Token mint address that was being purchased. | | `reason` | `string` | Machine readable reason the buy failed (e.g., `"insufficient_balance"`, `"no_route"`, `"below_min_liquidity"`, `"cooldown_active"`, `"max_positions_reached"`). | ## `mirror_wallet_auto_disabled` Sent when a watched wallet is automatically disabled by the consecutive loss breaker. After receiving this event the stream will no longer mirror trades from this wallet until you re-enable it. ```json theme={null} { "type": "mirror_wallet_auto_disabled", "watched_wallet": "WatchedWalletPubkey...", "reason": "consecutive_loss_limit_reached", "loss_count": 3 } ``` | Field | Type | Description | | ---------------- | -------- | --------------------------------------------------------------- | | `watched_wallet` | `string` | Public key of the wallet that was disabled. | | `reason` | `string` | Human readable description of why the wallet was disabled. | | `loss_count` | `number` | Number of consecutive losing trades that triggered the disable. | ## `pnl_update` Periodic profit and loss snapshot for a tracked position. ```json theme={null} { "type": "pnl_update", "position_id": 1, "profit_units": 25000000, "proceeds_units": 125000000, "server_time_ms": 1706000003000 } ``` | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------- | | `position_id` | `number` | Position identifier. | | `profit_units` | `number` | Current estimated profit in quote asset atomic units. | | `proceeds_units` | `number` | Current estimated total proceeds. | | `server_time_ms` | `number` | Server timestamp in milliseconds. | `pnl_update` messages are delivered at the interval defined by `limits.pnl_flush_ms`. When using lane splitting, they flow through the low priority channel. ## `liquidity_snapshot` **Tier 1+ only.** Liquidity snapshots are delivered to Professional and Advanced tier subscribers. Free tier sessions receive `pnl_update` messages only. Real time slippage bands and liquidity trend for a tracked position. Delivered alongside `pnl_update` at the same flush interval. Use this data to understand how much of a position can be sold at a given slippage threshold and whether pool liquidity is growing, stable, or draining. See the [announcement blog post](https://www.lasersell.io/blog/liquidity-snapshots-and-sdk-0-3) for background and examples. ```json theme={null} { "type": "liquidity_snapshot", "position_id": 1, "bands": [ { "slippage_bps": 100, "max_tokens": 500000, "coverage_pct": 10.0 }, { "slippage_bps": 200, "max_tokens": 1200000, "coverage_pct": 24.0 }, { "slippage_bps": 500, "max_tokens": 3000000, "coverage_pct": 60.0 }, { "slippage_bps": 1000, "max_tokens": 5000000, "coverage_pct": 100.0 } ], "liquidity_trend": "stable", "server_time_ms": 1706000003000 } ``` | Field | Type | Description | | ----------------- | -------- | -------------------------------------------------------------------------------------------- | | `position_id` | `number` | Position identifier. | | `bands` | `array` | Slippage bands, each describing how many tokens are sellable within that slippage threshold. | | `liquidity_trend` | `string` | Current liquidity direction: `"growing"`, `"stable"`, or `"draining"`. | | `server_time_ms` | `number` | Server timestamp in milliseconds. | ### `SlippageBandMsg` Each entry in the `bands` array: | Field | Type | Description | | -------------- | -------- | ----------------------------------------------------------------- | | `slippage_bps` | `number` | Slippage threshold in basis points (e.g. `100` = 1%). | | `max_tokens` | `number` | Maximum tokens sellable within this slippage band (atomic units). | | `coverage_pct` | `number` | Percentage of the full position covered by this band (0 to 100). | ### Interpreting slippage bands Slippage bands answer the question: "How much can I sell without exceeding X% slippage?" For example, if the 500 bps band shows `max_tokens: 3000000` and `coverage_pct: 60.0`, you can sell up to 3,000,000 tokens (60% of your position) with no more than 5% price impact. Use `session.getSlippageBands(positionId)` or `session.getMaxSellAtSlippage(positionId, slippageBps)` in any SDK to query the latest snapshot. ### Liquidity trend The `liquidity_trend` field tracks how pool liquidity is changing over recent observations: * **`"growing"`**: Liquidity is increasing. Sell conditions are improving. * **`"stable"`**: Liquidity is steady. No significant change. * **`"draining"`**: Liquidity is decreasing. Consider exiting sooner if selling a large position. Use `session.getLiquidityTrend(positionId)` in any SDK to read the trend. ## `trade_tick` Real-time notification when a trade occurs on a tracked position's token. Useful for monitoring trading activity on tokens you hold. ```json theme={null} { "type": "trade_tick", "position_id": 1, "time_ms": 1706000005000, "side": "buy", "token_amount": 500000, "quote_amount": 25000000, "price_quote": 50000, "maker": "TraderWallet...", "tx_signature": "5abc..." } ``` | Field | Type | Description | | -------------- | --------- | ----------------------------------------------- | | `position_id` | `number` | Position identifier. | | `time_ms` | `number` | Trade timestamp in milliseconds. | | `side` | `string` | Trade direction: `"buy"` or `"sell"`. | | `token_amount` | `number` | Token amount traded (atomic units). | | `quote_amount` | `number` | Quote asset amount traded (atomic units). | | `price_quote` | `number` | Price per token in quote asset (atomic units). | | `maker` | `string` | Wallet that initiated the trade. May be absent. | | `tx_signature` | `string` | On-chain transaction signature. May be absent. | | `watched` | `boolean` | `true` if this trade is from a watched wallet. | # Strategy Configuration Source: https://docs.lasersell.io/api/stream/strategy-configuration Configure target profit, stop loss, trailing stop, and sell on graduation thresholds for the Exit Intelligence Stream. ## `StrategyConfigMsg` Schema | Field | Type | Required | Description | | --------------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `target_profit_pct` | `number` | Yes | Take profit threshold as a percentage (e.g., `5` = 5%). | | `stop_loss_pct` | `number` | Yes | Stop loss threshold as a percentage (e.g., `1.5` = 1.5%). | | `trailing_stop_pct` | `number` | No | Trailing stop percentage from the peak. Activates only after the position is in profit. | | `sell_on_graduation` | `boolean` | No | If `true`, auto sells when a bonding curve token graduates to an AMM pool. | | `take_profit_levels` | `TakeProfitLevelMsg[]` | No | Exit ladder: sell partial amounts at multiple profit thresholds. Each level has `profit_pct` (trigger), `sell_pct` (portion to sell), and `trailing_stop_pct` (optional trailing stop for that level). | | `liquidity_guard` | `boolean` | No | When enabled, the stream checks available pool liquidity before generating an exit signal. Prevents exits into thin liquidity. Default: `false`. | | `breakeven_trail_pct` | `number` | No | A trailing stop that activates once the position breaks even, trailing from the breakeven point rather than the peak. | At least one of `target_profit_pct`, `stop_loss_pct`, `trailing_stop_pct`, or `deadline_timeout_sec` (on the configure message) must be greater than zero. ### `TakeProfitLevelMsg` Schema | Field | Type | Required | Description | | ------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `profit_pct` | `number` | Yes | Profit percentage at which this level triggers (e.g., `20` = 20%). | | `sell_pct` | `number` | Yes | Percentage of the position to sell at this level (e.g., `50` = sell 50%). | | `trailing_stop_pct` | `number` | No | Optional trailing stop for this level. If set, instead of selling immediately at `profit_pct`, a trailing stop activates at this level. Default: `0` (sell immediately). | ## Setting Strategy at Connection Time Strategy is provided in the `configure` message when you first connect: ```typescript TypeScript theme={null} const session = await StreamSession.connect(client, { wallet_pubkeys: ["WALLET_PUBKEY"], strategy: { target_profit_pct: 10, stop_loss_pct: 2, trailing_stop_pct: 5, sell_on_graduation: true, }, deadline_timeout_sec: 60, send_mode: "helius_sender", tip_lamports: 1000, }); ``` ```python Python theme={null} session = await StreamSession.connect( client, StreamConfigure( wallet_pubkeys=["WALLET_PUBKEY"], strategy={ "target_profit_pct": 10.0, "stop_loss_pct": 2.0, "trailing_stop_pct": 5.0, "sell_on_graduation": True, }, deadline_timeout_sec=60, send_mode="helius_sender", tip_lamports=1000, ), ) ``` ```rust Rust theme={null} let session = StreamSession::connect(&client, StreamConfigure { wallet_pubkeys: vec!["WALLET_PUBKEY".into()], strategy: StrategyConfigMsg { target_profit_pct: 10.0, stop_loss_pct: 2.0, trailing_stop_pct: Some(5.0), sell_on_graduation: Some(true), }, deadline_timeout_sec: Some(60), send_mode: Some("helius_sender".to_string()), tip_lamports: Some(1000), }).await?; ``` ```go Go theme={null} sendMode := "helius_sender" tipLamports := uint64(1000) session, err := stream.ConnectSession(ctx, client, stream.StreamConfigure{ WalletPubkeys: []string{"WALLET_PUBKEY"}, Strategy: stream.StrategyConfigMsg{ TargetProfitPct: 10.0, StopLossPct: 2.0, TrailingStopPct: 5.0, SellOnGraduation: true, }, DeadlineTimeoutSec: 60, SendMode: &sendMode, TipLamports: &tipLamports, }) ``` ## Using StrategyConfigBuilder All 4 SDKs provide a fluent builder for strategy configuration. The builder validates that at least one exit condition is set when you call `build()`. ```typescript TypeScript theme={null} import { StrategyConfigBuilder } from "@lasersell/lasersell-sdk"; const strategy = new StrategyConfigBuilder() .targetProfitPct(50) .stopLossPct(10) .trailingStopPct(5) .takeProfitLevels([ { profit_pct: 20, sell_pct: 25, trailing_stop_pct: 0 }, { profit_pct: 50, sell_pct: 50, trailing_stop_pct: 3 }, { profit_pct: 100, sell_pct: 100, trailing_stop_pct: 5 }, ]) .liquidityGuard(true) .breakevenTrailPct(2) .build(); ``` ```python Python theme={null} from lasersell_sdk.stream.client import StrategyConfigBuilder strategy = ( StrategyConfigBuilder() .target_profit_pct(50) .stop_loss_pct(10) .trailing_stop_pct(5) .take_profit_levels([ {"profit_pct": 20, "sell_pct": 25, "trailing_stop_pct": 0}, {"profit_pct": 50, "sell_pct": 50, "trailing_stop_pct": 3}, {"profit_pct": 100, "sell_pct": 100, "trailing_stop_pct": 5}, ]) .liquidity_guard(True) .breakeven_trail_pct(2.0) .build() ) ``` ```rust Rust theme={null} use lasersell_sdk::stream::client::StrategyConfigBuilder; use lasersell_sdk::stream::proto::TakeProfitLevelMsg; let strategy = StrategyConfigBuilder::new() .target_profit_pct(50.0) .stop_loss_pct(10.0) .trailing_stop_pct(5.0) .take_profit_levels(vec![ TakeProfitLevelMsg { profit_pct: 20.0, sell_pct: 25.0, trailing_stop_pct: 0.0 }, TakeProfitLevelMsg { profit_pct: 50.0, sell_pct: 50.0, trailing_stop_pct: 3.0 }, TakeProfitLevelMsg { profit_pct: 100.0, sell_pct: 100.0, trailing_stop_pct: 5.0 }, ]) .liquidity_guard(true) .breakeven_trail_pct(2.0) .build(); ``` ```go Go theme={null} import "github.com/lasersell/lasersell-sdk/go/stream" strategy := stream.NewStrategyConfigBuilder(). TargetProfitPct(50.0). StopLossPct(10.0). TrailingStopPct(5.0). TakeProfitLevels([]stream.TakeProfitLevelMsg{ {ProfitPct: 20.0, SellPct: 25.0, TrailingStopPct: 0.0}, {ProfitPct: 50.0, SellPct: 50.0, TrailingStopPct: 3.0}, {ProfitPct: 100.0, SellPct: 100.0, TrailingStopPct: 5.0}, }). LiquidityGuard(true). BreakevenTrailPct(2.0). Build() ``` ## Exit Ladder (Take Profit Levels) An exit ladder lets you take partial profits at multiple thresholds instead of exiting your entire position at a single price. Each level specifies a profit target and the percentage of the remaining position to sell. ### How it works 1. When profit reaches a level's `profit_pct`, the stream sells `sell_pct` of the current position. 2. If a level has `trailing_stop_pct > 0`, a trailing stop activates at that level instead of selling immediately. 3. Levels are evaluated in order from lowest to highest `profit_pct`. 4. The global `target_profit_pct` still applies as a hard cap; if reached before all levels fire, the remaining position is sold. ### Example With this exit ladder and an entry of 1 SOL: ```json theme={null} { "take_profit_levels": [ { "profit_pct": 20, "sell_pct": 25, "trailing_stop_pct": 0 }, { "profit_pct": 50, "sell_pct": 50, "trailing_stop_pct": 3 }, { "profit_pct": 100, "sell_pct": 100, "trailing_stop_pct": 5 } ], "stop_loss_pct": 15 } ``` * At 20% profit (position worth 1.2 SOL): sell 25% immediately. * At 50% profit (position worth 1.5 SOL): activate a 3% trailing stop on 50% of the remaining position. * At 100% profit (position worth 2 SOL): activate a 5% trailing stop on the remaining position. ## Liquidity Guard When `liquidity_guard` is enabled, the stream checks pool liquidity before generating an exit signal. If the pool cannot absorb the sell at a reasonable slippage, the signal is deferred until liquidity improves or a timeout forces the exit. This is useful for large positions in thin pools where an immediate sell would result in excessive slippage. ```json theme={null} { "target_profit_pct": 50, "stop_loss_pct": 10, "liquidity_guard": true } ``` ## Breakeven Trail The `breakeven_trail_pct` field enables a trailing stop that activates once a position breaks even (profit >= 0), trailing from the breakeven point rather than the peak. Unlike the standard `trailing_stop_pct` which trails from the highest observed profit, the breakeven trail protects you from giving back all gains on a position that briefly went profitable. ### Example With `breakeven_trail_pct: 2` and an entry of 1 SOL: | Time | Profit (%) | Breakeven Trail Active | Trigger | | ---- | ---------- | ---------------------- | ----------------------------------------- | | t=0 | -5 | No (not at breakeven) | — | | t=1 | 0 | Yes (at breakeven) | — | | t=2 | 3 | Yes | No trigger (profit > 0) | | t=3 | -1 | Yes | No trigger (drop \< 2% from breakeven) | | t=4 | -2.5 | Yes | **Triggered** (drop >= 2% from breakeven) | Combine with a standard `trailing_stop_pct` to protect both the breakeven point and the profit peak. ## Trailing Stop Explained The trailing stop tracks the highest profit observed since the position opened. When profit drops from that peak by `trailing_stop_pct`, an exit signal fires. ### Numerical Example | Time | Profit (%) | Peak (%) | Drop from Peak (%) | Trailing Stop (5%) | | ---- | ---------- | -------- | ------------------ | -------------------------- | | t=0 | 0 | 0 | 0 | Not active | | t=1 | 3 | 3 | 0 | Not active | | t=2 | 8 | 8 | 0 | Active, no trigger | | t=3 | 6 | 8 | 2 | Active, no trigger | | t=4 | 4 | 8 | 4 | Active, no trigger | | t=5 | 2.5 | 8 | 5.5 | **Triggered** (drop >= 5%) | The trailing stop only activates once the position is in profit. If the position goes negative before ever being in profit, the stop loss triggers instead. ## Updating Strategy Mid Session You can change the strategy at any time without reconnecting by sending an `update_strategy` message: ```json theme={null} { "type": "update_strategy", "strategy": { "target_profit_pct": 15, "stop_loss_pct": 3, "trailing_stop_pct": 7 } } ``` Using the SDK: ```typescript TypeScript theme={null} session.updateStrategy({ target_profit_pct: 15, stop_loss_pct: 3, trailing_stop_pct: 7, }); ``` ```python Python theme={null} session.sender().update_strategy({ "target_profit_pct": 15.0, "stop_loss_pct": 3.0, "trailing_stop_pct": 7.0, }) ``` ```rust Rust theme={null} session.update_strategy(StrategyConfigMsg { target_profit_pct: 15.0, stop_loss_pct: 3.0, trailing_stop_pct: Some(7.0), sell_on_graduation: None, }, None); ``` ```go Go theme={null} session.Sender().UpdateStrategy(stream.StrategyConfigMsg{ TargetProfitPct: 15.0, StopLossPct: 3.0, TrailingStopPct: 7.0, }) ``` The new strategy takes effect immediately for all tracked positions. No positions are closed or reopened; the server simply re evaluates existing positions against the new thresholds. ## Deadline Timeout The `deadline_timeout_sec` is set on the `configure` message (not on the strategy object itself). It defines the maximum time in seconds to hold a position before requesting an exit signal, regardless of profit or loss. Setting `deadline_timeout_sec: 60` means the Exit Intelligence Stream will attempt to exit any position held for more than 60 seconds. When using `StreamSession`, the deadline timer runs client side and automatically calls `requestExitSignal` when it fires. If you are using `StreamClient` directly, you must implement deadline logic yourself. ## Validation Rules * `target_profit_pct` must be >= 0. * `stop_loss_pct` must be >= 0. * `trailing_stop_pct` (if provided) must be >= 0. * `deadline_timeout_sec` must be >= 0. * At least one of these values must be > 0. Violating these rules raises a `StreamClientError` with kind `"protocol"`. ## Per-Position Strategy Override You can override the global strategy for individual positions without affecting other tracked positions. This is useful when you want different exit rules for specific tokens. ```json theme={null} { "type": "update_position_strategy", "position_id": 1, "strategy": { "target_profit_pct": 200, "stop_loss_pct": 5, "trailing_stop_pct": 10 } } ``` Using the SDK: ```typescript TypeScript theme={null} session.sender().updatePositionStrategy(positionId, { target_profit_pct: 200, stop_loss_pct: 5, trailing_stop_pct: 10, }); ``` ```python Python theme={null} session.sender().update_position_strategy(position_id, { "target_profit_pct": 200.0, "stop_loss_pct": 5.0, "trailing_stop_pct": 10.0, }) ``` ```rust Rust theme={null} session.sender().update_position_strategy(position_id, StrategyConfigMsg { target_profit_pct: 200.0, stop_loss_pct: 5.0, trailing_stop_pct: 10.0, ..Default::default() })?; ``` ```go Go theme={null} sender.UpdatePositionStrategy(positionID, stream.StrategyConfigMsg{ TargetProfitPct: 200.0, StopLossPct: 5.0, TrailingStopPct: 10.0, }) ``` The override applies only to the specified position. All other positions continue using the global strategy. To revert a position back to the global strategy, send another `update_position_strategy` with the current global strategy values. Per-position overrides are ephemeral and are not persisted across reconnections. # Send Targets Source: https://docs.lasersell.io/api/transactions/send-targets Choose how to submit signed transactions: Default RPC, custom RPC, Helius Sender, or Astralane Iris. ## What is a Send Target? A `SendTarget` tells the SDK where to submit your signed transaction. The LaserSell SDKs support four targets, each optimized for different use cases. ## Available Targets ### Default RPC Uses the Solana public mainnet beta endpoint (`https://api.mainnet-beta.solana.com`). This endpoint is rate limited and best suited for testing only. ```typescript TypeScript theme={null} import { sendTargetDefaultRpc } from "@lasersell/lasersell-sdk"; const target = sendTargetDefaultRpc(); ``` ```python Python theme={null} from lasersell_sdk.tx import SendTargetRpc target = SendTargetRpc() # defaults to mainnet-beta ``` ```rust Rust theme={null} use lasersell_sdk::tx::SendTarget; let target = SendTarget::Rpc { url: lasersell_sdk::tx::MAINNET_BETA_RPC_URL.into() }; ``` ```go Go theme={null} target := lasersell.SendTargetDefaultRpc() ``` ### Custom RPC Specify your own RPC endpoint URL. Recommended for production with a private RPC provider such as Helius or Chainstack (free tiers available). ```typescript TypeScript theme={null} import { sendTargetRpc } from "@lasersell/lasersell-sdk"; const target = sendTargetRpc("https://your-private-rpc.example.com"); ``` ```python Python theme={null} from lasersell_sdk.tx import SendTargetRpc target = SendTargetRpc(url="https://your-private-rpc.example.com") ``` ```rust Rust theme={null} let target = SendTarget::Rpc { url: "https://your-private-rpc.example.com".into() }; ``` ```go Go theme={null} target := lasersell.SendTargetRpc("https://your-private-rpc.example.com") ``` ### Helius Sender Routes through the Helius Sender `/fast` endpoint for optimized transaction landing. No API key required for Helius Sender. ```typescript TypeScript theme={null} import { sendTargetHeliusSender } from "@lasersell/lasersell-sdk"; const target = sendTargetHeliusSender(); ``` ```python Python theme={null} from lasersell_sdk.tx import SendTargetHeliusSender target = SendTargetHeliusSender() ``` ```rust Rust theme={null} let target = SendTarget::HeliusSender; ``` ```go Go theme={null} target := lasersell.SendTargetHeliusSender() ``` **Connection Warming (TypeScript only)**: For the best latency, the TypeScript SDK provides helpers to keep the Helius Sender connection warm: ```typescript theme={null} import { startHeliusSenderWarmLoop, pingHeliusSender } from "@lasersell/lasersell-sdk"; // One-time ping const ok = await pingHeliusSender(); // Continuous warm loop (pings every 30s) const { stop } = startHeliusSenderWarmLoop({ intervalMs: 30_000 }); // Call stop() when done ``` The Rust SDK sends a single ping on first use. The Python and Go SDKs do not require connection warming. ### Astralane Iris Routes through the Astralane Iris gateway. Requires an Astralane API key and supports regional endpoints for latency optimization. ```typescript TypeScript theme={null} import { sendTargetAstralane } from "@lasersell/lasersell-sdk"; const target = sendTargetAstralane("ASTRALANE_API_KEY", "fr"); ``` ```python Python theme={null} from lasersell_sdk.tx import SendTargetAstralane target = SendTargetAstralane(api_key="ASTRALANE_API_KEY", region="fr") ``` ```rust Rust theme={null} let target = SendTarget::Astralane { api_key: "ASTRALANE_API_KEY".into(), region: Some("fr".into()), }; ``` ```go Go theme={null} target := lasersell.SendTargetAstralane("ASTRALANE_API_KEY", "fr") ``` ## Available Regions (Astralane) | Code | Location | Notes | | ------ | ------------- | ----------- | | `fr` | Frankfurt | Recommended | | `fr2` | Frankfurt 2 | | | `ams` | Amsterdam | Recommended | | `ams2` | Amsterdam 2 | | | `la` | San Francisco | | | `ny` | New York | | | `jp` | Tokyo | | | `sg` | Singapore | | | `lim` | Limburg | | | `lit` | Lithuania | | If no region is specified, the SDK defaults to `fr` (Frankfurt). ## Recommendations | Use Case | Recommended Target | | ------------------------------- | ------------------ | | Testing and development | Default RPC | | Production (general) | Helius Sender | | Production (latency sensitive) | Astralane Iris | | Production (existing RPC setup) | Custom RPC | For automated trading bots, **Helius Sender** or **Astralane Iris** provide the best transaction landing rates. Avoid using the public Solana RPC in production as it is heavily rate limited. ## Send Mode vs Send Target The `send_mode` field on `BuildSellTxRequest` and `BuildBuyTxRequest` tells the **server** which endpoint context to optimize the transaction for. The `SendTarget` controls where **your client** submits the signed transaction. For best results, align them: | `send_mode` (server) | `SendTarget` (client) | | -------------------- | -------------------------- | | `"rpc"` | `sendTargetRpc(url)` | | `"helius_sender"` | `sendTargetHeliusSender()` | | `"astralane"` | `sendTargetAstralane(...)` | # Transaction Signing Source: https://docs.lasersell.io/api/transactions/signing Non custodial signing flow, signing functions per language, and loading Solana keypairs. ## Non Custodial Flow LaserSell never touches your private key. Every transaction follows this pattern: 1. **Build**: The API returns a base64 encoded unsigned `VersionedTransaction`. 2. **Sign**: You decode, sign, and re encode locally using your keypair. 3. **Submit**: You send the signed transaction to the Solana network via a [send target](/api/transactions/send-targets). ```mermaid theme={null} flowchart LR A[LaserSell API] -->|unsigned_tx_b64| B[Sign Locally] -->|signed_tx_b64| C[Solana] ``` ## Loading a Keypair All SDKs expect a Solana keypair. The standard format is a JSON file containing an array of 64 bytes (as used by `solana-keygen`). ```typescript TypeScript theme={null} import { readFile } from "node:fs/promises"; import { Keypair } from "@solana/web3.js"; const raw = await readFile("./keypair.json", "utf8"); const keypair = Keypair.fromSecretKey( Uint8Array.from(JSON.parse(raw)) ); ``` ```python Python theme={null} import json from pathlib import Path from solders.keypair import Keypair raw = json.loads(Path("./keypair.json").read_text()) keypair = Keypair.from_bytes(bytes(raw)) ``` ```rust Rust theme={null} use solana_sdk::signature::read_keypair_file; let keypair = read_keypair_file("./keypair.json")?; ``` ```go Go theme={null} import "github.com/gagliardetto/solana-go" privateKey, err := solana.PrivateKeyFromSolanaKeygenFile("./keypair.json") ``` ## Signing Functions ### Standard Sign Decodes the unsigned transaction, signs it with your keypair, and returns a signed `VersionedTransaction` object. ```typescript TypeScript theme={null} import { signUnsignedTx } from "@lasersell/lasersell-sdk"; const signedTx = signUnsignedTx(unsignedTxB64, keypair); ``` ```python Python theme={null} from lasersell_sdk.tx import sign_unsigned_tx signed_tx = sign_unsigned_tx(unsigned_tx_b64, keypair) ``` ```rust Rust theme={null} use lasersell_sdk::tx::sign_unsigned_tx; let signed_tx = sign_unsigned_tx(&unsigned_tx_b64, &keypair)?; ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" signedTx, err := lasersell.SignUnsignedTx(unsignedTxB64, privateKey) ``` ### Fast Sign (TypeScript) The TypeScript SDK provides `signUnsignedTxB64Fast`, which signs the raw bytes in place without deserializing through `VersionedTransaction`. This avoids allocation on the hot path and returns the signed transaction as a base64 string directly. ```typescript theme={null} import { signUnsignedTxB64Fast } from "@lasersell/lasersell-sdk"; const signedTxB64 = signUnsignedTxB64Fast(unsignedTxB64, keypair); ``` ### Sign and Send (One Step) Combines signing and submission into a single call. ```typescript TypeScript theme={null} import { signAndSendUnsignedTxB64, sendTargetHeliusSender } from "@lasersell/lasersell-sdk"; const signature = await signAndSendUnsignedTxB64( sendTargetHeliusSender(), unsignedTxB64, keypair, ); ``` ## Submitting the Signed Transaction After signing, submit to the Solana network using a send target: ```typescript TypeScript theme={null} import { sendTransaction, sendTargetHeliusSender } from "@lasersell/lasersell-sdk"; const signature = await sendTransaction(sendTargetHeliusSender(), signedTx); ``` ```python Python theme={null} from lasersell_sdk.tx import send_transaction, SendTargetHeliusSender signature = await send_transaction(SendTargetHeliusSender(), signed_tx) ``` ```rust Rust theme={null} use lasersell_sdk::tx::{SendTarget, send_transaction}; let http = reqwest::Client::new(); let signature = send_transaction(&http, &SendTarget::HeliusSender, &signed_tx).await?; ``` ```go Go theme={null} import lasersell "github.com/lasersell/lasersell-sdk/go" sig, err := lasersell.SendTransaction(ctx, nil, lasersell.SendTargetHeliusSender(), signedTx) ``` ## Error Types Signing and submission errors are surfaced through `TxSubmitError`: | Kind | Description | | ------------------------- | --------------------------------------------------- | | `decode_unsigned_tx` | Base64 decoding of the unsigned transaction failed. | | `deserialize_unsigned_tx` | Transaction bytes could not be deserialized. | | `sign_tx` | Signing failed (wrong keypair, corrupted data). | | `serialize_tx` | Serialization of the signed transaction failed. | | `request_send` | Network error during submission. | | `response_read` | Could not read the RPC response body. | | `http_status` | Non 2xx HTTP response from the RPC endpoint. | | `decode_response` | Response body was not valid JSON. | | `rpc_error` | RPC returned an error object. | | `missing_result` | Response did not contain a transaction signature. | ## Security Best Practices * **Never log or transmit** your private key. * **Load keys from environment variables** or encrypted files in production. * **Verify the wallet address** matches the `user_pubkey` used in the build request. The API constructs the transaction for that specific signer. # Transaction Build Latency Source: https://docs.lasersell.io/benchmarks/tx-build-latency Open-source benchmarks comparing Solana transaction build latency across LaserSell API, PumpFun SDK, PumpPortal API, and Jupiter's Metis API. When LaserSell detects an exit condition, the sell transaction needs to be built and returned as fast as possible. Every millisecond of build latency is a millisecond your transaction is not on its way to the network. We open-sourced our transaction build benchmarks so anyone can verify these numbers. The benchmarks compare four methods of building Solana swap transactions: LaserSell API, PumpFun SDK, PumpPortal API, and Jupiter's Metis API. All benchmarks run 20 iterations with a warmup round, measuring the time from request to receiving an unsigned transaction. ## Co-Located Server (Exit Intelligence Stream) This is the latency that matters for automated exits. When the Exit Intelligence Stream detects that your take profit, stop loss, or trailing stop has been triggered, it calls the LaserSell API over the co-located network to build the sell transaction. The transaction is then returned to your client for signing. ### Buy Transaction Build 0.01 SOL buy, 20% slippage, 20 iterations: | Method | Avg | Min | Max | Median | | :---------------- | :---------- | :---------- | :---------- | :---------- | | **LaserSell API** | **4.53 ms** | **3.39 ms** | **6.84 ms** | **4.10 ms** | | PumpPortal API | 65.24 ms | 26.18 ms | 264.29 ms | 46.34 ms | | Jupiter Metis API | 95.03 ms | 79.30 ms | 115.76 ms | 93.73 ms | | PumpFun SDK | 123.34 ms | 103.46 ms | 208.71 ms | 118.24 ms | ### Sell Transaction Build 1,000,000 tokens, 20% slippage, 20 iterations: | Method | Avg | Min | Max | Median | | :---------------- | :---------- | :---------- | :----------- | :---------- | | **LaserSell API** | **5.32 ms** | **3.38 ms** | **15.03 ms** | **4.46 ms** | | PumpPortal API | 80.74 ms | 27.75 ms | 576.28 ms | 38.18 ms | | PumpFun SDK | 120.81 ms | 96.53 ms | 143.23 ms | 121.65 ms | | Jupiter Metis API | 168.84 ms | 159.10 ms | 177.35 ms | 169.11 ms | On the co-located path, LaserSell builds transactions in under 5 ms on average. The next fastest method is over 12x slower. This is the path used by the Exit Intelligence Stream for automated exits. ## Public Internet (SDK and API) Standard API latency over the public internet, measured from a MacBook Pro. This represents the latency experienced by developers making direct REST API calls from their own applications. Desktop App users do not experience these numbers. The Desktop App receives pre-built exit transactions through the Exit Intelligence Stream, which builds transactions on the co-located path (the sub-5ms numbers above) and pushes them to the client over a persistent WebSocket connection. ### Buy Transaction Build 0.01 SOL buy, 20% slippage, 20 iterations: | Method | Avg | Min | Max | Median | | :---------------- | :----------- | :----------- | :----------- | :----------- | | **LaserSell API** | **71.21 ms** | **62.77 ms** | **81.30 ms** | **71.16 ms** | | PumpPortal API | 151.08 ms | 120.96 ms | 246.40 ms | 141.44 ms | | Jupiter Metis API | 179.83 ms | 153.17 ms | 370.01 ms | 168.11 ms | | PumpFun SDK | 790.94 ms | 278.57 ms | 4,570.26 ms | 364.92 ms | ### Sell Transaction Build 1,000,000 tokens, 20% slippage, 20 iterations: | Method | Avg | Min | Max | Median | | :---------------- | :----------- | :----------- | :------------ | :----------- | | **LaserSell API** | **74.77 ms** | **65.13 ms** | **108.67 ms** | **71.72 ms** | | PumpPortal API | 152.08 ms | 111.91 ms | 554.96 ms | 120.59 ms | | Jupiter Metis API | 181.73 ms | 155.88 ms | 222.54 ms | 174.92 ms | | PumpFun SDK | 541.00 ms | 277.65 ms | 2,029.10 ms | 317.28 ms | Over the public internet, LaserSell is consistently the fastest with the tightest variance. The PumpFun SDK shows the most volatility, with worst-case latencies exceeding 4 seconds on buys and 2 seconds on sells. These numbers are relevant for SDK and API integrators building their own tools. ## What is Being Compared Each method takes a different approach to building a swap transaction: | Method | Approach | | :-------------------- | :-------------------------------------------------------------------------------------------------- | | **LaserSell API** | Single API call. The server builds the full transaction and returns it ready to sign. | | **PumpFun SDK** | Local build. Makes multiple RPC calls to fetch on-chain state, then assembles instructions locally. | | **PumpPortal API** | Single API call to PumpPortal. | | **Jupiter Metis API** | Two API calls. First a quote request, then a swap build request. | LaserSell's single-call architecture eliminates round trips, which is why it consistently outperforms multi-step approaches. ## Why This Matters In automated trading, transaction build latency directly affects execution quality. When a memecoin starts dumping, the difference between a 5 ms build and a 170 ms build can determine whether your exit lands in the same block as the trigger or several blocks later, after the price has already moved further against you. For the Exit Intelligence Stream, this means your automated exits fire with minimal overhead between detection and broadcast. For SDK and API users building their own tools, it means faster round trips and more responsive applications. ## Run the Benchmarks Yourself The benchmarks are open source. Clone the repository and run them against your own infrastructure: ```bash theme={null} git clone https://github.com/lasersell/benchmarks cd benchmarks # Buy transaction build cd buy-tx-build npm install && npm run build npm run bench # Sell transaction build cd ../sell-tx-build npm install && npm run build npm run bench ``` You will need a LaserSell API key (free at [app.lasersell.io](https://app.lasersell.io)), a Solana RPC URL, and a Pump.fun token mint address. See the repository README for full configuration options. # Community Source: https://docs.lasersell.io/community Official LaserSell channels and social links. | Channel | Link | | ----------- | ---------------------------------------------------------------------------- | | Discord | [discord.gg/lasersell](https://discord.gg/lasersell) | | X / Twitter | [x.com/lasersellhq](https://x.com/lasersellhq) | | Telegram | [t.me/lasersell](https://t.me/lasersell) | | GitHub | [github.com/lasersell](https://github.com/lasersell) | | LinkedIn | [linkedin.com/company/lasersell](https://www.linkedin.com/company/lasersell) | # Active Positions Source: https://docs.lasersell.io/desktop/active-positions Monitor all your open positions in real time with live PnL, market data, and manual exit controls. The Active Positions tab is the main screen of the app. It shows every token position across your active wallets, updated in real time as new blocks are produced on Solana.