diff --git a/docs.json b/docs.json index 3f67ea78..55d015bd 100644 --- a/docs.json +++ b/docs.json @@ -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" ] @@ -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" + ] + } ] }, { @@ -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", { diff --git a/features/transaction-management/swap.mdx b/features/transaction-management/swap.mdx new file mode 100644 index 00000000..27af3541 --- /dev/null +++ b/features/transaction-management/swap.mdx @@ -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 +--- + + + Swaps are in early access. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization. + + +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. + + + 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. + + +## 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. | + + + 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`. + + +## 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 + + + + One-time parent-org setup with fee configuration. + + + + Indicative fee-aware quote for display. + + + + One activity, optionally gas-sponsored. + + + + Same-chain and cross-chain lifecycle. + + \ No newline at end of file diff --git a/features/transaction-management/swap/enable-swap.mdx b/features/transaction-management/swap/enable-swap.mdx new file mode 100644 index 00000000..dd657535 --- /dev/null +++ b/features/transaction-management/swap/enable-swap.mdx @@ -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 + + + `ACTIVITY_TYPE_UPSERT_SWAP_CONFIG` + + + + Timestamp (in milliseconds) of the request, used to verify liveness. + + + + Unique identifier of the **parent** organization. Requests from sub-organizations are rejected. + + + + 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)_. + + + + 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. + + + + Optional. A separate integrator fee in basis points applied to stablecoin-to-stablecoin swaps _(To Confirm — verify this is the exact scope)_. + + + + Optional. The DEX aggregator provider. Omit in V1 — `0x` is the only supported provider and is used by default. + + +```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: (see Stamps)" \ + --data '{ + "type": "ACTIVITY_TYPE_UPSERT_SWAP_CONFIG", + "timestampMs": " (e.g. 1745474677453)", + "organizationId": "", + "parameters": { + "feeReceiverWalletAddress": "", + "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: "", + parameters: { + feeReceiverWalletAddress: "", + feeBps: "50", + }, +}); +``` + +`upsert_swap_config` completes inline. The activity result echoes the applied configuration: + +```json +{ + "activity": { + "id": "", + "status": "ACTIVITY_STATUS_COMPLETED", + "type": "ACTIVITY_TYPE_UPSERT_SWAP_CONFIG", + "result": { + "upsertSwapConfigResult": { + "feeReceiverWalletAddress": "", + "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`. + + + Disabling swaps rejects all subsequent `get_swap_quote` and `execute_swap` requests until re-enabled. Wallets are unaffected — nothing on-chain changes. + + +## 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 \ No newline at end of file diff --git a/features/transaction-management/swap/execute-swap.mdx b/features/transaction-management/swap/execute-swap.mdx new file mode 100644 index 00000000..0b9db2b0 --- /dev/null +++ b/features/transaction-management/swap/execute-swap.mdx @@ -0,0 +1,183 @@ +--- +title: "Executing Swaps" +description: "Sign, broadcast, and settle a token swap from a Turnkey wallet. Handle quote refresh, ERC-20 approvals, and (optionally) gas sponsorship internally." +mode: "wide" +noindex: true +--- + +`ACTIVITY_TYPE_EXECUTE_SWAP` is the signing activity that runs a swap end-to-end. The caller signs a swap intent. Turnkey fetches a fresh executable quote from the provider, constructs the transaction (batching any required ERC-20 approvals via Permit2), signs it with the wallet in the intent, and broadcasts. Policy engines evaluate the derived transaction, so existing wallet policies apply to the resulting on-chain transfer. + +## Prerequisites + +- The parent organization has [configured swaps](/features/transaction-management/swap/enable-swap). Execute requests against orgs with no swap configuration set are rejected. +- The `walletAccount` in the intent holds enough of the input token to cover `inputAmount`. For non-sponsored swaps it also needs the origin chain's native token for gas. +- For cross-chain routes, the destination chain is supported by the provider. See [Supported providers, chains, and routes](/features/transaction-management/swap#supported-providers-chains-and-routes). + +## Submit execute\_swap + + + `ACTIVITY_TYPE_EXECUTE_SWAP` + + + + Timestamp (in milliseconds) of the request, used to verify liveness. + + + + Unique identifier of the sub-organization executing the swap. Fee configuration is loaded from the parent organization server-side. + + + + CAIP-19 identifier for the token being sold. Example: `eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913` (USDC on Base). The origin chain is derived from this identifier. + + + + CAIP-19 identifier for the token being bought. Example: `eip155:8453/erc20:0x4200000000000000000000000000000000000006` (WETH on Base). Same-chain if the CAIP-2 prefix matches `inputToken`. Otherwise a cross-chain route. + + + + Amount of the input token, in raw on-chain units. + + + + The wallet account address to sign with and swap from. Part of the user-signed intent. Authentication identifies the caller; it does not determine the signing wallet. + + + + The minimum output amount to accept, in raw on-chain units. This is the hard floor enforced at settlement — if the swap would return less, it fails. Typically set to the `minOutputAmount` returned by [`get_swap_quote`](/features/transaction-management/swap/get-swap-quote). + + + + Optional. Slippage tolerance in basis points, expressed as a stringified integer. Used when fetching the executable quote. `minOutputAmount` is the enforced floor at settlement _(To Confirm — exact interaction between `slippage` and `minOutputAmount`)_. + + + + Whether to sponsor the transaction's gas via Gas Station. Defaults to `false`. Requires a Pro plan or higher when set to `true` _(To Confirm: plan gating)_. + + + + Optional. The DEX aggregator provider. Omit in V1 — `0x` is the only supported provider and is used by default. + + +```bash title="cURL" +curl --request POST \ + --url https://api.turnkey.com/public/v1/submit/execute_swap \ + --header 'Accept: application/json' \ + --header 'Content-Type: application/json' \ + --header "X-Stamp: (see Stamps)" \ + --data '{ + "type": "ACTIVITY_TYPE_EXECUTE_SWAP", + "timestampMs": " (e.g. 1745474677453)", + "organizationId": "", + "parameters": { + "inputToken": "eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913", + "outputToken": "eip155:8453/erc20:0x4200000000000000000000000000000000000006", + "inputAmount": "1000000", + "walletAccount": "", + "minOutputAmount": "", + "slippage": "50", + "sponsor": false + } + }' +``` + +```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/execute_swap", { + type: "ACTIVITY_TYPE_EXECUTE_SWAP", + timestampMs: String(Date.now()), + organizationId: "", + parameters: { + inputToken: "eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913", + outputToken: "eip155:8453/erc20:0x4200000000000000000000000000000000000006", + inputAmount: "1000000", + walletAccount: "", + minOutputAmount: "", + slippage: "50", + sponsor: false, + }, +}); +``` + +The activity result returns a poll handle: + +```json +{ + "activity": { + "id": "", + "status": "ACTIVITY_STATUS_COMPLETED", + "type": "ACTIVITY_TYPE_EXECUTE_SWAP", + "result": { + "executeSwapResult": { + "sendTransactionStatusId": "", + "provider": "0x", + "quoteId": "" + } + } + } +} +``` + +`ExecuteSwapResult` returns `sendTransactionStatusId` (used to poll status), plus optional `provider` and `quoteId`. + +## Gas: sponsored vs. self-funded + +With `sponsor: true`, Gas Station pays the gas and the batch executes as an EIP-7702 sponsored transaction. The `walletAccount` needs no native token at all. This requires a Pro plan or higher. _(To Confirm.)_ + +With `sponsor: false`, the `walletAccount` pays gas itself. Fund it with the origin chain's native token before executing. + +## What the caller does not provide + +The user-signed intent covers only the swap parameters listed above. Turnkey generates or fetches the rest server-side and never accepts them from callers: + +- Provider quote JSON +- Transaction calldata +- Serialized transactions +- Permit2 payloads +- Approval payloads +- Fee receiver wallet address (loaded from parent org) +- Fee bps (loaded from parent org) + +This is a hard trust boundary. Requests that include provider payloads or fee overrides are rejected. + +## Approvals and Permit2 + +For ERC-20 swaps, Turnkey batches token approvals into `execute_swap` using Permit2. There is no separate approval activity for customers to run. Permit2 signatures are generated inside the activity and included in the transaction as needed. + +Native token swaps (e.g., ETH as `inputToken`) bypass Permit2 and require no approval step. + + + Some non-standard ERC-20 tokens may fail Permit2 approval flows. If a swap fails with a Permit2-related error, verify the token supports the standard Permit2 flow before retrying. + + +## Same-chain vs. cross-chain execution + +The execute call itself is identical for same-chain and cross-chain routes. The provider selects the route from the CAIP-19 pair. After broadcast, the lifecycle diverges: + +- **Same-chain swaps** complete on origin inclusion. Poll `get_send_transaction_status` with the returned handle. +- **Cross-chain swaps** continue past origin inclusion until the destination-chain leg settles or funds are recovered. Poll `get_swap_status` for the full lifecycle. + +See [Track swap status](/features/transaction-management/swap/track-swap-status) for the two lifecycle models. + +## Poll swap status (required) + + + `ACTIVITY_STATUS_COMPLETED` on the execute response means the activity was accepted and enqueued for broadcast. It does not mean the transaction landed on-chain. Poll the appropriate status endpoint until it reports a terminal state. + + +For same-chain, poll `get_send_transaction_status`. For cross-chain, poll `get_swap_status`. Full lifecycle semantics on [Track swap status](/features/transaction-management/swap/track-swap-status). + +## Next steps + +- [Track swap status](/features/transaction-management/swap/track-swap-status): same-chain vs. cross-chain polling. +- [End-to-end example](/features/transaction-management/swap/end-to-end-example): full flow from enable to confirmation. \ No newline at end of file diff --git a/features/transaction-management/swap/get-swap-quote.mdx b/features/transaction-management/swap/get-swap-quote.mdx new file mode 100644 index 00000000..822fd116 --- /dev/null +++ b/features/transaction-management/swap/get-swap-quote.mdx @@ -0,0 +1,132 @@ +--- +title: "Getting a Quote" +description: "Fetch a fee-aware indicative quote for a token pair. Displays expected output and the minimum output the caller should accept." +mode: "wide" +noindex: true +--- + +- The parent organization has [configured swaps](/features/transaction-management/swap/enable-swap) with a valid `feeReceiverWalletAddress` and `feeBps`. Quote requests against orgs with no swap configuration set are rejected. + +## Call get\_swap\_quote + +`get_swap_quote` is a **query**, not an activity — it returns synchronously and is not stamped into an activity envelope. Send the request fields at the top level (there is no `parameters` object, `type`, or `timestampMs`). + + + Unique identifier of the sub-organization requesting the quote. The parent organization's active `FEATURE_NAME_SWAP_CONFIG` is applied server-side. + + + + CAIP-19 identifier for the token being sold. Example: `eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913` (USDC on Base). The origin chain is derived from this identifier. + + + + CAIP-19 identifier for the token being bought. Example: `eip155:8453/erc20:0x4200000000000000000000000000000000000006` (WETH on Base). Same-chain if the CAIP-2 prefix matches `inputToken`. Otherwise a cross-chain route. + + + + Amount of the input token, in raw on-chain units. For example, `"1000000"` for 1 USDC at 6 decimals. + + + + The address of the wallet account that will perform the swap. Used to compute an accurate quote (balances and allowances) _(To Confirm — exact format: on-chain address vs. wallet-account identifier)_. + + + + Optional. Slippage tolerance in basis points, expressed as a stringified integer. For example, `"50"` = 0.5%. Applied to `minOutputAmount` computation _(To Confirm — server default when omitted)_. + + +```bash title="cURL" +curl --request POST \ + --url https://api.turnkey.com/public/v1/query/get_swap_quote \ + --header 'Accept: application/json' \ + --header 'Content-Type: application/json' \ + --header "X-Stamp: (see Stamps)" \ + --data '{ + "organizationId": "", + "inputToken": "eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913", + "outputToken": "eip155:8453/erc20:0x4200000000000000000000000000000000000006", + "inputAmount": "1000000", + "walletAccount": "", + "slippage": "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, + }), +); + +// Queries return the response body directly — there is no `activity` wrapper. +const { quotes } = await client.request("/public/v1/query/get_swap_quote", { + organizationId: "", + inputToken: "eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913", + outputToken: "eip155:8453/erc20:0x4200000000000000000000000000000000000006", + inputAmount: "1000000", + walletAccount: "", + slippage: "50", +}); + +const bestQuote = quotes[0]; +``` + +Response: + +```json +{ + "inputToken": "eip155:8453/erc20:0x833589fCD6EDB6E08f4c7C32D4f71b54bdA02913", + "outputToken": "eip155:8453/erc20:0x4200000000000000000000000000000000000006", + "inputAmount": "1000000", + "quotes": [ + { + "quoteId": "", + "provider": "0x", + "outputAmount": "", + "minOutputAmount": "" + } + ] +} +``` + +### Response fields + +The top-level `inputToken`, `outputToken`, and `inputAmount` are echoed from the request. Quotes are returned in a `quotes` array — in V1 this contains a single option; multiple options are on the roadmap. + +Each entry in `quotes` contains: + +- `quoteId`: opaque identifier for the quote. Informational only — `execute_swap` does not accept a `quoteId`, so it cannot be replayed. Useful for logging and correlation. +- `provider`: the DEX aggregator that produced this quote (`0x` in V1). +- `outputAmount`: expected output amount, fee-adjusted, in raw on-chain units. +- `minOutputAmount`: minimum output the caller should accept at execute time, given the requested slippage. Derived from `outputAmount` and the caller's slippage tolerance. + +## Quotes are indicative + +Quote outputs may differ from execution outputs. When you call `execute_swap`, Turnkey fetches fresh executable transaction data from the provider (not the quote's payload) and settles at market rates within the caller's slippage tolerance. Two implications: + +- Quote `outputAmount` is a hint. Actual receive amount at execute time can be higher or lower, bounded on the low side by the slippage-derived floor. +- Quotes are not persisted. Turnkey does not maintain a mapping from `quoteId` to executable data. The `quoteId` is opaque and cannot be replayed. + +If you display quote amounts to end users, refresh the quote near the moment of execution to keep the displayed value close to the settled value. + +## How fees are applied + +Each quote's `outputAmount` already reflects your organization's `feeBps` from `FEATURE_NAME_SWAP_CONFIG`. There is no separate fee amount to add or subtract on the client. The end user's displayed receive amount is a quote's `outputAmount` (or the actual settled amount at execute time). + +Fee configuration is snapshotted server-side per activity. If your fee configuration changes between quote and execute, the execute settles at the fee configuration active when it runs, not when the quote was fetched. + +## Same-chain vs. cross-chain quotes + +Quote shape is identical for same-chain and cross-chain routes. The provider selects the route based on the `inputToken` and `outputToken` CAIP-19 identifiers. + +Cross-chain quotes are subject to the same freshness and slippage semantics as same-chain quotes, but cross-chain execution has a longer post-broadcast lifecycle. See [Track swap status](/features/transaction-management/swap/track-swap-status) for the two lifecycle models. + +## Next steps + +- [Execute a swap](/features/transaction-management/swap/execute-swap): the signing activity that runs the swap end-to-end. +- [Track swap status](/features/transaction-management/swap/track-swap-status): same-chain vs. cross-chain polling. \ No newline at end of file diff --git a/features/transaction-management/swap/track-swap-status.mdx b/features/transaction-management/swap/track-swap-status.mdx new file mode 100644 index 00000000..b65ac573 --- /dev/null +++ b/features/transaction-management/swap/track-swap-status.mdx @@ -0,0 +1,128 @@ +--- +title: "Track Swap Status" +description: "Poll swap lifecycle from broadcast to settlement using get_swap_status. Same-chain and cross-chain operations normalize to a single PENDING/COMPLETED/FAILED model." +mode: "wide" +noindex: true +--- + +`execute_swap` returns a poll handle. Query `get_swap_status` with that handle to observe the full lifecycle. Same-chain and cross-chain swaps share the same endpoint and the same response shape. The endpoint normalizes provider-specific state machines to three states: `PENDING`, `COMPLETED`, `FAILED`. + +## Lifecycle model + +Turnkey normalizes swap lifecycle to three states across same-chain and cross-chain routes: + +- **PENDING**: still in flight. Broadcast pending, origin transaction not yet included, or (cross-chain) destination-side execution not yet settled. +- **COMPLETED**: the user received the destination asset. Terminal, happy path. +- **FAILED**: the swap will not fill as intended. Reported only when Turnkey knows what the user holds. Terminal. + +Intermediate provider states (`bridge_pending`, `delayed`, `submitted`, and so on) collapse into `PENDING` on purpose. Every state maps to exactly one customer action: `PENDING` means wait, `COMPLETED` means you got the output, `FAILED` means look at what you hold and consider re-swapping. + +## Query get\_swap\_status + + + Unique identifier of the sub-organization that executed the swap. + + + + The poll handle returned from `execute_swap`. + + +```bash title="cURL" +curl --request POST \ + --url https://api.turnkey.com/public/v1/query/get_swap_status \ + --header 'Accept: application/json' \ + --header 'Content-Type: application/json' \ + --header "X-Stamp: (see Stamps)" \ + --data '{ + "organizationId": "", + "sendTransactionStatusId": "" + }' +``` + +```javascript title="JavaScript" +const status = await client.request("/public/v1/query/get_swap_status", { + organizationId: "", + sendTransactionStatusId: "", +}); +``` + +Response: + +```json +{ + "status": "PENDING | COMPLETED | FAILED", + "swapKind": "SAME_CHAIN | CROSS_CHAIN", + "provider": "", + "inputToken": "", + "outputToken": "", + "inputAmount": "", + "originTxHash": "", + "destinationTxHash": "", + "outputAmount": "", + "settledAsset": "", + "settledAmount": "", + "settlementTxHash": "", + "providerStatus": "", + "updatedAt": "" +} +``` + +### Response fields + +- `status`: normalized state. `PENDING`, `COMPLETED`, or `FAILED`. +- `swapKind`: `SAME_CHAIN` or `CROSS_CHAIN`. Set at execute time; does not change. +- `provider`: the provider that executed the swap. Present when known. +- `inputToken`, `outputToken`, `inputAmount`: echoed from the execute intent. +- `originTxHash`: origin-chain transaction hash. Present once the swap is broadcast, for both same-chain and cross-chain routes. +- `destinationTxHash`: destination-chain transaction on `COMPLETED`. Cross-chain only. +- `outputAmount`: actual amount received, on `COMPLETED`. May lag the `status` transition by seconds while the monitor fetches the final settled amount. +- `settledAsset`, `settledAmount`, `settlementTxHash`: on `FAILED`. See [What FAILED means](#what-failed-means). +- `providerStatus`: raw provider status passthrough for UI use. Cross-chain only. Not normative. +- `updatedAt`: last observed state change, as a Unix timestamp in milliseconds (stringified). + +## Same-chain vs. cross-chain differences + +The response shape is identical. Only two behaviors differ in practice: + +- **Timing.** Same-chain reaches a terminal state within one block time. Cross-chain can take tens of seconds to several minutes depending on the route and bridge. +- **Field population.** `destinationTxHash` and `providerStatus` are cross-chain only. On same-chain `FAILED`, the settled triple derives directly from the origin transaction: `settledAsset = inputToken`, `settledAmount = inputAmount`, `settlementTxHash = ` (nothing moved on-chain in a swap sense). + +Use `swapKind` in the response to branch client-side handling only when it matters. + +## What FAILED means + +`FAILED` is reported only when Turnkey knows what the user holds. A bridge failure with a refund still in flight stays `PENDING` until the refund lands (or is confirmed impossible). + +On `FAILED`, three fields describe the outcome: + +- `settledAsset` (CAIP-19): the asset the user ended up with. +- `settledAmount`: raw on-chain amount of that asset. +- `settlementTxHash`: the on-chain transaction that settled the funds. + +Provider-specific recovery scenarios normalize to this triple: + +- **Provider refunded on the origin chain**: `settledAsset` is the origin-chain refund token. It may differ from the original input token if the route included an origin-side swap. +- **Funds never left the user** (e.g., origin transaction reverted): `settledAsset` equals `inputToken`, `settledAmount` equals `inputAmount`, `settlementTxHash` is the origin transaction hash. +- **Nothing was recoverable**: `settledAsset` is `null`. Internal alerts fire on this state. + +If `settledAsset` is non-null, you can call [`execute_swap`](/features/transaction-management/swap/execute-swap) again with the settled asset as the new input to re-attempt. Caveats worth surfacing to end users: + +- The refunded amount is reduced by refund gas and any origin-side swap losses. +- A re-swap pays its own fees and slippage. +- If the refund landed on the origin chain and the original destination was on a different chain, the re-swap is again cross-chain. + + + `FAILED` with `settledAsset: null` means Turnkey could not recover funds through the provider's automated flows. Contact support if you encounter this state. + + +## Polling cadence + +- **Same-chain**: poll every ~1 second while `PENDING`. Typically settles within one block time. +- **Cross-chain**: poll every 5 to 10 seconds while `PENDING`. Settlement can take from tens of seconds to several minutes depending on route. + +Websocket and event-driven updates are on the roadmap and will replace polling for cross-chain lifecycles. + +## Next steps + +- [End-to-end example](/features/transaction-management/swap/end-to-end-example): full flow from enable to confirmation. +- [Execute a swap](/features/transaction-management/swap/execute-swap): request semantics that generate the poll handle. \ No newline at end of file diff --git a/snippets/shared/swaps-beta-note.mdx b/snippets/shared/swaps-beta-note.mdx new file mode 100644 index 00000000..c82c338a --- /dev/null +++ b/snippets/shared/swaps-beta-note.mdx @@ -0,0 +1,7 @@ +--- +title: "swaps-beta-note.mdx" +--- + + + Swaps are in early access. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization. + \ No newline at end of file diff --git a/welcome.mdx b/welcome.mdx index fbe6e776..af8d8010 100644 --- a/welcome.mdx +++ b/welcome.mdx @@ -5,455 +5,458 @@ mode: "custom" ---
+ {/* ── HERO ─────────────────────────────────────────────────────────────── */} -{/* ── HERO ─────────────────────────────────────────────────────────────── */} +
+
+ -
-
- - -
-
-

- Secure, flexible, -
- and scalable wallet infrastructure -

-

- Turnkey is infrastructure for generating wallets and keys, signing transactions, and - controlling who can use them, when, and how. Private keys are secured in hardware-isolated - enclaves and never exposed — not even to Turnkey. -

-
{ - if (typeof window !== "undefined") { - document.dispatchEvent( - new KeyboardEvent("keydown", { key: "k", metaKey: true, bubbles: true }), - ); - } - }} - > - - - - Search... - ⌘K +
-
-
- Turnkey hero illustration - Turnkey hero illustration -
-
-
+
+

+ Secure, flexible, -{/* ── BUILD WITH TURNKEY ───────────────────────────────────────────────── */} +
-
+ and scalable wallet infrastructure +

-
-
-

Build with Turnkey

-

- Build at whichever level best meets your needs. Start with Solutions for common patterns, go - deeper with our SDKs for more control, or reach all the way down to the API for complete - flexibility. The full stack is always available. -

-
- - -
-
-
- Solutions layer - Solutions layer - SDK layer - SDK layer - API layer - API layer + + + + + Search... + ⌘K +
-
-
- -
+
+
+
+ Solutions layer -
+ Solutions layer -{/* ── SOLUTIONS ────────────────────────────────────────────────────────── */} + SDK layer -
-

Explore Turnkey Solutions

+ SDK layer -
+ API layer -
-
- Embedded Wallets -
-
- - Embedded Wallets - -

- Build wallets directly into your app. Users authenticate with email, passkeys, or social login. - You control the experience. -

-
- -
+ API layer +
+
-
-
- Company Wallets -
-
- - Company Wallets - -

- Automate your onchain operations with wallets, keys and programmable controls purpose built for - scale. -

-
- -
+
+ + +
-
+
+ { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'a'); el?.setAttribute('data-user-picked', 'true'); }} onFocus={(e) => { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'a'); el?.setAttribute('data-user-picked', 'true'); }} onTouchStart={(e) => { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'a'); el?.setAttribute('data-user-picked', 'true'); }}> + -
+ Solutions + Pre-built patterns for common use cases -{/* ── FEATURES ─────────────────────────────────────────────────────────── */} + +
-
-

Explore our features

+ { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'b'); el?.setAttribute('data-user-picked', 'true'); }} onFocus={(e) => { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'b'); el?.setAttribute('data-user-picked', 'true'); }} onTouchStart={(e) => { const el = e.currentTarget.closest('.tk-build'); el?.setAttribute('data-selected', 'b'); el?.setAttribute('data-user-picked', 'true'); }}> + - +
-
-

Control access

-
-
- - - - - - - Sessions - -
- - - - - - - Delegated access - -
- - - - - - - Policy engine - +
+ + {/* ── SOLUTIONS ────────────────────────────────────────────────────────── */} + +
+

Explore Turnkey Solutions

+ +
+
+
+ Embedded Wallets +
+ +
+ + Embedded Wallets + + +

+ Build wallets directly into your app. Users authenticate with email, passkeys, or social login. + You control the experience. +

+
+ +
-
-
-

Transact programmatically

-
-
- - - - - - - Transaction management - -
- - - - Gas Sponsorship - -
- - - - - - - Balances - -
- - - - - - - Fiat onramp - +
+
+ Company Wallets +
+ +
+ + Company Wallets + + +

+ Automate your onchain operations with wallets, keys and programmable controls purpose built for + scale. +

+
+ +
-
+
+
+ Key Management +
+ +
+ + Key Management + + +

+ Enterprise-grade security for your most sensitive keys — hardware-backed with programmable + access controls. +

+
+ + +
+
-
- -{/* ── WHITEPAPER CTA ───────────────────────────────────────────────────── */} - -
- -
-
-
-
- Turnkey Whitepaper + +
+ + {/* ── FEATURES ─────────────────────────────────────────────────────────── */} + +
+

Explore our features

+ +
+ -
-

- Read our whitepaper for an in-depth look at Turnkey's security model and verifiable key - management infrastructure. -

- - - Read the Turnkey Whitepaper - + + + +
-
-
+ {/* ── WHITEPAPER CTA ───────────────────────────────────────────────────── */} + +
+ +
+
+
+
+ Turnkey Whitepaper +
+ +
+

+ Read our whitepaper for an in-depth look at Turnkey's security model and verifiable key + management infrastructure. +

+ + + + + Read the Turnkey Whitepaper + +
+
+
+
+
\ No newline at end of file