---
name: deployment
description: Deployment workflows for Solana programs — devnet, mainnet, multisig upgrades, CI/CD pipelines, and rollback procedures.
---

# Deployment Workflows

## Deployment Strategy Overview

| Environment | Purpose | Commitment | Upgrades |
|------------|---------|------------|----------|
| **localnet** | Development | processed | Frequent, no restrictions |
| **devnet** | Testing | confirmed | Frequent, test multisig |
| **testnet** | Staging | confirmed | Controlled, production-like |
| **mainnet** | Production | finalized | Rare, full security review |

## Pre-Deployment Checklist

### Code Quality
- [ ] All tests passing (unit, integration, fuzz)
- [ ] Security audit completed (for mainnet)
- [ ] Code review approved
- [ ] No `unwrap()` or `expect()` in program code
- [ ] All arithmetic uses checked operations

### Build Verification
- [ ] Verifiable build successful
- [ ] Binary hash matches expected
- [ ] IDL generated and committed
- [ ] Client SDK generated and tested

### Security Review
- [ ] All accounts validated
- [ ] CPI targets hardcoded
- [ ] PDA bumps stored
- [ ] Accounts reloaded after CPIs
- [ ] Reentrancy considered

### Documentation
- [ ] CHANGELOG updated
- [ ] Migration guide if breaking changes
- [ ] User-facing documentation updated

---

## Devnet Deployment

### First Deployment

```bash
# 1. Build with verifiable flag
anchor build --verifiable

# 2. Get program keypair
solana-keygen new -o target/deploy/my_program-keypair.json

# 3. Airdrop devnet SOL
solana airdrop 2 --url devnet

# 4. Deploy to devnet
anchor deploy --provider.cluster devnet

# 5. Verify deployment
solana program show <PROGRAM_ID> --url devnet
```

### Upgrade Deployment

```bash
# 1. Build new version
anchor build --verifiable

# 2. Verify buffer before upgrade
solana program write-buffer target/deploy/my_program.so --url devnet
solana program show <BUFFER_ADDRESS> --url devnet

# 3. Deploy upgrade
anchor upgrade target/deploy/my_program.so \
    --program-id <PROGRAM_ID> \
    --provider.cluster devnet

# 4. Close old buffers to reclaim SOL
solana program close --buffers --url devnet
```

### IDL Update

```bash
# Update IDL on-chain (required for explorer parsing)
anchor idl upgrade --filepath target/idl/my_program.json \
    --provider.cluster devnet \
    <PROGRAM_ID>
```

---

## Mainnet Deployment

### Pre-Mainnet Verification

```bash
# 1. Full test suite
cargo test --all

# 2. Fuzz testing (critical paths)
trident fuzz run fuzz_0 --iterations 50000

# 3. Build verification
anchor build --verifiable
anchor verify <PROGRAM_ID> --provider.cluster devnet

# 4. Audit tool scan
soteria -analyzeAll .
```

### Mainnet First Deploy

```bash
# 1. Ensure sufficient SOL (deployment costs ~2-5 SOL depending on program size)
solana balance --url mainnet

# 2. Build verifiable
anchor build --verifiable

# 3. Deploy with confirmation prompts
anchor deploy --provider.cluster mainnet

# 4. Immediately verify on-chain
anchor verify <PROGRAM_ID> --provider.cluster mainnet

# 5. Publish IDL
anchor idl init --filepath target/idl/my_program.json \
    --provider.cluster mainnet \
    <PROGRAM_ID>
```

### Mainnet Upgrade (With Multisig)

For production programs, use a multisig upgrade authority:

