Skip to main content
Most mutation endpoints in the v1 API return partially-signed gas abstracted transactions that need to be signed by the required signers before submission to the Solana network.

Transaction Lifecycle

  1. Transaction Creation: API returns a partially-signed transaction
  2. Signer Collection: Required signers add their signatures
  3. Transaction Submission: Fully signed transaction is submitted to Solana

Understanding Transaction Responses

When you make a request that requires blockchain interaction, you’ll receive a response like:
{
  "transaction": "base58encodedtransaction...",
  "fee": {
    "amount": 5000,
    "amount_decimal": "0.000005",
    "currency": "SOL",
    "sol_equivalent": {
      "amount": 5000,
      "amount_decimal": "0.000005"
    }
  }
}
The transaction field contains a base58-encoded transaction that is:
  • Already signed by SQDS for gas abstraction
  • Ready for additional signatures from required signers
  • Valid for approximately 2 minutes due to Solana’s recent blockhash mechanism

Signing Process

Using Web3.js

import { Transaction } from "@solana/web3.js";

// Decode the transaction
const decodedTx = Transaction.from(
  Buffer.from(response.data.transaction, "base58")
);

// Sign with required signers
const signedTx = await wallet.signTransaction(decodedTx);

// Submit the transaction
await client.submitTransaction(signedTx);

Using the SQDS SDK

Our SDK provides helper methods for signing:
import { SqdsClient } from "@sqds/client";

const client = new SqdsClient({
  apiKey: "YOUR_API_KEY",
  cluster: "mainnet-beta",
});

// Sign and submit in one step
await client.signAndSubmit(response.data.transaction, [signer1, signer2]);

// Or handle signing manually
const signedTx = await client.collectSignatures(response.data.transaction, [
  signer1,
  signer2,
]);
await client.submitTransaction(signedTx);

Multi-Signature Transactions

For smart accounts requiring multiple signers:
// Collect signatures sequentially
let tx = Transaction.from(Buffer.from(response.data.transaction, "base58"));

// First signer
tx = await signer1.signTransaction(tx);

// Second signer
tx = await signer2.signTransaction(tx);

// Submit fully signed transaction
await client.submitTransaction(tx);
Or collect signatures in parallel:
const tx = Transaction.from(Buffer.from(response.data.transaction, "base58"));

// Collect signatures in parallel
const signatures = await Promise.all([
  signer1.signTransaction(tx),
  signer2.signTransaction(tx),
]);

// Combine signatures
const signedTx = client.combineSignatures(tx, signatures);

// Submit fully signed transaction
await client.submitTransaction(signedTx);

Transaction Validity

Transactions have a limited validity window (~2 minutes) due to Solana’s recent blockhash mechanism. If a transaction expires:
  1. Request a new transaction from the API
  2. Collect signatures again
  3. Submit the new transaction

Error Handling

Common signing-related errors:
try {
  await client.submitTransaction(signedTx);
} catch (error) {
  if (error.code === "INVALID_SIGNATURES") {
    // Missing required signatures
    console.error("Transaction missing required signatures");
  } else if (error.code === "EXPIRED_TRANSACTION") {
    // Transaction blockhash expired
    console.error("Transaction expired, request a new one");
  }
}

Best Practices

  1. Signature Order: Order doesn’t matter for Solana transactions
  2. Expiration Handling: Always check for and handle expired transactions
  3. Gas Abstraction: Don’t modify the SQDS signature for gas abstraction
  4. Parallel Signing: Use parallel signing for better UX with multiple signers
  5. Error Recovery: Implement proper error handling for failed signatures