Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@
"solutions/company-wallets/integration-guide/overview",
"solutions/company-wallets/integration-guide/javascript-server",
"solutions/company-wallets/integration-guide/golang",
"solutions/company-wallets/integration-guide/ruby",
"solutions/company-wallets/integration-guide/rust",
"solutions/company-wallets/integration-guide/python"
]
Expand Down Expand Up @@ -443,7 +442,18 @@
]
},
"features/transaction-management/balances",
"features/transaction-management/fiat-on-ramp"
"features/transaction-management/fiat-on-ramp",
{
"group": "Swaps",
"tag": "Early Access",
"pages": [
"features/transaction-management/swap",
"features/transaction-management/swap/enable-swap",
"features/transaction-management/swap/get-swap-quote",
"features/transaction-management/swap/execute-swap",
"features/transaction-management/swap/track-swap-status"
]
}
]
},
{
Expand Down Expand Up @@ -909,6 +919,7 @@
"solutions/company-wallets/integration-guide/javascript-server",
"solutions/company-wallets/integration-guide/golang",
"solutions/company-wallets/integration-guide/ruby",
"solutions/company-wallets/integration-guide/ruby",
"solutions/company-wallets/integration-guide/rust",
"solutions/company-wallets/integration-guide/python",
{
Expand Down
98 changes: 98 additions & 0 deletions features/transaction-management/swap.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: "Swaps"
description: "Execute token swaps from Turnkey wallets through a unified DEX aggregator abstraction, with on-chain integrator fees for your organization."
mode: "wide"
noindex: true
---

<Note>
Swaps are in early access. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
</Note>

Swaps allow wallets in your organization exchange tokens through DEX aggregators via a single Turnkey activity, with the usual audit trail and policy controls. Same-chain and EVM ↔ EVM cross-chain routes are supported. You can take an integrator fee on every swap, paid on-chain to a wallet you control.

## What are Swaps

Swaps connects Turnkey wallets to DEX aggregators through a unified interface. 0x is supported today; additional providers are on the roadmap. Callers submit signed intents describing what they want to swap while Turnkey handles quote fetching, transaction construction, ERC-20 approvals via Permit2, and broadcast. Callers never see provider quote payloads, transaction calldata, or approval data.

Swaps enablement is a one-time setup step per parent organization. After that, each swap is a signed intent and a poll.

## How it works

1. Enable swaps once on your parent organization via [`upsert_swap_config`](/features/transaction-management/swap/enable-swap), setting your integrator fee (bps) and fee-receiver wallet.
2. Optionally call [`get_swap_quote`](/features/transaction-management/swap/get-swap-quote) for an indicative quote to display to users. Quotes reflect your active fee configuration.
3. Submit [`execute_swap`](/features/transaction-management/swap/execute-swap) from the sub-organization whose wallet is swapping. Turnkey handles quote refresh, Permit2 approvals, and transaction construction internally. For cross-chain routes, the origin-chain transaction is broadcast here.
4. Poll swap status until settlement. Same-chain swaps complete on origin inclusion; cross-chain swaps have a longer lifecycle that continues until the destination-chain leg settles or funds are recovered. See [Track swap status](/features/transaction-management/swap/track-swap-status).

## Supported providers, chains, and routes

| Provider | Chains (V1) |
| :-- | :-- |
| 0x | Ethereum (`eip155:1`), Base (`eip155:8453`), Arbitrum (`eip155:42161`), Polygon (`eip155:137`), BNB Chain (`eip155:56`) |

**Route support in V1:**

- **Same-chain EVM ↔ EVM** — e.g., USDC → USDT on Base.
- **Cross-chain EVM ↔ EVM** — e.g., Arbitrum USDC → Base USDC, where supported by the provider.

**On the roadmap:** Solana ↔ Solana, EVM ↔ SVM (e.g., Ethereum ↔ Solana), additional providers, provider selection, and multiple quote options.

## Fees

Swap fees are integrator fees: a percentage of the swap amount, expressed in basis points, applied to quote amounts and collected onchain during execution.

- **Your integrator fee**: set via (`feeBps`), along with the payout wallet (`feeReceiverWalletAddress`, a wallet address you control). Fees accrue onchain to that wallet.
- **Cap**: `feeBps` is bounded by a Turnkey-controlled maximum threshold. Requests above the cap are rejected.

Fees are configured **on the parent organization** and applied to every swap submitted by any sub-organization under it. Sub-organizations cannot override the parent's fee configuration.

<Warning>
To change your fee configuration, submit `upsert_swap_config` again with the new values. Quote and execute activities submitted after the change immediately use the new fee configuration.
</Warning>

## API surface

Swaps add three activities:

| Activity | Endpoint | Purpose |
| :-- | :-- | :-- |
| `ACTIVITY_TYPE_UPSERT_SWAP_CONFIG` | `POST /public/v1/submit/upsert_swap_config` | Enable swaps on your parent organization and set your fee configuration |
| `ACTIVITY_TYPE_EXECUTE_SWAP` | `POST /public/v1/submit/execute_swap` | Sign, broadcast, and settle a swap from a sub-org wallet |
| `ACTIVITY_TYPE_CLAIM_SWAP_FEES` | `POST /public/v1/submit/claim_swap_fees` | Claim accrued integrator fees to your fee-receiver wallet |

and two queries:

| Query | Endpoint | Purpose |
| :-- | :-- | :-- |
| Swap quote | `POST /public/v1/query/get_swap_quote` | Indicative fee-aware quote for an input token / amount pair |
| Swap status | `POST /public/v1/query/get_swap_status` | Poll the full swap lifecycle. Handles same-chain and cross-chain with a unified response shape. |

<Note>
Swap requests are stamped and submitted like any other Turnkey request. See [Stamps](/api-reference/overview/stamps) and [Submissions](/api-reference/activities/overview). There are no Swap-specific SDK methods during the early access period, so the examples on these pages use cURL and the generic `request` method of [`@turnkey/http`](https://www.npmjs.com/package/@turnkey/http)'s `TurnkeyClient`.
</Note>

## Trust boundary

Swap intentionally does not expose executable provider data to API callers. The user-signed `ExecuteSwapIntent` is the source of truth for `inputToken`, `outputToken`, `inputAmount`, `walletAccount`, `sponsor`, and `slippage`. Fee configuration, provider quote payloads, transaction calldata, Permit2 signatures, and approval payloads are all handled server-side and never accepted from callers.

Policy engines evaluate the derived on-chain transaction, not the swap intent — so existing wallet policies apply to the resulting transfer behavior.

## Explore

<CardGroup cols={2}>
<Card title="Enable swaps" href="/features/transaction-management/swap/enable-swap">
One-time parent-org setup with fee configuration.
</Card>

<Card title="Get a quote" href="/features/transaction-management/swap/get-swap-quote">
Indicative fee-aware quote for display.
</Card>

<Card title="Execute a swap" href="/features/transaction-management/swap/execute-swap">
One activity, optionally gas-sponsored.
</Card>

<Card title="Track swap status" href="/features/transaction-management/swap/track-swap-status">
Same-chain and cross-chain lifecycle.
</Card>
</CardGroup>
129 changes: 129 additions & 0 deletions features/transaction-management/swap/enable-swap.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: "Enable Swaps"
description: "One-time parent-organization activity that enables swaps and sets your integrator fee configuration."
mode: "wide"
noindex: true
---

`ACTIVITY_TYPE_UPSERT_SWAP_CONFIG` is a parent-organization configuration activity that writes `FEATURE_NAME_SWAP_CONFIG` — the source of truth for your integrator fee configuration and the wallet that receives fees. This config must be set before any sub-organization can call `get_swap_quote` or `execute_swap`.

## Prerequisites

- The activity is submitted **against the parent organization**. Sub-organization requests are rejected.
- You control a wallet that will receive integrator fees. It must be a valid address for the target route and provider _(To Confirm — likely any EVM address in V1)_.
- You've decided on your fee rate in basis points. See [Choose your fee configuration](#choose-your-fee-configuration).

## Submit upsert\_swap\_config

<ParamField body="type" type="enum<string>" required>
`ACTIVITY_TYPE_UPSERT_SWAP_CONFIG`
</ParamField>

<ParamField body="timestampMs" type="string" required>
Timestamp (in milliseconds) of the request, used to verify liveness.
</ParamField>

<ParamField body="organizationId" type="string" required>
Unique identifier of the **parent** organization. Requests from sub-organizations are rejected.
</ParamField>

<ParamField body="parameters.feeReceiverWalletAddress" type="string" required>
The wallet address that receives integrator fees on-chain. Must be valid for the route and provider _(To Confirm — likely any EVM address in V1)_.
</ParamField>

<ParamField body="parameters.feeBps" type="string" required>
Your integrator fee in basis points, expressed as a stringified integer. For example, `"50"` = 0.5%. Must be non-negative and no greater than the Turnkey-defined maximum.
</ParamField>

<ParamField body="parameters.stableFeeBps" type="string">
Optional. A separate integrator fee in basis points applied to stablecoin-to-stablecoin swaps _(To Confirm — verify this is the exact scope)_.
</ParamField>

<ParamField body="parameters.provider" type="string">
Optional. The DEX aggregator provider. Omit in V1 — `0x` is the only supported provider and is used by default.
</ParamField>

```bash title="cURL"
curl --request POST \
--url https://api.turnkey.com/public/v1/submit/upsert_swap_config \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "X-Stamp: <string> (see Stamps)" \
--data '{
"type": "ACTIVITY_TYPE_UPSERT_SWAP_CONFIG",
"timestampMs": "<string> (e.g. 1745474677453)",
"organizationId": "<PARENT_ORGANIZATION_ID>",
"parameters": {
"feeReceiverWalletAddress": "<WALLET_ADDRESS>",
"feeBps": "50"
}
}'
```

```javascript title="JavaScript"
import { TurnkeyClient } from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
const client = new TurnkeyClient(
{ baseUrl: "https://api.turnkey.com" },
new ApiKeyStamper({
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
}),
);
const { activity } = await client.request("/public/v1/submit/upsert_swap_config", {
type: "ACTIVITY_TYPE_UPSERT_SWAP_CONFIG",
timestampMs: String(Date.now()),
organizationId: "<PARENT_ORGANIZATION_ID>",
parameters: {
feeReceiverWalletAddress: "<WALLET_ADDRESS>",
feeBps: "50",
},
});
```

`upsert_swap_config` completes inline. The activity result echoes the applied configuration:

```json
{
"activity": {
"id": "<ACTIVITY_ID>",
"status": "ACTIVITY_STATUS_COMPLETED",
"type": "ACTIVITY_TYPE_UPSERT_SWAP_CONFIG",
"result": {
"upsertSwapConfigResult": {
"feeReceiverWalletAddress": "<WALLET_ADDRESS>",
"feeBps": "50",
"stableFeeBps": "10"
}
}
}
}
```

_(Response shape `To Confirm` — assumes `enableSwapResult.config` echoes the applied configuration for confirmation.)_

## Choose your fee configuration

Your `feeBps` applies to every swap submitted under any sub-organization of your parent org. Two constraints to keep in mind:

- **Cap**: Turnkey enforces a maximum `feeBps`. Requests above the cap are rejected. The current maximum is documented in the API reference and may be adjusted over time _(To Confirm — current cap value)_.
- **Fee currency**: fees accrue on-chain to `feeReceiverWalletAddress` in the token that the swap route settles the fee in _(To Confirm — 0x currently collects the integrator fee in the sell token; document once verified)_.

Pick a `feeBps` that reflects your intended margin and confirm the receiver wallet is one you actively control. Fees paid to a wallet you don't control cannot be recovered by Turnkey.

## Change your fee configuration

Configuration changes go through the same `ACTIVITY_TYPE_UPSERT_SWAP_CONFIG` activity — submit again with the updated `feeBps`, `feeReceiverWalletAddress`, or `stableFeeBps`. There is no separate update activity.

Quote and execute activities submitted **after** the change immediately use the new configuration. Because executable provider data is fetched fresh during `execute_swap`, an in-flight quote followed by an execute will settle at the fee configuration active at execute time, not at quote time _(To Confirm)_.

To disable swaps entirely, submit `enable_swap` with `enabled: false`.

<Warning>
Disabling swaps rejects all subsequent `get_swap_quote` and `execute_swap` requests until re-enabled. Wallets are unaffected — nothing on-chain changes.
</Warning>

## Next steps

- [Get a quote](/features/transaction-management/swap/get-swap-quote) — fee-aware indicative pricing for display and selection
- [Execute a swap](/features/transaction-management/swap/execute-swap) — the signing activity that runs the swap end-to-end
Loading