```bash
# 1. Write new buffer (anyone can do this)
solana program write-buffer target/deploy/my_program.so \
    --url mainnet \
    --buffer-authority <MULTISIG_PDA>

# 2. Verify buffer contents
solana program dump <BUFFER_ADDRESS> buffer_dump.so --url mainnet
diff <(xxd target/deploy/my_program.so) <(xxd buffer_dump.so)

# 3. Create upgrade proposal via multisig UI (e.g., Squads)
# Members review and approve

# 4. Execute upgrade after threshold reached
# (Handled by multisig program)

# 5. Verify post-upgrade
anchor verify <PROGRAM_ID> --provider.cluster mainnet
```

---

## Multisig Setup (Squads v4)

### Creating a Squad for Program Authority

```typescript
import { Squads } from "@sqds/multisig";

async function createProgramSquad() {
  const squads = new Squads({ connection, wallet });

  // Create multisig with 2-of-3 threshold
  const multisigPda = await squads.createMultisig({
    threshold: 2,
    members: [
      { pubkey: member1.publicKey, permissions: { vote: true, execute: true } },
      { pubkey: member2.publicKey, permissions: { vote: true, execute: true } },
      { pubkey: member3.publicKey, permissions: { vote: true, execute: false } },
    ],
  });

  console.log("Multisig created:", multisigPda.toString());
  return multisigPda;
}
```

### Transferring Upgrade Authority to Multisig

```bash
# Transfer program upgrade authority to multisig
solana program set-upgrade-authority <PROGRAM_ID> \
    --new-upgrade-authority <MULTISIG_PDA> \
    --url mainnet
```

### Creating Upgrade Proposals

```typescript
async function proposeUpgrade(
  squads: Squads,
  multisigPda: PublicKey,
  programId: PublicKey,
  bufferAddress: PublicKey
) {
  const transaction = await squads.createTransaction({
    multisig: multisigPda,
    instructions: [
      SystemProgram.createUpgradeInstruction({
        programId,
        bufferAddress,
        upgradeAuthority: multisigPda,
        spillAccount: treasury,
      }),
    ],
  });

  console.log("Upgrade proposal created:", transaction.toString());
}
```

### Upgrade Authority Staging

<!-- Adapted from sendaifun/solana-new (deploy-to-mainnet), MIT -->
Stage the path to immutability rather than freezing at launch:

1. **Launch → ~3 months** — keep upgrade authority on a secured deployer key (hardware wallet). Bugs surface early; you need the ability to ship fixes fast.
2. **~3 months post-launch** — transfer authority to the Squads multisig (above). Removes single-key risk once the program is stable.
3. **Post-audit, battle-tested** — freeze: `solana program set-upgrade-authority <PROGRAM_ID> --final`. Irreversible and removes any rollback path — only after extensive mainnet history and a completed audit.

---

## CI/CD Pipeline

### GitHub Actions Workflow

```yaml
name: Solana Program CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  SOLANA_VERSION: "2.1.0"
  ANCHOR_VERSION: "0.31.1"

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Solana
        uses: solana-labs/solana-actions/setup@v1
        with:
          solana-version: ${{ env.SOLANA_VERSION }}

      - name: Setup Anchor
        run: |
          cargo install --git https://github.com/coral-xyz/anchor anchor-cli --tag v${{ env.ANCHOR_VERSION }}

      - name: Install dependencies
        run: yarn install

      - name: Build
        run: anchor build --verifiable

      - name: Test
        run: anchor test

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: program-artifacts
          path: |
            target/deploy/*.so
            target/idl/*.json

  deploy-devnet:
    needs: test
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: devnet
    steps:
      - uses: actions/checkout@v4

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: program-artifacts

      - name: Setup Solana
        uses: solana-labs/solana-actions/setup@v1

      - name: Configure keypair
        run: |
          echo "${{ secrets.DEVNET_DEPLOYER_KEYPAIR }}" > deployer.json
          solana config set --keypair deployer.json --url devnet

      - name: Deploy to devnet
        run: |
          anchor deploy --provider.cluster devnet

      - name: Verify deployment
        run: |
          anchor verify ${{ vars.PROGRAM_ID }} --provider.cluster devnet

  deploy-mainnet:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: mainnet
    steps:
      - uses: actions/checkout@v4

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: program-artifacts

      - name: Write buffer
        run: |
          # Only write buffer - manual multisig approval required
          echo "${{ secrets.MAINNET_BUFFER_KEYPAIR }}" > buffer.json
          solana program write-buffer target/deploy/my_program.so \
              --url mainnet \
              --keypair buffer.json

      - name: Output buffer address
        id: buffer
        run: echo "::set-output name=address::$(cat buffer-address.txt)"

      - name: Create PR comment
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `🚀 Buffer written to mainnet: \`${{ steps.buffer.outputs.address }}\`\n\nApprove upgrade via Squads multisig.`
            })
