How the Osmosis Agent Toolkit Handles Multi-Hop Routes with Split Routes Swap Messages
The Osmosis Agent Toolkit converts multi-hop swap quotes into atomic split-route swap messages by detecting route length in the encoder layer and routing to specialized protobuf constructors that handle multiple pools in a single transaction.
The jonator/osmosis-agent-toolkit abstracts complex Osmosis DEX routing into developer-friendly agent tools. When users request swaps that traverse multiple liquidity pools—known as multi-hop routes—the toolkit automatically packages these into split routes swap messages that the Osmosis Pool Manager executes atomically.
Retrieving Multi-Hop Quotes from the Sidecar Query Server
The toolkit queries the Osmosis Sidecar Query Server (SQS) to calculate optimal paths across multiple pools. In packages/core/src/queries/sqs/router.ts, the response type defines the route array that represents each hop:
// packages/core/src/queries/sqs/router.ts
export type SidecarOutGivenInQuoteResponse = {
// ...
route: {
in_amount: string
out_amount: string
'has-cw-pool': boolean
pools: {
id: number
token_out_denom: string
// ...
}[]
}[]
}
Each element in the route array corresponds to a specific hop. If the best price requires swapping through OSMO → USDC → ATOM, the route array contains two entries, each detailing the pool ID and token denominations for that leg.
Encoding Logic for Split-Route Swaps
The encoder layer in packages/core/src/tx/swap.ts determines whether to generate a single-hop message or a multi-hop split-route message. The makeSwapExactAmountOutEncodeObject and makeSwapExactAmountInEncodeObject functions inspect quote.route.length:
// packages/core/src/tx/swap.ts (exact-out path)
if (quote.route.length === 1) {
return makeSwapExactAmountOutMsg(/* single pool params */)
}
return makeSplitRoutesSwapExactAmountOutMsg({ /* multi-hop params */ })
// Exact-in follows identical branching logic
if (quote.route.length === 1) {
return makeSwapExactAmountInMsg(/* single pool params */)
}
return makeSplitRoutesSwapExactAmountInMsg({ /* multi-hop params */ })
This branching ensures that single-pool swaps use the standard MsgSwapExactAmountIn or MsgSwapExactAmountOut, while multi-hop routes trigger the split-route constructors.
Constructing Osmosis Protobuf Messages
The final message assembly occurs in packages/core/src/tx/msg.ts. The toolkit maps the SQS route data into the Osmosis poolmanager protobuf types splitRouteSwapExactAmountOut and splitRouteSwapExactAmountIn.
For exact-out swaps:
// packages/core/src/tx/msg.ts
export function makeSplitRoutesSwapExactAmountOutMsg({
routes,
tokenOutDenom,
tokenInMaxAmount,
userOsmoAddress
}) {
return osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl.splitRouteSwapExactAmountOut({
sender: userOsmoAddress,
routes: routes.map(({ pools, tokenOutAmount }) => ({
pools: pools.map(({ id, tokenInDenom }) => ({
poolId: BigInt(id),
tokenInDenom,
})),
tokenOutAmount,
})),
tokenOutDenom,
tokenInMaxAmount,
})
}
For exact-in swaps:
// packages/core/src/tx/msg.ts
export function makeSplitRoutesSwapExactAmountInMsg({
routes,
tokenInDenom,
tokenOutMinAmount,
userOsmoAddress
}) {
return osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl.splitRouteSwapExactAmountIn({
sender: userOsmoAddress,
routes: routes.map(({ pools, tokenInAmount }) => ({
pools: pools.map(({ id, tokenOutDenom }) => ({
poolId: BigInt(id),
tokenOutDenom,
})),
tokenInAmount,
})),
tokenInDenom,
tokenOutMinAmount,
})
}
These functions transform the toolkit's internal route representation into the exact protobuf structure required by the Osmosis chain, ensuring type safety and correct BigInt handling for pool IDs.
Practical Implementation with Agent Tools
End-to-end multi-hop swaps are executed through the toolkit's tool classes in packages/core/src/tools/swap.ts. The workflow involves two stages: quoting and transaction broadcasting.
First, retrieve a quote that may contain multiple hops:
import { SwapQuoteOutGivenInTool } from 'packages/core/src/tools/swap.js'
import { OsmosisSqsQueryClient } from 'packages/core/src/queries/sqs/client.js'
const sqs = new OsmosisSqsQueryClient()
const quoteTool = new SwapQuoteOutGivenInTool(sqs)
// Request quote for 100 OSMO → ATOM (potentially via USDC)
const quote = await quoteTool.call({
tickerIn: 'OSMO',
amountIn: '100',
tickerOut: 'ATOM',
})
console.log('Route length:', quote.route.length) // >1 indicates multi-hop
Then, execute the swap. The SendSwapOutGivenInQuoteTxTool automatically handles the split-route encoding:
import { SendSwapOutGivenInQuoteTxTool } from 'packages/core/src/tools/swap.js'
import { MyAccount } from './myAccount.js'
import { MemoryMap } from 'packages/core/src/tools/tool.js'
const memory = new MemoryMap<string, SidecarOutGivenInQuoteResponse>()
const account = new MyAccount(/* wallet implementation */)
const sendTool = new SendSwapOutGivenInQuoteTxTool(account, memory)
const { txHash } = await sendTool.call({
quoteId: quote.id,
slippageTolerancePercent: 0.5,
})
console.log('Multi-hop swap submitted:', txHash)
The toolkit abstracts the complexity: developers work with high-level quote and send tools while the encoder layer in swap.ts and msg.ts transparently constructs the appropriate split-route protobuf messages.
Summary
- Multi-hop detection occurs in
packages/core/src/tx/swap.ts, wherequote.route.lengthdetermines whether to generate a single-hop or split-route message. - Quote retrieval relies on the SQS client defined in
packages/core/src/queries/sqs/router.ts, which returns aroutearray containing pool IDs and token amounts for each hop. - Message construction happens in
packages/core/src/tx/msg.tsviamakeSplitRoutesSwapExactAmountInMsgandmakeSplitRoutesSwapExactAmountOutMsg, which map toolkit data to Osmosispoolmanagerprotobuf types. - Atomic execution ensures that multi-hop swaps either complete entirely or fail, preventing partial fills across the route.
Frequently Asked Questions
What is a split-route swap message in Osmosis?
A split-route swap message is a protobuf transaction type (MsgSplitRouteSwapExactAmountIn or MsgSplitRouteSwapExactAmountOut) that allows a single transaction to execute swaps through multiple consecutive liquidity pools. Instead of submitting separate transactions for each hop (e.g., OSMO → USDC, then USDC → ATOM), the split-route message encodes the entire path with specific pool IDs and token amounts, which the Osmosis Pool Manager executes atomically.
How does the toolkit decide between single-hop and multi-hop messages?
The toolkit inspects the route array length returned by the Sidecar Query Server. In packages/core/src/tx/swap.ts, the encoder helpers makeSwapExactAmountInEncodeObject and makeSwapExactAmountOutEncodeObject check quote.route.length. If the length equals 1, they call the single-hop constructors (makeSwapExactAmountInMsg or makeSwapExactAmountOutMsg). If the length is greater than 1, they route to the split-route constructors (makeSplitRoutesSwapExactAmountInMsg or makeSplitRoutesSwapExactAmountOutMsg).
Can developers manually construct multi-hop routes without using the quote tools?
While the toolkit is designed to work with SQS-generated quotes, developers can manually construct the route array expected by the encoder functions. The makeSplitRoutesSwapExactAmountInMsg and makeSplitRoutesSwapExactAmountOutMsg functions in packages/core/src/tx/msg.ts accept any array of routes containing pools (with id and token denominations) and token amounts. However, using the SwapQuoteOutGivenInTool or SwapQuoteInGivenOutTool is recommended to ensure optimal routing and correct slippage calculations based on current pool liquidity.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →