> ## 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.

# Trace Solana transactions from your bot with Mortem

> Wrap your Solana web3.js Connection with Mortem to record transaction signatures, confirmation status, and on-chain market context at decision time.

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
* **Cluster** — `mainnet`, `devnet`, or `localnet`, inferred from the RPC endpoint URL
* **Instruction names** — extracted from the transaction when using `sendTransaction` with a structured `Transaction` object
* **Confirmation status** — `processed`, `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

<Steps>
  <Step title="Initialize the Mortem client">
    Create a `Mortem` instance at module scope.

    ```ts theme={null}
    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",
    })
    ```

    <Note>
      `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.
    </Note>
  </Step>

  <Step title="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.

    ```ts theme={null}
    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`.
  </Step>

  <Step title="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.

    ```ts theme={null}
    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()
    }
    ```

    <Warning>
      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.
    </Warning>
  </Step>
</Steps>

## 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.

```ts theme={null}
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:

| Field            | Description                                           |
| ---------------- | ----------------------------------------------------- |
| `inAmount`       | Input token amount                                    |
| `outAmount`      | Expected output token amount                          |
| `inputMint`      | Input token mint address                              |
| `outputMint`     | Output token mint address                             |
| `priceImpactPct` | Estimated price impact as a percentage                |
| `routePlan`      | Array of route plan steps from the Jupiter API        |
| `responseHash`   | SHA-256 hash of the raw API response for verification |
| `capturedAtMs`   | Timestamp 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.

```ts theme={null}
import { fetchPythPrices } from "@mortemlabs/sdk"

const pythPrices = await fetchPythPrices(["SOL/USD", "JUP/USD"])
```

The return value is a `Record<string, PythPriceEntry>`. Each entry includes:

| Field         | Description                                     |
| ------------- | ----------------------------------------------- |
| `price`       | Current price                                   |
| `confidence`  | Confidence interval around the price            |
| `exponent`    | Decimal exponent for the price value            |
| `publishTime` | Unix timestamp of the price publication         |
| `attestation` | Raw 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.

```ts theme={null}
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`.

<Note>
  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.
</Note>

## Cluster detection

The cluster field on `solana_tx` events is inferred from the RPC endpoint URL:

| RPC URL pattern                     | Cluster recorded |
| ----------------------------------- | ---------------- |
| Contains `mainnet`                  | `mainnet`        |
| Contains `localhost` or `127.0.0.1` | `localnet`       |
| All other URLs                      | `devnet`         |

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.
