Intermediate12 min read
Edit on GitHub

Submitting Transactions

Submit signed transactions to the Cardano network

This guide covers how to submit signed transactions to the Cardano network and handle the response.

Choose Your Network

NetworkOgmios EndpointSubmit API Endpoint
Mainnetapi.nacho.builders/v1/ogmiosapi.nacho.builders/v1/submit
Preprodapi.nacho.builders/v1/preprod/ogmiosapi.nacho.builders/v1/preprod/submit

Always test on Preprod first! Get free test ADA from the Cardano Faucet.

Transaction Submission Flow

1. Build transaction (off-chain)
2. Sign transaction (wallet/key)
3. Serialize to CBOR
4. Submit via API
5. Wait for confirmation

The Nacho API accepts signed, serialized transactions in CBOR format. You must build and sign transactions using a library like cardano-serialization-lib, Lucid, or MeshJS before submitting.

Submit a Transaction

// Assuming you have a signed transaction in CBOR hex format
const signedTxCbor = '84a400...'; // Your signed transaction

const response = await fetch('https://api.nacho.builders/v1/ogmios', {
method: 'POST',
headers: {
  'Content-Type': 'application/json',
  'apikey': process.env.NACHO_API_KEY
},
body: JSON.stringify({
  jsonrpc: '2.0',
  method: 'submitTransaction',
  params: {
    transaction: {
      cbor: signedTxCbor
    }
  }
})
});

const { result, error } = await response.json();

if (error) {
console.error('Submission failed:', error.message);
console.error('Details:', error.data);
} else {
console.log('Transaction submitted!');
console.log('TxHash:', result.transaction.id);
}

Success Response

When a transaction is accepted:

{
  "jsonrpc": "2.0",
  "method": "submitTransaction",
  "result": {
    "transaction": {
      "id": "3e40d...abcdef"
    }
  }
}

The transaction is now in the mempool and will be included in a future block.

Common Submission Errors

Insufficient Funds

{
  "error": {
    "code": 3001,
    "message": "Insufficient funds",
    "data": {
      "required": { "ada": { "lovelace": 5000000 } },
      "available": { "ada": { "lovelace": 2000000 } }
    }
  }
}

Solution: Add more ADA to cover the transaction amount plus fees.

Expired TTL

{
  "error": {
    "code": 3003,
    "message": "Transaction expired",
    "data": {
      "ttl": 12345678,
      "currentSlot": 12345700
    }
  }
}

Solution: Rebuild the transaction with a future TTL (Time To Live).

Already Spent UTxO

{
  "error": {
    "code": 3005,
    "message": "UTxO already spent",
    "data": {
      "spentInputs": [
        { "transaction": { "id": "abc..." }, "index": 0 }
      ]
    }
  }
}

Solution: Query fresh UTxOs and rebuild the transaction.

Script Execution Failed

{
  "error": {
    "code": 3004,
    "message": "Script execution failed",
    "data": {
      "validationError": "ExUnitsTooBig",
      "scriptHash": "abc123..."
    }
  }
}

Solution: Use evaluateTransaction first to check execution units, or optimize your script.

Evaluate Before Submitting

Always evaluate transactions before submitting to catch errors early:

/**
 * Two-phase transaction submission: evaluate first, then submit.
 * This catches script errors before they cost real ADA in fees.
 *
 * @param signedTxCbor - Signed transaction in CBOR hex format
 * @returns Transaction hash on success
 */
async function evaluateAndSubmit(signedTxCbor) {
  // PHASE 1: Evaluate transaction (dry run)
  // This checks script execution without committing to the blockchain
  const evalResponse = await fetch('https://api.nacho.builders/v1/ogmios', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'apikey': process.env.NACHO_API_KEY
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'evaluateTransaction',
      params: {
        transaction: { cbor: signedTxCbor }
      }
    })
  });

  const evalData = await evalResponse.json();

  // Check if evaluation failed (script error, invalid inputs, etc.)
  if (evalData.error) {
    throw new Error(`Evaluation failed: ${evalData.error.message}`);
  }

  // Log execution units for debugging and fee estimation
  console.log('Execution units:', evalData.result);

  // PHASE 2: Submit transaction (only if evaluation passed)
  const submitResponse = await fetch('https://api.nacho.builders/v1/ogmios', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'apikey': process.env.NACHO_API_KEY
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'submitTransaction',
      params: {
        transaction: { cbor: signedTxCbor }
      }
    })
  });

  const submitData = await submitResponse.json();

  // Check for submission errors (expired TTL, already spent UTxOs, etc.)
  if (submitData.error) {
    throw new Error(`Submission failed: ${submitData.error.message}`);
  }

  // Return the transaction hash for tracking
  return submitData.result.transaction.id;
}

Wait for Confirmation

After submission, the transaction is in the mempool. To confirm it's on-chain:

Option 1: Poll the UTxO Set

/**
 * Wait for a transaction to be confirmed by polling UTxOs.
 * Simple approach suitable for most use cases.
 *
 * @param txHash - Transaction hash to wait for
 * @param outputAddress - Address that should receive the output
 * @param maxAttempts - Maximum poll attempts before timeout
 */
async function waitForConfirmation(txHash, outputAddress, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    // Query UTxOs at the output address
    const utxos = await getAddressUtxos(outputAddress);

    // Check if any UTxO was created by our transaction
    const found = utxos.find(u => u.transaction.id === txHash);
    if (found) {
      console.log('Transaction confirmed!');
      return found;  // Return the UTxO for further use
    }

    // Wait before next poll (blocks are ~20 seconds apart)
    await new Promise(r => setTimeout(r, 10000));
  }

  // Transaction didn't confirm in time - may need investigation
  throw new Error('Transaction not confirmed in time');
}

Use WebSocket chain sync to watch for your transaction:

// Connect to chain sync WebSocket
const ws = new WebSocket(`wss://api.nacho.builders/v1/ogmios?apikey=${API_KEY}`);

ws.onopen = () => {
  // Start syncing from recent point to catch new blocks
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'findIntersection',
    params: { points: ['origin'] }  // Use recent point in production
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.result?.block) {
    const block = data.result.block;

    // Search this block's transactions for our tx hash
    const found = block.transactions?.find(tx => tx.id === targetTxHash);
    if (found) {
      // Transaction is now confirmed on-chain!
      console.log('Transaction confirmed in block:', block.height);
      ws.close();  // Done watching - close the connection
      return;
    }
  }

  // Not found yet - request the next block
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'nextBlock'
  }));
};

Rate Limits for Submission

TierSubmit RateNotes
FREE10 tx/hourFor testing
PAIDUnlimitedProduction use

Transaction submission counts toward your API usage. Each submit call uses 1 credit on PAID tier.

Best Practices

  1. Always evaluate first - Catch errors before submitting
  2. Set appropriate TTL - Give enough time for inclusion (e.g., current slot + 7200 for ~2 hours)
  3. Handle retries carefully - Same transaction can only be submitted once
  4. Monitor mempool - Large transactions may take longer to include
  5. Verify confirmation - Don't assume success until on-chain

Was this page helpful?