Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mortemlabs.com/llms.txt

Use this file to discover all available pages before exploring further.

Mortem intercepts your Solana Connection object at the point of transaction submission, recording the signature, cluster, instruction names, and confirmation status for every on-chain action your bot takes. Paired with the built-in market context helpers for Jupiter and Pyth, you get a complete picture of what the chain state looked like at decision time.

What gets captured

Mortem records a solana_tx event for each call to sendTransaction or sendRawTransaction, containing:
  • Transaction signature — the base58 signature returned by the RPC
  • Clustermainnet, devnet, or localnet, inferred from the RPC endpoint URL
  • Instruction names — extracted from the transaction when using sendTransaction with a structured Transaction object
  • Confirmation statusprocessed, confirmed, or finalized, polled in the background after submission
The wrapper uses a Proxy so it intercepts calls transparently. Your existing code does not need to change.

Prerequisites

Install the SDK and create an agent in the dashboard before continuing. You need MORTEM_API_KEY and MORTEM_AGENT_ID set in your environment. @solana/web3.js must be installed separately — Mortem does not depend on it.

Integration

1

Initialize the Mortem client

Create a Mortem instance at module scope.
import { Mortem } from "@mortemlabs/sdk"

const mortem = new Mortem({
  apiKey: process.env.MORTEM_API_KEY ?? "",
  agentId: process.env.MORTEM_AGENT_ID,
  verifyToken: process.env.MORTEM_VERIFY_TOKEN, // remove after first verified run
  environment: "devnet",
})
verifyToken is only needed during your first deployment. Once the dashboard shows the agent as verified, remove MORTEM_VERIFY_TOKEN from your environment and code.
2

Wrap the Solana connection

Pass your Connection instance to mortem.wrapConnection. The wrapper returns a Proxy that behaves identically to the original connection for all methods except sendTransaction and sendRawTransaction, which are intercepted for tracing.
import { Connection } from "@solana/web3.js"

const connection = new Connection(
  process.env.HELIUS_RPC_URL ?? "https://api.devnet.solana.com",
  "confirmed"
)
const tracedConnection = mortem.wrapConnection(connection)
Use tracedConnection everywhere you would have used connection. The proxy is transparent to all other Connection methods including getBalance, getAccountInfo, and getLatestBlockhash.
3

Start a session and send transactions

