SWIFT API
SWIFT (Signed Wrapped Instant Fulfillment Transactions) allows market makers to receive signed taker orders offchain via WebSocket before they hit the JIT auction. This enables ultra-low-latency market making.
How SWIFT works
Flow overview
-
Taker signs order offchain
- Order signed with user’s private key
- Broadcast to SWIFT WebSocket feed
-
Makers receive order via WebSocket
- Order arrives before hitting onchain auction
- Makers inspect order (direction, size, auction params)
-
Makers submit place-and-make
- Places maker order onchain
- Includes ed25519 verification instruction
- Fills taker atomically
Key benefit: Market makers see orders 100-500ms before they land onchain, allowing faster response times and better fills.
SWIFT vs standard JIT: why the latency matters
In the standard JIT flow, the taker submits a transaction to Solana → it lands onchain → your RPC/gRPC subscription fires → you compute and submit your fill. This takes 1-2 seconds minimum.
With SWIFT, the taker broadcasts the signed order offchain to the SWIFT WebSocket simultaneously with submitting it onchain. You receive it over WebSocket in 100-500ms , before it even lands onchain. This head start lets you:
- Price more accurately , you see the order while the oracle is fresher
- Win more auctions , your fill tx can land at the same slot or even before other makers who rely on onchain feeds
- React to flow faster , critical for inventory management and toxic flow avoidance
The tradeoff: you need fast infrastructure (dedicated RPC node, low-latency WebSocket connection) to capitalize on this advantage. If your fill tx doesn’t land quickly, the latency edge is wasted.
Subscribing to SWIFT orders
Basic subscription
import { SwiftOrderSubscriber, loadKeypair } from "@drift-labs/sdk";
const swiftSubscriber = new SwiftOrderSubscriber({
driftClient,
driftEnv: "mainnet-beta",
marketIndexes: [0, 1, 2], // SOL, BTC, ETH perp markets to listen to
keypair: loadKeypair("<KEYPAIR_PATH>"), // used for WebSocket auth
// endpoint: "wss://swift.drift.trade/ws", // optional, defaults based on driftEnv
});
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 if you want to fill it
if (shouldFill(orderParams)) {
await fillSwiftOrder(signedMessage);
}
}
);With UserAccount getter
Optionally provide a userAccountGetter to resolve taker UserAccount details. This lets you inspect the taker’s positions, collateral, and health before deciding to fill , useful for toxic flow filtering.
import { UserMap, loadKeypair } from "@drift-labs/sdk";
const userMap = new UserMap({
driftClient,
connection,
subscriptionConfig: { type: "websocket" },
});
await userMap.subscribe();
const swiftSubscriber = new SwiftOrderSubscriber({
driftClient,
driftEnv: "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 a special instruction (placeAndMakeSignedMsgPerpOrder) that includes an ed25519 signature verification , proving the taker actually signed this order offchain. The SDK handles building this instruction for you.
For the standard onchain place-and-make pattern, see JIT Auctions - Place-and-make. The SWIFT variant adds the signature verification step.
Using DriftClient (recommended)
import {
getLimitOrderParams,
getUserAccountPublicKey,
getUserStatsAccountPublicKey,
isVariant,
PositionDirection,
PostOnlyParams,
} from "@drift-labs/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(
driftClient.program.programId, takerAuthority, subAccountId
);
const takerStatsPubkey = getUserStatsAccountPublicKey(
driftClient.program.programId, takerAuthority
);
// Build your 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,
});
// 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 driftClient.placeAndMakeSignedMsgPerpOrder(
signedOrderParams, // { orderParams: Buffer, signature: Buffer }
uuidBytes, // Uint8Array
{
taker: takerPubkey,
takerStats: takerStatsPubkey,
takerUserAccount,
signingAuthority,
},
makerOrderParams, // your maker order
);
console.log("Filled SWIFT order:", txSig);
}Build instructions manually
If you need more control over transaction construction (e.g., custom priority fees, specific ALTs, or bundling multiple instructions):
import { Transaction } from "@solana/web3.js";
// Same params as placeAndMakeSignedMsgPerpOrder, returns instruction array
const ixs = await driftClient.getPlaceAndMakeSignedMsgPerpOrderIxs(
signedOrderParams, // { orderParams, signature }
uuidBytes, // Uint8Array
{ taker: takerPubkey, takerStats: takerStatsPubkey, takerUserAccount, signingAuthority },
makerOrderParams,
);
// ixs includes:
// 1. ed25519 verification instruction (proves taker signature)
// 2. place-and-make instruction (places your order + fills taker)
// Send transaction with your preferred method
const tx = new Transaction().add(...ixs);
const txSig = await connection.sendTransaction(tx, [wallet.payer]);SWIFT vs onchain auction flow
SWIFT flow
Timeline:
- Taker signs order offchain
- Broadcast to SWIFT WebSocket (~50ms)
- Maker receives order (~100-500ms total)
- Maker submits place-and-make tx
- Taker’s order lands onchain (~1-2s total)
- Maker’s fill tx lands (ideally same slot)
Latency advantage:
- Makers see orders 100-500ms before onchain
- Faster reaction time → better auction slot
- Competitive edge for pricing and flow selection
Requirements:
- Subscribe to SWIFT WebSocket
- Handle ed25519 verification (SDK does this)
- Fast infrastructure to capitalize on latency advantage
- Dedicated RPC node recommended
Standard onchain auction
Timeline:
- Taker submits tx to Solana
- Tx lands onchain (~1-2s)
- RPC/gRPC subscription fires
- Maker sees auction, computes fill
- Maker submits place-and-make tx
- Fill lands (~1-2s later)
Total latency: 2-4 seconds from taker intent to maker fill
See JIT Auctions for full auction mechanics.
Filtering and risk management
Use the same filters as other JIT flows: oracle validation, position limits, and toxic-flow detection. See Bot Architecture - Risk and filtering for shared patterns and code.
Detecting SWIFT orders in onchain feeds
When subscribed to both SWIFT and onchain feeds, you may see the same order twice:
- First via SWIFT WebSocket (offchain)
- Again when it lands onchain
Use isSignedMsgOrder() to identify SWIFT-origin orders and avoid double-handling:
import { isSignedMsgOrder } from "@drift-labs/sdk";
// In your 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 , you already saw it via WebSocket
// Skip to avoid submitting a duplicate fill
continue;
}
// Handle regular onchain auction
await handleAuction(order, userAccount, pubkey, slot);
}
});Performance considerations
Latency optimization:
- Use dedicated RPC nodes for fastest transaction submission
- Pre-compute oracle prices and risk checks
- Keep WebSocket connection persistent (auto-reconnect on disconnect)
- Use
commitment: "processed"for fastest confirmations
Reliability:
- Handle WebSocket reconnections gracefully
- Have fallback to standard auction participation (via
AuctionSubscriber) - Monitor fill rates and adjust strategy
- Track SWIFT vs onchain fill success rates separately
Cost:
- SWIFT fills pay same maker rebates as regular fills
- No additional fees for SWIFT participation
- Transaction costs same as place-and-make
Gotchas
- SWIFT is not guaranteed flow , not all taker orders go through SWIFT. Takers using the standard SDK flow submit directly onchain. You should subscribe to both SWIFT and
AuctionSubscriberfor complete coverage, usingisSignedMsgOrder()to deduplicate. - ed25519 instruction ordering , the ed25519 verification instruction must be the first instruction in the transaction. The SDK handles this, but if building transactions manually, incorrect ordering causes silent verification failures.
- Latency advantage is perishable , the 100-500ms head start from SWIFT only helps if your fill transaction lands quickly. With a slow RPC node, the advantage is wasted. Use a dedicated RPC node with staked connections for fastest landing.
- UserAccount resolution , the
userAccountGettercallback should return quickly. If it requires an RPC call per order, you’ll negate the latency advantage. Pre-load user accounts viaUserMapsubscription. - Order expiry , SWIFT signed orders have a
maxTsfield. If your fill transaction lands after this timestamp, it will fail. Check remaining time before submitting. - Fallback strategy , if the SWIFT WebSocket disconnects, your bot should seamlessly fall back to standard
AuctionSubscriberflow. Don’t let a SWIFT outage stop you from filling auctions.
Related
- JIT Auctions - Understanding 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