Cloak API

Signing the transaction

The transaction field is a base64-encoded Solana transaction: v1 by default, or v0 if you ask for it. Sign the raw bytes and submit the exact signed bytes. That works for both versions.

Changed September 2026: deposit transactions are v1 by default

order.transaction is now a Solana transaction v1 unless you ask otherwise. Order and refresh responses carry transactionVersion (0 or 1) saying what was built.

What to do: sign the raw transaction bytes as shown below. If your signer cannot handle v1, notably @solana/web3.js 1.x, send "txVersion": 0 on POST /order (and ?txVersion=0 on refresh) to keep receiving v0.

What you receive

By default the transaction is a Solana transaction v1: every account is inline (no address lookup table), and the compute-unit limit, loaded-accounts-data limit and priority fee are in the message header instead of ComputeBudget instructions. The sender is the only signer. With "txVersion": 0 it is a v0 transaction against our deposit lookup table.

Branch on transactionVersion, not on what you requested

A v1 request comes back as v0 if Cloak cannot build v1. transactionVersion in the response is authoritative.

Library support

Stackv1 supportWhat to do
@veilo/sdk-core/tx-v1yesisV1Transaction + signV1Bytes(bytes, keypair) (0.9.1+). Send the bytes with sendRawTransaction or base64 sendTransaction.
@solana/kityesgetTransactionDecoder → signTransaction → getBase64EncodedWireTransaction. Verified on 6.5, 7.1 and 8.3.
Wallet Standardif the wallet doessolana:signTransaction with transaction: Uint8Array. Check that supportedTransactionVersions includes 1 at runtime (typed from @solana/wallet-standard-features 1.5.0); otherwise request txVersion: 0.
@solana/web3.js 1.xnoRequest txVersion: 0.
solders (Python)0.29.0+Same code as v0. Older: upgrade or request txVersion: 0.
solana-gov1.23.0+Same code as v0. Older: upgrade or request txVersion: 0.
solana-transaction (Rust)4.3 + wincodeDecode and encode with wincode, never bincode. Otherwise request txVersion: 0.

Sign bytes, not web3.js 1.x objects

@solana/web3.js 1.x cannot sign a v1 transaction: 1.99 decodes it, but VersionedTransaction.sign() and serialize() throw, and 1.98 and earlier cannot decode it at all. The same goes for wallet adapters that take a web3.js Transaction or VersionedTransaction object.

If you are on that stack, send "txVersion": 0 on POST /order (and ?txVersion=0 on refresh) and keep your existing v0 code. connection.sendRawTransaction(bytes) is fine for v1: it only base64-encodes the bytes you give it.

Staying on v0

  • Send "txVersion": 0 (a JSON number) when you create the order. Any value other than 0 or 1 returns 400 INVALID_TX_VERSION.
  • The order remembers the choice. A refresh without a query rebuilds the same version.
  • Orders created before v1 became the default rebuild as v1 on refresh. Pass ?txVersion=0 if you still need v0.

In a browser: Wallet Standard

Pass the raw bytes to the user's wallet and never touch a key. Check at runtime that the wallet can sign v1, and fall back to v0 if it cannot. Cloak has not confirmed which major wallets advertise v1 yet, and Ledger's Solana app did not sign v1 as of September 2026, so keep the txVersion: 0 fallback.

Wallet Standard, raw bytes
const bytes   = Uint8Array.from(atob(order.transaction), (c) => c.charCodeAt(0));
const feature = wallet.features["solana:signTransaction"];
if (order.transactionVersion === 1 && !feature.supportedTransactionVersions.includes(1)) {
  // This wallet cannot sign v1: have your backend refresh the order with
  // ?txVersion=0 and sign that transaction instead.
}
const [{ signedTransaction }] = await feature.signTransaction({
  account,                                // the sender's WalletAccount
  chain: "solana:mainnet",
  transaction: bytes,
});
// Submit signedTransaction exactly as returned.
const signature = await connection.sendRawTransaction(signedTransaction);
Examples in

Sign the bytes, submit

These examples are for a server-side or custodial signer. Every recipe works for both v1 and v0.

Sign the bytes, submit
# Signing is not possible from a shell: a private key must never transit
# a command line or environment variable in production.
#
# Pass order.transaction to your wallet layer instead:
#
#   1. base64-decode it (v1 unless order.transactionVersion is 0)
#   2. sign the raw bytes with the sender's key: a v1-capable signer, or
#      create the order with "txVersion": 0 if yours only handles v0
#   3. submit the exact signed bytes to your RPC:
#        sendTransaction(base64, { "encoding": "base64" })
#
# Every other tab shows that in a real language.

SDK-specific signing behavior

Some SDKs do not attach a signature when you modify a deserialized transaction directly. Follow the language example above: Python rebuilds from unsigned.message, and Rust uses try_new. Always submit the exact signed bytes, base64-encoded, rather than a re-parsed transaction object.

After submission

  • Confirm the transaction, then optionally call POST /deposit so Cloak can process it sooner.
  • If submission fails on an expired blockhash, call refresh and sign again. This cannot double-spend. Check transactionVersion on the refreshed transaction too.
  • Then poll status until final.

Keep SOL available for the deposit

Keep roughly 0.002 SOL available beyond the transfer amount for the deposit transaction.