Skip to content

Commit 9f3661e

Browse files
authored
Merge branch 'main' into jlin/add-multi-fee-guide
2 parents b6e0140 + 44391e8 commit 9f3661e

2 files changed

Lines changed: 297 additions & 3 deletions

File tree

fern/docs.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,10 @@ navigation:
138138
- section: Additional Topics
139139
slug: additional-topics
140140
contents:
141-
- page: How to Set Token Allowances
141+
- page: Set Token Allowances
142142
slug: how-to-set-your-token-allowances
143143
path: docs/pages/0x-swap-api/additional-topics/how-to-set-your-token-allowances.mdx
144-
- page: Handling Native Tokens
144+
- page: Handle Native Tokens
145145
slug: handling-native-tokens
146146
path: docs/pages/0x-swap-api/additional-topics/handling-native-tokens.mdx
147147
- page: 0x Parser
@@ -154,8 +154,11 @@ navigation:
154154
slug: buy-sell-tax-support
155155
path: docs/pages/0x-swap-api/additional-topics/buy-sell-tax-support.mdx
156156
- page: Multi-Fee Support
157-
slug: multi-Fee-support
157+
slug: multi-fee-support
158158
path: docs/pages/0x-swap-api/additional-topics/multi-fee-support.mdx
159+
- page: Swap and Send
160+
slug: swap-and-send
161+
path: docs/pages/0x-swap-api/additional-topics/swap-and-send.mdx
159162
- page: About the RFQ System
160163
slug: about-the-rfq-system
161164
path: docs/pages/0x-swap-api/additional-topics/about-the-rfq-system.mdx
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
title: "Swap and Send"
3+
description: "Send swapped tokens directly to any address using the recipient parameter."
4+
---
5+
6+
By default, swapped tokens are returned to the address that initiates the trade (the `taker`). With the `recipient` parameter, you can send the output tokens directly to any wallet address — in a single transaction.
7+
8+
This unlocks use cases like:
9+
10+
- Swapping and gifting tokens to another wallet
11+
- Purchasing tokens on behalf of a user
12+
- Automating treasury distributions in a different token
13+
- Powering "pay with any token" checkout flows
14+
15+
<Info>
16+
The `recipient` parameter is supported on both the **Swap API** and **Gasless
17+
API**.
18+
</Info>
19+
20+
---
21+
22+
## How it works
23+
24+
When you include `recipient` in your request, the `buyToken` is delivered to that address at settlement. The taker (the address signing and submitting the transaction) still pays and authorizes the swap — they just don't receive the output.
25+
26+
```
27+
Taker ──pays sellToken──► 0x Settler / AllowanceHolder ──sends buyToken──► Recipient
28+
```
29+
30+
If `recipient` is omitted, it defaults to the taker's address, so existing integrations are unaffected.
31+
32+
---
33+
34+
## API Reference
35+
36+
### `recipient`
37+
38+
| Property | Value |
39+
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40+
| Type | `string` |
41+
| Required | No |
42+
| Format | `^0x[a-fA-F0-9]{40}$` |
43+
| Default | Taker address |
44+
| Supported | Swap API ([price](https://docs.0x.org/api-reference/openapi-json/swap/allowanceholder-getprice), [quote](https://docs.0x.org/api-reference/openapi-json/swap/allowanceholder-getquote)), Gasless API ([price](https://docs.0x.org/api-reference/openapi-json/gasless/getprice#request.query), [quote](https://docs.0x.org/api-reference/openapi-json/gasless/getquote#request.query)) |
45+
46+
<Warning>
47+
`recipient` is **not supported** for wrap and unwrap operations (e.g. ETH ↔
48+
WETH). Passing it for those trade types will result in an error. Check for
49+
native/wrapped token pairs and omit `recipient` before calling the API.
50+
</Warning>
51+
52+
---
53+
54+
## Swap API Example
55+
56+
Include `recipient` as a query parameter when fetching a quote and submitting the swap.
57+
58+
<Tabs>
59+
<Tab title="TypeScript">
60+
61+
```typescript
62+
const params = new URLSearchParams({
63+
sellToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH
64+
buyToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
65+
sellAmount: "1000000000000000000", // 1 ETH in wei
66+
taker: "0xYourTakerAddress",
67+
recipient: "0xRecipientAddress", // buyToken sent here
68+
});
69+
70+
const response = await fetch(
71+
`https://api.0x.org/swap/permit2/quote?${params}`,
72+
{
73+
headers: {
74+
"0x-api-key": process.env.ZEROX_API_KEY!,
75+
"0x-version": "v2",
76+
},
77+
},
78+
);
79+
80+
const quote = await response.json();
81+
```
82+
83+
</Tab>
84+
<Tab title="Python">
85+
86+
```python
87+
import requests
88+
import os
89+
90+
params = {
91+
"sellToken": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # ETH
92+
"buyToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC
93+
"sellAmount": "1000000000000000000", # 1 ETH in wei
94+
"taker": "0xYourTakerAddress",
95+
"recipient": "0xRecipientAddress", # buyToken sent here
96+
}
97+
98+
response = requests.get(
99+
"https://api.0x.org/swap/permit2/quote",
100+
headers={
101+
"0x-api-key": os.environ["ZEROX_API_KEY"],
102+
"0x-version": "v2",
103+
},
104+
params=params,
105+
)
106+
107+
quote = response.json()
108+
109+
```
110+
111+
</Tab>
112+
<Tab title="cURL">
113+
```bash
114+
curl "https://api.0x.org/swap/permit2/quote?\
115+
sellToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&\
116+
buyToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
117+
sellAmount=1000000000000000000&\
118+
taker=0xYourTakerAddress&\
119+
recipient=0xRecipientAddress" \
120+
-H "0x-api-key: $ZEROX_API_KEY" \
121+
-H "0x-version: v2"
122+
```
123+
</Tab>
124+
</Tabs>
125+
126+
---
127+
128+
## Gasless API Example
129+
130+
The `recipient` parameter works the same way with the Gasless API. Include it in the quote request body — the relayer will deliver `buyToken` to the recipient address on settlement.
131+
132+
<Tabs>
133+
<Tab title="TypeScript">
134+
135+
```typescript
136+
const params = new URLSearchParams({
137+
sellToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
138+
buyToken: "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI
139+
sellAmount: "1000000000", // 1000 USDC (6 decimals)
140+
taker: "0xYourTakerAddress",
141+
recipient: "0xRecipientAddress", // buyToken sent here
142+
});
143+
144+
const response = await fetch(`https://api.0x.org/gasless/quote?${params}`, {
145+
headers: {
146+
"0x-api-key": process.env.ZEROX_API_KEY!,
147+
"0x-version": "v2",
148+
},
149+
});
150+
151+
const quote = await response.json();
152+
```
153+
154+
</Tab>
155+
<Tab title="Python">
156+
```python
157+
import requests
158+
import os
159+
160+
params = {
161+
"sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC
162+
"buyToken": "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI
163+
"sellAmount": "1000000000", # 1000 USDC (6 decimals)
164+
"taker": "0xYourTakerAddress",
165+
"recipient": "0xRecipientAddress", # buyToken sent here
166+
}
167+
168+
response = requests.get(
169+
"https://api.0x.org/gasless/quote",
170+
headers={
171+
"0x-api-key": os.environ["ZEROX_API_KEY"],
172+
"0x-version": "v2",
173+
},
174+
params=params,
175+
)
176+
177+
quote = response.json()
178+
179+
````
180+
181+
</Tab>
182+
<Tab title="cURL">
183+
```bash
184+
curl "https://api.0x.org/gasless/quote?\
185+
sellToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
186+
buyToken=0x6B175474E89094C44Da98b954EedeAC495271d0F&\
187+
sellAmount=1000000000&\
188+
taker=0xYourTakerAddress&\
189+
recipient=0xRecipientAddress" \
190+
-H "0x-api-key: $ZEROX_API_KEY" \
191+
-H "0x-version: v2"
192+
```
193+
</Tab>
194+
</Tabs>
195+
196+
---
197+
198+
## Errors
199+
200+
The API validates recipient at the price and quote stage — before any transaction is submitted — so no gas is wasted. There are two common error cases: an [invalid recipient address](/docs/0x-swap-api/additional-topics/swap-and-send#invalid-recipient-address) and passing recipient on a [wrap/unwrap operation](/docs/0x-swap-api/additional-topics/swap-and-send#wrapunwrap-operations).
201+
202+
### Invalid recipient address
203+
204+
The `recipient` value must be a valid Ethereum address — a 42-character hex string starting with `0x`. This error is triggered by things like a truncated address, a non-hex character, or passing a raw ENS name instead of a resolved address.
205+
206+
<Callout type="info">
207+
Always resolve ENS names to addresses client-side before passing them to the API. The `recipient` field does not support ENS.
208+
</Callout>
209+
210+
```json
211+
{
212+
"name": "INPUT_INVALID",
213+
"message": "The input is invalid",
214+
"data": {
215+
"zid": "0x8843f11733fbf965f90ef10a",
216+
"details": [
217+
{
218+
"field": "recipient",
219+
"reason": "Invalid ethereum address"
220+
}
221+
]
222+
}
223+
}
224+
```
225+
226+
To catch this before hitting the API, validate the address client-side:
227+
228+
```typescript
229+
import { isAddress, getAddress } from "viem";
230+
231+
const raw = userInputAddress;
232+
233+
if (!isAddress(raw)) {
234+
throw new Error("Invalid recipient address");
235+
}
236+
237+
const recipient = getAddress(raw); // normalizes to EIP-55 checksum
238+
```
239+
240+
### Wrap/unwrap operations
241+
242+
`recipient` is not supported when `sellToken` and `buyToken` are a native/wrapped pair (e.g. ETH ↔ WETH, MATIC ↔ WMATIC). These operations don't route through the swap protocol, so the `recipient` field has no effect and is rejected.
243+
244+
```json
245+
{
246+
"name": "RECIPIENT_NOT_SUPPORTED",
247+
"message": "The recipient parameter is not supported for wrap/unwrap operations",
248+
"data": {
249+
"zid": "0x05e4e8147d97597183b471ca"
250+
}
251+
}
252+
```
253+
254+
Guard against this by detecting wrap/unwrap pairs before building your request and omitting `recipient` in those cases:
255+
256+
```typescript
257+
const WRAP_UNWRAP_PAIRS: [string, string][] = [
258+
["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], // ETH ↔ WETH
259+
// add other pairs as needed
260+
];
261+
262+
const isWrapUnwrap = (sellToken: string, buyToken: string) =>
263+
WRAP_UNWRAP_PAIRS.some(
264+
([a, b]) =>
265+
(sellToken.toLowerCase() === a.toLowerCase() && buyToken.toLowerCase() === b.toLowerCase()) ||
266+
(sellToken.toLowerCase() === b.toLowerCase() && buyToken.toLowerCase() === a.toLowerCase())
267+
);
268+
269+
const params = {
270+
sellToken,
271+
buyToken,
272+
sellAmount,
273+
taker,
274+
...(!isWrapUnwrap(sellToken, buyToken) && { recipient }),
275+
};
276+
```
277+
278+
The `zid` in the error response is a trace ID — include it when contacting support to help diagnose the issue.
279+
280+
---
281+
282+
283+
284+
## Key considerations
285+
286+
- **Taker still pays.** The `taker` signs and funds the transaction. Only the output destination changes.
287+
- **Omitting `recipient` is safe**, it falls back to the taker address, so no changes are needed for existing flows.
288+
- **Validate before sending.** Always validate `recipient` client-side to avoid wasted gas on failed transactions.
289+
- **Wrap/unwrap is not supported.** Check for native/wrapped token pairs and strip `recipient` before calling the API.
290+
291+
````

0 commit comments

Comments
 (0)