Skip to content

TypeScript SDK

@candledottv/agent-sdk is a typed, fetch-based client with a small set of audited crypto dependencies, for the whole agent rail: launch configuration, launches (blocking or async), job polling, market state, quotes, feed reads, verification, activity reporting, agent profiles, and image uploads. It runs on Bun and Node 18+.

Terminal window
npm install @candledottv/agent-sdk # or: bun add @candledottv/agent-sdk

The source lives in the public candledottv/agentic repo if you would rather vendor it or read along.

import { CandleClient } from "@candledottv/agent-sdk"
const candle = new CandleClient({
apiUrl: "https://api.alpha.candle.tv",
apiKey: process.env.CANDLE_AGENT_API_KEY, // cndl_live_... or cndl_test_...
})
const { imageUrl } = await candle.uploadImage(pngBytes, "image/png")
const launch = await candle.launch({
chain: "solana",
quoteAsset: "sol",
mode: "open",
name: "Trend Coin",
symbol: "TREND",
imageUrl,
buyAmount: 1_000_000,
})
console.log(launch.mint, launch.links.candle)

clientLaunchId is generated for you when you omit it, so the retry policy below has a stable identity to reuse.

new CandleClient({ apiUrl, apiKey?, fetch?, maxRetries?, allowInsecureHttp?, privyAppId?, secretStore?, solanaRpcUrl?, evmRpcUrl?, hyperliquidBuilder?, hyperliquidNetwork? })

apiUrl must be https://. Every authenticated call sends x-api-key, so a cleartext http:// URL hands the key to anything on the path, and a redirect to HTTPS does not help because the first request has already gone. The constructor throws rather than letting that happen. Loopback (localhost, 127.0.0.0/8, ::1) is exempt and needs no flag; for a trusted local endpoint that is not loopback, such as a devcontainer reaching its host, pass allowInsecureHttp: true.

The signing options are needed only on the paths that sign locally:

  • privyAppId: Privy’s public app id, the one the sign relay uses. Required to sign for a linked or TEE wallet.
  • secretStore: where the wallet’s own P-256 signer lives (KeychainSecretStore, EncryptedFileSecretStore for a passphrase-encrypted file on Node, or InMemorySecretStore for tests and short runs). Candle never sees it.
  • solanaRpcUrl / evmRpcUrl: the RPCs broadcastSignedTransaction() and the linked-wallet one-shots (trade(), selfLaunch()) read and send over. A Hood linked payer needs evmRpcUrl.
  • hyperliquidBuilder / hyperliquidNetwork: the perps builder to pin, and mainnet (default) or testnet.

apiKey is required by the write paths (launch, dry run, jobs, activity reporting, image upload, wallet import, self-signed launches, trades) and not required by the public reads (it is sent with them when set). getAgentTier() is the one exception: the endpoint accepts either an agent key or a Privy session, so it sends whichever apiKey the client was built with, key or none. Pass your own fetch to route through a proxy or to test without a network.

