SWIFT API
SWIFT is Velocity's offchain signed-order protocol: it lets market makers receive signed taker orders over WebSocket before they hit the onchain JIT auction. In the SDK, SWIFT orders are called signed-message orders (SignedMsgOrderParams, isSignedMsgOrder, SwiftOrderSubscriber). The "SWIFT" name is the product-facing term for the same feature.
This page covers the maker side: receiving and filling signed orders. For the taker side, how these orders are signed and submitted, see Swift in the SDK docs.
How SWIFT works
In the standard onchain flow, the taker submits a transaction to Solana, it lands, the maker's RPC or gRPC subscription fires, and only then is a fill computed and submitted. That is 2 to 4 seconds from taker intent to maker fill, of which the first 1 to 2 seconds are pure waiting.
SWIFT removes that wait. The taker signs the order offchain and broadcasts it to the SWIFT WebSocket at the same time as submitting it onchain. A subscribed maker receives it in 100 to 500 ms, before the taker's own transaction lands, inspects direction, size, and auction parameters, then submits a place-and-make transaction carrying an ed25519 verification of the taker's signature, which fills the taker atomically.
Three things that head start buys:
- More accurate pricing. The order arrives while the oracle behind the quote is fresher.
- Better auction position. The fill transaction can land in the same slot as the taker's, or ahead of makers who only watch onchain feeds.
- Faster flow reaction. Inventory management and toxic-flow avoidance both improve when the order arrives earlier.
The tradeoff is infrastructure. The advantage is perishable: it exists only in the window between the WebSocket message and the taker's transaction landing, so a slow RPC node spends the whole head start on transaction submission and the edge is gone. A dedicated RPC node with staked connections and a persistent, auto-reconnecting WebSocket are the price of entry.
SWIFT is also not guaranteed flow. Takers using the standard SDK path submit directly onchain and never touch SWIFT. Subscribing to both SWIFT and AuctionSubscriber gives full coverage, deduplicated with isSignedMsgOrder() as described in Detecting SWIFT orders in onchain feeds.
Subscribing to SWIFT orders
SwiftOrderSubscriber connects to the feed, authenticates with the maker's keypair, and invokes a callback for each signed order on the subscribed markets.
Endpoint is provisional. SwiftOrderSubscriber defaults endpoint based on velocityEnv when none is passed explicitly: wss://swift.velocity.exchange/ws for mainnet-beta, wss://swift.master.velocity.exchange/ws for devnet. Neither hostname has been confirmed as a final production endpoint. Check with the team before depending on them in production.
import { SwiftOrderSubscriber, loadKeypair } from "@velocity-exchange/sdk";
const swiftSubscriber = new SwiftOrderSubscriber({
velocityClient,
velocityEnv: "mainnet-beta",
marketIndexes: [0, 1, 2], // perp market indexes to listen to
keypair: loadKeypair("<KEYPAIR_PATH>"), // used for WebSocket auth
// endpoint: "wss://swift.velocity.exchange/ws", // optional, defaults from velocityEnv
});
await swiftSubscriber.subscribe(
async (orderMessageRaw, signedMessage, isDelegateSigner) => {
// Inspect the incoming signed order
const orderParams = signedMessage.signedMsgOrderParams;
console.log("Market:", orderParams.marketIndex);
console.log("Direction:", orderParams.direction);
console.log("Size:", orderParams.baseAssetAmount);
console.log("Auction start:", orderParams.auctionStartPrice);
console.log("Auction end:", orderParams.auctionEndPrice);
// Decide whether to fill it
if (shouldFill(orderParams)) {
await fillSwiftOrder(signedMessage);
}
}
);Pass an optional userAccountGetter to resolve taker UserAccount details inside the callback, which makes the taker's positions, collateral, and health available before the fill decision. A subscribed UserMap satisfies the interface. Keep it in-memory: a getter that makes an RPC call per order spends the very latency advantage SWIFT exists to provide.
import { UserMap, loadKeypair } from "@velocity-exchange/sdk";
const userMap = new UserMap({
velocityClient,
connection,
subscriptionConfig: { type: "websocket" },
});
await userMap.subscribe();
const swiftSubscriber = new SwiftOrderSubscriber({
velocityClient,
velocityEnv: "mainnet-beta",
marketIndexes: [0, 1, 2],
keypair: loadKeypair("<KEYPAIR_PATH>"),
// userAccountGetter implements { mustGetUserAccount(publicKey: string): Promise<UserAccount> }
userAccountGetter: userMap,
});Place-and-make with SWIFT
SWIFT fills use placeAndMakeSignedMsgPerpOrder, which includes an ed25519 signature verification proving the taker actually signed this order offchain. It is the standard onchain place-and-make pattern, described in JIT Auctions, plus that verification step. The SDK builds the instruction.
import {
getLimitOrderParams,
getUserAccountPublicKey,
getUserStatsAccountPublicKey,
isVariant,
PositionDirection,
PostOnlyParams,
OrderParamsBitFlag,
} from "@velocity-exchange/sdk";
import { PublicKey } from "@solana/web3.js";
// From the SWIFT subscription callback
async function fillSwiftOrder(orderMessageRaw, signedMessage, isDelegateSigner) {
const takerAuthority = new PublicKey(orderMessageRaw.taker_authority);
const signingAuthority = new PublicKey(orderMessageRaw.signing_authority);
const subAccountId = signedMessage.subAccountId;
// Build the signed order params (message + signature from the raw order)
const signedOrderParams = {
orderParams: Buffer.from(orderMessageRaw.order_message, "hex"),
signature: Buffer.from(orderMessageRaw.order_signature, "base64"),
};
// Build UUID as Uint8Array
const uuidBytes = new TextEncoder().encode(orderMessageRaw.uuid);
// Resolve taker account addresses
const takerPubkey = await getUserAccountPublicKey(
velocityClient.program.programId, takerAuthority, subAccountId
);
const takerStatsPubkey = getUserStatsAccountPublicKey(
velocityClient.program.programId, takerAuthority
);
// Build the maker order params (opposite direction of taker)
const takerIsLong = isVariant(signedMessage.signedMsgOrderParams.direction, "long");
const makerOrderParams = getLimitOrderParams({
marketIndex: signedMessage.signedMsgOrderParams.marketIndex,
direction: takerIsLong ? PositionDirection.SHORT : PositionDirection.LONG,
baseAssetAmount: signedMessage.signedMsgOrderParams.baseAssetAmount,
price: myFillPrice,
postOnly: PostOnlyParams.MUST_POST_ONLY,
// Required: the signed-msg place-and-make handler rejects any maker order
// that isn't IOC + post-only + limit with InvalidOrderIOCPostOnly.
bitFlags: OrderParamsBitFlag.ImmediateOrCancel,
});
// Resolve taker user account (from UserMap or userAccountGetter)
const takerUserAccount = await userMap.mustGetUserAccount(takerPubkey.toString());
// Submit place-and-make (SDK handles ed25519 verification ix)
const txSig = await velocityClient.placeAndMakeSignedMsgPerpOrder(
signedOrderParams, // { orderParams: Buffer, signature: Buffer }
uuidBytes, // Uint8Array
{
taker: takerPubkey,
takerStats: takerStatsPubkey,
takerUserAccount,
signingAuthority,
},
makerOrderParams, // the maker order
);
console.log("Filled SWIFT order:", txSig);
}For control over transaction construction, custom priority fees, specific ALTs, or bundling with other instructions, getPlaceAndMakeSignedMsgPerpOrderIxs takes the same arguments and returns the instruction array instead of sending it.
import { Transaction } from "@solana/web3.js";
// Same params as placeAndMakeSignedMsgPerpOrder, returns instruction array
const ixs = await velocityClient.getPlaceAndMakeSignedMsgPerpOrderIxs(
signedOrderParams, // { orderParams, signature }
uuidBytes, // Uint8Array
{ taker: takerPubkey, takerStats: takerStatsPubkey, takerUserAccount, signingAuthority },
makerOrderParams,
);
// Returns [ed25519VerifyIx, placeSignedMsgTakerOrderIx, placeAndMakeSignedMsgPerpOrderIx]:
// 1. ed25519 verification instruction (proves taker signature)
// 2. registers the taker's signed order onchain (same as placeSignedMsgTakerOrder)
// 3. places the maker order and fills the taker atomically
// Send the transaction by any preferred method
const tx = new Transaction().add(...ixs);
const txSig = await connection.sendTransaction(tx, [wallet.payer]);The ed25519 verification instruction must sit at the index the place instruction references, which is not necessarily first. Both methods accept precedingIxs, the instructions that will come before these in the final transaction, used only to compute the verify instruction's sysvar index, and overrideCustomIxIndex for an explicit override. Any prepended instruction, compute-budget instructions included, has to be passed in precedingIxs. Getting this wrong causes silent verification failures.
Detecting SWIFT orders in onchain feeds
Subscribing to both SWIFT and onchain feeds surfaces the same order twice: once over the SWIFT WebSocket before it lands, and again in the onchain subscription stream when it does. Use isSignedMsgOrder() in the onchain loop to skip the ones already handled, so no duplicate fill goes out.
import { isSignedMsgOrder } from "@velocity-exchange/sdk";
// In the AuctionSubscriber callback
auctionSubscriber.eventEmitter.on("onAccountUpdate", async (userAccount, pubkey, slot) => {
for (const order of userAccount.orders) {
if (order.baseAssetAmount.isZero()) continue;
if (isSignedMsgOrder(order)) {
// This came from SWIFT, already seen via WebSocket
// Skip to avoid submitting a duplicate fill
continue;
}
// Handle regular onchain auction
await handleAuction(order, userAccount, pubkey, slot);
}
});Running SWIFT in production
Filtering is the same as any other JIT flow: oracle validation, position limits, and toxic-flow detection. See Bot Architecture for the shared patterns and code.
The operational differences are about latency and failure modes. Submit through a dedicated RPC node, pre-compute oracle prices and risk checks so the callback does no work it could have done earlier, keep the WebSocket persistent with automatic reconnection, and use commitment: "processed" for the fastest confirmations. Track fill rates for SWIFT and onchain separately, because a SWIFT fill rate that falls while the onchain rate holds points at latency rather than pricing.
Plan for the feed being unavailable. If the SWIFT WebSocket disconnects, fall back to standard AuctionSubscriber participation rather than stopping. Fills through SWIFT pay the same maker rebates and the same transaction costs as any other place-and-make, so the fallback costs the latency edge and nothing else.
Gotchas
- Latency advantage is perishable: the 100 to 500 ms head start only helps if the fill transaction lands quickly. On a slow RPC node it is spent entirely on submission.
userAccountGettermust return fast: an RPC call per order negates the head start. Pre-load accounts through a subscribedUserMap.- Order expiry: SWIFT signed orders carry a
maxTs. A fill transaction that lands after that timestamp fails. Check the remaining time before submitting. - ed25519 instruction ordering: pass
precedingIxswhenever other instructions are prepended, or the verify instruction's sysvar index is wrong and verification fails silently. - SWIFT is not guaranteed flow: subscribe to
AuctionSubscriberas well, and deduplicate withisSignedMsgOrder().
Related
- JIT Auctions: auction mechanics and pricing
- JIT-only MM: JIT market making strategy
- Orderbook & Matching: how orders are matched
- Bot Architecture: reconnection, error handling, and production patterns
- Indicative Quotes: signal liquidity offchain alongside SWIFT fills