Deposit with the JavaScript SDK
Use the SDK to sign a deposit drafted by an open Hydra head. The node adds its
fuel-wallet witness; the client adds the spending witness and submits the
complete transaction to Cardano. The API endpoint remains POST /commit.
Set up
Use Node.js 22 or later, a preprod Hydra node with an open head and fuel, a funded preprod wallet, and a Blockfrost preprod project. This example pins the SDK version so its signing behavior can be reproduced.
mkdir deposit-demo
cd deposit-demo
npm init -y
npm install --save-exact @hydra-sdk/core@1.1.5 dotenv@17.2.3 ws@8.18.3
npm install --save-dev --save-exact tsx@4.21.0 typescript@5.9.3 @types/ws@8.18.1 @types/node@22
Keep the generated lockfile. Put these values in .env, using a wallet
created only for test funds. Exclude .env from version control.
BLOCKFROST_API_KEY=your_preprod_project_id
WALLET_MNEMONIC=your test wallet mnemonic
HYDRA_API=http://127.0.0.1:4001
HYDRA_WS=ws://127.0.0.1:4001
Save config.ts:
import 'dotenv/config'
import { AppWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core'
function required(name: string): string {
const value = process.env[name]
if (!value) throw new Error(`Set ${name} in .env`)
return value
}
const provider = new ProviderUtils.BlockfrostProvider({
apiKey: required('BLOCKFROST_API_KEY'),
network: 'preprod',
})
export const wallet = new AppWallet({
key: { type: 'mnemonic', words: required('WALLET_MNEMONIC').trim().split(/\s+/) },
networkId: NETWORK_ID.PREPROD,
fetcher: provider.fetcher,
submitter: provider.submitter,
})
export async function post(path: string, body: string) {
const response = await fetch(`${process.env.HYDRA_API ?? 'http://127.0.0.1:4001'}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
})
const text = await response.text()
if (!response.ok) throw new Error(`${response.status}: ${text}`)
return text ? JSON.parse(text) : null
}
Observe settlement
Save monitor.ts and run it in a separate terminal before submitting a
deposit. It does not initialize or use the wallet.
import 'dotenv/config'
import WebSocket from 'ws'
const url = new URL(process.env.HYDRA_WS ?? 'ws://127.0.0.1:4001')
url.searchParams.set('history', 'no')
const socket = new WebSocket(url)
const depositEvents = new Set([
'CommitRecorded', 'DepositActivated', 'CommitApproved',
'CommitFinalized', 'DepositExpired', 'CommitRecovered',
])
socket.on('open', () => console.log('Listening for deposit events'))
socket.on('error', console.error)
socket.on('message', data => {
const event = JSON.parse(data.toString())
if (depositEvents.has(event.tag)) console.log(event)
})
npx tsx monitor.ts
CommitRecorded means the deposit was observed on L1. DepositActivated
means it is eligible for inclusion; CommitApproved means all parties signed
the snapshot. Wait for CommitFinalized for your deposit transaction ID
before spending its outputs. Default activation takes roughly an hour plus
the upper-validity offset. See deposit timing.
Sign and submit
Save deposit.ts. With no argument it deposits one ADA-only UTxO from the
wallet. With a filename it sends that deposit request unchanged, preserving
exact integer values in its JSON.
import { readFile } from 'node:fs/promises'
import { Converter } from '@hydra-sdk/core'
import { post, wallet } from './config'
async function main() {
let request: string
if (process.argv[2]) {
request = await readFile(process.argv[2], 'utf8')
} else {
const address = wallet.getAccount().baseAddressBech32
const utxos = await wallet.queryUTxOs(address)
const selected = utxos.find(utxo => {
const assets = utxo.output.amount
return assets.length === 1 && assets[0].unit === 'lovelace'
&& BigInt(assets[0].quantity) >= 5_000_000n
&& BigInt(assets[0].quantity) <= BigInt(Number.MAX_SAFE_INTEGER)
})
if (!selected) throw new Error('Fund an ADA-only UTxO of at least 5 ADA')
request = JSON.stringify(Converter.convertUTxOToUTxOObject([selected]))
}
const draft = await post('/commit', request)
// Partial signing adds a witness and preserves the node's existing witness.
const signed = await wallet.signTx(draft.cborHex, true)
const result = await post('/cardano-transaction', JSON.stringify({
type: 'Tx ConwayEra',
description: 'Signed deposit',
cborHex: signed,
}))
console.log('Submitted to L1:', result)
console.log('Wait for CommitFinalized before spending on L2')
}
main().catch(error => {
console.error(error)
process.exitCode = 1
})
npx tsx deposit.ts
In this SDK version, signTx(cbor, true) returns a complete transaction with
an added witness, not a standalone witness set. Do not change the transaction
body or output order after the node drafts it.
Blueprint and script deposits
The same signing code accepts a request produced by the partial-deposit tutorial or script-UTxO tutorial:
npx tsx deposit.ts commit-request.json
Build the blueprint using the actual inputs owned by this wallet, or arrange
all other required witnesses before submission. Include the UTxO context for
its spending and reference inputs. Use explicit outputs when including
reference-input context: a blueprint without outputs deposits the entire
supplied UTxO map. Explicit blueprint outputs become L2
outputs; changeAddress returns the remaining input value on L1. Do not add
an L1 change output to the blueprint itself unless you intend to deposit it.
For a script deposit, preserve its script address and datum if it must remain script-controlled on L2. A blueprint that pays a key address changes that ownership. Follow dApp validator guidance for application-specific checks.
Recovery and errors
Inspect HTTP errors before retrying. DepositTooLow means the recorded
outputs need more ADA to cover the deposit output's inline datum;
DepositTooLarge means the deposit cannot be claimed within L1 limits. A
submitted deposit that expires may be recovered after its deadline, subject
to the rollback restrictions.

