Skip to main content
The Server SDK is currently experimental and not ready for production use. Reach out to partnerships@phantom.com for access.
This guide covers how to sign and send transactions using the Phantom Server SDK across different blockchain networks.

Overview

The SDK provides two methods for handling transactions:
  • signAndSendTransaction signs the transaction and submits it to the network in one step.
  • signTransaction signs the transaction and returns it without submitting. Use this when you need to broadcast the transaction yourself or inspect the signed payload before sending.
// Sign and send in one step
const result = await sdk.signAndSendTransaction({
  walletId,
  transaction,
  networkId,
});

// Sign only (no network submission)
const signed = await sdk.signTransaction({
  walletId,
  transaction,
  networkId,
});

Sign without sending

Use signTransaction when you want to sign a transaction without broadcasting it. This is useful when:
  • You want to broadcast via your own RPC endpoint or a relay service.
  • You need to inspect the signed transaction before sending.
  • You are collecting multiple signatures before submission.

Solana example

import { ServerSDK, NetworkId } from "@phantom/server-sdk";
import { Transaction, SystemProgram, PublicKey, Connection } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const { blockhash } = await connection.getLatestBlockhash();

const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: new PublicKey(fromAddress),
    toPubkey: new PublicKey(toAddress),
    lamports: 1_000_000, // 0.001 SOL
  }),
);
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(fromAddress);

// Sign without sending
const signed = await sdk.signTransaction({
  walletId: wallet.walletId,
  transaction,
  networkId: NetworkId.SOLANA_MAINNET,
});

console.log("Signed transaction:", signed.rawTransaction);

// Submit later using your own connection
await connection.sendRawTransaction(
  Buffer.from(signed.rawTransaction, "base64url"),
);

Ethereum example

const evmTransaction = {
  to: "0x742d35Cc6634C0532925a3b8D4C8db86fB5C4A7E",
  value: 1000000000000000000n, // 1 ETH in wei
  data: "0x",
  gasLimit: 21000n,
};

const signed = await sdk.signTransaction({
  walletId: wallet.walletId,
  transaction: evmTransaction,
  networkId: NetworkId.ETHEREUM_MAINNET,
});

console.log("Signed transaction:", signed.rawTransaction);
// Broadcast using your preferred provider

Sign and send

Use signAndSendTransaction when you want the SDK to handle both signing and network submission.

Basic Solana transfer

The following is a complete example of sending SOL from one address to another:
import { ServerSDK, NetworkId } from '@phantom/server-sdk';
import { 
  Connection, 
  Transaction, 
  SystemProgram, 
  PublicKey,
  LAMPORTS_PER_SOL 
} from '@solana/web3.js';
import bs58 from 'bs58';

async function sendSOL(
  sdk: ServerSDK,
  walletId: string,
  fromAddress: string,
  toAddress: string,
  amountSOL: number
) {
  // Connect to Solana network
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  
  // Create transaction
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: new PublicKey(fromAddress),
      toPubkey: new PublicKey(toAddress),
      lamports: amountSOL * LAMPORTS_PER_SOL
    })
  );
  
  // Get recent blockhash
  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.feePayer = new PublicKey(fromAddress);
  

  // Sign and send - SDK handles both!
  const signedResult = await sdk.signAndSendTransaction({
    walletId,
    transaction,
    networkId: NetworkId.SOLANA_MAINNET
  });

  console.log('Transaction signed and sent!');
  
  // Extract signature from the returned signed transaction
  const signedTx = Transaction.from(
    Buffer.from(signedResult.rawTransaction, 'base64url')
  );
  
  let signature: string;
  if (signedTx.signature) {
    signature = bs58.encode(signedTx.signature);
  } else if (signedTx.signatures?.[0]?.signature) {
    signature = bs58.encode(signedTx.signatures[0].signature);
  } else {
    throw new Error('Failed to extract signature');
  }
  
  console.log(`Transaction signature: ${signature}`);
  
  // Wait for confirmation
  const confirmation = await connection.confirmTransaction(signature);
  
  return {
    signature,
    confirmed: !confirmation.value.err
  };
}

Complete example with priority fees

Based on the SDK demo, the following example shows how to send a transaction with priority fees:
import { ComputeBudgetProgram } from '@solana/web3.js';

async function sendWithPriorityFee(
  sdk: ServerSDK,
  walletId: string,
  fromAddress: string,
  toAddress: string,
  amountSOL: number
) {
  const connection = new Connection('https://api.devnet.solana.com');
  const transaction = new Transaction();
  
  // Add priority fee instruction first
  const priorityFee = 1000; // micro-lamports per compute unit
  transaction.add(
    ComputeBudgetProgram.setComputeUnitPrice({
      microLamports: priorityFee
    })
  );
  
  // Add transfer instruction
  transaction.add(
    SystemProgram.transfer({
      fromPubkey: new PublicKey(fromAddress),
      toPubkey: new PublicKey(toAddress),
      lamports: Math.floor(amountSOL * LAMPORTS_PER_SOL)
    })
  );
  
  // Prepare transaction
  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.feePayer = new PublicKey(fromAddress);
  

  // Sign and send (SDK does both!)
  const signedResult = await sdk.signAndSendTransaction({
    walletId,
    transaction,
    networkId: NetworkId.SOLANA_DEVNET
  });

  // Extract signature
  const signedTx = Transaction.from(
    Buffer.from(signedResult.rawTransaction, 'base64url')
  );
  const signature = bs58.encode(signedTx.signatures[0].signature);
  
  console.log(`Transaction sent with signature: ${signature}`);
  
  // Wait for confirmation
  const startTime = Date.now();
  let confirmed = false;
  let attempts = 0;
  
  while (!confirmed && attempts < 30) {
    const status = await connection.getSignatureStatus(signature);
    
    if (status?.value) {
      if (status.value.err) {
        throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`);
      }
      
      if (status.value.confirmationStatus === 'confirmed' || 
          status.value.confirmationStatus === 'finalized') {
        confirmed = true;
        const elapsed = (Date.now() - startTime) / 1000;
        console.log(`Confirmed in ${elapsed.toFixed(1)} seconds`);
      }
    }
    
    if (!confirmed) {
      attempts++;
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  return { signature, confirmed };
}

Best practices

  • The SDK handles both signing AND sending: You don’t need to manually submit transactions.
  • Always use fresh blockhashes: Get a new blockhash right before creating the transaction.
  • Extract signatures from the result: The SDK returns the signed transaction, not the signature directly.
  • Handle confirmations separately: The SDK sends the transaction but doesn’t wait for confirmation.
  • Use proper error handling: Network issues and blockchain errors can occur.

Next steps

Disclaimers

The Server SDK is a beta version, and Phantom will not be liable for any losses or damages suffered by you or your end users. Any suggestions, enhancement requests, recommendations, or other feedback provided by you regarding the Server SDK will be the exclusive property of Phantom. By using this beta version and providing feedback, you agree to assign any rights in that feedback to Phantom.