# GET /allowance
Source: https://docs.trustware.io/api-reference/allowance
Read the ERC-20 allowance an owner has granted to a spender on a given EVM chain. Proxied through Trustware so you do not need a public RPC endpoint.
`GET https://api.trustware.io/api/v1/sdk/rpc/evm/allowance`
Returns the ERC-20 allowance an `ownerAddress` has granted to a `spenderAddress` on a specific EVM chain. Trustware proxies the underlying call so you don't have to manage public RPC endpoints or rate limits per chain.
Use this before broadcasting a route transaction whose source token is an ERC-20. If `allowance` is below `fromAmount`, prompt the user to approve the spender first.
The spender is always a provider contract, never a Trustware contract. When the
route response includes `route.execution.approvals`, each entry is authoritative
for one allowance: check against its `spender`, not against
`route.execution.transaction.to`. Re-quoting can change the selected provider
and therefore the required allowances, so read them from the latest
`POST /route` response. See [token approvals](/api-reference/route#token-approvals).
## Query parameters
| Parameter | Required | Description |
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chainId` | Yes | EVM chain ID as a string (e.g. `"8453"` for Base, `"1"` for Ethereum). |
| `tokenAddress` | Yes | ERC-20 token contract address. |
| `ownerAddress` | Yes | Wallet address whose allowance you are reading. |
| `spenderAddress` | Yes | The spender to check allowance against. Take it from `route.execution.approvals[].spender` in the latest `POST /route` response, which is authoritative when present. |
## Request
```bash theme={null}
curl -G "https://api.trustware.io/api/v1/sdk/rpc/evm/allowance" \
-H "X-API-Key: $TW_KEY" \
--data-urlencode "chainId=8453" \
--data-urlencode "tokenAddress=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" \
--data-urlencode "ownerAddress=0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201" \
--data-urlencode "spenderAddress=0xce16F69375520ab01377ce7B88f5BA8C48F8D666"
```
## Response
```json theme={null}
{
"success": true,
"data": {
"chainId": "8453",
"tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"ownerAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"spenderAddress": "0xce16f69375520ab01377ce7b88f5ba8c48f8d666",
"allowance": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"rpcHost": "base-mainnet.example"
}
}
```
| Field | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `success` | `true` if the call resolved against the chain. |
| `data.chainId` | Chain ID the call was made against. |
| `data.tokenAddress` | Lowercased token contract address. |
| `data.ownerAddress` | Lowercased owner address. |
| `data.spenderAddress` | Lowercased spender address. |
| `data.allowance` | Allowance as a string-encoded `uint256` in the token's smallest unit. The value `2^256 - 1` indicates an "unlimited" approval. |
| `data.rpcHost` | The upstream RPC host Trustware used. Useful for debugging. |
## Errors
The endpoint returns `{ "success": false, "error": "...", ... }` for validation failures (invalid address, unsupported chain) or upstream RPC failures. Inspect the HTTP status code alongside the error payload:
* `400`: malformed address, unsupported chain, or missing query parameter.
* `429`: rate limit hit; honor the `Retry-After` header.
* `500` / `502`: upstream RPC failed after retries.
Compare `allowance` to the `amount` on the matching
`route.execution.approvals` entry, or to your route's `fromAmount` when no
approvals are returned (both in the token's smallest unit). If the allowance is
lower, prompt the user to approve the spender before broadcasting.
# GET /balances
Source: https://docs.trustware.io/api-reference/balances
Fetch cross-chain token holdings for a wallet address across every chain compatible with that address format.
`GET https://api.trustware.io/api/v1/data/balances/:address`
Returns token holdings for a wallet address across every chain compatible with that address format, scanned in parallel. Use this to populate an asset selector. The response includes token symbol, balance, decimals, USD price, and logo URI for each holding.
The response may arrive while balance data is still streaming in from some chains. Check the `partial` flag: if `true`, some chains have not yet responded and you may want to poll again or handle the incomplete state in your UI.
## Path parameters
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | The wallet address to scan. The format determines which chains return results: EVM `0x...`, Solana base58, Cosmos bech32 (`sei1...`, `nibi1...`), or Bitcoin bech32. The endpoint validates the address against each chain it scans. |
## Response
```json theme={null}
{
"address": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"partial": true,
"results": [
{
"chain_id": "1329",
"source": "alchemy",
"balances": [
{
"chain_key": "1329",
"category": "native",
"symbol": "SEI",
"decimals": 18,
"balance": "0"
}
],
"count": 1,
"error": null
},
{
"chain_id": "25",
"source": "fallback",
"balances": [
{
"chain_key": "25",
"category": "native",
"symbol": "CRO",
"decimals": 18,
"balance": "0"
}
],
"count": 1,
"error": null
}
]
}
```
## Response fields
| Field | Description |
| -------------------- | --------------------------------------------------------------------------------- |
| `address` | The queried wallet address. |
| `partial` | `true` if the response is incomplete, meaning some chains have not yet responded. |
| `results` | Array of per-chain balance objects. |
| `results[].chain_id` | Chain ID for this result set. |
| `results[].source` | Data source used for this chain (`alchemy`, `fallback`, etc.). |
| `results[].balances` | Array of token balances on this chain. |
| `results[].count` | Number of tokens returned for this chain. |
| `results[].error` | Error message if balance fetching failed for this chain, otherwise `null`. |
## Balance object fields
| Field | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `chain_key` | Chain identifier (matches `chain_id` for EVM chains; may be a string key for non-EVM). |
| `category` | Token category: `native`, `erc20`, `spl`, `btc`, or `bank`. Cosmos balances are tagged `bank`, including the chain's native token. |
| `contract` | Token contract address (for ERC-20 and SPL tokens). |
| `symbol` | Token ticker symbol. |
| `decimals` | Token decimal places. Divide `balance` by `10^decimals` for a human-readable amount. |
| `balance` | Raw balance in the smallest unit of the token. |
| `name` | Full token name. |
| `logoURI` | Token logo image URL. |
| `usdPrice` | Current USD price per token. |
## Partial responses
When `partial: true`, some chains responded with an error or have not yet returned data. The `results[].error` field on each chain entry indicates if that chain encountered an issue. Chains with `error: null` returned successfully.
# GET /chains and /tokens
Source: https://docs.trustware.io/api-reference/discovery
Discovery endpoints that list the chains and tokens Trustware discovers from upstream liquidity providers. Use these to populate selectors dynamically.
These optional discovery endpoints return metadata for the chains and tokens Trustware discovers from its upstream liquidity providers. Use them to populate chain and token selectors in your UI rather than hardcoding assets. They are not required for the core integration flow.
Discovery coverage is broader than confirmed routing support. See the caveat under `/chains` below, and confirm a destination with a [quote](/api-reference/quote) call before relying on it.
## GET /chains
`GET https://api.trustware.io/api/v1/routes/chains`
Returns metadata for every chain discovered from upstream providers, including chain ID, name, type, native currency, RPC URL, block explorer URLs, and the providers available for that chain.
This list reflects raw upstream liquidity coverage. It is not a guarantee that
every entry is routable as a destination. The Cosmos family is the widest gap:
many Cosmos chains appear in this response, and Nibiru (`cataclysm-1`) is the
Cosmos destination with end-to-end support. Treat the remaining Cosmos
entries, and the `sui-mainnet` entry, as informational, and confirm a
destination with a [quote](/api-reference/quote) call before building against
it.
Sei has two identifiers, and only one of them appears here. Sei EVM is
returned as chain ID `1329`. Sei-Cosmos (`pacific-1`) is still recognized by
token discovery and by the SDK's address validation, but is currently absent
from this endpoint, so use `1329` unless you have confirmed a `pacific-1`
route with a quote call.
### Response
```json theme={null}
[
{
"axelarChainName": "Avalanche",
"blockExplorerUrls": ["https://avascan.info/blockchain/c/"],
"chainIconURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/webp128/chains/avalanche.webp",
"chainId": "43114",
"chainName": "Chain 43114",
"chainType": "evm",
"id": "43114",
"nativeCurrency": {
"decimals": 18,
"name": "Avalanche",
"symbol": "AVAX"
},
"networkName": "Avalanche",
"providersSupported": ["squid", "lifi"],
"rpc": "https://api.avax.network/ext/bc/C/rpc",
"type": "evm"
}
]
```
### Chain object fields
| Field | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `chainId` | Chain ID as a string. Numeric on EVM chains (`"8453"`); a named identifier on Cosmos chains (`"cataclysm-1"`). Use this value in `fromChain` and `toChain` fields. |
| `chainName` | Internal chain name. |
| `networkName` | Human-readable network name for display. |
| `chainType` | Chain type. Current values are `evm`, `cosmos`, `solana`, `bitcoin`, and `sui`. Distinct from the `category` field on balance rows, which uses `btc` for Bitcoin. |
| `type` | Alias for `chainType`. |
| `nativeCurrency` | Native token metadata: `name`, `symbol`, `decimals`. |
| `rpc` | Public RPC endpoint for this chain. |
| `blockExplorerUrls` | Array of block explorer URLs. |
| `chainIconURI` | Chain logo image URL. |
| `providersSupported` | Routing providers available for this chain. |
***
## GET /tokens
`GET https://api.trustware.io/api/v1/routes/tokens`
Returns tokens supported by Trustware. Called with no query parameters, it returns the full unpaginated list. Passing any of the optional query parameters below switches the endpoint into paginated mode and the response shape changes accordingly.
### Query parameters
| Parameter | Required | Description |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `chainId` | No | Restrict results to a single chain (e.g. `"8453"` for Base). Passing this also activates pagination. |
| `limit` | No | Page size when paginating. No fixed default. Omit to use the server-side page size. |
| `cursor` | No | Pagination cursor returned by a previous paginated response. |
| `q` | No | Free-text search across token symbol and name. |
| `includeTotal` | No | Set to `true` to include the total result count in the paginated response. |
Pagination activates as soon as **any** of these query parameters is
present. Call with no query string to receive the full token list in a
single response.
### Response
```json theme={null}
[
{
"address": "0x4acc81dc9c03e5329a2c19763a1d10ba9308339f",
"chainId": "8453",
"decimals": 18,
"logoURI": "https://token-media.defined.fi/8453_0x4acc81dc9c03e5329a2c19763a1d10ba9308339f_large.png",
"name": "Base Baboon",
"symbol": "$BOON",
"type": "evm",
"usdPrice": 9.35818822286e-8
}
]
```
### Token object fields
| Field | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | Token identifier on this chain: the contract address on EVM chains, the mint address on Solana, or the native denom string on Cosmos. Use this in `fromToken` and `toToken`. |
| `chainId` | Chain this token belongs to. |
| `symbol` | Token ticker symbol. |
| `name` | Full token name. |
| `decimals` | Token decimal places. |
| `type` | Token type (e.g. `evm`). |
| `usdPrice` | Current USD price per token. |
| `logoURI` | Token logo image URL. |
# API overview
Source: https://docs.trustware.io/api-reference/overview
Authentication, base URL, and the four-step integration pattern for the Trustware REST API.
The Trustware REST API is the backend integration path. Use it when you need server-side control over signing, when you're running a custody wallet, or when you're building outside of React. It is a REST API served over HTTPS. All requests use JSON bodies and return JSON responses. Authentication is via an API key passed in a request header.
**SDK vs. API:** If you're building a React app and want a prebuilt deposit widget, start with the [SDK introduction](/introduction). If you're building server-side, using a custody wallet, or need direct control over transaction signing and submission, use this API.
## Base URL
```
https://api.trustware.io
```
All endpoint paths below are relative to this base URL. Use the `/api/v1/` path prefix. Legacy `/api/` aliases exist but are sunset on 2026-12-31.
## Authentication
Pass your API key in the `X-API-Key` header on every request.
```js theme={null}
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.TW_KEY,
},
body: JSON.stringify(payload),
});
```
API keys can be **origin-locked** to a specific domain so they cannot be used from unauthorized origins. Contact Trustware to configure origin locking for your key.
Never expose your API key in client-side code. Store it in an environment variable and make Trustware API calls from your backend.
## Integration pattern
Every integration follows the same pattern regardless of source chain or use case:
Call `POST /api/v1/routes/quote` to get a fee estimate and expected output before the user confirms. AML/OFAC screening runs at this step. If the call returns an error, no funds move.
Call `POST /api/v1/routes/route` to generate the full transaction payload. Pass a `metadata` object with any fields you need echoed back in status responses (user ID, withdrawal ID, etc.). An intent record is created internally.
To deposit into a contract rather than pay a recipient address, add `hooks.postHook` to the request. See [vault destinations](/guides/vault-destinations).
Your signing infrastructure (custody wallet, MetaMask, or equivalent) builds the transaction from the payload, signs it, and broadcasts it to the source chain. Trustware never touches private keys.
If the source token is an ERC-20, grant any allowances the route requires before broadcasting it. When the route response includes `route.execution.approvals`, each entry gives the token, spender, and exact amount to approve. See [token approvals](/api-reference/route#token-approvals).
Immediately submit the transaction hash to `POST /api/v1/route-intent/:id/receipt`. Then poll `GET /api/v1/route-intent/:id/status` until the status reaches `success` or `failed`.
## What Trustware does not do
Trustware never takes custody of funds. The API generates a transaction payload that your signing infrastructure executes. Private keys stay with you. Funds flow peer-to-peer via on-chain contracts. Trustware is the routing and orchestration layer, not a custodian.
## Rate limits
The API is rate-limited per API key. If you exceed the limit, the response returns `429 Too Many Requests` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. Implement exponential backoff on `429` responses, especially on the `/status` polling endpoint.
## Versioning
The current API version is `v1`. Breaking changes will introduce a new version prefix. The legacy `/api/` (unversioned) aliases will return deprecation headers and stop working on 2026-12-31.
# POST /quote
Source: https://docs.trustware.io/api-reference/quote
Get a fee estimate and expected output before the user confirms. AML/OFAC screening runs at this step.
`POST https://api.trustware.io/api/v1/routes/quote`
Returns a fee estimate, expected output amount, slippage, and estimated completion time for a proposed route. Call this before `POST /route` to show the user a confirmation screen. AML/OFAC screening runs synchronously. If the source or destination address is flagged, this call returns an error and no funds move.
## Request
```json theme={null}
{
"fromChain": "43114",
"toChain": "137",
"fromToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"toToken": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"fromAmount": "215124226346059100",
"fromAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"toAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"slippage": 1
}
```
| Field | Required | Description |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fromChain` | Yes | Source chain ID as a string (e.g. `"43114"` for Avalanche). |
| `toChain` | Yes | Destination chain ID as a string (e.g. `"137"` for Polygon). |
| `fromToken` | Yes | Source token identifier: contract address on EVM, mint address on Solana, native denom on Cosmos. Use `"0xeeee...eeee"` for the native token on EVM chains. |
| `toToken` | Yes | Destination token identifier: contract address on EVM, mint address on Solana, native denom on Cosmos (e.g. `"unibi"`). |
| `fromAmount` | Yes | Amount in the smallest unit of the source token (wei for EVM). |
| `fromAddress` | Yes | Sender wallet address. AML screening runs against this address. |
| `toAddress` | Yes | Recipient wallet address. |
| `slippage` | No | Slippage tolerance as a percentage. Defaults to `1`. The accepted ceiling is provider dependent: some providers reject a route above `3`, others impose no limit, so a value above `3` can narrow which providers can serve the route. |
## Response
```json theme={null}
{
"data": {
"estimate": {
"fromAmount": "215124226346059100",
"toAmount": "1994795",
"toAmountMin": "1974049",
"toAmountUsd": "1.99",
"totalFeesUsd": "0.050017",
"fees": [
{
"type": "Gas receiver fee",
"amount": "3795341166702376",
"amountUsd": "0.04",
"token": {
"chainId": "43114",
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"symbol": "AVAX",
"decimals": 18
}
},
{
"type": "Service fee",
"amount": "10020",
"amountUsd": "0.010017",
"token": {
"chainId": "137",
"address": "0x750e4c4984a9e0f12978ea6742bc1c5d248f40ed",
"symbol": "USDC.axl",
"decimals": 6
}
}
]
}
}
}
```
| Field | Description |
| ----------------------- | ------------------------------------------------------------------------------------------- |
| `estimate.fromAmount` | Source amount in the smallest unit. |
| `estimate.toAmount` | Expected destination amount in the smallest unit of the destination token. |
| `estimate.toAmountMin` | Minimum destination amount accounting for slippage. |
| `estimate.toAmountUsd` | Expected destination value in USD. |
| `estimate.totalFeesUsd` | Total fees in USD across all fee types. |
| `estimate.fees` | Itemized fee breakdown. Each entry has `type`, `amount`, `amountUsd`, and `token` metadata. |
## AML behavior
If Trustware's AML/OFAC screening flags the source or destination address, this endpoint returns an error. No funds move. The specific reason is never exposed in the error response, so your application should handle the error generically and show a neutral message to the user.
Always call `/quote` before `/route`. The AML check runs here. If you skip the quote step and call `/route` directly, you bear the risk of initiating a transaction that would have been rejected at the quote stage.
# POST /receipt
Source: https://docs.trustware.io/api-reference/receipt
Submit the transaction hash after signing and broadcasting. This is the handoff point where Trustware begins tracking.
`POST https://api.trustware.io/api/v1/route-intent/:intentId/receipt`
Submit the transaction hash after your signing infrastructure (or the user's wallet) has broadcast the transaction. This is the handoff point: from the moment Trustware receives the hash, it begins tracking the cross-chain route through to final settlement.
Submit this call **immediately** after broadcast, before any other operation. Implement retry logic around this call. If it fails and is never submitted, Trustware cannot track the route.
## Path parameters
| Parameter | Description |
| ---------- | ----------------------------------------- |
| `intentId` | The `intentId` returned by `POST /route`. |
## Request
```json theme={null}
{
"txHash": "0x4c8f91363c5f5e3ac2154f94995f5beac2993cc595af552c2681538af627e05d"
}
```
| Field | Required | Description |
| -------- | -------- | ---------------------------------------- |
| `txHash` | Yes | The transaction hash from the broadcast. |
## Response
```json theme={null}
{
"data": {
"ok": true,
"transaction_id": "bc5a5e82-1ecb-453b-bf78-c7c3ccdc3223"
}
}
```
| Field | Description |
| ---------------- | ------------------------------------------- |
| `ok` | `true` if the receipt was accepted. |
| `transaction_id` | Trustware's internal transaction record ID. |
## Idempotency
This call is idempotent. Retrying with the same `intentId` and `txHash` is safe and will return the same response. If the broadcast succeeds but the receipt call fails, retry until you receive a successful response.
If you broadcast the transaction but never submit the receipt, Trustware cannot track or report the status of the route. Capture the transaction hash immediately after broadcast and submit it before any other operation.
# POST /route
Source: https://docs.trustware.io/api-reference/route
Generate the transaction payload for signing. Creates a Trustware intent record that can be tracked through to completion.
`POST https://api.trustware.io/api/v1/routes/route`
Builds the full transaction payload for signing and broadcasting. Call this after the user confirms the quote. Trustware creates an intent record internally so the transaction can be tracked from receipt submission through final settlement.
Pass a `metadata` object with any fields you need echoed back in all subsequent status responses, such as user ID, withdrawal ID, fiat destination, or any other reference data.
## Request
```json theme={null}
{
"fromChain": "43114",
"toChain": "137",
"fromToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"toToken": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"fromAmount": "215124226346059100",
"fromAmountUSD": "2",
"fromAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"toAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"slippage": 1,
"slippageBps": 100,
"metadata": {
"userId": "usr_abc123",
"withdrawalId": "wdl_xyz789"
}
}
```
| Field | Required | Description |
| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fromChain` | Yes | Source chain ID as a string. |
| `toChain` | Yes | Destination chain ID as a string (e.g. `"8453"` for Base, `"cataclysm-1"` for Nibiru). |
| `fromToken` | Yes | Source token identifier: contract address on EVM, mint address on Solana, native denom on Cosmos. Use `"0xeeee...eeee"` for the native token on EVM chains. |
| `toToken` | Yes | Destination token identifier: contract address on EVM, mint address on Solana, native denom on Cosmos (e.g. `"unibi"`). |
| `fromAmount` | Yes | Amount in the smallest unit of the source token. |
| `fromAmountUSD` | No | USD equivalent of `fromAmount`. Used for display and analytics. |
| `fromAddress` | Yes | Sender wallet address. |
| `toAddress` | Yes | Recipient wallet address. |
| `slippage` | No | Slippage tolerance as a percentage. Defaults to `1`. |
| `slippageBps` | No | Slippage in basis points. Equivalent to `slippage * 100`. |
| `metadata` | No | Arbitrary key-value object echoed in all status responses. Use for user attribution. |
| `hooks` | No | Optional destination call. `hooks.postHook` executes a contract call on the destination chain once funds arrive. See [destination calls](#destination-calls-with-hooks-posthook) and the [vault destinations guide](/guides/vault-destinations). Omit `hooks` for a normal route. |
## Destination calls with hooks.postHook
Pass `hooks.postHook` to execute a contract call on the destination chain once the routed funds arrive, instead of paying the destination token to a recipient address. Destination calls are EVM only.
This example routes the native asset on Avalanche into a contract on Base, funding the call with the destination native asset. `callData` is the ABI encoding of `depositNativeFor(address recipient)` for the recipient in `toAddress`.
```json theme={null}
{
"fromChain": "43114",
"toChain": "8453",
"fromToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"toToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"fromAmount": "900000000000000000",
"fromAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"toAddress": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"hooks": {
"postHook": {
"target": "0xYourVaultAddress",
"callData": "0xYourEncodedCallData",
"value": "2000000000000000",
"fundToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"fundAmount": "2000000000000000",
"estimatedGas": "150000"
}
}
}
```
| Field | Required | Description |
| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `target` | Yes | Contract address to call on the destination chain. Must be a valid EVM address. |
| `callData` | Yes | ABI-encoded calldata for the call. |
| `value` | Native-value calls only | Native value to send with the call, in destination base units. |
| `fundToken` | No | Token the call acts on. Defaults to the route's `toToken`. |
| `fundAmount` | Yes unless `fullAmount` is set | Predetermined destination-token amount the call is funded with, in destination base units. Portable across providers. |
| `estimatedGas` | Recommended | Gas limit hint for the destination call. Some providers require it, so omitting it narrows which providers can serve the request. |
| `toApprovalAddress` | ERC-20 destination calls | Address allowed to pull `fundToken` when the destination contract uses `transferFrom`, normally the contract itself. Must not be set for native-value calls. How the allowance is granted depends on the resolving provider: some prepend an explicit `approve()` call, others pass the address to their own execution engine. |
| `fullAmount` | No | **Provider dependent.** Patch the encoded call with the amount that actually arrives rather than a predetermined `fundAmount`. Requires `amountInputPos`. Only providers that implement amount patching are selected for these requests. |
| `amountInputPos` | With `fullAmount` | **Provider dependent**, and required whenever `fullAmount` is set. The zero-based index of the ABI argument to patch. Index `0` is valid. |
| `toFallbackAddress` | No | **Provider dependent.** Recipient if the destination call fails, when the resolving provider implements a fallback. Not every provider does, so this is not a guarantee. |
| `description` | No | Optional free-text label for the call. Not forwarded by every provider, so do not depend on it appearing downstream. |
Requests are rejected before routing when `target` or `callData` is missing, when `fullAmount` is `true` without `amountInputPos`, or when neither `fundAmount` nor `fullAmount` is supplied. A posthook the API rejects returns `400` with a message describing the problem.
`hooks.postHook` is also accepted on `POST /api/v1/routes/deposit-address`. Deposit-address routing is provider dependent, and so are deposit-address routes with a posthook, so that path is not available on every route.
The route status reflects the provider's aggregate result. There is no separate
field reporting the destination call on its own, so verify destination state by
reading your own contract.
## Response
```json theme={null}
{
"data": {
"intentId": "32c423a8-0531-453e-80b6-c318625cba4d",
"route": {
"estimate": {
"fromAmount": "215124226346059100",
"toAmount": "1994795",
"toAmountMin": "1974049",
"toAmountUsd": "1.99",
"totalFeesUsd": "0.050017",
"fees": [...]
},
"execution": {
"transaction": {
"to": "0xce16F69375520ab01377ce7B88f5BA8C48F8D666",
"data": "0x84…ed56",
"value": "219426580165239030",
"gasLimit": "682400",
"gasPrice": "12900585",
"maxFeePerGas": "26170938",
"maxPriorityFeePerGas": "1000",
"type": "ON_CHAIN_EXECUTION"
}
},
"provider": "squid",
"requestId": "b17a041c3f5633e3d46e9eb44041ed56",
"reliabilityScore": 1
}
}
}
```
| Field | Description |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `intentId` | Unique identifier for this intent. Pass this to `/receipt` and `/status`. |
| `route.estimate` | Fee and amount estimates (same structure as the `/quote` response). |
| `route.execution.transaction` | The transaction object to sign and broadcast. Pass this to your signing infrastructure. |
| `route.execution.transaction.to` | The provider contract this transaction is sent to. Changes when the selected provider changes between quotes. This is not necessarily the ERC-20 spender; see [Token approvals](#token-approvals). |
| `route.execution.transaction.data` | Encoded calldata for the route transaction. |
| `route.execution.transaction.value` | Native token value to send with the transaction (in wei). |
| `route.execution.approvals` | Present when the route requires ERC-20 allowances before the route transaction can succeed. Each entry is authoritative for one allowance. |
| `route.execution.approvals[].tokenAddress` | The ERC-20 token to approve. |
| `route.execution.approvals[].spender` | The address to approve the token for. Always a provider contract, never a Trustware contract. |
| `route.execution.approvals[].amount` | The exact amount to approve, in the token's base units. |
| `route.execution.approvals[].chainId` | The chain the approval belongs on. |
| `route.provider` | The routing provider selected for this route, as a runtime value such as `"squid"` or `"lifi"`. |
| `route.requestId` | Provider-level request ID for debugging. |
| `route.reliabilityScore` | Provider reliability score for this route, from 0 to 1. |
Every field on an approval entry is optional, so skip any entry that does not carry a token, a spender, and a non-zero amount.
## Token approvals
If the source token is an ERC-20, the required allowances must be granted before the route transaction is broadcast. The spender is always a provider contract, never a Trustware contract.
When `route.execution.approvals` is present, each entry is the authoritative source for one allowance: approve `amount` of `tokenAddress` for `spender` on `chainId`. Grant each one, wait for it to confirm, then broadcast `route.execution.transaction`.
An ERC-20 source route returns them inside `route.execution`:
```json theme={null}
{
"data": {
"route": {
"execution": {
"approvals": [
{
"chainId": "43114",
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"spender": "0xce16F69375520ab01377ce7B88f5BA8C48F8D666",
"amount": "2000000"
}
]
}
}
}
}
```
Do not assume the spender is `route.execution.transaction.to`. It often is, but
a route can require an allowance for a different address, and it can require
more than one. When `route.execution.approvals` is present, use it.
Re-quoting can change the selected provider, which changes the required allowances. Rebuild the route and re-read `approvals` rather than reusing an earlier response. You can also check a specific allowance with [`GET /allowance`](/api-reference/allowance) before broadcasting.
This is a source-chain allowance and is unrelated to `hooks.postHook.toApprovalAddress`, which controls a destination-chain allowance for a destination contract call.
The `@trustware/sdk` headless core handles the source allowances automatically on EVM routes: `Trustware.sendRouteTransaction()` reads the current allowance, submits an approval for the exact amount when one is missing, waits for confirmation, and then sends the route transaction.
# GET /status
Source: https://docs.trustware.io/api-reference/status
Poll transaction progress after submitting the receipt. Returns status, transaction hashes, and the metadata you passed to /route.
`GET https://api.trustware.io/api/v1/route-intent/:intentId/status`
Poll this endpoint after submitting the receipt to track routing and settlement progress through to completion. The `metadata` field echoes exactly what you passed in the `POST /route` call, so use it to attribute the transaction to the correct user or internal record.
Poll until status reaches `success` or `failed`.
## Path parameters
| Parameter | Description |
| ---------- | ----------------------------------------- |
| `intentId` | The `intentId` returned by `POST /route`. |
## Response
```json theme={null}
{
"data": {
"id": "bc5a5e82-1ecb-453b-bf78-c7c3ccdc3223",
"intent_id": "d9a10f42-7243-498d-914a-269a18b93660",
"sdk_request": true,
"from_address": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"to_address": "0xeb212fe20f26243ec4d4df2dd49c8aa9fa75a201",
"from_chain_id": "43114",
"to_chain_id": "137",
"source_tx_hash": "0x4c8f91363c5f5e3ac2154f94995f5beac2993cc595af552c2681538af627e05d",
"dest_tx_hash": "0x9b7e3f0a...",
"request_id": "eeaa53a4f2e254896ae690edac309e68",
"status": "bridging",
"routing_provider": "squid",
"quote_to_amount_usd": "0.19000000",
"to_amount_wei": "198584",
"poll_attempt": 1,
"next_poll_at": "2026-04-15T03:13:54.289994Z",
"create_date": "2026-04-15T03:13:22.592483Z",
"update_date": "2026-04-15T03:13:24.290292Z"
}
}
```
## Status values
The `status` field on the response is one of four values:
| `status` | Meaning |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `submitted` | The receipt has been submitted; tracking has started. Continue polling. |
| `bridging` | Assets are in transit to the destination chain. Continue polling. |
| `success` | Route completed successfully. Final amounts and destination hash are available. |
| `failed` | The route failed. If funds moved on the source chain, a refund has been initiated back to the source address. |
If `intentId` is unknown or no receipt has been submitted yet, the endpoint returns HTTP `404 Not Found` rather than a status value.
### Auxiliary status fields
A few separate fields surface conditions that are not part of the main `status` enum:
| Field | Meaning |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `gas_status` | Set to `"needs_gas"` when the destination chain step stalled due to insufficient gas. Present only when relevant. |
| `routing_provider` | The routing provider used for this route, as a runtime value such as `"squid"` or `"lifi"`. Useful when triaging a `failed` status. |
## Response fields
| Field | Description |
| --------------------- | --------------------------------------------------------------------------------------- |
| `id` | Trustware internal transaction record ID. |
| `intent_id` | The `intentId` from the route response. |
| `sdk_request` | `true` when the intent originated from a Trustware SDK API key. |
| `from_address` | Source wallet address. |
| `to_address` | Destination wallet address. |
| `from_chain_id` | Source chain ID. |
| `to_chain_id` | Destination chain ID. |
| `source_tx_hash` | Transaction hash on the source chain. |
| `dest_tx_hash` | Transaction hash on the destination chain. Populated once the route settles. |
| `status` | Current status. One of `submitted`, `bridging`, `success`, `failed`. |
| `routing_provider` | Routing provider used for this route, as a runtime value such as `"squid"` or `"lifi"`. |
| `quote_to_amount_usd` | Expected destination USD value from the quote. |
| `to_amount_wei` | Destination token amount received (in smallest unit). Populated on `success`. |
| `gas_status` | Optional. Surfaces conditions such as `"needs_gas"` when the destination step stalls. |
| `poll_attempt` | Number of times this intent has been polled. |
| `next_poll_at` | Suggested timestamp for the next poll. |
| `create_date` | When the intent record was created (ISO 8601). |
| `update_date` | When the status was last updated (ISO 8601). |
## Polling guidance
Use `next_poll_at` as the suggested time for your next request rather than a fixed interval. This avoids unnecessary requests while the route is in transit.
On `failed` status: if the source chain transaction confirmed but the route failed, Trustware initiates a refund to the source address. Your application should not trigger any downstream payout until `success` is confirmed.
The `status` endpoint will become a WebSocket stream in a future release. The polling interface will remain supported.
# TypeScript types exported from @trustware/sdk
Source: https://docs.trustware.io/api-reference/types
All public TypeScript types and enums exported by @trustware/sdk: config shapes, error classes, event types, and wallet interfaces.
All types below are exported from `@trustware/sdk` and are available as named imports. You can use them to type your own integration code without importing runtime values.
```ts theme={null}
import type {
TrustwareConfigOptions,
TrustwareEvent,
TrustwareError,
WalletInterFaceAPI,
} from "@trustware/sdk";
```
### Public import paths
The package exposes its public surface through the root entry plus four scoped sub-paths. Pick whichever import keeps your bundle smallest:
| Import path | Contents |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `@trustware/sdk` | Everything: `Trustware`, `TrustwareProvider`, `TrustwareWidget`, types, constants, wallet helpers. Most integrations use only this. |
| `@trustware/sdk/wallet` | Wallet adapters and detection: `useWagmi`, `useEIP1193`, `connectDetectedWallet`, related helpers. |
| `@trustware/sdk/core` | The `Trustware` facade and core REST methods, without widget code. |
| `@trustware/sdk/react` | The `TrustwareWidget` component bundle. |
| `@trustware/sdk/constants` | `SDK_VERSION`, `API_ROOT`, `WALLETCONNECT_PROJECT_ID`, and other constants. |
***
## Configuration
### `TrustwareConfigOptions`
The shape passed to `TrustwareProvider` and `Trustware.init`. It is a discriminated union on `mode`: `routes` is required in deposit mode and optional in swap mode.
```ts theme={null}
type TrustwareConfigOptions = CommonConfigOptions &
(
| { mode?: "deposit"; routes: RoutesConfig }
| { mode: "swap"; routes?: Partial }
);
```
`RoutesConfig` is exported and importable. `CommonConfigOptions`, which holds `apiKey` and every non-`routes` field, is internal to the declaration and is not exported, so import `TrustwareConfigOptions` itself rather than composing from its parts.
Expanded for deposit mode, which is what you get when `mode` is omitted. In swap mode the same shape applies except that `mode: "swap"` is required and `routes` becomes optional:
```ts theme={null}
// Deposit mode (mode omitted or "deposit")
type TrustwareConfigOptions = {
apiKey: string;
mode?: "deposit" | "swap";
routes: { // optional when mode is "swap"
toChain: string;
toToken: string;
fromToken?: string;
fromChain?: string;
fromAddress?: string;
toAddress?: string;
defaultSlippage?: number;
options?: {
routeRefreshMs?: number;
fixedFromAmount?: string | number;
minAmountOut?: string | number;
maxAmountOut?: string | number;
};
};
autoDetectProvider?: boolean;
theme?: TrustwareTheme;
messages?: Partial;
retry?: RetryConfig;
walletConnect?: WalletConnectConfig;
features?: FeatureFlags;
onError?: (error: TrustwareError) => void;
onSuccess?: (transaction: Transaction) => void;
onEvent?: (event: TrustwareEvent) => void;
};
```
| Field | Required | Description |
| ------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` | Yes | Your Trustware API key. |
| `mode` | No | `"deposit"` (default) or `"swap"`. Requires SDK 1.1.9 or later. |
| `routes.toChain` | In deposit mode | Default destination chain ID (e.g. `"8453"` for Base). |
| `routes.toToken` | In deposit mode | Default destination token identifier: contract address on EVM, mint address on Solana, native denom on Cosmos. |
| `routes.fromToken` | No | Preferred source token, pre-selected on the source side. |
| `routes.fromChain` | No | Preferred source chain, pre-selected on the source side. Also the chain `routes.fromAddress` is validated against; falls back to `routes.toChain`. |
| `routes.toAddress` | No | Recipient address. Can be set later via `Trustware.setDestinationAddress`. |
| `routes.defaultSlippage` | No | Slippage percentage. Defaults to `1`. |
| `autoDetectProvider` | No | Whether to auto-detect the wallet provider. Defaults to `false`. |
| `features.shouldAllowGA4` | No | Set to `false` to disable SDK GA4 analytics tracking. Defaults to `true`. |
| `theme` | No | Widget color scheme: `"light"`, `"dark"`, or `"system"`. Defaults to `"system"`. See `TrustwareTheme`. |
| `messages` | No | Widget title and description overrides. See `TrustwareWidgetMessages`. |
| `retry` | No | Retry and rate-limit callback configuration. See `RetryConfig`. |
| `walletConnect` | No | WalletConnect connector overrides. See `WalletConnectConfig`. |
| `onError` | No | Callback fired on any `TrustwareError`. |
| `onSuccess` | No | Callback fired when a transaction is confirmed. Receives the `Transaction` object. |
| `onEvent` | No | Callback fired for all SDK lifecycle events. Receives a `TrustwareEvent`. |
***
### `RoutesConfig`
The `routes` object, exported as a named type since SDK 1.1.9. Required in deposit mode; optional and partial in swap mode.
```ts theme={null}
type RoutesConfig = {
toChain: string;
toToken: string;
fromToken?: string;
fromChain?: string;
fromAddress?: string;
toAddress?: string;
defaultSlippage?: number;
options?: {
routeRefreshMs?: number;
fixedFromAmount?: string | number;
minAmountOut?: string | number;
maxAmountOut?: string | number;
};
};
```
See [route configuration](/configuration/routes) for what each field does.
***
### `ResolvedTrustwareConfig`
The config object returned by `Trustware.getConfig()`. All optional fields have defaults applied.
```ts theme={null}
type ResolvedTrustwareConfig = {
apiKey: string;
mode: "deposit" | "swap";
routes: {
toChain: string;
toToken: string;
fromToken?: string;
fromAddress?: string;
toAddress?: string;
defaultSlippage: number;
options: {
routeRefreshMs?: number;
fixedFromAmount?: string | number;
minAmountOut?: string | number;
maxAmountOut?: string | number;
};
};
autoDetectProvider: boolean;
theme: TrustwareTheme;
messages: TrustwareWidgetMessages;
retry: ResolvedRetryConfig;
walletConnect?: ResolvedWalletConnectConfig;
features: ResolvedFeatureFlags;
onError?: (error: TrustwareError) => void;
onSuccess?: (transaction: Transaction) => void;
onEvent?: (event: TrustwareEvent) => void;
};
```
Config resolution carries `routes.fromChain` through to the resolved object at
runtime, but the exported `ResolvedTrustwareConfig` declaration above does not
list it. Do not rely on reading `fromChain` back off `Trustware.getConfig()`
in typed code until the declaration is updated.
***
### `RetryConfig`
Controls how the SDK handles `429 Too Many Requests` responses.
```ts theme={null}
type RetryConfig = {
autoRetry?: boolean;
maxRetries?: number;
baseDelayMs?: number;
onRateLimitInfo?: (info: RateLimitInfo) => void;
onRateLimited?: (info: RateLimitInfo, retryCount: number) => void;
onRateLimitApproaching?: (info: RateLimitInfo, threshold: number) => void;
approachingThreshold?: number;
};
```
| Field | Default | Description |
| ---------------------- | ------- | --------------------------------------------------------------- |
| `autoRetry` | `true` | Retry automatically on 429 responses. |
| `maxRetries` | `3` | Maximum retry attempts. |
| `baseDelayMs` | `1000` | Base delay for exponential backoff in milliseconds. |
| `approachingThreshold` | `5` | Remaining-request count that triggers `onRateLimitApproaching`. |
***
### `RateLimitInfo`
Passed to `RetryConfig` callbacks when rate limit headers are present in a response.
```ts theme={null}
type RateLimitInfo = {
limit: number;
remaining: number;
reset: number;
retryAfter?: number;
};
```
***
### `WalletConnectConfig`
Optional WalletConnect overrides. The SDK includes built-in defaults for all fields.
```ts theme={null}
type WalletConnectConfig = {
projectId?: string;
chains?: number[];
optionalChains?: number[];
metadata?: {
name: string;
description?: string;
url: string;
icons?: string[];
};
relayUrl?: string;
showQrModal?: boolean;
disabled?: boolean;
};
```
***
### `SwapTokenRef`
A token identified by its contract address and chain ID. Used by the swap-mode fields in `FeatureFlags`.
```ts theme={null}
type SwapTokenRef = {
address: string;
chainId: number;
};
```
`chainId` is numeric, so `SwapTokenRef` can only express EVM destinations.
Cosmos chains are identified by named strings such as `cataclysm-1` and cannot
be set through `swapDefaultDestToken` or `swapAllowedDestTokens`.
***
### `FeatureFlags`
Controls optional SDK behaviours. Pass as `features` in `TrustwareConfigOptions`.
```ts theme={null}
type FeatureFlags = {
tokensPagination?: boolean;
balanceStreaming?: boolean;
shouldAllowGA4?: boolean;
/** @deprecated Use the top-level `mode: "swap"` instead. */
swapMode?: boolean;
swapDefaultDestToken?: SwapTokenRef;
swapLockDestToken?: boolean;
swapAllowedDestTokens?: SwapTokenRef[];
};
```
| Field | Default | Description |
| ----------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tokensPagination` | `true` | Paginated token list fetching in the widget's token picker. Emits `token_page_loaded` and `token_page_error` events. Set to `false` to fetch the full token list in a single request. |
| `balanceStreaming` | `false` | Streaming balance fetches across chains. Set to `true` to render balances per chain as they arrive, which emits `balance_stream_chunk` and `balance_stream_fallback` events. Off by default, so the widget waits for all chains before rendering balances. |
| `shouldAllowGA4` | `true` | When `false`, disables the SDK's built-in Google Analytics 4 (GA4) tracking. Set this to `false` if your app has its own analytics pipeline, your users have opted out of tracking, or you need to comply with privacy regulations such as GDPR or CCPA. |
| `swapMode` | `false` | Deprecated in SDK 1.1.9. Enables swap mode and is treated as equivalent to the top-level `mode: "swap"`, which is the preferred field. Setting it logs a deprecation warning. See [swap mode](/guides/swap-mode). |
| `swapDefaultDestToken` | none | Pre-selects the destination token (`SwapTokenRef`) shown when the widget opens in swap mode. |
| `swapLockDestToken` | `false` | Locks the swap destination so the user cannot change it. Use with `swapDefaultDestToken`. |
| `swapAllowedDestTokens` | none | Restricts the swap destination-token picker to this list of `SwapTokenRef` entries. Everything else is locked out. |
**Example: disable GA4 while leaving the other flags at their defaults.**
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
features: {
shouldAllowGA4: false,
},
} satisfies TrustwareConfigOptions;
```
***
## Widget
### `TrustwareWidgetRef`
The type of the imperative handle returned when you pass a `ref` to `TrustwareWidget`.
```ts theme={null}
interface TrustwareWidgetRef {
open: () => void;
close: () => void;
isOpen: () => boolean;
}
```
***
## Events
### `TrustwareEvent`
A discriminated union of all events emitted via `config.onEvent`. Each variant has a `type` field you can use to narrow the type.
```ts theme={null}
type TrustwareEvent =
| { type: "error"; error: TrustwareError }
| { type: "transaction_started" }
| { type: "transaction_success"; txHash: string; transaction?: Transaction }
| { type: "wallet_connected"; address: string }
| {
type: "token_page_loaded";
chainRef: string;
query?: string;
count: number;
hasNextPage: boolean;
cursor?: string;
}
| {
type: "token_page_error";
chainRef: string;
query?: string;
cursor?: string;
message: string;
}
| {
type: "balance_stream_chunk";
address: string;
chunkSize: number;
}
| {
type: "balance_stream_fallback";
address: string;
message: string;
}
| {
type: "swap_route_changed";
fromChain: string;
fromToken: string;
toChain: string;
toToken: string;
amount?: string;
};
```
**Event reference:**
| `type` | When it fires |
| ------------------------- | ----------------------------------------------------------------------------------------- |
| `error` | Any SDK error, including API failures and invalid config. |
| `transaction_started` | A transaction has been initiated. |
| `transaction_success` | A transaction completed successfully. |
| `wallet_connected` | A wallet was attached to the SDK. |
| `token_page_loaded` | A page of tokens was fetched (pagination). |
| `token_page_error` | A token page fetch failed. |
| `balance_stream_chunk` | A chunk of balances arrived from the streaming endpoint. |
| `balance_stream_fallback` | Streaming failed and the SDK fell back to a single request. |
| `swap_route_changed` | In swap mode, the resolved route changed (source or destination chain, token, or amount). |
***
## Errors
### `TrustwareError`
Extends the native `Error` class. All SDK errors are instances of this class.
```ts theme={null}
class TrustwareError extends Error {
code: TrustwareErrorCode;
userMessage?: string;
cause?: unknown;
}
```
| Field | Description |
| ------------- | -------------------------------------------------------- |
| `code` | Machine-readable error code (`TrustwareErrorCode` enum). |
| `message` | Developer-facing error message. |
| `userMessage` | Optional user-facing message suitable for display in UI. |
| `cause` | The underlying error that triggered this one, if any. |
***
### `TrustwareErrorCode`
Enum of all error codes that can appear on a `TrustwareError`.
```ts theme={null}
enum TrustwareErrorCode {
INVALID_CONFIG = "INVALID_CONFIG",
INVALID_API_KEY = "INVALID_API_KEY",
WALLET_NOT_CONNECTED = "WALLET_NOT_CONNECTED",
BRIDGE_FAILED = "BRIDGE_FAILED",
NETWORK_ERROR = "NETWORK_ERROR",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
INPUT_ERROR = "INPUT_ERROR",
}
```
| Code | Description |
| ---------------------- | ---------------------------------------------------------------------- |
| `INVALID_CONFIG` | The config object is missing required fields or has invalid values. |
| `INVALID_API_KEY` | API key validation against the Trustware backend failed. |
| `WALLET_NOT_CONNECTED` | A wallet operation was attempted before a wallet was attached. |
| `BRIDGE_FAILED` | The bridge transaction failed on-chain or was rejected by the backend. |
| `NETWORK_ERROR` | A network request failed (timeout, DNS failure, etc.). |
| `UNKNOWN_ERROR` | An unexpected error with no specific classification. |
| `INPUT_ERROR` | Invalid user input, such as a malformed address or unsupported chain. |
***
### `SDKRPCError`
`Trustware.sendRouteTransaction()` polls the approval transaction status while it handles source token approvals. If that lookup fails, it throws an error whose `name` is `"SDKRPCError"`. A failed allowance read does not throw; the SDK submits the approval anyway.
This class is not exported, so identify it by name rather than with `instanceof`.
```ts theme={null}
try {
await Trustware.sendRouteTransaction(route, 42161);
} catch (error) {
if (error instanceof Error && error.name === "SDKRPCError") {
// An approval transaction-status lookup failed. Retryable.
}
}
```
It is not a `TrustwareError`, so it carries no `TrustwareErrorCode`.
***
## Routes & Transactions
### `BuildRouteResult`
Returned by `Trustware.buildRoute`.
```ts theme={null}
type BuildRouteResult = {
intentId: string;
txReq: TxRequest;
actions: unknown[];
finalExchangeRate: {
fromAmountUSD?: string;
toAmountMinUSD?: string;
};
route: RoutePlan | undefined;
sponsorship?: RouteSponsorship;
};
```
`txReq` is the transaction to sign and broadcast. Its type is not currently exported under its own name, so refer to it as `BuildRouteResult["txReq"]` in your own code rather than importing a named type. `sponsorship` is present when the route is gas sponsored.
***
### `PostHookRequest`
An optional contract call executed on the destination chain once routed funds arrive. Pass it as `hooks.postHook` on `Trustware.buildRoute` or `Trustware.buildDepositAddress`. Omit `hooks` entirely and route behavior is unchanged. Destination calls are EVM only.
```ts theme={null}
type PostHookRequest = {
target: string;
callData: string;
value?: string;
fundToken?: string;
fundAmount?: string;
fullAmount?: boolean;
amountInputPos?: number;
estimatedGas?: string;
toApprovalAddress?: string;
toFallbackAddress?: string;
description?: string;
};
```
| Field | Required | Description |
| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `target` | Yes | Contract address to call on the destination chain. Must be a valid EVM address. |
| `callData` | Yes | ABI-encoded calldata for the call. |
| `value` | Native-value calls only | Native value to send with the call, in destination base units. |
| `fundToken` | No | Token the call acts on. Defaults to the route's `toToken`. |
| `fundAmount` | Yes unless `fullAmount` is set | Predetermined destination-token amount the call is funded with, in destination base units. Portable across providers. |
| `estimatedGas` | Recommended | Gas limit hint for the destination call. Some providers require it, so omitting it narrows which providers can serve the route. |
| `toApprovalAddress` | ERC-20 destination calls | Address allowed to pull `fundToken` when the destination contract uses `transferFrom`, normally the contract itself. Must not be set for native-value calls. |
| `fullAmount` | No | **Provider dependent.** Patch the encoded call with the amount that actually arrives instead of a predetermined `fundAmount`. Requires `amountInputPos`. |
| `amountInputPos` | With `fullAmount` | **Provider dependent**, and required whenever `fullAmount` is set. The zero-based index of the ABI argument to patch. Index `0` is valid. |
| `toFallbackAddress` | No | **Provider dependent.** Recipient if the destination call fails, when the resolving provider implements a fallback. Not a guarantee. |
| `description` | No | Optional free-text label for the call. Not forwarded by every provider, so do not depend on it appearing downstream. |
`buildRoute` rejects the request before it is sent when `target` or `callData` is missing, when `fullAmount` is `true` without `amountInputPos`, or when neither `fundAmount` nor `fullAmount` is supplied. These rejections are thrown as a plain `Error`, not a `TrustwareError`, so they carry no `code`.
Requires SDK 1.1.10 or later. Destination posthooks are available through the
headless core and the REST API. The widget and `Trustware.runTopUp()` do not
accept a `hooks` field. See [vault destinations](/guides/vault-destinations).
***
### `RouteApproval`
An ERC-20 allowance on the source chain that must be granted before `route.execution.transaction` can succeed. Appears on `RoutePlan.execution.approvals`.
```ts theme={null}
type RouteApproval = {
chainId?: string;
tokenAddress?: string;
spender?: string;
amount?: string;
};
```
| Field | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `chainId` | Chain the allowance belongs on. Optional. The SDK falls back to the route transaction's chain, and skips the entry if neither is available. |
| `tokenAddress` | Token to approve. |
| `spender` | Address to approve the token for. Always a provider contract, never a Trustware contract. |
| `amount` | Exact amount to approve, in the token's base units. |
Every field is optional, so an entry can arrive incomplete. `Trustware.sendRouteTransaction()` skips any entry without a `spender`, a `tokenAddress`, and a non-zero `amount`, and your own signing code should do the same.
When `execution.approvals` is present, treat it as authoritative rather than
assuming the spender is `execution.transaction.to`. See
[token approvals](/api-reference/route#token-approvals). This is a source-chain
allowance and is unrelated to `PostHookRequest.toApprovalAddress`, which
controls a destination-chain allowance.
***
### `RoutePlan`
The resolved route on `BuildRouteResult.route`. Every field is optional.
```ts theme={null}
type RoutePlan = {
estimate?: RouteEstimate;
execution?: { transaction?: TxRequest; approvals?: RouteApproval[] };
steps?: unknown[];
provider?: string;
requestId?: string;
reliabilityScore?: number;
diagnostics?: { rawPayload?: unknown };
sponsorship?: RouteSponsorship;
};
```
| Field | Description |
| ----------------------- | --------------------------------------------------------------------------------- |
| `estimate` | Amount and fee estimates for this route. |
| `execution.transaction` | The transaction to sign and broadcast. |
| `execution.approvals` | Source-chain ERC-20 allowances required before the route transaction can succeed. |
| `steps` | Provider-level route steps. Opaque; for debugging only. |
| `provider` | The liquidity provider selected for this route. |
| `requestId` | Provider-level request ID, useful for support. |
| `reliabilityScore` | Provider reliability score for this route. |
| `diagnostics` | Raw provider payload, for debugging. |
| `sponsorship` | Present when the route is gas sponsored. |
`execution.transaction` is typed as `TxRequest` in the SDK declaration, but that
name is not exported from a package entry point. Refer to the shape as
`BuildRouteResult["txReq"]` in your own code rather than importing it.
***
### `RouteSponsorship`
Present on `BuildRouteResult.sponsorship` and `RoutePlan.sponsorship` when the route is gas sponsored. Exported from `@trustware/sdk`. See [Paymasters](/guides/paymasters) for how sponsorship is configured and when it applies.
```ts theme={null}
type RouteSponsorship = {
requestId: string;
paymaster: string;
entryPoint: string;
chainId: string;
callDataHash: string;
maxCost: string;
paymasterAndData: string;
signature: string;
signer: string;
typedDataHash: string;
approval: SponsorshipApproval;
};
```
| Field | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `requestId` | Sponsorship request identifier, useful for support. |
| `paymaster` | Paymaster contract address for this sponsorship. |
| `entryPoint` | EntryPoint contract the sponsored operation targets. |
| `chainId` | Chain the sponsorship applies to. |
| `callDataHash` | Hash of the route calldata this sponsorship was issued for. `Trustware.sendRouteTransaction()` checks the route calldata against this before relying on the sponsorship. |
| `maxCost` | Maximum sponsored cost, in base units. |
| `paymasterAndData` | The paymaster payload a smart-account wallet forwards when sending. |
| `signature` | Sponsorship signature. |
| `signer` | Address that signed the sponsorship. |
| `typedDataHash` | Hash of the signed typed data. |
| `approval` | The underlying approval record. |
When a route carries a sponsorship whose `callDataHash` does not match the route
calldata, the SDK ignores the sponsorship and falls back to the ordinary path,
including the source token approval.
***
### `RouteEstimate`
Amount and fee estimates for a resolved route, on `RoutePlan.estimate`. Every field is optional.
```ts theme={null}
type RouteEstimate = {
fromAmount?: string;
toAmount?: string;
toAmountMin?: string;
fromAmountUsd?: string;
toAmountUsd?: string;
totalFeesUsd?: string;
toAmountMinUsd?: string;
fees?: unknown[];
};
```
| Field | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `fromAmount` | Source amount, in the source token's base units. |
| `toAmount` | Expected destination amount, in the destination token's base units. |
| `toAmountMin` | Guaranteed minimum destination amount after slippage. Use this when you need to know what the route will definitely deliver. |
| `fromAmountUsd` | USD value of the source amount. |
| `toAmountUsd` | USD value of the expected destination amount. |
| `toAmountMinUsd` | USD value of the guaranteed minimum. |
| `totalFeesUsd` | Total fees for the route, in USD. |
| `fees` | Per-fee breakdown. Opaque; provider shaped. |
The USD fields here use lowercase `Usd`. The separate
`BuildRouteResult.finalExchangeRate` object uses uppercase `USD`
(`fromAmountUSD`, `toAmountMinUSD`). Both spellings are correct for their own
object.
***
### `Transaction`
Returned by `Trustware.getStatus` and `Trustware.pollStatus`. Also passed to `config.onSuccess`.
```ts theme={null}
type Transaction = {
id: string;
intentId: string;
fromAddress: string;
toAddress: string;
origin_eoa?: string;
fromChainId: string | number;
toChainId: string | number;
sourceTxHash: string;
destTxHash: string;
requestId: string;
transactionRequest: unknown;
status: "submitted" | "bridging" | "success" | "failed";
statusRaw?: unknown;
routePath?: unknown;
routeStatus?: unknown;
toAmountWei?: string | number;
landed_amount_verified?: boolean;
fromChainBlock: number;
toChainBlock: number;
fromChainTxUrl?: string;
toChainTxUrl?: string;
gasStatus?: string;
isGMPTransaction?: boolean;
axelarTransactionUrl?: string;
createdDate: Date | string;
updatedDate: Date | string;
timeSpentMs?: number;
};
```
`origin_eoa` is the connected EOA that originated the payment when the sender is a smart account. It is optional, EVM and smart-account relevant, and distinct from `fromAddress`.
`toAmountWei` is a pre-trade quote estimate copied in when the transaction was submitted. It is exact only once `landed_amount_verified` is `true`, which indicates Trustware confirmed the destination amount by reading the destination chain. Check the flag before you use the amount to drive anything automatic.
***
## Wallet
### `WalletInterFaceAPI`
The union type accepted by `Trustware.useWallet` and the `wallet` prop on `TrustwareProvider`. It is either an EVM wallet interface or a Solana wallet interface.
```ts theme={null}
type WalletInterFaceAPI = EvmWalletInterface | SolanaWalletInterface;
type EvmWalletInterface = {
ecosystem: "evm";
getAddress(): Promise;
getChainId(): Promise;
switchChain(chainId: number): Promise;
disconnect?(): Promise;
} & (
| {
type: "eip1193";
request(args: { method: string; params?: unknown[] | object }): Promise;
}
| {
type: "wagmi";
sendTransaction(tx: {
to: `0x${string}`;
data: `0x${string}`;
value?: bigint;
chainId?: number;
}): Promise<{ hash: `0x${string}` }>;
}
);
type SolanaWalletInterface = {
ecosystem: "solana";
type: "solana";
getAddress(): Promise;
getChainKey(): Promise;
sendSerializedTransaction(
serializedTransactionBase64: string,
chainId?: string
): Promise;
disconnect?(): Promise;
};
```
***
## Balances
### `BalanceRow`
A single token balance entry returned by `Trustware.getBalances`.
```ts theme={null}
type BalanceRow = {
chain_key: string;
category: "native" | "erc20" | "spl" | "btc";
contract?: string;
address?: string;
symbol?: string;
decimals: number;
balance: string;
name?: string;
logoURI?: string;
usdPrice?: number;
};
```
Cosmos balances arrive with `category: "bank"`, including the chain's native
token. The union above is the type as the SDK currently declares it and does
not enumerate that value, so widen your own narrowing if you switch on
`category`. Do not assume `"native"` identifies the native row on a Cosmos
chain.
***
### `WalletAddressBalanceWrapper`
Wraps a set of `BalanceRow` entries for a specific chain, returned by `Trustware.getBalancesByAddress`.
```ts theme={null}
type WalletAddressBalanceWrapper = {
chain_id: string;
balances: BalanceRow[];
count: number;
error: string | null;
source: string;
};
```
***
### `BalanceStreamOptions`
Options accepted by `Trustware.getBalancesByAddress` and `Trustware.getBalancesByAddressStream`.
```ts theme={null}
type BalanceStreamOptions = {
stream?: boolean;
signal?: AbortSignal;
strict?: boolean;
};
```
# GET /validate
Source: https://docs.trustware.io/api-reference/validate
Verify that an API key is active and authorized. Use this for connectivity testing and key validation at startup.
`GET https://api.trustware.io/api/v1/sdk/validate`
Verifies that an API key is active and returns metadata about the key. Use this at application startup to confirm your key is valid before making route requests, or as a lightweight connectivity check.
## Request
No request body. Pass your API key in the `X-API-Key` header.
```js theme={null}
const response = await fetch("https://api.trustware.io/api/v1/sdk/validate", {
headers: { "X-API-Key": process.env.TW_KEY },
});
const { valid, status, message } = (await response.json());
```
## Response
```json theme={null}
{
"valid": true,
"status": "active",
"message": "API key is valid and active",
"key_id": "80fe6ff0-5320-4330-ad40-bb034a474e55",
"label": "Trustware Swap",
"project_id": "b7a4c5d1-9f3e-4b8a-a210-d2c44d3e6f80",
"created_at": "2026-04-07T01:45:02.397093Z",
"last_used_at": "2026-04-15T15:38:28.94439Z",
"quote": {
"id": "99",
"text": "What about elevenses?"
}
}
```
| Field | Description |
| -------------- | -------------------------------------------------------------------------- |
| `valid` | `true` if the key is active and authorized. |
| `status` | Key status: `active` or `inactive`. |
| `message` | Human-readable validation message. |
| `key_id` | Unique identifier for this key. |
| `label` | Label assigned to this key when it was created. |
| `project_id` | Identifier of the project this key belongs to. |
| `created_at` | When the key was created (ISO 8601). |
| `last_used_at` | When the key was last used (ISO 8601). Useful for auditing. |
| `quote` | Heartbeat payload: a lightweight response included on every validate call. |
## Startup validation
Call this endpoint once at application startup to fail fast if the key is misconfigured:
```js theme={null}
async function validateKey() {
const res = await fetch("https://api.trustware.io/api/v1/sdk/validate", {
headers: { "X-API-Key": process.env.TW_KEY },
});
if (!res.ok) throw new Error("Trustware API key validation failed");
const { valid, message } = await res.json();
if (!valid) throw new Error(`Trustware key invalid: ${message}`);
}
```
# TrustwareConfigOptions configuration reference
Source: https://docs.trustware.io/configuration/overview
TrustwareConfigOptions is the root config for TrustwareProvider. Controls routing, theming, wallet detection, retry behavior, and lifecycle callbacks.
`TrustwareConfigOptions` is the single object you pass to `TrustwareProvider`. Every aspect of the SDK (which chain and token to route to, how the widget looks, how retries are handled, and which wallet connectors are available) is controlled through this shape.
`apiKey` is always required. In deposit mode, which is the default, a `routes` object containing at minimum `toChain` and `toToken` is required as well. In swap mode the user chooses both sides of the trade in the widget, so `routes` is optional. Everything else falls back to a sensible default.
## Complete config shape
Shown for deposit mode, the default. In swap mode set `mode: "swap"` and `routes` becomes optional; everything else is identical. The SDK types this as a discriminated union on `mode`, shown in the [types reference](/api-reference/types#trustwareconfigoptions).
```ts theme={null}
type TrustwareConfigOptions = {
apiKey: string;
mode?: "deposit" | "swap"; // defaults to "deposit"
routes: { // optional when mode is "swap"
toChain: string;
toToken: string;
fromToken?: string;
fromChain?: string;
fromAddress?: string;
toAddress?: string;
defaultSlippage?: number;
options?: {
routeRefreshMs?: number;
fixedFromAmount?: string | number;
minAmountOut?: string | number;
maxAmountOut?: string | number;
};
};
autoDetectProvider?: boolean;
theme?: TrustwareTheme;
messages?: Partial;
retry?: RetryConfig;
walletConnect?: WalletConnectConfig;
features?: FeatureFlags;
onError?: (error: TrustwareError) => void;
onSuccess?: (transaction: Transaction) => void;
onEvent?: (event: TrustwareEvent) => void;
};
```
## Required fields
Your Trustware API key. Used for all requests the SDK makes to the Trustware
backend. Store this in an environment variable and avoid committing it to
source control.
The two `routes` fields below are required in deposit mode, which is the default. In swap mode they are optional, because the user selects both the source and the destination in the widget. See [swap mode](/guides/swap-mode).
The destination chain ID as a string (for example, `"8453"` for Base). See
the [route configuration](/configuration/routes) page for the full list of
supported values.
The destination token identifier: the contract address on EVM, the mint
address on Solana, or the native denom string (for example `"unibi"`) on
Cosmos. See [route configuration](/configuration/routes) for details.
## Config groups
Each top-level key beyond `apiKey` and `routes` is documented on its own page.
| Key | Description |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode` | `"deposit"` (default) or `"swap"`. Selects which widget experience runs. Requires SDK 1.1.9 or later. See [swap mode](/guides/swap-mode). |
| `routes` | Controls the destination chain and token, preferred source chain and token, slippage, and amount constraints. See [route configuration](/configuration/routes). |
| `theme` | Sets the widget color scheme to `"light"`, `"dark"`, or `"system"`. See [appearance customization](/configuration/theme). |
| `messages` | Overrides the widget title and description copy. See [appearance customization](/configuration/theme). |
| `walletConnect` | Configures or disables the WalletConnect connector. See [WalletConnect configuration](/configuration/walletconnect). |
| `retry` | Controls automatic retry behavior on rate-limited requests. See [retry configuration](/configuration/retry). |
| `features` | Controls optional SDK behaviours and modes: `tokensPagination`, `balanceStreaming`, `shouldAllowGA4`, and swap mode (`swapMode`, `swapDefaultDestToken`, `swapLockDestToken`, `swapAllowedDestTokens`). See [swap mode](/guides/swap-mode) and the [types reference](/api-reference/types#featureflags). |
| `autoDetectProvider` | For headless usage; when `true`, the core API auto-picks up `window.ethereum` when called directly. Defaults to `false`. See note below. |
**`autoDetectProvider` vs `autoDetect`**: two related but distinct controls.
* `autoDetect` is a prop on `` that triggers wallet detection on mount. Defaults to `true`. Set `autoDetect={false}` when passing your own `wallet` (see [host wallet](/integration/host-wallet)) to avoid duplicate discovery.
* `autoDetectProvider` is a field inside `TrustwareConfigOptions` for headless usage. When `true`, the core API auto-picks up `window.ethereum` when called directly. Defaults to `false`.
## Lifecycle callbacks
Called whenever a `TrustwareError` is thrown during a route or transaction
operation. Use this to surface errors to your own UI or logging pipeline.
Called after a transaction is successfully submitted and confirmed. Receives
the completed `Transaction` object.
Called for widget lifecycle events such as step transitions. Use this for
analytics or custom instrumentation.
# Rate limiting and retry configuration
Source: https://docs.trustware.io/configuration/retry
Configure Trustware SDK rate-limit handling: automatic retry with exponential backoff, max retry count, and callbacks for monitoring rate limit status.
The Trustware SDK automatically retries requests that receive a `429 Too Many Requests` response. The `retry` field in `TrustwareConfigOptions` lets you tune that behavior: how many times to retry, how long to wait between attempts, and whether to receive callbacks as the rate limit window fills up.
`autoRetry` controls client-side retry behavior only. It does not disable or
modify backend rate limits; those are enforced server-side regardless of
this setting.
## RetryConfig type
```ts theme={null}
type RetryConfig = {
autoRetry?: boolean;
maxRetries?: number;
baseDelayMs?: number;
approachingThreshold?: number;
onRateLimitInfo?: (info: RateLimitInfo) => void;
onRateLimited?: (info: RateLimitInfo, retryCount: number) => void;
onRateLimitApproaching?: (info: RateLimitInfo, threshold: number) => void;
};
```
## Default values
```ts theme={null}
const DEFAULT_RETRY_CONFIG = {
autoRetry: true,
maxRetries: 3,
baseDelayMs: 1000,
approachingThreshold: 5,
};
```
## Properties
When `true`, the SDK automatically retries any request that receives a `429`
response, using exponential backoff calculated from `baseDelayMs`. Set to
`false` to disable automatic retry and handle `429` responses yourself via
`onRateLimited`.
The maximum number of retry attempts before the SDK gives up and throws a
`RateLimitError`. Once this limit is exhausted, the error propagates to your
`onError` callback if one is configured.
The base delay in milliseconds used for exponential backoff. The delay before
each retry attempt is calculated as `baseDelayMs * 2^retryCount`. With the
default of `1000`:
* Retry 1: \~1 second
* Retry 2: \~2 seconds
* Retry 3: \~4 seconds
The number of remaining requests in the current rate limit window at which the
SDK calls `onRateLimitApproaching`. For example, the default of `5` means the
callback fires when 5 or fewer requests remain before the window resets.
## Callbacks
Called on every response that includes rate limit headers from the server,
regardless of whether the limit has been reached. Use this for passive
monitoring or to display a rate limit gauge in your UI.
```ts theme={null}
onRateLimitInfo: (info) => {
console.log(`${info.remaining} of ${info.limit} requests remaining`);
}
```
Called when a `429` response is received. `retryCount` is the current attempt
number (starting at `1`). Use this to log rate limit events or show a warning
to the user.
```ts theme={null}
onRateLimited: (info, retryCount) => {
console.warn(`Rate limited. Retry attempt ${retryCount}. Resets at ${info.reset}`);
}
```
Called when the remaining request count in the current window falls below
`approachingThreshold`. Use this as an early warning to reduce request
frequency before hitting the limit.
```ts theme={null}
onRateLimitApproaching: (info, threshold) => {
console.warn(`Approaching rate limit: ${info.remaining} requests left (threshold: ${threshold})`);
}
```
## RateLimitInfo shape
All three callbacks receive a `RateLimitInfo` object:
```ts theme={null}
type RateLimitInfo = {
limit: number; // Maximum requests allowed in the current window
remaining: number; // Requests remaining in the current window
reset: number; // Unix timestamp when the window resets
retryAfter?: number; // Seconds until retry is allowed (only present on 429)
};
```
## What happens when retries are exhausted
When `maxRetries` is reached without a successful response, the SDK throws a `RateLimitError`. If you have configured an `onError` callback on `TrustwareConfigOptions`, it will be called with that error. If you are using the headless API directly, the promise rejects with `RateLimitError`.
If `autoRetry` is `false`, the SDK will throw `RateLimitError` immediately on
the first `429` without any retry attempts.
## Example
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
retry: {
autoRetry: true,
maxRetries: 3,
baseDelayMs: 1000,
approachingThreshold: 5,
onRateLimitApproaching: (info, threshold) => {
console.warn(`Rate limit approaching: ${info.remaining} requests remaining`);
},
onRateLimited: (info, retryCount) => {
console.warn(`Rate limited on attempt ${retryCount}. Resets at ${new Date(info.reset * 1000).toISOString()}`);
},
},
} satisfies TrustwareConfigOptions;
```
# Route configuration: chains, tokens, and amounts
Source: https://docs.trustware.io/configuration/routes
The routes field sets the destination chain and token, preferred source chain and token, slippage tolerance, and amount constraints like fixed deposits and min/max guardrails.
The `routes` field in `TrustwareConfigOptions` defines the destination for every transaction the widget processes. At a minimum you must tell the SDK which chain and token to route to. Everything else (source chain and token preference, slippage, amount constraints) is optional and can be configured incrementally.
This page describes deposit mode, the default. In [swap mode](/guides/swap-mode)
the user selects both sides of the trade in the widget, so `routes` is optional
and the fields below are ignored as a destination. The swap destination is set
through the `swap` fields under `features`.
## Required fields (deposit mode)
The destination chain ID as a string. Numeric on EVM chains, so `"8453"`
targets Base mainnet; a named identifier on Cosmos chains, so
`"cataclysm-1"` targets Nibiru. This must be a chain the Trustware backend
supports.
The destination token identifier on the destination chain. On EVM this is the
token contract address, on Solana the mint address, and
`"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"` is the conventional address
used for the native gas token on EVM chains. On Cosmos chains it is the
native denom string, for example `"unibi"` for NIBI on Nibiru.
## Optional fields
A preferred source token. When set, the widget pre-selects this token on the
source side. The user can still change it.
A preferred source chain. When set, the widget pre-selects this chain on the
source side. The user can still change it. This is also the chain that
`routes.fromAddress` is validated against; when omitted, `routes.toChain` is
used for that validation.
Override the source wallet address used when building routes. Useful when you
want to pre-specify the sending address independently of the connected wallet.
Override the destination wallet address. You can also set this at runtime
using `Trustware.setDestinationAddress(address)` without rebuilding the
provider.
Slippage tolerance as a percentage. Defaults to `1` (1%). Increase this for
volatile pairs or routes with low liquidity.
## routes.options
The nested `options` object controls route refresh behavior and amount constraints.
How often (in milliseconds) the widget automatically re-fetches a fresh route
preview while the user is on the amount entry screen. If omitted, routes are
not automatically refreshed.
```ts theme={null}
options: {
routeRefreshMs: 15000, // refresh every 15 seconds
}
```
Locks the widget's amount input to a specific USD amount. When set, the user
cannot change the amount; the widget behaves as a fixed-price checkout.
```ts theme={null}
options: {
fixedFromAmount: "25", // USD amount, cannot be changed by the user
}
```
The minimum USD amount the user is allowed to enter. Amounts below this
threshold are rejected by the widget before a route is requested.
The maximum USD amount the user is allowed to enter. Amounts above this
threshold are rejected by the widget before a route is requested.
## Setting the destination address at runtime
If you only know the destination address after the provider has mounted (for example, after a user logs in), use `Trustware.setDestinationAddress` instead of rebuilding the config:
```ts theme={null}
import { Trustware } from "@trustware/sdk";
Trustware.setDestinationAddress("0xYourDestinationAddress");
```
`Trustware.setDestinationAddress` overwrites `routes.toAddress` for the
current session. You do not need to unmount and remount the provider.
`routes.toAddress` and `Trustware.setDestinationAddress()` set a recipient
address. They do not configure a contract call on the destination chain. To run
a contract call once funds arrive, pass `hooks.postHook` on the individual
route-build request. See [vault destinations](/guides/vault-destinations).
## Examples
### Minimal required config
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
} satisfies TrustwareConfigOptions;
```
### Cosmos destination
Cosmos destinations use the chain's named identifier and the token's native denom string rather than the `0xEeee...` sentinel used on EVM chains. This example settles into native NIBI on Nibiru.
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "cataclysm-1",
toToken: "unibi",
},
} satisfies TrustwareConfigOptions;
```
When `routes.toAddress` is set on a Cosmos destination, it must be a bech32
address with that chain's prefix, so `nibi1...` for Nibiru. Sei is a special
case: address validation accepts either a `sei1...` bech32 address or a `0x`
EVM address, and Sei is discoverable as EVM chain ID `1329`. Its Cosmos
identifier `pacific-1` is not currently returned by chain discovery. See
[supported chains and assets](/supported-chains) for what is available.
### Full route config with all options
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
defaultSlippage: 1,
options: {
routeRefreshMs: 15000,
},
},
} satisfies TrustwareConfigOptions;
```
### Fixed-amount checkout
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
options: {
fixedFromAmount: "25",
},
},
} satisfies TrustwareConfigOptions;
```
### Min/max amount guardrails
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
options: {
minAmountOut: "10",
maxAmountOut: "250",
routeRefreshMs: 10000,
},
},
} satisfies TrustwareConfigOptions;
```
# Customize the Trustware widget appearance
Source: https://docs.trustware.io/configuration/theme
Set the Trustware widget color scheme to light, dark, or system, and override the widget title and description copy with the messages field.
The Trustware widget ships with a default visual style. You can set its color scheme and override its copy to fit your product. Customization happens at two levels: the `theme` and `messages` fields in `TrustwareConfigOptions`, and a handful of props directly on ``.
## theme field
The `theme` field sets the widget's color scheme.
```ts theme={null}
type TrustwareTheme = "light" | "dark" | "system";
```
The color scheme applied to the widget. `"system"` follows the user's OS
preference.
### Example
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
theme: "dark",
} satisfies TrustwareConfigOptions;
```
Color and border radius customization, the `primaryColor`, `backgroundColor`,
`radius`, and related fields of the former `TrustwareWidgetTheme` object, was
available through `@trustware/sdk` 1.1.7 and was replaced by this light, dark,
and system setting in 1.1.8. If your project is pinned below 1.1.8, refer to
the type definitions shipped with your installed version.
## Widget-level theme prop
`` accepts a `theme` prop that takes the same three values.
```tsx theme={null}
```
Sets the color scheme for this widget instance. In swap mode the prop takes
precedence over the `theme` config field, falling back to `"system"`.
The prop currently applies in swap mode only. In deposit mode the widget reads
the color scheme from the `theme` config field and ignores the prop, so set
`theme` on your config rather than on the component if you need to control the
deposit widget's appearance.
### showThemeToggle
When `true`, a light/dark mode toggle is rendered inside the widget, letting
the user switch modes themselves. Defaults to `true`. Set to `false` when
your application owns the color scheme and you do not want users to override
it from the widget surface.
```tsx theme={null}
```
`showThemeToggle` applies to deposit mode. The swap mode surface does not
render a theme toggle, so the prop has no effect when the widget runs in swap
mode.
## messages field
The `messages` field overrides the title and description text rendered at the top of the widget. Both properties are optional.
```ts theme={null}
type TrustwareWidgetMessages = {
title: string;
description: string;
};
```
### Default messages
```ts theme={null}
const DEFAULT_MESSAGES: TrustwareWidgetMessages = {
title: "Trustware SDK",
description: "Accept deposits and transactions from any asset on any chain.",
};
```
### Message properties
The heading shown at the top of the widget.
The subheading or descriptor shown beneath the title.
### Example
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
messages: {
title: "Deposit",
description: "Move funds into the destination asset and chain.",
},
} satisfies TrustwareConfigOptions;
```
Because `messages` accepts `Partial`, you can
override just the title without providing a description, and the SDK will keep
the default for the omitted field.
# WalletConnect configuration for Trustware
Source: https://docs.trustware.io/configuration/walletconnect
Override the WalletConnect connector settings in the Trustware SDK: project ID, supported chains, dApp metadata, relay URL, QR modal, or disable it entirely.
The Trustware SDK includes WalletConnect support out of the box via the Reown AppKit Universal Connector. A built-in project ID and sane defaults are provided, so in most cases you do not need to touch `walletConnect` at all. This page covers when and how to override those defaults.
## WalletConnectConfig type
```ts theme={null}
type WalletConnectConfig = {
projectId?: string;
chains?: number[];
optionalChains?: number[];
metadata?: {
name: string;
description?: string;
url: string;
icons?: string[];
};
relayUrl?: string;
showQrModal?: boolean;
disabled?: boolean;
};
```
## Properties
Your WalletConnect Cloud project ID. The SDK ships with a Trustware-managed
default that works out of the box. Override only if you want to track your
dApp's WalletConnect stats with your own ID.
The primary chain IDs the connector will support. Defaults to `[1]`
(Ethereum mainnet).
Additional chain IDs that the connector advertises as optional. Wallets that
support these chains can switch to them without being required to at
connection time.
Metadata for your dApp or business, shown inside the wallet during a
WalletConnect session. If omitted, the connector uses generic Trustware
defaults.
The display name of your dApp or business shown in the wallet.
A short description of your dApp or business.
The canonical URL of your dApp or business. Must be a valid https URL.
An array of icon URLs for your dApp or business. The wallet will use the
first icon in the list.
Override the WalletConnect relay server URL. Defaults to WalletConnect's
public relay. Only change this if you are running a private relay.
Controls whether the SDK renders its built-in QR modal when initiating a
WalletConnect session. Set to `false` if you want to handle the QR display
yourself.
Set to `true` to disable WalletConnect entirely. The connector will not be
initialized and will not appear as a connection option in the widget. Useful
when your app already owns wallet state and you are using the host wallet
integration pattern.
## When to configure vs leave as default
**Leave as default when:**
* You are prototyping or building an internal tool.
* You are using the drop-in widget pattern and want the shortest path to production.
**Override when:**
* You want your own WalletConnect dashboard stats for your dApp (optional; the Trustware-managed project ID works in production).
* You want your dApp name and icon to appear correctly inside connected wallets.
* You are using the host wallet integration pattern and want to disable WalletConnect to avoid duplicate connection UI.
## WalletConnect via Reown AppKit
WalletConnect is handled internally through the [Reown AppKit Universal Connector](https://reown.com/appkit). The connector is initialized once when `TrustwareProvider` mounts. You do not need to install or configure AppKit directly; the SDK manages the integration for you.
All WalletConnect behavior in the current SDK version goes through the
Reown AppKit Universal Connector. You do not need to install or configure
AppKit separately.
## Example
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
walletConnect: {
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!,
chains: [1, 8453],
optionalChains: [137, 42161],
metadata: {
name: "My App",
description: "Accept deposits and transactions from any asset on any chain.",
url: "https://myapp.example.com",
icons: ["https://myapp.example.com/icon.png"],
},
},
} satisfies TrustwareConfigOptions;
```
### Disable WalletConnect for host-wallet integrations
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
walletConnect: {
disabled: true,
},
} satisfies TrustwareConfigOptions;
```
# Handle errors from the Trustware SDK
Source: https://docs.trustware.io/events-errors/error-handling
Catch and respond to errors using the onError callback or try/catch. Use TrustwareErrorCode to identify error types and act on them precisely.
The Trustware SDK surfaces errors through a typed `TrustwareError` class, so you always know what went wrong and why. You can handle errors reactively via the `onError` callback, imperatively by catching thrown errors in the headless core, or by listening for the `error` event in the [lifecycle events](/events-errors/lifecycle-events) stream.
## The `TrustwareError` class
`TrustwareError` extends the native `Error` class with three additional fields:
| Field | Type | Description |
| ------------- | ---------------------- | --------------------------------------------------------- |
| `code` | `TrustwareErrorCode` | Machine-readable error code; use this for branching logic |
| `message` | `string` | Technical description of the error |
| `userMessage` | `string \| undefined` | Optional human-readable message suitable for display |
| `cause` | `unknown \| undefined` | The original error that triggered this one, if any |
Import it directly from the SDK:
```ts theme={null}
import { TrustwareError } from "@trustware/sdk";
```
## Error codes
The `TrustwareErrorCode` enum covers all error conditions the SDK can produce.
| Code | Typical cause |
| ---------------------- | --------------------------------------------------------------------------- |
| `INVALID_CONFIG` | The configuration object is missing required fields or has an invalid value |
| `INVALID_API_KEY` | The API key was rejected; thrown during `Trustware.init()` |
| `WALLET_NOT_CONNECTED` | A wallet operation was attempted before a wallet was connected |
| `BRIDGE_FAILED` | The cross-chain bridge transaction could not be completed |
| `NETWORK_ERROR` | A network request failed (timeout, DNS failure, or server error) |
| `INPUT_ERROR` | The input to a core method (amount, address, etc.) is invalid |
| `UNKNOWN_ERROR` | An unexpected error with no more specific classification |
`INVALID_API_KEY` is thrown synchronously during initialization. Make sure to handle it at startup rather than only in your per-transaction error handler.
## Handling errors via `onError`
Pass an `onError` callback in your `TrustwareConfigOptions` to receive all errors emitted by the widget and provider. This is the simplest approach for most integrations.
```ts theme={null}
import { TrustwareProvider, type TrustwareConfigOptions } from "@trustware/sdk";
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
onError: (error) => {
console.error(error.code, error.message);
},
} satisfies TrustwareConfigOptions;
```
If you want to display a user-friendly message instead of a raw technical error, use `error.userMessage` when it is defined:
```ts theme={null}
onError: (error) => {
const display = error.userMessage ?? "Something went wrong. Please try again.";
showErrorBanner(display);
},
```
In production, log `error.code` and `error.message` to your error tracking service and display `error.userMessage` to users. This gives you diagnostic detail without exposing internal error strings.
## Handling errors with try/catch
When using the headless core API, errors are thrown as exceptions. Wrap core calls in a `try/catch` block.
```ts theme={null}
import { Trustware, TrustwareError, TrustwareErrorCode } from "@trustware/sdk";
try {
const route = await Trustware.buildRoute({
fromChain: "1",
toChain: "8453",
fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
fromAmount: "1000000",
fromAddress: await Trustware.getAddress(),
toAddress: "0xDestination...",
});
} catch (error) {
if (error instanceof TrustwareError) {
if (error.code === TrustwareErrorCode.WALLET_NOT_CONNECTED) {
promptWalletConnection();
} else {
console.error(error.code, error.message);
}
}
}
```
## Errors that are not `TrustwareError`
Some SDK rejections are thrown as a plain `Error` with no `code` property. An `instanceof TrustwareError` check will not match them and `error.code` will be `undefined`, so handle them by message or by name.
### Destination posthook validation
`Trustware.buildRoute()` and `Trustware.buildDepositAddress()` reject a structurally incomplete `hooks.postHook` before sending the request.
| Cause | Message |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `target` missing or blank | `hooks.postHook.target is required.` |
| `callData` missing or blank | `hooks.postHook.callData is required.` |
| `fullAmount` set without `amountInputPos` | `hooks.postHook.amountInputPos is required when fullAmount is true.` |
| Neither `fundAmount` nor `fullAmount` set | `hooks.postHook.fundAmount is required unless fullAmount is set.` |
`fullAmount` and `amountInputPos` are provider dependent. See [dynamic landed-balance mode](/guides/destination-call-options#dynamic-landed-balance-mode).
These checks confirm the request is complete. They do not verify that your calldata is correct, that `target` is a contract, or that the destination call will succeed. The API is authoritative for everything the client cannot check, and rejects a posthook it will not route with a `400` and a message describing the problem. See [vault destinations](/guides/vault-destinations).
### Source token approvals
`Trustware.sendRouteTransaction()` can submit ERC-20 approvals before the route transaction. Three failures can surface from that step.
| Cause | Message or name |
| -------------------------------------------- | --------------------------------------------- |
| The approval transaction reverted on chain | `Approval transaction reverted` |
| The approval did not confirm in time | `Timed out waiting for approval confirmation` |
| An approval transaction-status lookup failed | an `Error` whose `name` is `"SDKRPCError"` |
All three are recoverable. Let the user retry rather than treating the deposit as failed.
```ts theme={null}
import { Trustware, TrustwareError } from "@trustware/sdk";
try {
const route = await Trustware.buildRoute({ /* ... */ });
const txHash = await Trustware.sendRouteTransaction(route, 1);
await Trustware.submitReceipt(route.intentId, txHash);
} catch (error) {
if (error instanceof TrustwareError) {
handleSdkError(error.code, error.message);
return;
}
if (error instanceof Error && error.name === "SDKRPCError") {
retryDeposit();
return;
}
// Posthook validation and approval failures land here.
handlePlainError(error instanceof Error ? error.message : String(error));
}
```
`SDKRPCError` is not exported from the package, so identify it by `error.name` rather than with `instanceof`.
### A resolved poll is not a success
`Trustware.pollStatus()` resolves when the route reaches `success`, when it reaches `failed`, and when polling times out. Always branch on the status.
```ts theme={null}
const result = await Trustware.pollStatus(route.intentId);
if (result.status !== "success") {
showIncomplete(route.intentId, result.status);
return;
}
```
## The `RateLimitError` class
When your integration hits the API rate limit, the SDK surfaces the 429 as a `RateLimitError`. It extends the native `Error` class directly (not `TrustwareError`), so a single `instanceof TrustwareError` check will not match it; handle it as a separate case.
```ts theme={null}
import { RateLimitError, Trustware } from "@trustware/sdk";
try {
await Trustware.runTopUp({ fromAmount: "1000000" });
} catch (error) {
if (error instanceof RateLimitError) {
// Rate limited; decide how your app should respond.
}
}
```
The `rateLimitInfo` property has the following shape:
| Field | Type | Description |
| ------------ | --------------------- | -------------------------------------------------------------- |
| `limit` | `number` | Maximum requests allowed in the current window |
| `remaining` | `number` | Requests remaining in the current window |
| `reset` | `number` | Unix timestamp when the rate limit window resets |
| `retryAfter` | `number \| undefined` | Seconds until the limit resets (only present on 429 responses) |
# Trustware SDK lifecycle events reference
Source: https://docs.trustware.io/events-errors/lifecycle-events
Subscribe to typed lifecycle events from the Trustware SDK using the onEvent callback in your config.
The Trustware SDK emits typed events as users move through the deposit flow. You can subscribe to these events to track progress, update your own UI state, or send analytics without polling or manual state management.
## The `TrustwareEvent` type
All events share a discriminated union type called `TrustwareEvent`. Each variant has a `type` string field you can use to narrow the event in a handler.
```ts theme={null}
import type { TrustwareEvent } from "@trustware/sdk";
```
### Event types
| `type` | When it fires | Additional fields |
| ------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `error` | Any SDK error (includes API key validation, tx failures, etc.) | `error: TrustwareError` |
| `transaction_started` | Wallet opens with the transaction prompt | none |
| `transaction_success` | Tx confirmed on-chain; carries `txHash` and optional `Transaction` record | `txHash: string`, `transaction?: Transaction` |
| `wallet_connected` | Wallet attached; carries `address` | `address: string` |
| `token_page_loaded` | Token list page fetched (with pagination metadata) | `chainRef: string`, `count: number`, `hasNextPage: boolean`, `query?: string`, `cursor?: string` |
| `token_page_error` | Token list fetch failed | `chainRef: string`, `message: string`, `query?: string`, `cursor?: string` |
| `balance_stream_chunk` | Wallet balance scan chunk received | `address: string`, `chunkSize: number` |
| `balance_stream_fallback` | Balance scan fell back to a slower method | `address: string`, `message: string` |
| `swap_route_changed` | Swap-mode route changed (different source or destination chain, token, or amount) | `fromChain: string`, `fromToken: string`, `toChain: string`, `toToken: string`, `amount?: string` |
`token_page_loaded`, `token_page_error`, `balance_stream_chunk`, and `balance_stream_fallback` are gated by the `tokensPagination` and `balanceStreaming` feature flags. `tokensPagination` is enabled by default, so set it to `false` in `features` to suppress its events. `balanceStreaming` is disabled by default, so `balance_stream_chunk` and `balance_stream_fallback` do not fire until you set it to `true`. `swap_route_changed` fires only when the widget runs in swap mode.
## How to subscribe
Pass an `onEvent` callback in your `TrustwareConfigOptions`. This works whether you are using `TrustwareProvider` or calling `Trustware.init()` directly in the headless core.
```ts theme={null}
import { TrustwareProvider, type TrustwareConfigOptions } from "@trustware/sdk";
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
onEvent: (event) => {
if (event.type === "transaction_success") {
console.log("TX hash:", event.txHash);
}
},
} satisfies TrustwareConfigOptions;
```
TypeScript narrows `event` to the correct shape inside each `if` branch, so accessing `event.txHash` is fully type-safe.
## The `onSuccess` shortcut
For the common case of reacting to a completed deposit, you can use `onSuccess` instead of filtering inside `onEvent`. It receives the `Transaction` object directly.
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
onSuccess: (transaction) => {
console.log(transaction);
},
} satisfies TrustwareConfigOptions;
```
`onSuccess` and `onEvent` are independent; you can use both at the same time.
## Example: tracking deposit completion
The following example shows how to use `onEvent` to display a success notification when a deposit completes, and log any errors to an external service.
```ts theme={null}
onEvent: (event) => {
switch (event.type) {
case "transaction_success":
showToast(`Deposit confirmed (tx: ${event.txHash})`);
break;
case "wallet_connected":
analytics.track("wallet_connected", { address: event.address });
break;
case "error":
errorLogger.capture(event.error);
break;
}
},
```
# Set minimum and maximum deposit amounts
Source: https://docs.trustware.io/guides/amount-guardrails
Use minAmountOut and maxAmountOut in routes.options to set the deposit amount range while still letting users pick a value within those bounds.
When you want users to choose their own deposit amount but need to enforce a floor or ceiling (to meet minimum transaction thresholds, cap exposure, or comply with business rules), set `minAmountOut` and `maxAmountOut` in your route options. The widget constrains its slider and input field to the range you configure.
## The minAmountOut and maxAmountOut options
Both options live inside `routes.options` in your `TrustwareConfigOptions`. You can set either one independently or both together.
```ts theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const guardedConfig = {
...trustwareConfig,
routes: {
...trustwareConfig.routes,
options: {
minAmountOut: "10",
maxAmountOut: "250",
routeRefreshMs: 10000,
},
},
} satisfies TrustwareConfigOptions;
```
Pass this config to `TrustwareProvider`:
```tsx theme={null}
```
## Value format
Both options accept a string or a number representing a USD amount:
```ts theme={null}
minAmountOut: "10" // string: minimum of $10 USD
maxAmountOut: "250" // string: maximum of $250 USD
```
Values are interpreted as USD amounts. Do not include a currency symbol or unit suffix.
## routeRefreshMs in this context
The example above also sets `routeRefreshMs: 10000`. This controls how often the widget re-fetches route previews, in milliseconds. When users are actively adjusting an amount within a constrained range, a shorter refresh interval keeps quotes current. The default is `15000` ms (15 seconds).
`minAmountOut` and `maxAmountOut` constrain the widget UI. They do not replace server-side validation. Your backend should independently enforce any deposit limits that are critical to your business logic.
If you want to lock users to a single amount rather than a range, use `fixedFromAmount` instead. See the [fixed amount guide](/guides/fixed-amount) for details.
# Destination call options
Source: https://docs.trustware.io/guides/destination-call-options
ERC-20 destination calls, dynamic landed amounts, fallback addresses, deposit-address routes, and posthook error handling.
The [vault destinations walkthrough](/guides/vault-destinations) covers the primary recipe: a predetermined destination-token amount funding a native-value call. This page covers what varies from it.
Read [deposit into vault destinations](/guides/vault-destinations) first. Every
example here assumes that six-step flow and changes only the posthook. The full
`hooks.postHook` field reference is there too.
Jump to your case:
* your function pulls an ERC-20 instead of taking native value: [ERC-20 destination calls](#erc-20-destination-calls)
* the amount is unknown at build time: [dynamic landed-balance mode](#dynamic-landed-balance-mode)
* funds need somewhere to land if the call fails: [fallback behavior](#fallback-behavior)
* source funds arrive by plain payment: [deposit-address routes](#deposit-address-routes)
* the destination is not a vault: [call other destination contracts](#call-other-destination-contracts)
## Source token approvals
Source token approvals and destination call funding are two different mechanisms. Do not conflate them.
**Source token approval** is a wallet action on the source chain. For an ERC-20 source asset, the route response can include `route.execution.approvals[]`, each entry carrying the `tokenAddress`, `spender`, and `amount` to approve before the route transaction can succeed. The spender is a provider contract, never a Trustware one.
`sendRouteTransaction` handles these on EVM routes: it reads the current allowance, approves the exact amount when one is missing, waits for confirmation, then requests the route signature. It never grants an unlimited allowance. If the allowance read fails, it approves anyway rather than sending a transaction it knows would revert.
Two consequences for your UI:
* The user can see more than one wallet prompt. Tell them an approval may come first, then the deposit itself.
* Each approval must confirm on chain before the route signature is requested, so the call can stay pending for a minute or more.
If you sign yourself, through the REST API or a custom signer, implement the same sequence: read `route.execution.approvals[]`, grant each allowance, wait for confirmation, then send `route.execution.transaction`. Every field is optional, so skip any entry missing a `spender`, `tokenAddress`, or non-zero `amount`.
**Destination call funding** is `toApprovalAddress`, covered next. It is not a wallet prompt.
## ERC-20 destination calls
If your destination function pulls tokens with `transferFrom` instead of taking native value, set `toApprovalAddress` to the address allowed to pull `fundToken`. That is normally the contract performing the `transferFrom`, usually the vault itself.
```ts theme={null}
const erc20VaultAbi = parseAbi([
"function depositFor(address recipient, address token, uint256 amount)",
]);
const erc20FundAmount = "5000000"; // 5 USDC, destination base units
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const callData = encodeFunctionData({
abi: erc20VaultAbi,
functionName: "depositFor",
args: ["0xYourRecipientAddress", USDC_BASE, BigInt(erc20FundAmount)],
});
const route = await Trustware.buildRoute({
fromChain: "42161",
toChain: "8453",
fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
toToken: USDC_BASE,
fromAmount: "3000000000000000",
fromAddress: await Trustware.getAddress(),
toAddress: "0xYourRecipientAddress",
hooks: {
postHook: {
target: "0xYourVaultAddress",
callData,
fundToken: USDC_BASE,
fundAmount: erc20FundAmount,
toApprovalAddress: "0xYourVaultAddress",
estimatedGas: "200000",
},
},
});
```
`depositFor` is illustrative. Substitute your own pull-based deposit function and ABI.
Note there is no `value` field. The call is funded with an ERC-20, so nothing native is attached.
How the allowance is granted depends on the provider that resolves the route. Some prepend an ERC-20 `approve()` ahead of your call, patching the approval amount alongside your call's amount in dynamic landed-balance mode. Others hand it to their own execution engine. You set the same field either way.
Do not set `toApprovalAddress` for native-value destination calls. Native
assets need no allowance, and the API rejects a request that pairs
`toApprovalAddress` with a native `fundToken`.
## Dynamic landed-balance mode
The exact amount arriving on the destination chain is not knowable at build time, since it depends on execution-time pricing and slippage. Dynamic landed-balance mode lets the provider patch the real landed amount into your calldata, so the call acts on exactly what arrived.
This mode is **provider dependent**. A request using it is only routed to a
provider that implements amount patching, so enabling it narrows provider
selection. For the widest eligibility, use a predetermined `fundAmount`.
Set `fullAmount: true` and tell the provider which ABI argument holds the amount.
```ts theme={null}
const erc20VaultAbi = parseAbi([
"function depositFor(address recipient, address token, uint256 amount)",
]);
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const callData = encodeFunctionData({
abi: erc20VaultAbi,
functionName: "depositFor",
// The amount encoded here is a placeholder. The provider replaces it.
args: ["0xYourRecipientAddress", USDC_BASE, 0n],
});
const route = await Trustware.buildRoute({
fromChain: "42161",
toChain: "8453",
fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
toToken: USDC_BASE,
fromAmount: "3000000000000000",
fromAddress: await Trustware.getAddress(),
toAddress: "0xYourRecipientAddress",
hooks: {
postHook: {
target: "0xYourVaultAddress",
callData,
fundToken: USDC_BASE,
fullAmount: true,
amountInputPos: 2,
toApprovalAddress: "0xYourVaultAddress",
estimatedGas: "200000",
},
},
});
```
`amountInputPos` is the zero-based index of the ABI argument the provider overwrites. In `depositFor(address recipient, address token, uint256 amount)` the amount is the third argument, so it is `2`. Index `0` is valid, so do not treat a falsy index as unset.
Do not send `fundAmount` in this mode. `buildRoute` rejects the request before it is sent if `fullAmount` is `true` and `amountInputPos` is missing.
## Fallback behavior
`toFallbackAddress` names an address that receives the funds if the destination call fails.
`toFallbackAddress` is **provider dependent**. It is forwarded to the provider
that resolves the route and implemented there. Not every provider offers an
equivalent, so never treat fallback as a guarantee or build a recovery flow that
assumes it applies.
```ts theme={null}
import { encodeFunctionData, parseAbi } from "viem";
const vaultAbi = parseAbi([
"function depositNativeFor(address recipient) payable",
]);
const fundAmount = "2000000000000000"; // 0.002 ETH in wei
const callData = encodeFunctionData({
abi: vaultAbi,
functionName: "depositNativeFor",
args: ["0xYourRecipientAddress"],
});
const hooks = {
postHook: {
target: "0xYourVaultAddress",
callData,
value: fundAmount,
fundAmount,
estimatedGas: "150000",
toFallbackAddress: "0xYourTreasuryAddress",
},
};
```
There is no destination-call status field, so the route status alone will not tell you whether a fallback ran. Read your destination contract, and the fallback address balance to distinguish the two outcomes.
## Deposit-address routes
`Trustware.buildDepositAddress()` accepts the same `hooks.postHook` shape as `buildRoute`. Use it when the source funds arrive by plain payment to an address rather than from a wallet you can prompt.
Deposit-address routing is **provider dependent**, and so are deposit-address
routes with a posthook. This path needs a provider that supports both, so it
is not available on every route.
Client-side posthook validation is identical on both calls. `buildDepositAddress()` takes the same request body as `buildRoute()` and returns a deposit address instead of a signable transaction.
## Call other destination contracts
Nothing about `hooks.postHook` is vault specific. It executes an arbitrary encoded call, so the same pattern covers staking contracts, margin accounts, lending pools, and anything else you control. Only the ABI and the function you encode change.
Two questions decide which sections apply to you:
* **Does your function accept native value or pull an ERC-20?** Native value uses `value`. An ERC-20 pull uses `toApprovalAddress` and no `value`.
* **Do you know the amount in advance?** If yes, use a predetermined `fundAmount`. If not, use dynamic landed-balance mode and accept that it narrows provider selection.
## Errors and failure handling
`buildRoute` and `buildDepositAddress` reject a structurally incomplete posthook before sending. These four are thrown as a plain `Error` with no `code`, so `instanceof TrustwareError` and `error.code` branching do not apply.
| Cause | Message |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `target` missing or blank | `hooks.postHook.target is required.` |
| `callData` missing or blank | `hooks.postHook.callData is required.` |
| `fullAmount` set without `amountInputPos` | `hooks.postHook.amountInputPos is required when fullAmount is true.` |
| Neither `fundAmount` nor `fullAmount` set | `hooks.postHook.fundAmount is required unless fullAmount is set.` |
`fullAmount` and `amountInputPos` are provider dependent, as covered in [dynamic landed-balance mode](#dynamic-landed-balance-mode).
These checks only confirm completeness. The API is authoritative for what the client cannot check: calldata correctness, destination compatibility, and provider eligibility. A rejected posthook returns `400` with a message describing the problem.
Two more plain `Error` cases come from the approval step inside `sendRouteTransaction`: `Approval transaction reverted` and `Timed out waiting for approval confirmation`. Treat both as recoverable and let the user retry.
```ts theme={null}
try {
const route = await Trustware.buildRoute({ /* ... */ });
const txHash = await Trustware.sendRouteTransaction(route, 42161);
await Trustware.submitReceipt(route.intentId, txHash);
const finalStatus = await Trustware.pollStatus(route.intentId);
if (finalStatus.status !== "success") {
showRouteIncomplete(route.intentId, finalStatus.status);
return;
}
await verifyVaultCredit(route.intentId, finalStatus.destTxHash);
} catch (error) {
reportDepositError(error);
}
```
Everything else follows the normal [error handling](/events-errors/error-handling) patterns.
## Related reference
The six-step walkthrough, the `hooks.postHook` field reference, and the complete example.
The full `Trustware` namespace API, including `buildRoute`, `sendRouteTransaction`, and `pollStatus`.
The REST request and response schema, including `hooks.postHook` and `route.execution.approvals`.
Posthook validation errors, approval failures, and the imperative try/catch pattern.
# Withdraw from an embedded wallet
Source: https://docs.trustware.io/guides/embedded-wallet-withdrawals
Build and send a withdrawal route from an embedded wallet with the headless core: getBalances, buildRoute, useWallet, sendRouteTransaction, and submitReceipt.
There is no prebuilt widget for withdrawals. To move funds out of an embedded wallet, call the [headless core](/integration/headless-core) directly with the embedded wallet as the source. The flow is the same five calls every time:
1. `Trustware.getBalances()` to load what the wallet can send
2. `Trustware.buildRoute()` to get a signable route
3. `Trustware.useWallet()` to attach the embedded wallet
4. `Trustware.sendRouteTransaction()` to sign and broadcast
5. `Trustware.submitReceipt()` to confirm the transaction and enable status tracking
The embedded wallet signs the withdrawal, and funds move through the routing provider's contracts to the destination the user chooses. Trustware builds the route and tracks settlement; it never holds the funds.
This guide covers users withdrawing funds held in an embedded wallet your app provisioned. It is unrelated to paymaster fund management in the Client Dashboard.
## When to use this pattern
Choose this pattern when:
* your app provisions embedded wallets and users hold balances in them
* users need to move those funds to an external wallet or another chain
* you want to build the withdrawal UI in your own design system
## Prerequisites
* An adapted embedded wallet. Follow [use Trustware with embedded wallets](/guides/embedded-wallets) to wrap the wallet's EIP-1193 provider with `useEIP1193`.
* An initialized SDK. Either mount `TrustwareProvider` with your config, or call `Trustware.init(config)` before the calls below.
```ts theme={null}
import { Trustware, type TrustwareConfigOptions } from "@trustware/sdk";
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
} satisfies TrustwareConfigOptions;
await Trustware.init(config);
```
`routes` is required at init in deposit mode, which is the default, so supply
a destination even for a withdrawal flow. Each `buildRoute` call below passes
its own chains, tokens, and addresses, and those take precedence over these
config defaults.
## 1. Load embedded wallet balances
`getBalances` returns the token balances for an address on one chain. Filter to funded rows to drive your source-token picker:
```ts theme={null}
const rows = await Trustware.getBalances(fromChain, embeddedAddress, {
forceRefresh: true,
});
const funded = rows.filter((row) => BigInt(row.balance || "0") > 0n);
```
The SDK caches `getBalances` results per chain and address. Pass `forceRefresh: true` to force a fresh on-chain scan, for example right after a deposit lands; omit it to reuse the cache when the user is only switching between chains.
Each row is a `BalanceRow`:
```ts theme={null}
type BalanceRow = {
chain_key: string;
category: "native" | "erc20" | "spl" | "btc";
contract?: string;
address?: string;
symbol?: string;
decimals: number;
balance: string; // base units
name?: string;
logoURI?: string;
usdPrice?: number;
};
```
Cosmos balances arrive with `category: "bank"`, including the chain's native
token. The union above is the type as the SDK currently declares it and does
not enumerate that value, so widen your own narrowing if you switch on
`category`.
## 2. Build the withdrawal route
Build a route with the embedded wallet as `fromAddress` and the user's chosen destination as `toAddress`:
```ts theme={null}
const route = await Trustware.buildRoute({
fromChain,
toChain,
fromToken,
toToken,
fromAmount, // base units, e.g. "1500000" for 1.5 USDC
fromAddress: embeddedAddress,
toAddress: destinationAddress,
slippageBps: 100, // 1% slippage tolerance
});
```
`fromAmount` is in the token's smallest unit. Convert human-readable input with the token's `decimals` before calling the SDK, for example with viem's `parseUnits`. See [headless core](/integration/headless-core) for the full `BuildRouteBody` and `BuildRouteResult` shapes.
## 3. Attach the embedded wallet and send
Attach the adapted wallet, then sign and broadcast the route. `sendRouteTransaction` switches the wallet to the route's chain if needed, grants any ERC-20 allowance the route requires, and returns the transaction hash:
```ts theme={null}
Trustware.useWallet(wallet);
const txHash = await Trustware.sendRouteTransaction(route, fromChain);
```
When the source asset is an ERC-20, the user can see more than one wallet prompt. `sendRouteTransaction` reads the current allowance, requests an approval for the exact amount when one is missing, waits for it to confirm, and only then requests the route signature. Surface that in your UI so the second prompt is not a surprise.
## 4. Submit the receipt and track status
Submit the transaction hash so Trustware can track settlement, then poll until the route resolves:
```ts theme={null}
await Trustware.submitReceipt(route.intentId, txHash);
const result = await Trustware.pollStatus(route.intentId);
if (result.status === "success") {
console.log("Withdrawal settled:", result.destTxHash);
}
```
`pollStatus` resolves when the route reaches `success`, when it reaches `failed`, and when polling times out, so always check `result.status` rather than treating a resolved promise as success. Use `Trustware.getStatus(route.intentId)` instead if you want to poll on your own schedule.
## Gas reserve for native withdrawals
When the user withdraws the chain's native token, the wallet still needs gas to send the transaction. A "Use max" control should fill the balance minus a small reserve rather than the full balance. Size the reserve for the chain you target: a tiny flat reserve covers a transaction on an L2 like Base, while L1 mainnet needs a larger one for its higher and more volatile gas costs.
## Complete example
A complete Next.js implementation of this flow, including balance display, amount entry with max handling, and route sending, is available in the Trustware examples repo alongside the deposit flow from the [embedded wallets guide](/guides/embedded-wallets).
# Use Trustware with embedded wallets
Source: https://docs.trustware.io/guides/embedded-wallets
Adapt an app-provisioned embedded wallet with useEIP1193, pass it to TrustwareProvider, and run swaps with it or deposits into it.
Embedded wallets are provisioned by your app through a provider such as Privy, instead of being installed by the user as a browser extension. Trustware treats an embedded wallet like any other host wallet: your app owns the wallet session and hands the wallet to the SDK. Your app and the embedded wallet provider control the wallet; Trustware builds routes, helps send transactions through the SDK, and tracks settlement. It never takes custody of the wallet or its funds.
This guide covers the adapter pattern and the two widget flows: swapping with an embedded wallet and depositing into one. For moving funds back out, see [embedded wallet withdrawals](/guides/embedded-wallet-withdrawals).
## Embedded wallets vs EOA wallets
* **EOA wallets** (MetaMask, Phantom, Coinbase Wallet, etc.) are injected into the page and expose a public address as soon as they connect. The SDK can discover them on its own with auto-detection.
* **Embedded wallets** are created or connected after the user logs in. No address exists at app boot, and the SDK cannot discover them. Your app must resolve the wallet from the embedded wallet provider and pass it to Trustware explicitly.
## Adapt the embedded wallet
`useEIP1193` from `@trustware/sdk/wallet` adapts any EIP-1193 provider into the `WalletInterFaceAPI` that `TrustwareProvider` accepts. The pattern is always the same three steps: resolve the embedded wallet, get its EIP-1193 provider, and wrap it.
This example uses Privy, but any embedded wallet provider that exposes an EIP-1193 provider works the same way:
```tsx theme={null}
import { useEffect, useState } from "react";
import { useWallets, getEmbeddedConnectedWallet } from "@privy-io/react-auth";
import { type WalletInterFaceAPI } from "@trustware/sdk";
import { useEIP1193 } from "@trustware/sdk/wallet";
export function useEmbeddedWallet() {
const { wallets } = useWallets();
const [state, setState] = useState<{
address: string;
wallet?: WalletInterFaceAPI;
}>({ address: "" });
useEffect(() => {
let cancelled = false;
const embedded = getEmbeddedConnectedWallet(wallets);
if (!embedded?.address) {
setState({ address: "" });
return;
}
embedded.getEthereumProvider().then((provider) => {
if (!cancelled) {
setState({
address: embedded.address,
wallet: useEIP1193(provider),
});
}
});
return () => {
cancelled = true;
};
}, [wallets]);
return state;
}
```
Resolve the actual embedded wallet, for example with Privy's `getEmbeddedConnectedWallet`. Do not fall back to the first wallet in the provider's list: if the user also has an EOA connected, that EOA could silently be treated as the embedded wallet.
## Swap with an embedded wallet
In this flow the embedded wallet is the host wallet: it signs the swap and receives the output. Turn on [swap mode](/guides/swap-mode) with `mode: "swap"`, pass the adapted wallet through the `wallet` prop, and set `autoDetect={false}` so the SDK does not run its own wallet discovery alongside it.
`mode` requires `@trustware/sdk` 1.1.9 or later. On 1.1.8 use
`features.swapMode: true` with a `routes` object. See the version note in the
[swap mode guide](/guides/swap-mode#enable-swap-mode).
```tsx theme={null}
import {
TrustwareProvider,
TrustwareWidget,
type TrustwareConfigOptions,
} from "@trustware/sdk";
const swapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
mode: "swap",
features: {
swapDefaultDestToken: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
chainId: 8453,
},
},
} satisfies TrustwareConfigOptions;
export function EmbeddedSwap() {
const { wallet } = useEmbeddedWallet();
if (!wallet) return
Log in to create or connect an embedded wallet.
;
return (
);
}
```
The widget handles the full swap flow from there. See [host wallet](/integration/host-wallet) for why `autoDetect={false}` is required whenever you pass a `wallet` prop.
## Deposit into an embedded wallet
In this flow the embedded wallet is only the destination. The payer is an EOA the user connects inside the widget, so leave auto-detection on. Because the embedded wallet address does not exist until after login, set it at runtime with `Trustware.setDestinationAddress()` once the provider is ready:
```tsx theme={null}
import { useEffect } from "react";
import {
Trustware,
TrustwareProvider,
TrustwareWidget,
useTrustware,
type TrustwareConfigOptions,
} from "@trustware/sdk";
function SyncDestinationAddress({ address }: { address: string }) {
const { status } = useTrustware();
useEffect(() => {
if (status === "ready" && address) {
Trustware.setDestinationAddress(address);
}
}, [address, status]);
return null;
}
export function DepositIntoEmbeddedWallet({ address }: { address: string }) {
const depositConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
toAddress: address || undefined,
},
} satisfies TrustwareConfigOptions;
return (
);
}
```
Setting `routes.toAddress` in config covers the case where the address is already known at mount; the runtime setter covers wallets provisioned after mount. See [runtime destination](/guides/runtime-destination) for the full setter reference.
### Refresh balances after a deposit
If your app displays the embedded wallet balance, refresh it when a deposit lands. Both `onSuccess` and the `transaction_success` event work; the example below uses both so the balance updates as soon as the transaction succeeds and again when settlement completes.
```ts theme={null}
const depositConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
toAddress: address || undefined,
},
onSuccess: () => refreshEmbeddedBalances(),
onEvent: (event) => {
if (event.type === "transaction_success") {
refreshEmbeddedBalances();
}
},
} satisfies TrustwareConfigOptions;
```
## Next: withdrawals
There is no prebuilt widget for moving funds back out of an embedded wallet. Withdrawals use the headless core with the embedded wallet as the source.
Build and send a withdrawal route from an embedded wallet with getBalances, buildRoute, useWallet, sendRouteTransaction, and submitReceipt.
Both flows on this page are available as complete Next.js apps in the Trustware examples repo.
# Lock the deposit widget to a fixed amount
Source: https://docs.trustware.io/guides/fixed-amount
Use fixedFromAmount in routes.options to pre-set the Trustware deposit amount so users see a locked USD value and cannot change it at checkout.
For checkout flows where the amount is predetermined (subscription top-ups, fixed-price checkout, one-click refills), set `fixedFromAmount` to lock the widget's amount input to a single USD value.
## The fixedFromAmount option
`fixedFromAmount` lives inside `routes.options` in your `TrustwareConfigOptions`. When it is set, the amount input in the widget becomes read-only and displays the value you provide.
```ts theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const fixedAmountConfig = {
...trustwareConfig,
routes: {
...trustwareConfig.routes,
options: {
fixedFromAmount: "25",
},
},
} satisfies TrustwareConfigOptions;
```
Pass this config to `TrustwareProvider`:
```tsx theme={null}
```
## Value format
`fixedFromAmount` accepts a string or a number representing a USD amount:
```ts theme={null}
fixedFromAmount: "25" // string: "25 USD"
fixedFromAmount: 25 // number: equivalent
```
The value is interpreted as a USD amount. Do not include a currency symbol or unit suffix.
When `fixedFromAmount` is set, the widget amount input is locked. Users can still select their source token, but they cannot modify the deposit amount.
# Sponsor gas with Paymasters
Source: https://docs.trustware.io/guides/paymasters
Deploy a paymaster contract you own, fund it, set budgets and SDK-key rules in the Client Dashboard, and let eligible deposit routes execute with gas sponsored.
Trustware Paymasters are a new ownership standard for gas sponsorship. You deploy a paymaster contract, you own it, and you fund it. Trustware decides which of your eligible operations get sponsored against the budget and rules you configure, and it never holds the funds.
The result for your users is a deposit that goes through without them holding native gas to pay for it on the source chain. One exception remains: a user's first sponsored deposit of a given ERC-20 needs a one-time approval transaction they pay for themselves, which [use sponsored routes](#use-sponsored-routes) explains. The result for you is a sponsorship balance you can audit on chain at any time.
Paymaster deployment is rolling out chain by chain. The **Paymasters** tab in
the [Client Dashboard](https://dashboard.trustware.io) shows which chains you
can deploy on, and marks the rest **Pending**.
## How ownership works
When you deploy a paymaster from the Client Dashboard, the connected wallet becomes the **client owner** of a paymaster contract that belongs to your project. Every project and chain combination gets its own contract, so budgets and balances never mix across projects.
Sponsorship funds sit in that paymaster's on-chain EntryPoint deposit.
| Who | Controls |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| You, through the owner wallet | Withdrawing the EntryPoint deposit, and administering the contract |
| Trustware, through its off-chain authorization layer | Deciding which eligible operations are authorized for sponsorship, against your active budget and SDK-key rules |
Keep those two apart when you reason about risk. Trustware authorizes sponsorship, and your paymaster verifies that authorization before it pays. Trustware cannot move, withdraw, or redirect client sponsorship funds.
Anyone can top up the deposit, since funding is an open payment into the contract. Only the owner wallet can withdraw it.
One operational caveat goes with that. Paymaster operations can be paused in an emergency, which temporarily stops sponsorship and withdrawals on the affected paymaster. A pause is a stop, not a transfer of authority. It gives Trustware no ability to withdraw or redirect your funds, and withdrawal stays with the owner wallet once operations resume.
## When sponsorship applies
Sponsorship is additive to routing. It changes how an eligible route executes, and it never changes whether a route resolves.
Gas is sponsored on the **source chain**, where the route executes and gas is charged. That chain needs a paymaster of yours with active coverage on it. The destination chain is unconstrained, so if your coverage is on Base, a Base to Arbitrum deposit can be sponsored while an Ethereum to Base deposit cannot.
| Route | Sponsorship |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| An EVM route executing from a smart account on a chain where you have paymaster coverage | Eligible. The route can carry a `sponsorship` object. |
| An EVM route executing from any other source chain | Not sponsored. Coverage is per chain, and the source chain needs its own. |
| A plain EOA transaction | Not sponsored. An EOA send does not carry an ERC-4337 paymaster payload. |
| A non-EVM route, such as Solana or Bitcoin | Not sponsored. ERC-4337 paymasters are EVM only. |
| A deposit-address flow | Not sponsored. `Trustware.buildDepositAddress()` returns no sponsorship. |
Not every route is gasless. A route outside those boundaries, or one with no active deployment, budget, or matching rule, still resolves and executes normally. It just pays its own gas.
## Set up a paymaster
Sponsorship configuration is scoped to a project, because rules attach to that project's SDK keys. Switch to the project you want before you start.
Go to the **Paymasters** tab in the Client Dashboard sidebar.
Click **New Deployment**, connect the wallet you want as the owner, choose a chain you can deploy on, and deploy. The connected wallet becomes the client owner of the contract.
Use a wallet you can keep long term. The owner wallet is the only wallet
that can withdraw the sponsorship balance later.
Open the deployment and click **Top Up** to deposit ETH into the paymaster's EntryPoint balance. The dashboard shows that balance in ETH and its approximate USD value.
Under **Chain Budget**, add a monthly cap for the project on that chain. A budget is active as soon as you create it. The budget is the ceiling for everything below it, the dashboard tracks spend against it, and editing it later changes that monthly amount.
Under **SDK Rules**, create at least one active rule for an SDK key. A deployment and a budget on their own sponsor nothing, because rules are what make a given key eligible.
A chain is only ready when all three exist: a deployment, an active budget, and active rule coverage. The dashboard shows that readiness per chain.
## Configure sponsorship rules
A rule attaches to one SDK key on one chain and sets how much you are willing to sponsor for a user of that key.
| Rule type | What it limits |
| ------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Onboarding transaction | A number of sponsored transactions per sender, so you can cover a user's first deposit and nothing after it |
| Per-user lifetime budget | A total sponsored spend per sender, in USD, that never resets |
| Per-user monthly budget | A sponsored spend per sender, in USD, that resets monthly |
Every rule also takes two optional controls:
* **Priority.** Rules are evaluated from the lowest priority number upward, and the first eligible rule wins. New rules default to `100`, so leave room above and below it.
* **Max cost per transaction.** A USD ceiling per operation. Anything above it is not sponsored under that rule.
The project-chain monthly budget sits above all of them. Once monthly spend reaches that cap, no rule sponsors anything more on that chain until the next month, regardless of per-user headroom.
To hold a rule back without deleting it, set its status to disabled and it drops out of evaluation.
## Use sponsored routes
Nothing changes in how you request a route. When a route is fully eligible and authorized, `Trustware.buildRoute()` returns an optional `sponsorship` object alongside the usual result.
```ts theme={null}
const route = await Trustware.buildRoute({
fromChain: "8453", // Base, the source chain in this example
toChain: "42161",
fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
toToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
fromAmount: "1000000", // 1 USDC, source base units
fromAddress: await Trustware.getAddress(),
toAddress: "0xDestination...",
});
if (route.sponsorship) {
// This route is authorized for gas sponsorship from your paymaster.
}
```
See [`RouteSponsorship`](/api-reference/types#routesponsorship) for the full field list.
**With the widget**, there is nothing to wire up. When a sponsored route resolves for an ERC-20 source asset on an EVM chain, the widget executes it as a smart-account user operation so your paymaster can pay the gas. If that path fails, the widget falls back to the ordinary route on the next confirm.
**With the headless core**, which function you send the route through decides whether the paymaster is reached.
* `sendRouteAsUserOperation()`, exported from the `@trustware/sdk/smart-account` entry point, is the supported way to execute a sponsored route yourself. It runs the route as a smart-account user operation against your paymaster, signed by the wallet you already have. This is the same path the widget takes.
* `Trustware.sendRouteTransaction()` attaches the sponsorship payload only on the custom `sendTransaction` wallet interface. Given a standard EIP-1193 wallet it sends a plain transaction, not a user operation. Do not pass a sponsored route to it with a standard EIP-1193 wallet. Use `sendRouteAsUserOperation()` instead.
Both paths run the route from a smart account, which adds one step for an ERC-20 source asset.
A sponsored ERC-20 deposit pulls the source tokens through Permit2, so on the
first one the SDK asks the connected wallet for a plain approval transaction and
waits for it to confirm. That approval is unlimited and lives per token per
chain, so it is requested once and later sponsored deposits of the same token
skip it. It is
an ordinary transaction rather than a user operation, so the user pays its gas
and needs a small native balance for that first deposit. Native source assets
skip Permit2 entirely.
Call `sendRouteAsUserOperation()` with the sponsored route itself, so keep it inside the `route.sponsorship` check. Every other argument describes the source side the sponsorship was issued against:
```ts theme={null}
import { sendRouteAsUserOperation } from "@trustware/sdk/smart-account";
import { base } from "viem/chains";
// `provider` is the EIP-1193 provider of the wallet you already connected.
if (route.sponsorship) {
const sent = await sendRouteAsUserOperation({
route,
fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
fromAmountWei: 1_000_000n, // 1 USDC, source base units
fromDecimals: 6,
eoaAddress: (await Trustware.getAddress()) as `0x${string}`,
chainId: 8453, // Base, the chain the route executes on
viemChain: base,
eip1193Request: (args) => provider.request(args),
});
// The receipt is submitted for you, so poll for the destination result.
const result = await Trustware.pollStatus(sent.intentId);
if (result.status === "success") {
console.log("Deposit complete:", result.destTxHash);
}
}
```
The call returns the `userOpHash`, the route's `intentId`, and the source `txHash` once the operation lands in a block. If the wait for inclusion times out, `txHash` comes back undefined while the operation can still land, which is another reason to poll on the `intentId`. Give it a route with no sponsorship and it throws, because there is no paymaster payload to execute against.
Every sponsorship is issued for one exact route. `Trustware.sendRouteTransaction()`
checks the route calldata against the sponsorship's `callDataHash` first, and if they
do not match it ignores the sponsorship and falls back to the ordinary path, including
the source token approval. That check keeps a sponsorship bound to the route it was
issued for. It does not make `sendRouteTransaction()` a sponsored path.
## Operate the paymaster
Everything below lives on the **Paymasters** tab, per chain.
* **Watch the balance.** Expand the deployment to see the EntryPoint deposit. Treat it as an operational metric rather than a set-and-forget deposit, because sponsorship stops silently when it runs dry.
* **Watch the budget.** The chain budget row shows spend against your monthly cap, and the summary cards show live chains, total monthly budget, remaining budget, and active rule count.
* **Top up.** Click **Top Up** and deposit ETH. This does not need the owner wallet.
* **Withdraw.** Click **Withdraw** and connect the owner wallet. The dashboard warns you when the connected wallet is not the owner, because the transaction reverts in that case.
* **Review activity.** The [Transactions](/quickstart#client-dashboard) tab flags each transaction as sponsored or unsponsored, and Analytics reports gas sponsored in USD per chain and per key.
## Why sponsorship may be absent
An absent `sponsorship` object is not a route failure. Work down this list when you expected one and did not get it.
Check the route against [when sponsorship applies](#when-sponsorship-applies). Non-EVM routes, deposit-address flows, and plain EOA execution never carry a paymaster.
Gas is sponsored on the chain the route executes from, so that is the chain that needs a supported paymaster configuration. A route that starts on a chain without one resolves normally but carries no sponsorship. A route that starts on a covered chain can be sponsored whatever its destination chain is.
Confirm the chain shows a deployment, an active budget, and at least one active rule. Any one of the three missing means no sponsorship.
Rules attach to a specific SDK key on a specific chain. A request made with a different key from the same project matches nothing.
The sender may have used their onboarding allowance or per-user budget, the operation may cost more than the rule's max cost per transaction, or the project-chain monthly cap may be reached.
Check the EntryPoint balance on the deployment and top it up.
If none of those explain it, send the route's `sponsorship.requestId`, or the `intentId` when there is no sponsorship, to [support@trustware.io](mailto:support@trustware.io).
## Related reference
The `RouteSponsorship` field list, including `callDataHash`, `maxCost`, and `paymasterAndData`.
The full `Trustware` namespace API, including `buildRoute`, `sendRouteTransaction`, and `pollStatus`.
The Paymasters, Transactions, and Analytics tabs, and where SDK keys come from.
# Update the deposit destination at runtime
Source: https://docs.trustware.io/guides/runtime-destination
Use Trustware.setDestinationAddress(), setDestinationChain(), and setDestinationToken() to update the route target dynamically after the provider has mounted.
The deposit destination (chain, token, and recipient address) can be set upfront in your config or updated at runtime without remounting the provider. This is useful when the values aren't known until after a user logs in, selects a target network, or an async lookup resolves.
## Setting values in config at initialization
If all destination values are known when you create your config, set them directly in the `routes` object:
```ts theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const trustwareConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
toAddress: "0xYourDestinationAddress",
},
} satisfies TrustwareConfigOptions;
```
## EOA vs embedded wallets
The right approach depends on the wallet type your app integrates with.
* **EOA wallets** (MetaMask, Phantom, Coinbase Wallet, etc.) expose a public address as soon as they connect. If you leave `routes.toAddress` blank in your config, the connected wallet address is used as the destination automatically.
* **Embedded wallets** provisioned after login do not have a known address at app boot. Set `routes.toAddress` at runtime with `Trustware.setDestinationAddress()` once the wallet is available, typically right after sign-in. See [use Trustware with embedded wallets](/guides/embedded-wallets) for the full deposit pattern.
## Updating destination values at runtime
Three methods let you update the destination without remounting the provider. All return `Trustware` for chaining.
### `setDestinationAddress`
Updates `routes.toAddress`. Pass `null` or `undefined` to clear a previously set address.
```ts theme={null}
import { Trustware } from "@trustware/sdk";
Trustware.setDestinationAddress("0xDestination...");
// Clear it:
Trustware.setDestinationAddress(null);
```
### `setDestinationChain`
Updates `routes.toChain`. Use this when the target network is selected dynamically (for example, when a user picks a destination chain from a dropdown).
```ts theme={null}
Trustware.setDestinationChain("42161"); // Arbitrum
```
### `setDestinationToken`
Updates `routes.toToken`. Use this when the destination token is determined at runtime.
```ts theme={null}
Trustware.setDestinationToken("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"); // USDC on Arbitrum
```
### Chaining all three
The three setters can be chained in a single expression:
```ts theme={null}
Trustware
.setDestinationChain("42161")
.setDestinationToken("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")
.setDestinationAddress("0xYourAddress...");
```
## When to use each approach
| Situation | Recommended approach |
| -------------------------------------------- | ---------------------------------------------- |
| All values known at app initialization | `routes` object in config |
| Address fetched after login or session setup | `Trustware.setDestinationAddress()` at runtime |
| User selects a destination network | `Trustware.setDestinationChain()` at runtime |
| User selects a destination token | `Trustware.setDestinationToken()` at runtime |
| Need to clear a previously set address | `Trustware.setDestinationAddress(null)` |
The widget reads the resolved config when the user reaches the confirm step. You do not need to set these values before the widget mounts; setting them before the user confirms is sufficient.
These setters change where funds are delivered. They do not configure a
contract call on the destination chain. If you need a contract call to run once
funds arrive, pass `hooks.postHook` on each `Trustware.buildRoute()` request.
Set `toAddress` to the recipient address for the route; the destination
contract call is controlled separately by `postHook.target`. See
[vault destinations](/guides/vault-destinations).
# Run the widget in swap mode
Source: https://docs.trustware.io/guides/swap-mode
Set mode to swap to turn the Trustware widget into a standalone swap experience where users swap any asset on any chain into their own wallet, with optional control over the output token.
Swap mode turns the Trustware widget into a standalone swap experience, the same flow users expect from a frontend like Uniswap. Users swap from any supported asset on any supported chain and receive the output directly in their own connected wallet, with no destination address to configure. It is an alternative to deposit mode: instead of routing funds to a destination you set, the output settles back to the user.
In deposit mode, funds route to a destination you configure in `routes`. In swap mode, funds settle to the user's own connected wallet. The Route Handler and the quote, route, sign, broadcast, receipt, and status lifecycle are identical; only the configuration and where funds settle change.
## Enable swap mode
Set `mode: "swap"` on the top-level config. Because the user picks both sides of the trade in the widget, `routes` is not required in swap mode. With no destination flags set, the user selects the output token themselves. This is the open, Uniswap-style configuration.
```ts theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const swapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
mode: "swap",
} satisfies TrustwareConfigOptions;
```
Pass the config to `TrustwareProvider`:
```tsx theme={null}
```
`mode` defaults to `"deposit"`, which keeps the widget in deposit mode. Inputs are never restricted: users can always pay in from any supported asset on any supported chain. Only the output can be restricted, using the fields below.
`mode` requires `@trustware/sdk` 1.1.9 or later.
In 1.1.8, enable swap mode with `features.swapMode: true` and include a
`routes` object with `toChain` and `toToken`. That version requires both
fields in every mode, so a config without `routes` throws
`TrustwareConfig: 'routes.toChain' and 'routes.toToken' are required.` before
the widget renders. The values are not used as the swap destination, which is
governed by the `swap` fields below.
Swap mode does not exist before 1.1.8. Versions up to and including 1.1.7 ship
no `swapMode` flag and no swap destination fields, so upgrade rather than
trying to configure it there.
```ts theme={null}
// @trustware/sdk 1.1.8
const swapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
features: {
swapMode: true,
},
} satisfies TrustwareConfigOptions;
```
`features.swapMode` still works in 1.1.9 and is treated as equivalent to
`mode: "swap"`, but it is deprecated and logs a deprecation warning to the
console. Prefer `mode` in new integrations.
## Lock the destination token
To settle every swap into one token, set `swapDefaultDestToken` and lock it with `swapLockDestToken: true`. For example, a client on Base can lock the output so users only ever receive USDC on Base, paying in from any asset on any chain.
```ts theme={null}
const lockedSwapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
mode: "swap",
features: {
// Pre-select USDC on Base as the destination
swapDefaultDestToken: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
chainId: 8453,
},
// Lock it so the user cannot change the destination
swapLockDestToken: true,
},
} satisfies TrustwareConfigOptions;
```
## Restrict outputs to an allowlist
To allow a small set of output tokens, list them in `swapAllowedDestTokens`. The destination-token picker is limited to this list; everything else is locked out. This restricts outputs only. Users can still pay in with any supported asset on any chain.
```ts theme={null}
const allowlistSwapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
mode: "swap",
features: {
// Users may only swap into USDC or USDT on Base
swapAllowedDestTokens: [
{ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", chainId: 8453 }, // USDC on Base
{ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", chainId: 8453 }, // USDT on Base
],
},
} satisfies TrustwareConfigOptions;
```
## Config reference
| Field | Type | Description |
| ----------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `mode` | `"deposit" \| "swap"` | Top-level field. Set to `"swap"` to enable swap mode. Defaults to `"deposit"`. Requires SDK 1.1.9 or later. |
| `swapDefaultDestToken` | `{ address: string; chainId: number }` | Pre-selects the destination token shown when the widget opens. |
| `swapLockDestToken` | `boolean` | Locks the destination so the user cannot change it. Use with `swapDefaultDestToken`. |
| `swapAllowedDestTokens` | `Array<{ address: string; chainId: number }>` | Restricts the destination-token picker to this list. Everything else is locked out. |
| `swapMode` | `boolean` | Deprecated. Equivalent to `mode: "swap"`. Use `mode` instead. |
`mode` is a top-level field on `TrustwareConfigOptions`. The three `swap` destination fields and the deprecated `swapMode` flag live under `features`. See the [configuration overview](/configuration/overview) for the full config reference.
Destination locking supports EVM chains only. `SwapTokenRef.chainId` is
numeric, and Cosmos chains are identified by named strings such as
`cataclysm-1`, so Cosmos destinations cannot be set through
`swapDefaultDestToken` or `swapAllowedDestTokens`.
## React to route changes
In swap mode, the SDK emits a `swap_route_changed` event whenever the resolved route updates, for example when the user changes the source or destination chain, token, or amount. Subscribe with `onEvent` to keep your own UI in sync.
```ts theme={null}
const swapConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
mode: "swap",
onEvent: (event) => {
if (event.type === "swap_route_changed") {
console.log("Route updated:", event.fromToken, "to", event.toToken);
}
},
} satisfies TrustwareConfigOptions;
```
The event carries `fromChain`, `fromToken`, `toChain`, `toToken`, and an optional `amount`. See [lifecycle events](/events-errors/lifecycle-events) for the full event reference.
## Swap with an embedded wallet
Swap mode also works when the host wallet is an embedded wallet your app provisions, for example through Privy. Adapt the wallet's EIP-1193 provider with `useEIP1193`, pass it to `TrustwareProvider` through the `wallet` prop, and set `autoDetect={false}` so the SDK does not run its own wallet discovery alongside it.
```tsx theme={null}
```
The embedded wallet signs the swap and receives the output. See [use Trustware with embedded wallets](/guides/embedded-wallets) for the full adapter pattern.
## Works with every integration style
Swap mode is a configuration flag, so it works with all four integration paths without code changes.
The fastest path: render `TrustwareWidget` with swap mode enabled in your config.
Pass your app's existing Wagmi or viem wallet and run it in swap mode.
Open and close the swap widget programmatically from your own UI.
Build a custom swap interface on top of the routing and transaction APIs.
# Deposit into vault destinations
Source: https://docs.trustware.io/guides/vault-destinations
Deposit any supported asset into a vault or another destination contract in one flow, using a destination posthook on Trustware.buildRoute.
A destination posthook calls a contract on the destination chain once routed funds arrive, instead of paying the destination token to a recipient address. A user holding USDC on Arbitrum ends up deposited in a vault on Base, without visiting the vault.
The flow is the same six steps every time:
1. Encode the destination contract call with viem or ethers
2. `Trustware.buildRoute()` with `hooks.postHook` to get a signable route
3. Show the user the final route estimate
4. `Trustware.sendRouteTransaction()` to sign and send on the source chain
5. `Trustware.submitReceipt()` and `Trustware.pollStatus()` to track settlement
6. Read the destination contract to confirm the deposit landed
Requires `@trustware/sdk` 1.1.10 or later, through the
[headless core](/integration/headless-core) or the
[REST API](/api-reference/route). The widget and `Trustware.runTopUp()` do not
accept `hooks`.
## When to use this pattern
Choose this pattern when:
* your product holds user funds in a vault, staking contract, or margin account and you want deposits to arrive already credited
* your users hold assets on chains your contract is not deployed on
* you want to remove the step where a user has to approve and deposit on the destination chain themselves
If funds only need to arrive at an address, use a normal route and set the recipient through [route configuration](/configuration/routes) or [`Trustware.setDestinationAddress()`](/guides/runtime-destination). A posthook is only for running a contract call.
## Requirements and support
| Requirement | Detail |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| SDK version | `@trustware/sdk` 1.1.10 or later |
| Integration path | Headless core or REST API. The widget and `runTopUp()` do not accept `hooks`. |
| Destination | An EVM chain, with your contract already deployed and its ABI known to you |
| Amount | A predetermined destination-token amount, or dynamic landed-balance mode where supported |
| Signatures | One route signature on the source chain, plus a source token approval if the source asset is an ERC-20 without sufficient allowance |
`fundAmount` is the destination-token amount for the contract call. It is unrelated to `fixedFromAmount`, which locks the widget's source USD amount; see [fixed deposit amounts](/guides/fixed-amount).
Destination contract calls are an EVM-only capability. `postHook.target` must be a valid EVM address, and the destination chain must be an EVM chain.
Some capabilities depend on which liquidity provider resolves the route.
| Capability | Support |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Predetermined `fundAmount` | Portable. Works with every provider. Include `estimatedGas` to keep the widest provider eligibility. |
| Native-value destination calls | Supported. Use `value` and omit `toApprovalAddress`. |
| ERC-20 destination calls through `toApprovalAddress` | Supported, though the mechanism differs by provider. See [ERC-20 destination calls](/guides/destination-call-options#erc-20-destination-calls). |
| Dynamic landed-balance mode (`fullAmount`) | **Provider dependent.** Only providers that implement amount patching are selected for these requests. |
| Destination-call fallback (`toFallbackAddress`) | **Provider dependent.** Not implemented by every provider, so never rely on it as a guarantee. |
| Deposit-address routes with a posthook | **Provider dependent.** Needs a provider that supports both deposit-address routing and destination calls. |
Trustware validates only that your posthook is structurally complete. It does
not check that your calldata is correct, that `target` is a contract, that the
funding amount is sufficient, or that the destination contract accepts the call.
A posthook encoding the wrong function or amount still routes, then fails on the
destination chain. Test every destination call on the route you intend to use.
## How the flow works
The user signs one route transaction on the source chain, plus a source token approval if the source asset is an ERC-20 without sufficient allowance. The provider then executes your call on the destination chain, funded with the destination asset. There is no second signature.
The destination call is executed by the provider, not by the user's wallet and not by a Trustware contract. Trustware builds and tracks the route; it never holds the funds.
The destination call is a separate on-chain execution, so a route can move value and still have the call fail. There is no destination-call status field, so confirm the route reached a terminal `success` status and then read the destination contract, as in steps 5 and 6.
## 1. Encode the destination call
Encode the function you want to run on the destination chain. This example uses viem.
```ts theme={null}
import { encodeFunctionData, parseAbi } from "viem";
const vaultAbi = parseAbi([
"function depositNativeFor(address recipient) payable",
"function nativeBalanceOf(address account) view returns (uint256)",
]);
const callData = encodeFunctionData({
abi: vaultAbi,
functionName: "depositNativeFor",
args: ["0xYourRecipientAddress"],
});
```
`depositNativeFor(address)` and `nativeBalanceOf(address)` are illustrative.
Substitute your own deposit function, balance accessor, and ABI. If your deposit
function takes an amount argument, encode the same value you pass as
`fundAmount`, or the call and the funds disagree.
## 2. Build the route with a destination posthook
Pass the encoded call as `hooks.postHook`. Everything outside `hooks` is a normal route request.
```ts theme={null}
import { Trustware } from "@trustware/sdk";
const fundAmount = "2000000000000000"; // 0.002 ETH in wei, destination base units
const route = await Trustware.buildRoute({
fromChain: "42161",
toChain: "8453",
fromToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // native asset on Base
fromAmount: "25000000", // 25 USDC, source base units
fromAddress: await Trustware.getAddress(),
toAddress: "0xYourRecipientAddress",
hooks: {
postHook: {
target: "0xYourVaultAddress",
callData,
value: fundAmount,
fundToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
fundAmount,
estimatedGas: "150000",
},
},
});
```
`hooks` is optional and additive. Omit it and `buildRoute` behaves exactly as it did before 1.1.10.
### Posthook fields
| Field | Required | Description |
| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | Yes | The contract address to call on the destination chain. Must be a valid EVM address. |
| `callData` | Yes | ABI-encoded calldata for the call. |
| `value` | Native-value calls only | Native value to send with the call, in destination base units. |
| `fundToken` | No | The token the call acts on. Defaults to the route's `toToken`. |
| `fundAmount` | Yes unless `fullAmount` is set | The predetermined destination-token amount the call is funded with, in destination base units. |
| `estimatedGas` | Recommended | Gas limit hint for the destination call. Some providers require it, so omitting it narrows which providers can serve the route. |
| `toApprovalAddress` | ERC-20 destination calls | The address allowed to pull `fundToken` when your contract uses `transferFrom`. Must not be set for native-value calls. See [ERC-20 destination calls](/guides/destination-call-options#erc-20-destination-calls). |
| `fullAmount` | No | **Provider dependent.** Patch the encoded call with the amount that actually arrives instead of a predetermined `fundAmount`. Requires `amountInputPos`. See [dynamic landed-balance mode](/guides/destination-call-options#dynamic-landed-balance-mode). |
| `amountInputPos` | With `fullAmount` | **Provider dependent**, and required whenever `fullAmount` is set. The zero-based index of the ABI argument to patch. Index `0` is valid. |
| `toFallbackAddress` | No | **Provider dependent.** Where funds go if the destination call fails, when the resolving provider implements a fallback. See [fallback behavior](/guides/destination-call-options#fallback-behavior). |
| `description` | No | Optional free-text label for the call. Not forwarded by every provider, so do not depend on it appearing downstream. |
## 3. Review the final route estimate
Build the route with `hooks` attached before the user confirms, and show that route's estimate. A quote taken without `hooks` is not execution truth: a destination call changes gas and can change the selected provider.
```ts theme={null}
console.log(route.finalExchangeRate.fromAmountUSD);
console.log(route.finalExchangeRate.toAmountMinUSD);
console.log(route.route?.estimate?.toAmountMin);
```
Check that the amount the route guarantees to deliver covers the `fundAmount` you encoded. If it does not, lower `fundAmount`, raise `fromAmount`, or rebuild the route.
## 4. Sign and send the source transaction
```ts theme={null}
const txHash = await Trustware.sendRouteTransaction(route, 42161);
```
`sendRouteTransaction` switches the wallet to the source chain if needed, grants any source token allowance the route requires, then sends the route transaction. An ERC-20 source asset can mean an approval prompt before the deposit prompt, so plan for more than one signature. See [source token approvals](/guides/destination-call-options#source-token-approvals).
## 5. Submit the receipt and poll status
```ts theme={null}
await Trustware.submitReceipt(route.intentId, txHash);
const finalStatus = await Trustware.pollStatus(route.intentId);
if (finalStatus.status !== "success") {
throw new Error(`Route did not complete: ${finalStatus.status}`);
}
```
`pollStatus` resolves on `success`, on `failed`, and on a polling timeout, so a
resolved promise is not proof of success. Check
`finalStatus.status === "success"` before showing a completed state.
The route status reflects the provider's aggregate result for the whole route. There is no separate field reporting the destination call on its own, which is why step 6 exists.
## 6. Verify the deposit landed
Read your own contract before you build the route and again after settlement, so you are asserting a change rather than an absolute value.
```ts theme={null}
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";
const client = createPublicClient({ chain: base, transport: http() });
// Read this before step 2, so you have a baseline.
const balanceBefore = await client.readContract({
address: "0xYourVaultAddress",
abi: vaultAbi,
functionName: "nativeBalanceOf",
args: ["0xYourRecipientAddress"],
});
// Read it again after the route reaches a terminal success status.
const balanceAfter = await client.readContract({
address: "0xYourVaultAddress",
abi: vaultAbi,
functionName: "nativeBalanceOf",
args: ["0xYourRecipientAddress"],
});
if (balanceAfter - balanceBefore < BigInt(fundAmount)) {
// The route settled but the deposit did not credit as expected.
// Investigate with finalStatus.destTxHash before showing a success state.
}
```
`finalStatus.destTxHash` and `finalStatus.toChainTxUrl` give you the destination-chain execution to inspect or to attach to a support request alongside `route.intentId`.
`finalStatus.toAmountWei` reports the destination amount, but treat it as an estimate unless `finalStatus.landed_amount_verified` is `true`. That flag means Trustware read the amount from the destination chain rather than carrying forward a pre-trade quote. Check it before crediting a user or triggering anything automatic.
```ts theme={null}
if (finalStatus.landed_amount_verified) {
// finalStatus.toAmountWei is the confirmed destination amount.
} else {
// Still an estimate. Prefer your own contract read.
}
```
Reading your own contract, as above, is the strongest check and works regardless of the flag.
That is the primary recipe: a predetermined destination-token amount funding a
native-value call. For ERC-20 funding, provider-patched landed amounts, fallback
addresses, deposit-address routes, source token approvals, and the posthook and
approval errors to handle, see
[destination call options](/guides/destination-call-options).
## Security considerations
* **You own the calldata.** Trustware encodes nothing on your behalf. Build `callData` from an ABI you control, on your own backend or from a constant in your app, never from unvalidated user input.
* **Pin `target`.** Treat the destination contract address as configuration, not something a client can choose. A posthook calls whatever address you give it.
* **Keep the encoded amount and the funding amount in agreement.** If your function takes an amount argument and you are not using dynamic landed-balance mode, encode the same value you pass as `fundAmount`.
* **Do not treat a settled route as a credited deposit.** Read your own contract, as in step 6.
* **Non-custodial throughout.** Trustware never holds the funds and never signs on the user's behalf. The route transaction is signed by the user's wallet and the destination call is executed by the provider.
## Complete example
```ts theme={null}
import { Trustware } from "@trustware/sdk";
import { createPublicClient, encodeFunctionData, http, parseAbi } from "viem";
import { base } from "viem/chains";
const VAULT = "0xYourVaultAddress";
const RECIPIENT = "0xYourRecipientAddress";
const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const vaultAbi = parseAbi([
"function depositNativeFor(address recipient) payable",
"function nativeBalanceOf(address account) view returns (uint256)",
]);
const client = createPublicClient({ chain: base, transport: http() });
export async function depositIntoVault(
fromChain: string,
fromToken: string,
fromAmount: string,
) {
const fundAmount = "2000000000000000"; // 0.002 ETH in wei
const balanceBefore = await client.readContract({
address: VAULT,
abi: vaultAbi,
functionName: "nativeBalanceOf",
args: [RECIPIENT],
});
const callData = encodeFunctionData({
abi: vaultAbi,
functionName: "depositNativeFor",
args: [RECIPIENT],
});
const route = await Trustware.buildRoute({
fromChain,
toChain: "8453",
fromToken,
toToken: NATIVE,
fromAmount,
fromAddress: await Trustware.getAddress(),
toAddress: RECIPIENT,
hooks: {
postHook: {
target: VAULT,
callData,
value: fundAmount,
fundToken: NATIVE,
fundAmount,
estimatedGas: "150000",
},
},
});
const txHash = await Trustware.sendRouteTransaction(route, Number(fromChain));
await Trustware.submitReceipt(route.intentId, txHash);
const finalStatus = await Trustware.pollStatus(route.intentId);
if (finalStatus.status !== "success") {
throw new Error(`Route did not complete: ${finalStatus.status}`);
}
const balanceAfter = await client.readContract({
address: VAULT,
abi: vaultAbi,
functionName: "nativeBalanceOf",
args: [RECIPIENT],
});
return {
intentId: route.intentId,
sourceTxHash: txHash,
destTxHash: finalStatus.destTxHash,
credited: balanceAfter - balanceBefore,
};
}
```
## Related reference
ERC-20 destination calls, dynamic landed-balance mode, fallback, deposit-address routes, and error handling.
The full `Trustware` namespace API, including `buildRoute`, `sendRouteTransaction`, and `pollStatus`.
The REST request and response schema, including `hooks.postHook` and `route.execution.approvals`.
`PostHookRequest`, `RouteApproval`, `RoutePlan`, and `RouteEstimate`.
Posthook validation errors, approval failures, and the imperative try/catch pattern.
# Control the widget open state with a ref
Source: https://docs.trustware.io/integration/controlled-widget
Use a TrustwareWidgetRef to open or close the deposit flow programmatically, and configure initial step, theme, and visibility callbacks.
By default, `TrustwareWidget` manages its own open/closed state. When your app needs to trigger the deposit flow from elsewhere (a button in a nav bar, a checkout step, a game event), you can take control of that state using a ref.
## When to use this pattern
Choose this pattern when:
* your app needs to open or close the deposit flow from outside the widget
* you want to set the initial step the user lands on
* you need lifecycle callbacks for when the widget opens or closes
If you do not need programmatic control, the [drop-in widget](/integration/drop-in-widget) is simpler to set up.
## Setup
```tsx theme={null}
import { useRef } from "react";
import {
TrustwareProvider,
TrustwareWidget,
type TrustwareWidgetRef,
} from "@trustware/sdk";
```
```tsx theme={null}
const widgetRef = useRef(null);
```
```tsx theme={null}
export function ControlledWidget() {
const widgetRef = useRef(null);
return (
console.log("opened")}
onClose={() => console.log("closed")}
/>
);
}
```
## Widget props
| Prop | Type | Description |
| ----------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `theme` | `"light" \| "dark" \| "system"` | Sets the widget color scheme. Defaults to `"system"`. |
| `initialStep` | `"home" \| "select-token" \| "crypto-pay" \| "processing" \| "success" \| "error"` | The step the widget opens on. Defaults to `"home"`. |
| `defaultOpen` | `boolean` | Whether the widget renders open on first mount. |
| `onOpen` | `() => void` | Callback fired when the widget opens. |
| `onClose` | `() => void` | Callback fired when the widget closes. |
| `showThemeToggle` | `boolean` | Whether to show the theme toggle control inside the widget. |
## Using the ref
`TrustwareWidgetRef` exposes three methods:
| Method | Description |
| ---------- | --------------------------------------------------------------------------------------- |
| `open()` | Open the widget. |
| `close()` | Close the widget. Shows a confirmation dialog first if a transaction is in progress. |
| `isOpen()` | Returns `true` when the widget is currently open. Useful for syncing your own UI state. |
```tsx theme={null}
// Open the widget from a button
// Close from a parent component
// Reflect open state in your own UI
const open = widgetRef.current?.isOpen() ?? false;
```
The `?.` optional chaining guard is important: `ref.current` is `null` until the widget mounts. Using `widgetRef.current?.open()` prevents a runtime error if the button is clicked before the widget renders.
## Starting on a specific step
Use `initialStep` to land the user at a particular point in the flow rather than the home screen. This is useful when you already know the context: for example, if the user has already selected a token elsewhere in your UI.
```tsx theme={null}
```
Combine `defaultOpen={true}` with `initialStep` to open the widget immediately at the right step when the page loads.
# Drop-in widget with Trustware wallet detection
Source: https://docs.trustware.io/integration/drop-in-widget
The zero-config integration path: wrap your component with TrustwareProvider and drop in TrustwareWidget. Trustware handles wallet selection for you.
The drop-in widget is the fastest way to add a deposit flow to your app. You provide a config, Trustware handles wallet discovery, and users move through the full hosted flow without any wallet state management on your side.
## When to use this pattern
Choose this pattern when:
* your app does not already have a connected wallet state
* you want the complete built-in UX: wallet selection, token picker, amount entry, and confirmation
* you want to reach production with the shortest integration path
If you already own wallet connection through Wagmi, RainbowKit, or a custom adapter, use the [host wallet pattern](/integration/host-wallet) instead.
The widget does not accept a `hooks` field, so destination contract calls are
not available through it. To deposit into a contract on the destination chain,
use the [headless core](/integration/headless-core) or the
[REST API](/api-reference/route). See
[vault destinations](/guides/vault-destinations).
## Setup
```bash theme={null}
npm install @trustware/sdk
# or
pnpm add @trustware/sdk
```
The SDK requires React `18.2+` or `19`.
Create a `TrustwareConfigOptions` object. At minimum you need `apiKey` and `routes.toChain` / `routes.toToken`.
```tsx theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const trustwareConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
defaultSlippage: 1,
options: {
routeRefreshMs: 15000,
},
},
autoDetectProvider: true,
messages: {
title: "Deposit",
description: "Move funds into the destination asset and chain.",
},
} satisfies TrustwareConfigOptions;
```
`autoDetectProvider: true` tells Trustware to manage wallet discovery on its own. Leave this enabled when using the drop-in pattern.
Wrap a component with `TrustwareProvider` and place `TrustwareWidget` anywhere inside it.
```tsx theme={null}
import { TrustwareProvider, TrustwareWidget } from "@trustware/sdk";
export function DepositPanel() {
return (
);
}
```
`TrustwareProvider` initializes the SDK, runs wallet detection, and makes configuration available to the widget. You do not need to pass anything else.
## Widget flow
Once rendered, the widget walks users through these steps in order:
1. **Home**: entry point; users choose to pay with crypto or fiat
2. **Select Token**: all routable assets in the connected wallet are displayed for the transaction
3. **Confirm Deposit**: amount entry with slider, token carousel, and fee summary
4. **Processing**: transaction submitted; the widget waits for confirmation
5. **Success / Error**: final result screen
## When to prefer this pattern
The drop-in widget is the right default for most new integrations. If you find yourself needing to control the wallet, open the widget programmatically, or build custom deposit UI, look at the other integration patterns.
| Scenario | Recommended pattern |
| ---------------------------------- | --------------------------------------------------- |
| No existing wallet | Drop-in widget (this page) |
| Already using Wagmi / RainbowKit | [Host wallet](/integration/host-wallet) |
| Open/close widget programmatically | [Controlled widget](/integration/controlled-widget) |
| Custom deposit UI, no widget | [Headless core](/integration/headless-core) |
# Build custom deposit UI with the headless core
Source: https://docs.trustware.io/integration/headless-core
Use Trustware routing, wallet plumbing, transaction orchestration, and settlement automations without any widget UI by calling the Trustware core API directly.
The headless core gives you Trustware's routing and transaction logic without the prebuilt widget. You build your own UI and the SDK handles route construction, quotes, wallet calls, settlement, and transaction submission.
## When to use this pattern
Choose this pattern when:
* you want full control over the deposit UI
* your design system requires a custom amount entry or confirmation flow
* you want to embed deposit logic into an existing interface without a floating widget
* you need a flow the widget does not provide, such as [withdrawing from an embedded wallet](/guides/embedded-wallet-withdrawals)
* you need to deposit into a contract on the destination chain, such as [depositing into a vault destination](/guides/vault-destinations)
If you want built-in wallet selection, token picker, amount entry, and confirmation screens, use the [drop-in widget](/integration/drop-in-widget) instead.
## Setup
The headless core still requires `TrustwareProvider` for config context. Mount it once near the root of your app; you do not need to render `TrustwareWidget`.
```tsx theme={null}
import { TrustwareProvider, type TrustwareConfigOptions } from "@trustware/sdk";
const trustwareConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
defaultSlippage: 1,
options: {
routeRefreshMs: 15000,
},
},
} satisfies TrustwareConfigOptions;
export function App() {
return (
{/* your custom UI goes here */}
);
}
```
Then import the core in any component or hook:
```ts theme={null}
import { Trustware } from "@trustware/sdk";
```
## Wallet setup
You have two options for attaching a wallet to the headless core.
### Bring your own wallet
Adapt an existing Wagmi wallet client into Trustware using `useWallet`:
```ts theme={null}
import { useEffect } from "react";
import { useWalletClient } from "wagmi";
import { useWagmi } from "@trustware/sdk/wallet";
import { Trustware } from "@trustware/sdk";
export function useTrustwareWalletBridge() {
const { data } = useWalletClient();
useEffect(() => {
if (!data) return;
Trustware.useWallet(useWagmi(data));
}, [data]);
}
```
### Let Trustware detect wallets
If you do not manage wallet state, call `autoDetect` once at startup:
```ts theme={null}
await Trustware.autoDetect();
```
## Core operations
### 1. Build a route
`buildRoute` constructs a route. Use this when you want to inspect route details before asking the user to confirm.
```ts theme={null}
const route = await Trustware.buildRoute({
fromChain: "1",
toChain: "8453",
fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
fromAmount: "1000000", // amount in token's smallest unit
fromAddress: await Trustware.getAddress(),
toAddress: "0xDestination...",
});
```
**`BuildRouteBody` shape:** (`?` indicates optional)
```ts theme={null}
export type BuildRouteBody = {
fromChain: string;
toChain: string;
fromToken: string;
toToken: string;
fromAmount: string;
fromAddress: string;
toAddress: string;
fromAmountUsd?: string;
fromAmountUSD?: string;
refundAddress?: string;
direction?: string;
slippage?: number;
slippageBps?: number;
memo?: string;
hooks?: { postHook?: PostHookRequest };
};
```
`hooks.postHook` executes a contract call on the destination chain once routed funds arrive, instead of paying the destination token to `toAddress`. It requires SDK 1.1.10 or later, is fully optional, and omitting `hooks` leaves route behavior unchanged. See [`PostHookRequest`](/api-reference/types#posthookrequest) for the field reference and [vault destinations](/guides/vault-destinations) for the end-to-end flow.
### 2. Inspect route details
The returned `BuildRouteResult` contains exchange rate information you can display to the user before they confirm.
```ts theme={null}
const route = await Trustware.buildRoute({
fromChain: "1",
toChain: "8453",
fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
fromAmount: "1000000",
fromAddress: await Trustware.getAddress(),
toAddress: "0xDestination...",
});
// Show the user what they will receive before confirming
console.log(route.finalExchangeRate.fromAmountUSD);
console.log(route.finalExchangeRate.toAmountMinUSD);
```
**`BuildRouteResult` shape:**
```ts theme={null}
type BuildRouteResult = {
intentId: string; // pass to receipt and status calls
txReq: TxRequest; // transaction object to sign and broadcast
actions: unknown[];
finalExchangeRate: {
fromAmountUSD?: string;
toAmountMinUSD?: string;
};
route: RoutePlan | undefined;
sponsorship?: RouteSponsorship; // present when the route is gas sponsored
};
```
The `txReq` type is not exported under its own name. Refer to it as `BuildRouteResult["txReq"]` rather than importing a named type.
### 3. Sign and send a route
`sendRouteTransaction` switches the wallet to the route's chain if needed, grants any ERC-20 allowance the route requires, and then sends the route transaction. It returns the source transaction hash.
```ts theme={null}
const txHash = await Trustware.sendRouteTransaction(route, 1);
await Trustware.submitReceipt(route.intentId, txHash);
const result = await Trustware.pollStatus(route.intentId);
if (result.status === "success") {
console.log("Deposit complete:", result.destTxHash);
}
```
On EVM routes, allowance handling is automatic. When the route response includes `route.execution.approvals`, the SDK reads the current allowance, submits an approval for the exact amount when one is missing, waits for that approval to confirm, and only then requests the route signature.
Plan your UI around two consequences:
* The user can see more than one wallet prompt for a single deposit. An ERC-20 source asset usually means an approval prompt followed by the deposit prompt.
* Each approval must confirm on chain before the route signature is requested, so the call can stay pending for a minute or more.
Every approval the route lists is for the exact amount required, never an unlimited allowance. An ordinary EOA route processes all of them. If you sign transactions yourself, process `route.execution.approvals` in the same order: read, approve, wait, then send.
A route carrying valid gas sponsorship is the exception. `sendRouteTransaction`
skips the listed approvals in that case, because the sponsored path handles the
allowance itself: it grants the bridge allowance inside the user operation, and
it pulls the source tokens through a one-time unlimited Permit2 approval that it
requests separately. Neither is a `route.execution.approvals` entry. That path is
smart-account execution, so do not pass a sponsored route to
`sendRouteTransaction` with a standard EIP-1193 wallet: it sends a plain
transaction and the approval is skipped with nothing to replace it. Use
`sendRouteAsUserOperation()` instead, as [Paymasters](/guides/paymasters)
describes.
### 4. Run the full flow
`runTopUp` handles route construction, wallet approval, transaction submission, and status polling in a single call. Use this when you want the SDK to orchestrate the full deposit path.
Only `fromAmount` is required; every other field is optional and falls back to the corresponding value in your `TrustwareConfigOptions.routes` config when omitted.
```ts theme={null}
const result = await Trustware.runTopUp({
fromAmount: "1000000", // required: amount in token's smallest unit
fromChain: "1", // optional: overrides config.routes.fromChain
toChain: "8453", // optional: overrides config.routes.toChain
fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // optional: overrides config.routes.fromToken
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // optional: overrides config.routes.toToken
toAddress: "0xDestination...", // optional: overrides config.routes.toAddress
});
if (result.status === "success") {
console.log("Deposit complete:", result.destTxHash);
}
```
**Full signature:**
```ts theme={null}
Trustware.runTopUp(params: {
fromAmount: string | number; // required
fromChain?: string; // overrides config.routes.fromChain
toChain?: string; // overrides config.routes.toChain
fromToken?: string; // overrides config.routes.fromToken
toToken?: string; // overrides config.routes.toToken
toAddress?: string; // overrides config.routes.toAddress
});
```
## Working with chains and tokens
The same chain and token discovery the widget uses internally is available to your own UI through three helpers on the `Trustware` facade. All three respect the active `TrustwareProvider` config and reuse its cache.
### `Trustware.useChains()`
React hook that returns the supported chain list, split into popular and other groups, with loading and error state. Use it to drive your own chain selector.
```tsx theme={null}
import { Trustware } from "@trustware/sdk";
const useTrustwareChains = Trustware.useChains;
function ChainPicker() {
const { popularChains, otherChains, chains, isLoading, error } =
useTrustwareChains();
if (isLoading) return ;
if (error) return ;
return ;
}
```
### `Trustware.useTokens(chainId)`
React hook that returns the token list for a given chain with built-in pagination and search. Pass `null` to skip fetching.
```tsx theme={null}
const {
filteredTokens,
hasNextPage,
isLoading,
isLoadingMore,
error,
loadMore,
searchQuery,
setSearchQuery,
} = Trustware.useTokens(activeChain?.chainId ?? null);
```
### `Trustware.validateAddressForChain(address, chain)`
Synchronous helper that returns `{ isValid: boolean; error?: string }` for the given destination address against the rules of the selected chain (EVM checksum, Solana base58, Bitcoin, Cosmos prefixes, etc.). Use it for live form validation.
```ts theme={null}
const validation = Trustware.validateAddressForChain(
destinationAddress,
activeChain,
);
if (!validation.isValid) {
showError(validation.error);
}
```
## Lifecycle callbacks
`TrustwareConfigOptions` exposes three optional callbacks you can pass alongside your routes config to react to SDK activity:
```ts theme={null}
onError?: (error: TrustwareError) => void;
onSuccess?: (transaction: Transaction) => void;
onEvent?: (event: TrustwareEvent) => void;
```
* **`onEvent`** fires for every lifecycle event the SDK emits. It receives the full `TrustwareEvent` discriminated union, which you can narrow by `type` (`transaction_started`, `transaction_success`, `wallet_connected`, etc.).
* **`onSuccess`** fires once when a deposit settles on the destination chain. It receives the resolved `Transaction` with `destTxHash` populated.
* **`onError`** fires for any `TrustwareError` thrown during a route or transaction operation. Use it as a single place to log errors or push them into your UI state. `RateLimitError` extends native `Error` (not `TrustwareError`) and does not flow through this callback; handle it with the imperative `try/catch` pattern below.
```ts theme={null}
const config = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
onEvent: (event) => {
if (event.type === "transaction_success") {
console.log("TX hash:", event.txHash);
}
if (event.type === "wallet_connected") {
console.log("Wallet:", event.address);
}
},
onSuccess: (transaction) => {
console.log("Deposit complete:", transaction.destTxHash);
},
onError: (error) => {
console.error("Trustware error:", error.code, error.message);
},
} satisfies TrustwareConfigOptions;
```
`onError` runs in addition to any `try/catch` you wrap around individual core calls; see [Error handling](#error-handling) below for the imperative pattern. See [lifecycle events](/events-errors/lifecycle-events) for the full list of event types.
## Error handling
Wrap core calls in try/catch. Import `RateLimitError` to handle rate-limiting specifically:
```ts theme={null}
import { RateLimitError, Trustware } from "@trustware/sdk";
try {
const route = await Trustware.buildRoute({
fromChain: "1",
toChain: "8453",
fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
fromAmount: "1000000",
fromAddress: await Trustware.getAddress(),
toAddress: "0xDestination...",
});
} catch (error) {
if (error instanceof RateLimitError) {
console.error("Rate limited", error.rateLimitInfo);
}
}
```
## When to use the widget instead
If you want built-in wallet selection, token picker, amount input, and processing/success screens, the widget patterns require significantly less code. The headless core is best when your UI requirements cannot be met by the prebuilt widget.
| You want | Use |
| --------------------------- | --------------------------------------------------- |
| Full built-in UX | [Drop-in widget](/integration/drop-in-widget) |
| Widget with your own wallet | [Host wallet](/integration/host-wallet) |
| Programmatic open/close | [Controlled widget](/integration/controlled-widget) |
| Custom UI, SDK routing | Headless core (this page) |
# Connect your existing wallet to Trustware
Source: https://docs.trustware.io/integration/host-wallet
Pass your existing Wagmi, RainbowKit, or viem wallet to TrustwareProvider using the wallet prop and autoDetect={false} to keep a single wallet state.
If your app already manages wallet connection (through Wagmi, RainbowKit, or a custom adapter), you can pass that wallet directly to Trustware instead of letting the SDK run its own discovery. The widget UX stays the same; only wallet ownership changes, and the user does not have to sign twice or reconnect their wallet.
## When to use this pattern
Choose this pattern when:
* your app has an existing Wagmi or RainbowKit setup
* you have custom wallet orchestration and do not want the SDK to interfere
* you want the full widget flow but need Trustware to use your connected wallet
If your app does not yet manage wallet state, use the [drop-in widget](/integration/drop-in-widget) instead.
## Setup
```bash theme={null}
npm install @trustware/sdk
# or
pnpm add @trustware/sdk
```
The `useWagmi` adapter is exported from a separate sub-path to keep the main bundle lean. It converts a viem `WalletClient` into the wallet interface that `TrustwareProvider` expects.
```tsx theme={null}
import { useWagmi } from "@trustware/sdk/wallet";
```
`useWalletClient` returns a new object reference on every render. Wrap the adapter in `useMemo` so the wallet reference only changes when `walletClient` itself changes.
```tsx theme={null}
import { useMemo } from "react";
import { useWalletClient } from "wagmi";
import { useWagmi } from "@trustware/sdk/wallet";
const { data: walletClient } = useWalletClient();
const wallet = useMemo(
() => (walletClient ? useWagmi(walletClient) : undefined),
[walletClient]
);
```
Provide the memoized wallet via the `wallet` prop and set `autoDetect={false}` to prevent the SDK from running its own wallet discovery alongside your host-managed wallet.
```tsx theme={null}
import { useMemo } from "react";
import { useWalletClient } from "wagmi";
import { TrustwareProvider, TrustwareWidget } from "@trustware/sdk";
import { useWagmi } from "@trustware/sdk/wallet";
export function DepositPanel() {
const { data: walletClient } = useWalletClient();
const wallet = useMemo(
() => (walletClient ? useWagmi(walletClient) : undefined),
[walletClient]
);
return (
);
}
```
## How it works
### What `useWagmi` does
`useWagmi` adapts a viem `WalletClient` to the `WalletInterFaceAPI` that `TrustwareProvider` accepts. It exposes the address, chain, and signing methods Trustware needs to build and submit routes, without requiring you to implement that adapter yourself.
### `autoDetect={false}`
Setting `autoDetect={false}` tells `TrustwareProvider` not to run its own wallet discovery. Without this, the SDK would attempt auto-detection even when a wallet is already provided, which can cause conflicts with your existing wallet state.
Always set `autoDetect={false}` when passing a `wallet` prop. Leaving auto-detection enabled alongside a host wallet can result in unexpected wallet switching.
### Why `useMemo` matters
`useWalletClient` from Wagmi returns a new object reference on every render cycle, even when the underlying wallet hasn't changed. Without `useMemo`, every render would produce a new `wallet` value, causing `TrustwareProvider` to treat each one as a wallet change and unnecessarily re-initialize its internal state.
## Using with RainbowKit
`TrustwareProvider` must sit **inside** your Wagmi and RainbowKit providers so it has access to their React context. If you place it outside `WagmiProvider`, calls to `useWalletClient` will fail.
```tsx theme={null}
import { WagmiProvider } from "wagmi";
import { QueryClientProvider } from "@tanstack/react-query";
import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { TrustwareProvider } from "@trustware/sdk";
function Providers({ children }) {
const { data: walletClient } = useWalletClient();
const wallet = useMemo(
() => (walletClient ? useWagmi(walletClient) : undefined),
[walletClient]
);
return (
{children}
);
}
```
## Using with embedded wallets
The same pattern works for embedded wallets your app provisions, for example through Privy. Use `useEIP1193` from `@trustware/sdk/wallet` to adapt any EIP-1193 provider, including an embedded wallet's provider, into the wallet interface `TrustwareProvider` accepts. As with every host wallet, pass it through the `wallet` prop and set `autoDetect={false}`.
See [use Trustware with embedded wallets](/guides/embedded-wallets) for the full pattern, including how to resolve the embedded wallet safely and run swaps and deposits against it.
# What is Trustware
Source: https://docs.trustware.io/introduction
Trustware is the Universal Deposit Layer, powering dynamic routing that lets any app accept any asset on any chain and settle to any destination.
Trustware is programmable infrastructure for digital asset routing. It solves the deposit conversion problem: when a user sends USDC on Polygon but you need ETH on Base, Trustware handles the route, swap, and settlement automatically, without you building any of that plumbing yourself.
## How it works
Every integration follows the same three-step flow, whether you use the SDK or the REST API directly.
A route is requested with four inputs: source asset, source chain, destination chain, and destination address. Trustware's Route Handler evaluates available paths across 130+ source chains spanning EVM, Solana, Bitcoin, and Cosmos ecosystems, with 25,000+ supported source tokens, and returns the optimal route with fees, expected output, and estimated completion time. AML/OFAC screening runs at this step; if an address is flagged, the route never generates and no funds move.
Once a route is selected, the transaction payload is returned to your signing infrastructure. Your application, or the user's wallet, signs and broadcasts the transaction to the source chain. Trustware never holds private keys or takes custody of funds at any point. The receipt is then submitted so Trustware can track settlement through to the destination chain.
## What you can build
Route any supported asset and deposit it into a vault or another destination contract, with no separate deposit step for the user.
Embed a prebuilt cross-chain deposit flow in your dApp in minutes using the React SDK. Wallet detection, quoting, and transaction execution are handled for you.
Let users swap any asset on any chain directly into their own wallet, with optional control over the output token.
Use the headless core to build your own deposit interface on top of Trustware's routing and transaction APIs. You own every pixel.
Integrate server-side using the REST API. Compatible with any custody wallet or signing infrastructure; no React required.
Change destination chain, token, or address at runtime. Build flows that adapt settlement dynamically based on liquidity conditions or user preferences.
## Choose your integration path
Prebuilt deposit widget and headless core for React 18+ and 19. Install in minutes.
Backend integration for any stack. Full control over signing, routing, and settlement.
## Key capabilities
Trustware sources the most efficient routes across on-chain and off-chain liquidity for execution.
Users send any supported asset. Recipients receive their preferred asset on their preferred chain. Settlement targets can be changed dynamically.
Trustware generates transaction payloads but never holds private keys or takes custody of funds. Transactions execute peer-to-peer via on-chain contracts.
Screening runs at the quote step. If an address is flagged, the route never generates and no funds move.
Keys can be scoped to a specific domain, preventing unauthorized use from other origins.
Attach metadata to a route intent through the REST API; it is echoed back in every status response. Build settlement logic that responds to thresholds, liquidity conditions, or routing events.
Sponsor gas for deposit flows without taking custody of user funds or signing keys. Sponsorship funds sit in on-chain contracts you control and can verify.
Self-serve console to manage your organization, projects, and SDK keys, with transaction and usage analytics.
## Coming soon
*Coming soon.* Fiat on-ramp and off-ramp integrations, letting users fund and settle deposits in local currency alongside on-chain assets.
# Get started with the Trustware SDK
Source: https://docs.trustware.io/quickstart
Install @trustware/sdk, configure TrustwareConfigOptions, and render your first cross-chain deposit widget in a React app in under five minutes.
This quickstart covers the React SDK integration path. For server-side or backend integrations, start with the [API Overview](/api-reference/overview).
This guide walks you through installing the SDK, obtaining an API key, configuring your route, and rendering the deposit widget. By the end you'll have a working deposit flow embedded in your React app.
Add `@trustware/sdk` to your project using your preferred package manager:
```bash npm theme={null}
npm install @trustware/sdk
```
```bash pnpm theme={null}
pnpm add @trustware/sdk
```
The package is published as [`@trustware/sdk`](https://www.npmjs.com/package/@trustware/sdk) on npm.
The SDK requires React 18.2+ or React 19. Install the peer dependencies if you haven't already:
```bash npm theme={null}
npm install react react-dom viem
```
```bash pnpm theme={null}
pnpm add react react-dom viem
```
`react` and `react-dom` are marked as optional peer dependencies; if your app already has them installed, no extra step is needed. `viem` is required.
`apiKey` is a required field in `TrustwareConfigOptions`. Create one in the [Client Dashboard](https://dashboard.trustware.io): create an organization, create a project, then spin up an SDK key. The raw key is shown only once, so copy it immediately. See the [Client Dashboard](#client-dashboard) section below for the full tour.
Store the key as an environment variable:
```bash theme={null}
NEXT_PUBLIC_TRUSTWARE_API_KEY=your-api-key-here
```
Never commit your API key to source control. Use your framework's environment variable support: `NEXT_PUBLIC_` prefix for Next.js, `VITE_` for Vite.
In deposit mode, which is the default, the `routes.toChain` and `routes.toToken` fields are required; everything else is optional:
```ts theme={null}
import { type TrustwareConfigOptions } from "@trustware/sdk";
const trustwareConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453", // destination chain ID (Base)
toToken: "0xEeee...", // destination token address
defaultSlippage: 1, // slippage percentage, defaults to 1
options: {
routeRefreshMs: 15000, // route preview refresh interval
},
},
autoDetectProvider: true, // let the SDK discover injected wallets
messages: {
title: "Deposit",
description: "Move funds into the destination asset and chain.",
},
} satisfies TrustwareConfigOptions;
```
| Field | Required | Description |
| --------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `apiKey` | Yes | Your Trustware API key |
| `mode` | No | `"deposit"` (default) or `"swap"`. Requires SDK 1.1.9 or later. See [swap mode](/guides/swap-mode) |
| `routes.toChain` | In deposit mode | Destination chain ID as a string |
| `routes.toToken` | In deposit mode | Destination token identifier: contract address on EVM and Solana, native denom on Cosmos |
| `routes.toAddress` | No | Recipient address. Can be updated at runtime via `Trustware.setDestinationAddress` |
| `routes.defaultSlippage` | No | Slippage percentage. Defaults to `1` |
| `routes.options` | No | Refresh and amount-constraint settings (`routeRefreshMs`, `fixedFromAmount`, `minAmountOut`, `maxAmountOut`) |
| `autoDetectProvider` | No | Enable SDK-managed wallet discovery. Defaults to `false` |
| `messages` | No | Override widget title and description copy |
| `theme` | No | Widget color scheme: `"light"`, `"dark"`, or `"system"` |
| `walletConnect` | No | WalletConnect connector overrides |
| `retry` | No | Rate-limit retry behavior |
| `features` | No | Feature flags (`tokensPagination`, `balanceStreaming`, `shouldAllowGA4`) |
| `onError`, `onSuccess`, `onEvent` | No | Lifecycle callbacks |
See [configuration overview](/configuration/overview) for the full reference of every field.
Mount `TrustwareProvider` once near the root of your component tree and pass in your config:
```tsx theme={null}
import { TrustwareProvider } from "@trustware/sdk";
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
All `TrustwareWidget` instances and `useTrustware` calls within the tree share this config and wallet state.
Drop `` anywhere inside the provider tree:
```tsx theme={null}
import { TrustwareWidget } from "@trustware/sdk";
export function DepositPage() {
return (
Top up your balance
);
}
```
The widget handles the full deposit flow: wallet connection, token selection, amount entry, and transaction confirmation.
## Full working example
Here's the complete setup in a single file, copied directly from the SDK README:
```tsx theme={null}
import {
TrustwareProvider,
TrustwareWidget,
type TrustwareConfigOptions,
} from "@trustware/sdk";
const trustwareConfig = {
apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
routes: {
toChain: "8453",
toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
defaultSlippage: 1,
options: {
routeRefreshMs: 15000,
},
},
autoDetectProvider: true,
messages: {
title: "Deposit",
description: "Move funds into the destination asset and chain.",
},
} satisfies TrustwareConfigOptions;
export function App() {
return (
);
}
```
## Client Dashboard
The [Client Dashboard](https://dashboard.trustware.io) is where you manage everything about your Trustware integration: your organization, your projects, your SDK keys, and the transaction and analytics data flowing through the SDK.
You can run multiple projects under a single organization, each with its own SDK keys and isolated data. This is useful for separating environments (e.g. staging vs. production) or distinct products.
### Quick start
You land on the **Overview** tab, which shows the account you're signed in as and your authentication status.
On first sign-in you're prompted to name your organization (a description is optional).
Give it a project name, a slug, and an optional description.
Go to the **Projects** tab, select your project, and create a key. Creating the org and project does not create a key automatically. You do this step manually.
The raw SDK key is shown only once, in a one-time pop-up. Copy it immediately and store it securely.
The raw key can't be retrieved again later.
Once you have a key, plug it into `TrustwareConfigOptions` as shown in the steps above and start routing transactions.
### Navigation
The dashboard has seven tabs in the left sidebar, plus quick switchers for your organization, project, and account (and a sign-out control).
**Overview.** Your home base. Shows your account and authentication status, the tenants and workspaces you can access, and shortcut sections: *Jump back in* (pills into a project's access and analytics pages), *Quick actions* (manage projects, manage members, view analytics, update profile), *At a glance* (current setup state with a recommended next step), and *Common paths* (invite a teammate, set up project access, review SDK usage).
**Organizations.** View organization status, create new organizations, and switch between the orgs you belong to. Shows the creation date, seat/member count, and a member list with each person's role, status, last active time, and email, including who the owners and admins are. Note: only owners can change owner rows, and an org must always keep at least one active owner.
**Projects.** Choose between projects or create a new one. Each project shows its active status, creation date, slug, and SDK key limit (default 3). This is where you manage SDK keys:
* **Create keys** (owners and admins only) with a label, an optional origin URL, and optional notes. Each key shows its creation date, SDK ID, and active status. The raw key appears once on creation via a copy-to-clipboard pop-up.
* **Edit, rotate, revoke, or delete** keys as needed.
* Manage **project members and roles**: grant admin access or suspend accounts.
**Paymasters.** Deploy and manage gas sponsorship for the selected project. Deploy a paymaster contract you own, fund its EntryPoint balance, set a monthly chain budget, and create per-SDK-key sponsorship rules. See [Sponsor gas with Paymasters](/guides/paymasters).
**Transactions.** A live view of activity for a selected project and SDK key: loaded activity, successful transactions, and total quoted destination amount (\$). The activity list shows each transaction's date, status, sponsored/unsponsored flag, route taken, amount, and the source and destination wallets, with links out to block explorers. Expanding the route shows which provider and which LP pools were used. Filter by SDK key, status, source chain, destination chain, provider, and date range, or look up a specific transaction by hash.
**Analytics.** Aggregate insight across your usage. Filter by organization/project/key, time window (last 7/30/90 days), granularity (daily/weekly/monthly), and chain. Headline stats cover requests/total volume, successful routes, observed failures, and paymaster usage (gas sponsored, \$). Below that: a request-volume trend graph, a per-chain breakdown (requests, routes, errors, sponsored \$), and your top SDK key and top projects for the selected window.
**Settings.** Manage your profile: update your account email, display name, and an optional description shown in the dashboard. Includes account deletion and a help/support panel ([support@trustware.io](mailto:support@trustware.io)).
### Need help?
Reach the team any time at [support@trustware.io](mailto:support@trustware.io).
## Next steps
* **Deposit into a contract**: route assets into a vault or another destination contract with [Vault destinations](/guides/vault-destinations).
* **Sponsor gas for your users**: deploy a paymaster you own and set sponsorship budgets and rules with [Paymasters](/guides/paymasters).
* **Bring your own wallet**: if your app already manages wallet connection through Wagmi or viem, see [Host wallet](/integration/host-wallet).
* **Control the widget programmatically**: open and close the widget from your own UI with [Controlled widget](/integration/controlled-widget).
* **Build a custom UI**: skip the widget entirely and use the [Headless core API](/integration/headless-core).
* **Customize appearance**: set the color scheme and copy in [Configuration](/configuration/overview).
# Supported chains and assets
Source: https://docs.trustware.io/supported-chains
Browse the source chains and assets users can transact with on Trustware, and search them by name, ID, or address.
Trustware supports thousands of assets across many source chains. Use the explorer below to find a chain, then browse or search its supported assets. Select a chain to load its asset list on demand.
This list is generated from Trustware discovery data, so counts can change as the dataset refreshes. It shows the source chains and assets users can transact with.
Chain icons are served from this documentation site. Token logos load from public asset hosts, so they appear only where a logo is available and fall back to a lettered badge otherwise.
## Query these programmatically
The explorer above reads the same public data your app can consume at runtime, so you never need to hardcode supported chains or assets.
* **REST API**: call the [discovery endpoints](/api-reference/discovery) (`GET /chains` and `GET /tokens`) to populate selectors dynamically.
* **SDK**: use `Trustware.useChains()` and `Trustware.useTokens(chainId)` to fetch the same lists inside a React app.
The explorer is a generated snapshot. Use the REST API or SDK at runtime for the live set; an entry shown here reflects availability at the snapshot’s last refresh.