Concepts
The submitViaApi flow
Why some Solana transactions must be signed by the wallet but submitted through POST /v1/orders/submit, and how to do it.
What the flag means#
A Solana transaction (vm: "svm") can come with submitViaApi: true. Then the wallet must only sign it. Do not broadcast it to an RPC node; send the signed bytes to POST /v1/orders/submit and LayerHop forwards them to the provider.
{ "vm": "svm", "serializedTx": "AQAAAA…", "submitViaApi": true }Why#
Today this applies to Jupiter. Jupiter's best routes are often RFQ quotes (a market maker co-signs the transaction) or gasless swaps (Jupiter pays the fee and co-signs). Those transactions only become valid when the provider adds its signature, so a plain RPC broadcast will never land them. Jupiter executes every order it builds through its own endpoint, so LayerHop marks all Jupiter transactions submitViaApi: true.
Flow#
POST /v1/orders→{ vm: "svm", serializedTx, submitViaApi: true }.- Deserialize, sign with the user's wallet, serialize, base64-encode.
POST /v1/orders/submitwith{ quoteId, signedTransaction }.- The response is
{ provider, txHash }— the Solana transaction signature.
import { VersionedTransaction } from "@solana/web3.js";
const tx = await lh.buildTx(quote.quoteId);
if (tx.vm === "svm") {
const signed = await wallet.signTransaction(VersionedTransaction.deserialize(Buffer.from(tx.serializedTx, "base64")));
const b64 = Buffer.from(signed.serialize()).toString("base64");
const signature = tx.submitViaApi
? await lh.submit(quote.quoteId, b64) // through LayerHop
: await connection.sendRawTransaction(signed.serialize()); // straight to the network
}Rules#
- Calling submit for a quote whose provider broadcasts from the wallet returns
INVALID_PARAMS. - Submit within seconds. If the provider rejects the transaction (price moved, quote stale) you get
PROVIDER_ERROR; get a new quote and sign again. - LayerHop only relays the bytes your user signed. It cannot change them, and it never sees a private key.