MethodWhat it does
getQuotePairs(chain?)The launch matrix, with matrixVersion
getPresets()First-party named launch configurations
expandPreset(presets, name, overrides?)Local merge of a preset into a launch body. No request
dryRunLaunch(req)Full validation with no spend and no cap slot consumed
launch(req)Blocking launch with built-in idempotency and retries
launchAsync(req)Returns once accepted, with a jobUrl to poll
getLaunchJob(clientLaunchId)One attempt’s ledger row
waitForLaunch(clientLaunchId, opts?)Polls until the job reaches a terminal state
getMarket(chain, mint)Lifecycle, tradability, migration status
getQuote(chain, mint, opts)Exact-integer quote for one side
getFeed(bucket, chain?)Public feed buckets
verify(chain, mint)Is this a genuine Candle launch
reportActivity(chain, signature)Report a client-executed transaction
getAgentProfile(idOrWallet)Public agent track record
getAgentTier()The calling account’s tier snapshot: display/live tier, fee rate, lifetime fee totals, and the plan table as planTable
getPlans()The plan table this deployment serves, no key: each plan’s price, agent fee, perps builder fee, limits and capabilities, plus promoMaxDays. planTableRows() and planTableMarkdown() render it
uploadImage(bytes, contentType)Hosted image, returns a launch-ready imageUrl
importWallet(params)Ciphertext-only import of an existing wallet as a spend-capable linked wallet (Pro/Max)
buildSelfLaunch(req)Unsigned launch transaction (or Hood calldata) for a linked wallet to sign itself
confirmSelfLaunch(req)Verifies a self-signed launch’s own broadcast, records it like launch() does
buildTrade(req)Build a trade; executes inline for a main payer, or returns an unsigned artifact for a linked one
confirmTrade(req)Confirm a linked payer’s broadcast trade after it signs and sends it
submit(req)Hand Candle already-signed legs to broadcast and confirm in one call, instead of broadcasting yourself
trade(req)One-shot build, sign through the relay, and submit. Solana submits server-side; Hood broadcasts client-side
signLinkedTransaction(params)Authorize a build-bound artifact with the wallet’s own P-256 signer, through the Candle sign relay
broadcastSignedTransaction(...)Broadcast bytes you already signed yourself
swapFromLinked(req)Cross-chain: SOL on a linked wallet into ETH or USDG, own-wallets-only destinations
selfLaunch(req)One-shot self-signed launch: build, sign, confirm
buildAtomicLaunch(req)Assemble a launch plus 1 to 4 first buys as one Jito bundle (Pro/Max, Solana)
submitAtomicLaunch(req)Submit that bundle. Returns landed, failed, or timeout as a value, never a throw
launchAtomic(req)Build and submit the atomic bundle in one call
listWallets(opts)The account’s linked wallets
getProfilePnl(keyPrefix)One profile’s realized P&L, fees and marked open positions, split by paying wallet (byWallet) and by chain (byChain), with chain on every position
getPortfolio()What the account’s embedded and TEE wallets hold on Solana and Hood, with prices. Hood is in hood; needs account:read
getSpendLimits()The calling key’s own spend caps (GET /agent/keys/self/limits)
getProfileWallets(keyPrefix), setProfileWallets(keyPrefix, walletIds), setProfileWalletScope(keyPrefix, scope)Read or replace which wallets a profile may spend from, and its selected or all scope; see Agent wallets
getProfileTrades(keyPrefix, opts?)One profile’s trade history, failed trades included
swap(req)One-shot base-asset swap through the account’s embedded wallets, bridging between Solana and Hood when the pair spans them. Moves real funds; clientSwapId makes it idempotent
wallets.swapReceipt(hash)A one-shot swap’s settlement, by any of its hashes
wallets.selfBalances(opts?)Balances of exactly the wallets this key may spend from, with optional extra mints and a cursor
previewCloseEmptyAccounts(req?)The embedded Solana wallet’s empty token accounts and the rent a close returns. Read-only
closeEmptyAccounts(req)Close them and return the rent to the same wallet. Needs transfer:write; clientTradeId makes it idempotent
getCloseEmptyAccountsJob(clientTradeId)Read a close run without sending anything
perpsSetup(ref)Hyperliquid perps: approve Candle’s builder fee once (0.1%) and report the account’s mode and balance; signs nothing when the approval is already there
perpsOpen(params)Open or add to a perps position (market without price, limit with it, optional takeProfit / stopLoss): build, check, sign through the relay, submit to Hyperliquid
perpsClose(params)Close all or part of a position with a reduce-only IOC order
perpsCancel(params)Cancel an order Candle built, by its cloid
perpsModify(params)Change a resting order’s price or size; the replacement gets a new cloid
perpsLeverage(params)Set a market’s leverage and margin mode, within the key’s maximum leverage
perpsMargin(params)Add or remove isolated margin on an isolated position
perpsPositions(walletId), perpsOrders(walletId), perpsFills(walletId), perpsFunding(walletId, startTime?)Live reads from Hyperliquid by the wallet’s address, and Candle’s record of every action it built
perpsConfig()The server’s network, Candle’s builder, this key’s fee and its limits
perpsDeposit(params), perpsDepositStatus(clientDepositId)Fund the Hyperliquid account from a TEE wallet through Relay, and read the deposit’s job. perpsDepositProblem(build, params) is the check the client runs on the build before signing

The perps methods sign from a linked EVM TEE wallet, so they need privyAppId and a secretStore holding the wallet’s signer, like signLinkedTransaction(), and a key with perps:write. Every build is checked before anything is signed: the client recomputes the action hash from the plaintext action, checks that the action type is one Candle builds and that the builder is Candle’s, and refuses otherwise with no relay call. Pin Candle’s builder with the hyperliquidBuilder option; unset, the release’s CANDLE_HYPERLIQUID_BUILDER_ADDRESS applies when it carries one, else the builder perpsConfig() reports the first time. hyperliquidNetwork: "testnet" trades testnet. A failed submit keeps the signature, which stays valid until the nonce leaves Hyperliquid’s two-day window.

launch() reuses the same clientLaunchId across retries, which is what makes retrying safe: the server resolves whether the original attempt already landed instead of minting a second token. It retries network errors, 5xx responses, and the retryable in-flight 409, with jittered exponential backoff bounded by maxRetries (default 3). It never retries a non-retryable envelope.

Two deliberate exceptions, both to keep control with the caller:

  • 429 rate limits are not auto-retried. RATE_LIMITED and DAILY_CAP_REACHED mean waiting minutes or until tomorrow, which is a scheduling decision your agent should make, not a blocking sleep inside a library call.
  • waitForLaunch returns a failed job as a normal terminal result rather than throwing. Branch on status; only exceeding the timeout throws.

Every structured error response becomes a CandleApiError carrying code, status, retryable, and field where the API supplied one. Branch on code, never on message text. Responses that are not structured envelopes throw the same error type with code set to HTTP_<status>.

try {
await candle.launch(req)
} catch (err) {
if (err instanceof CandleApiError && err.code === "INSUFFICIENT_OWNER_BALANCE") {
await topUpWallet()
}
}
import { verifyWebhookSignature } from "@candledottv/agent-sdk"
const ok = verifyWebhookSignature(
process.env.CANDLE_WEBHOOK_SECRET,
req.headers["x-candle-signature"],
rawBody,
Math.floor(Date.now() / 1000),
)

Constant-time comparison and a 300 second default tolerance window. Pass the raw body text exactly as received, not a re-serialized object. See Webhooks for the full delivery contract.