Unified Balance Kit: Partial Liquidity, Routing, and Fallback Patterns
.jpg)
Summary
Part three of the Unified Balance Kit series focuses on partial liquidity and fallback rules for apps that start from available USDC but still need to validate each execution route.
Once an app has a clean state model for spendable and non-spendable funds, the next design question is routing. The app still has to decide whether a transfer should execute on the available route, whether destination-specific requirements are satisfied, and what to do when they are not.
Unified Balance Kit keeps the product model centered on available USDC, but the app still decides which routes it will accept, which destination requirements it must validate before execution, and when fallback behavior is appropriate.
Start with the default allocation model
For most integrations, auto-allocation may be the right place to begin: provide the amount to spend and the destination chain, then let the kit allocate across available balance sources.
This keeps the product focused on the user action instead of source-chain inventory. The app can check readiness and route viability, while the execution layer handles sourcing details unless the workflow needs tighter control.
For many applications, the default route is enough when the action only needs to succeed on any supported path, the app does not need to constrain the exact source allocation, and the destination behavior is acceptable across the routes the app supports. Explicit allocation logic is still useful for routing policy, testing, or operational constraints, but most applications do not need to start there.
Validating Route Capability Before Signing
Some flows depend on route capabilities that are not available everywhere. If a spend depends on forwarding support, it can be worth filtering supported chains before the user reaches signing.
const forwarderDestChains = kit.getSupportedChains("USDC", {
forwarderSupported: "destination",
})
In practice, route capability is better treated as an execution check than a UI hint. If the flow depends on automatic destination mint handling, unsupported paths are better rejected or rerouted before the app commits the user to the transfer.
Where forwarding fits
If the integration depends on destination forwarding behavior, it helps to treat that as a distinct route type. Forwarding changes fee composition and can also change the execution details the app needs to track.
When you use useForwarder: true, the route can include separate forwarder behavior and fees, and the spend result includes additional route-specific details such as transferId.
const result = await kit.spend({
amount: "1.00",
token: "USDC",
from: [{ adapter: sourceAdapter }],
to: {
chain: "Arc_Testnet",
recipientAddress: recipientAddress,
useForwarder: true,
},
})
Give forwarding its own route checks and execution state because it affects fees and the details the app needs to persist. For example, an app might use getSupportedChains() to confirm forwarding is available on the destination chain, use estimateSpend() to confirm that the forwarder fee is acceptable before signing, and then persist the transferId returned by spend() so support or ops can follow that specific forwarded transfer.
Destination Differences as Routing Inputs
Destination-specific validation still belongs in routing even when the spend call looks similar.
- For EVM destinations,
recipientAddressis the wallet address you want to receive funds. - For Solana destinations, the recipient must be the initialized USDC token account or associated token account, not just the wallet address.
If the app supports both destination families, it is better to validate the recipient format before execution than to treat destination handling as a last-mile detail.
Browser-wallet behavior also differs on Solana. If the spend flow depends on wallet-based burn-intent signing, test the exact wallet path you plan to support. Phantom does not support Gateway burn-intent signing on Solana, so browser-wallet flows should use a compatible wallet such as Solflare or Backpack.
Using estimateSpend() for Route Decisions
estimateSpend() is not only for showing fees. It can also be used in pre-execution checks to confirm that the route being offered is still the route the app wants to execute.
const estimate = await kit.estimateSpend({
amount: "100",
token: "USDC",
from: [{ adapter: evmAdapter }, { adapter: solanaAdapter }],
to: {
adapter: evmAdapter,
chain: "Arc_Testnet",
recipientAddress,
},
})
console.log("Estimated fees:", estimate.fees)
Before execution, the estimate can be used to check fees, recipient-side outcome, forwarding impact, and whether the route still fits the action the app is about to perform.
Partial Liquidity as a Product State
Available USDC across chains does not mean every requested move is equally straightforward at every moment.
An app can have enough total USDC to support the user’s intent while still needing to decide whether the current route is acceptable. That usually means one of three states:
- insufficient total spendable balance
- enough spendable balance, but no route that satisfies the app's requirements
- enough balance, with execution continuing on a fallback path instead of the preferred one
These states are more useful than a generic "unable to transfer" error because they make it clearer whether the issue is balance, route availability or fallback behavior.
Fallback Rules
If the app needs fallback rules, Unified Balance Kit exposes the methods those rules usually depend on:
getSupportedChains()to rule out unsupported pathsestimateSpend()to inspect the route before executiongetBalances()to distinguish balance problems from route problemsspend()with explicitfrom.allocationswhen the app wants tighter control over sourcinggetDelegateStatus()when a route depends on delegate readinessgateway.spend.*events when the app needs to monitor execution and surface fallback state
What the App Still Owns
Unified Balance Kit handles orchestration. The application still decides what counts as an acceptable route, when the default path is enough, when route-specific validation is required before signing, and how partial liquidity or fallback behavior should be surfaced in the product.
The series began with the architectural shift to available-USDC-first design. Here, the focus was the state model behind that shift: separating confirmed balance, pending balance, and funds already in motion. The next post focuses on what happens even after the route is chosen: how to add production safeguards before and after spend(), especially around fees, delegation, and recovery.
USDC is issued by regulated affiliates of Circle. See Circle’s list of regulatory authorizations.
Arc testnet is offered by Circle Technology Services, LLC ("CTS"). CTS is a software provider and does not provide regulated financial or advisory services. You are solely responsible for services you provide to users, including obtaining any necessary licenses or approvals and otherwise complying with applicable laws.
Arc has not been reviewed or approved by the New York State Department of Financial Services.
The product features described in these materials are for informational purposes only. All product features may be modified, delayed, or cancelled without prior notice, at any time and at the sole discretion of Circle Technology Services, LLC. Nothing herein constitutes a commitment, warranty, guarantee or investment advice.
Circle Technology Services, LLC (“CTS”) is a software provider and does not provide regulated financial or advisory services. You are solely responsible for services you provide to users, including obtaining any necessary licenses or approvals and otherwise complying with applicable laws. For additional details, please click here to see the Circle Developer terms of service.