Create a session and run your trading logic inside session.run. Any transaction submitted through the wrapped connection inside the callback is automatically linked to the active trace.
import { Transaction, SystemProgram, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"

const session = await mortem.startSession({
  inputSummary: "Submit SOL transfer based on agent decision",
  tags: ["solana", "devnet"],
})

try {
  const result = await session.run(async () => {
    const transaction = new Transaction().add(
      SystemProgram.transfer({
        fromPubkey: wallet.publicKey,
        toPubkey: recipient,
        lamports: 0.01 * LAMPORTS_PER_SOL,
      })
    )

    const signature = await tracedConnection.sendTransaction(transaction, [wallet])
    return signature
  })

  await session.complete(`Transaction submitted: ${result}`)
} catch (error) {
  await session.fail(error)
} finally {
  await mortem.close()
}
Always call mortem.close() in a finally block. It flushes the trace buffer and ensures all events reach the ingest service before the process exits.

Recording market context

Mortem provides two helper functions for capturing market state at the moment your bot makes a decision. Use these to record a Jupiter quote or Pyth prices as a custom event on the trace, giving you a precise snapshot of the market context that drove the decision.

Jupiter quotes

fetchJupiterQuote fetches a swap quote from the Jupiter v6 Quote API and returns a structured record you can attach to a custom trace event.
import { fetchJupiterQuote } from "@mortemlabs/sdk"

const SOL_MINT = "So11111111111111111111111111111111111111112"
const JUP_MINT = "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"

const jupiterContext = await fetchJupiterQuote({
  inputMint: SOL_MINT,
  outputMint: JUP_MINT,
  amount: "1000000000", // 1 SOL in lamports
  slippageBps: 50,      // 0.5% slippage tolerance
})
The return value is a Record<string, JupiterRoute> keyed by inputMint:outputMint:amount. Each route includes:
FieldDescription
inAmountInput token amount
outAmountExpected output token amount
inputMintInput token mint address
outputMintOutput token mint address
priceImpactPctEstimated price impact as a percentage
routePlanArray of route plan steps from the Jupiter API
responseHashSHA-256 hash of the raw API response for verification
capturedAtMsTimestamp when the quote was fetched
fetchJupiterQuote has a 5-second timeout and returns an empty object on network or parsing errors, so it will not crash your agent.

Pyth prices

fetchPythPrices fetches benchmark price feeds from the Pyth Network API for one or more symbols.
import { fetchPythPrices } from "@mortemlabs/sdk"

const pythPrices = await fetchPythPrices(["SOL/USD", "JUP/USD"])
The return value is a Record<string, PythPriceEntry>. Each entry includes:
FieldDescription
priceCurrent price
confidenceConfidence interval around the price
exponentDecimal exponent for the price value
publishTimeUnix timestamp of the price publication
attestationRaw attestation or VAA string from the Pyth API
Results are cached in memory for 60 seconds. On network failure, fetchPythPrices returns the cached result if available, otherwise an empty object.

Recording context as custom events

Attach market data to the trace using session.beginEvent with the "custom" type. Record the snapshot before your agent makes its decision so the trace shows what the bot saw at the critical moment.
import { Mortem, fetchJupiterQuote, fetchPythPrices } from "@mortemlabs/sdk"
import { Connection } from "@solana/web3.js"

const mortem = new Mortem({
  apiKey: process.env.MORTEM_API_KEY ?? "",
  agentId: process.env.MORTEM_AGENT_ID,
  verifyToken: process.env.MORTEM_VERIFY_TOKEN,
  environment: "devnet",
})

const connection = mortem.wrapConnection(
  new Connection(process.env.HELIUS_RPC_URL ?? "https://api.devnet.solana.com", "confirmed")
)

const SOL_MINT = "So11111111111111111111111111111111111111112"
const JUP_MINT = "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"

const session = await mortem.startSession({
  inputSummary: "Evaluate SOL→JUP swap opportunity and submit if conditions are met",
  tags: ["solana", "jupiter", "pyth", "devnet"],
})

try {
  const result = await session.run(async () => {
    // Fetch market context
    const [jupiterQuote, pythPrices] = await Promise.all([
      fetchJupiterQuote({
        inputMint: SOL_MINT,
        outputMint: JUP_MINT,
        amount: "1000000000",
        slippageBps: 50,
      }),
      fetchPythPrices(["SOL/USD", "JUP/USD"]),
    ])

    // Record the market snapshot as a custom event
    const marketEvent = session.beginEvent("custom", {
      step: "market-context",
      jupiterRoutes: jupiterQuote,
      pythPrices,
    })
    marketEvent.complete({
      payload: {
        step: "market-context",
        jupiterRoutes: jupiterQuote,
        pythPrices,
      },
    })

    // Make and record the trading decision
    const solPrice = pythPrices["SOL/USD"]?.price ?? 0
    const shouldSwap = solPrice > 100

    const decisionEvent = session.beginEvent("custom", {
      step: "decision",
      shouldSwap,
      solPrice,
    })
    decisionEvent.complete({
      payload: { step: "decision", shouldSwap, solPrice },
    })

    if (!shouldSwap) {
      return { submitted: false, reason: "price below threshold" }
    }

    // Submit the transaction — automatically traced by the wrapped connection
    const transaction = buildSwapTransaction(jupiterQuote)
    const signature = await connection.sendTransaction(transaction, [wallet])

    return { submitted: true, signature }
  })

  await session.complete(
    result.submitted ? `Swap submitted: ${result.signature}` : result.reason
  )
} catch (error) {
  await session.fail(error)
} finally {
  await mortem.close()
}

Confirmation polling

When a transaction signature is returned by sendTransaction or sendRawTransaction, the wrapped connection automatically starts background polling for confirmation. It calls connection.confirmTransaction(signature, "confirmed") up to 10 times at 1-second intervals and updates the solana_tx event payload with the final confirmationStatus when the transaction reaches confirmed or finalized.
Confirmation polling is best-effort and runs in the background. It does not block your agent or throw on timeout — the solana_tx event is recorded immediately when the signature is received, and the confirmation status is appended when available.

Cluster detection

The cluster field on solana_tx events is inferred from the RPC endpoint URL:
RPC URL patternCluster recorded
Contains mainnetmainnet
Contains localhost or 127.0.0.1localnet
All other URLsdevnet
If you run on a custom RPC with a non-standard URL, the cluster will be recorded as devnet. This affects display in the dashboard but has no impact on tracing behavior.