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
| Network | Ogmios Endpoint | Submit API Endpoint |
|---|---|---|
| Mainnet | api.nacho.builders/v1/ogmios | api.nacho.builders/v1/submit |
| Preprod | api.nacho.builders/v1/preprod/ogmios | api.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 confirmationThe 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');
}Option 2: Chain Synchronization (Recommended)
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
| Tier | Submit Rate | Notes |
|---|---|---|
| FREE | 10 tx/hour | For testing |
| PAID | Unlimited | Production use |
Transaction submission counts toward your API usage. Each submit call uses 1 credit on PAID tier.
Best Practices
- Always evaluate first - Catch errors before submitting
- Set appropriate TTL - Give enough time for inclusion (e.g., current slot + 7200 for ~2 hours)
- Handle retries carefully - Same transaction can only be submitted once
- Monitor mempool - Large transactions may take longer to include
- Verify confirmation - Don't assume success until on-chain
Was this page helpful?