---
title: Deploying Programs
description:
  Deploy Solana programs into a Surfnet from local artifacts. Auto-discover
  Anchor workspaces or pass an explicit .so path, raw bytes, and IDL.
---

The SDK can deploy a program from local build artifacts in one call. There are
two flavors: **auto-discovery** for Anchor/Agave workspaces with conventional
paths, and **explicit deployment** when you want to control where the bytes come
from.

Both flavors register the program at the IDL's declared address (or at the
keypair file's public key) and, if an IDL is available, register it with the
runtime via `surfnet_registerIdl` so RPC clients can decode account data.

## Auto-Discover An Anchor Workspace

`deployProgram` / `deploy_program` takes the program name and looks for these
files relative to the current working directory:

| File                                | Purpose                                                            |
| ----------------------------------- | ------------------------------------------------------------------ |
| `target/deploy/{name}.so`           | Compiled program bytecode (required)                               |
| `target/deploy/{name}-keypair.json` | Program keypair — the public key becomes the program ID (required) |
| `target/idl/{name}.json`            | Anchor IDL JSON (optional; registered if present)                  |

<CodeTabs storage="surfpool-lang">

```rust !! title="Rust"
use surfpool_sdk::Surfnet;

let surfnet = Surfnet::start().await?;
let cheats = surfnet.cheatcodes();

let program_id = cheats.deploy_program("my_program")?;
println!("deployed at {program_id}");
```

```ts !! title="TypeScript"
import { Surfnet } from "@solana/surfpool";

const surfnet = Surfnet.start();
try {
  const programId = surfnet.deployProgram("my_program");
  console.log("deployed at", programId);
} finally {
  surfnet.stop();
}
```

</CodeTabs>

<Callout type="info" title="Build before you deploy">
  Run `anchor build` (or `cargo build-sbf`) before calling `deployProgram` so
  the `.so` and keypair artifacts actually exist on disk. The SDK does not
  invoke the build for you.
</Callout>

## Explicit Deployment

When the artifacts live somewhere non-standard — a built CI artifact, a
pre-baked test fixture, an embedded byte slice — pass an explicit deploy
configuration.

<CodeTabs storage="surfpool-lang">

```rust !! title="Rust"
use surfpool_sdk::cheatcodes::builders::DeployProgram;
use surfpool_sdk::Surfnet;

let cheats = Surfnet::start().await?.cheatcodes();

// Derive the program ID from a keypair file.
let program_id = cheats.deploy(
    DeployProgram::from_keypair_path("fixtures/my_program-keypair.json")?
        .so_path("fixtures/my_program.so")
        .idl_path("fixtures/my_program.idl.json"),
)?;
```

```ts !! title="TypeScript"
const surfnet = Surfnet.start();
const programId = "DemoProgram111111111111111111111111111111111";

surfnet.deploy({
  programId,
  soPath: "fixtures/my_program.so",
  idlPath: "fixtures/my_program.idl.json"
});

surfnet.stop();
```

</CodeTabs>

## Deploy From Raw Bytes

When the bytecode is already in memory — generated by a build step, downloaded
from an artifact store, or embedded via `include_bytes!` — skip the file system
entirely.

<CodeTabs storage="surfpool-lang">

```rust !! title="Rust"
use surfpool_sdk::cheatcodes::builders::DeployProgram;
use surfpool_sdk::{Pubkey, Surfnet};

const PROGRAM_BYTES: &[u8] = include_bytes!("../fixtures/my_program.so");

let cheats = Surfnet::start().await?.cheatcodes();
let program_id: Pubkey = "DemoProgram111111111111111111111111111111111".parse()?;

cheats.deploy(
    DeployProgram::new(program_id).so_bytes(PROGRAM_BYTES.to_vec()),
)?;
```

```ts !! title="TypeScript"
import { readFileSync } from "node:fs";

const surfnet = Surfnet.start();
const programBytes = readFileSync("fixtures/my_program.so");

surfnet.deploy({
  programId: "DemoProgramID11111111111111111111111111111",
  soBytes: programBytes
});

surfnet.stop();
```

</CodeTabs>

## DeployProgram Builder Reference (Rust)

| Constructor / setter               | Signature                                         | Purpose                                                            |
| ---------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
| `DeployProgram::new`               | `(program_id: Pubkey) -> Self`                    | Build a deployment for a known program ID.                         |
| `DeployProgram::from_keypair_path` | `(path: impl AsRef<Path>) -> SurfnetResult<Self>` | Read a Solana keypair file; the public key becomes the program ID. |
| `.so_path`                         | `(path: impl Into<PathBuf>)`                      | Path to the compiled `.so` artifact on disk.                       |
| `.so_bytes`                        | `(bytes: Vec<u8>)`                                | Raw program bytes (mutually exclusive with `so_path`).             |
| `.idl_path`                        | `(path: impl Into<PathBuf>)`                      | Optional Anchor IDL JSON to register after deployment.             |

## DeployOptions Type Reference (JS)

```ts
type ByteArrayLike = Uint8Array | number[];

interface DeployOptions {
  programId: string; // base58 address to deploy at
  soPath?: string; // path to .so on disk
  soBytes?: ByteArrayLike; // or raw bytes in memory
  idlPath?: string; // optional Anchor IDL JSON
}
```

`soPath` and `soBytes` are mutually exclusive — provide exactly one.

## What Gets Registered

After a successful deploy, the runtime does three things:

1. **Writes the program account** at `programId` with the `.so` bytes via
   `surfnet_writeProgram`.
2. **Marks the account executable** so it can be invoked through normal RPC
   calls.
3. **Registers the IDL** (if provided) via `surfnet_registerIdl`, which lets RPC
   clients decode account data using the program's IDL schema.

<Callout type="info" title="Rare: non-executable program account">
  If you want to write program bytes to an account but keep it non-executable,
  use `SetAccount` directly instead of `deploy`. The `deploy` path always marks
  the account executable.
</Callout>

## Errors

| Cause                                                              | Symptom                                                              |
| ------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `.so` file is missing or unreadable                                | `SurfnetError::Cheatcode("...failed to read...")` or thrown JS error |
| Program ID does not match the keypair file                         | `SurfnetError::Cheatcode("...mismatched program id...")`             |
| IDL JSON is malformed                                              | `SurfnetError::Cheatcode("...invalid IDL...")`                       |
| Surfnet is offline and the program account already exists upstream | Deploy still succeeds; the local copy overrides the upstream version |