```

---

## Rollback Procedures

### Devnet Rollback

```bash
# Redeploy previous version
anchor deploy --provider.cluster devnet \
    --program-id <PROGRAM_ID> \
    target/deploy/my_program_v1.so
```

### Mainnet Rollback

1. **Immediate**: If caught quickly, create emergency multisig proposal for rollback
2. **Planned**: Keep previous verified buffer, create upgrade proposal to previous version
3. **Emergency freeze**: If program has freeze capability, pause operations while preparing rollback

### Freeze Pattern (Program Design)

```rust
#[account]
pub struct GlobalState {
    pub authority: Pubkey,
    pub is_frozen: bool,  // Emergency stop
}

// In each instruction
pub fn some_instruction(ctx: Context<SomeInstruction>) -> Result<()> {
    require!(!ctx.accounts.global_state.is_frozen, ErrorCode::ProgramFrozen);
    // ... instruction logic
}
```

---

## Environment Configuration

### `.env.development`
```env
SOLANA_RPC_URL=http://localhost:8899
SOLANA_WS_URL=ws://localhost:8900
PROGRAM_ID=<LOCAL_PROGRAM_ID>
```

### `.env.devnet`
```env
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_WS_URL=wss://api.devnet.solana.com
PROGRAM_ID=<DEVNET_PROGRAM_ID>
```

### `.env.mainnet`
```env
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
SOLANA_WS_URL=wss://api.mainnet-beta.solana.com
PROGRAM_ID=<MAINNET_PROGRAM_ID>
# Consider premium RPC providers for production:
# - Helius
# - QuickNode
# - Triton
```

---

## Deployment Cost Estimation

| Action | Approximate Cost |
|--------|------------------|
| Deploy small program (<100KB) | ~1.5 SOL |
| Deploy medium program (100-500KB) | ~3-5 SOL |
| Deploy large program (>500KB) | ~5-10 SOL |
| Upgrade (buffer write) | Same as deploy |
| IDL init/upgrade | ~0.01 SOL |
| Close old buffers | Returns ~90% of rent |

**Tip**: Close unused buffers after upgrades to reclaim SOL:
```bash
solana program close --buffers --url mainnet
```

---

## Post-Deployment Verification

### On-Chain Verification

```bash
# Verify program binary matches source
anchor verify <PROGRAM_ID> --provider.cluster mainnet

# Check program authority
solana program show <PROGRAM_ID> --url mainnet

# Verify IDL
anchor idl fetch <PROGRAM_ID> --provider.cluster mainnet > fetched_idl.json
diff target/idl/my_program.json fetched_idl.json
```

### Functional Verification

```bash
# Run smoke tests against deployed program
anchor test --skip-build --provider.cluster devnet

# Monitor logs for first transactions
solana logs <PROGRAM_ID> --url devnet
```

---

## Best Practices Summary

1. **Always use verifiable builds** for production deployments
2. **Use multisig** for mainnet upgrade authority
3. **Test on devnet** before mainnet
4. **Keep deployment keys secure** (HSM or multisig)
5. **Document every deployment** with version and hash
6. **Have a rollback plan** before deploying
7. **Monitor post-deployment** for anomalies
8. **Close old buffers** to reclaim SOL
