@switch-win/sdk 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -80
- package/ROBINHOOD.md +629 -597
- package/package.json +1 -1
- package/src/index.ts +26 -26
- package/src/limit-orders.ts +60 -30
- package/src/networks/index.ts +20 -20
- package/src/networks/robinhood.ts +327 -315
|
@@ -1,321 +1,333 @@
|
|
|
1
|
-
import { API_BASE } from "../constants.js";
|
|
2
|
-
|
|
3
|
-
/** API network value used by Switch endpoints. */
|
|
4
|
-
export const ROBINHOOD_NETWORK = "robinhood" as const;
|
|
5
|
-
|
|
6
|
-
/** Robinhood Chain mainnet metadata. */
|
|
7
|
-
export const ROBINHOOD_CHAIN = {
|
|
8
|
-
id: 4663,
|
|
9
|
-
name: "Robinhood Chain",
|
|
10
|
-
nativeCurrency: {
|
|
11
|
-
name: "Ether",
|
|
12
|
-
symbol: "ETH",
|
|
13
|
-
decimals: 18,
|
|
14
|
-
},
|
|
15
|
-
rpcUrls: ["https://rpc.mainnet.chain.robinhood.com"],
|
|
16
|
-
blockExplorerUrl: "https://robinhoodchain.blockscout.com",
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
|
-
/** Native-ETH sentinel accepted by the Switch quote API. */
|
|
20
|
-
export const ROBINHOOD_NATIVE_ETH =
|
|
21
|
-
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
22
|
-
|
|
23
|
-
/** Switch contracts deployed on Robinhood Chain. */
|
|
1
|
+
import { API_BASE } from "../constants.js";
|
|
2
|
+
|
|
3
|
+
/** API network value used by Switch endpoints. */
|
|
4
|
+
export const ROBINHOOD_NETWORK = "robinhood" as const;
|
|
5
|
+
|
|
6
|
+
/** Robinhood Chain mainnet metadata. */
|
|
7
|
+
export const ROBINHOOD_CHAIN = {
|
|
8
|
+
id: 4663,
|
|
9
|
+
name: "Robinhood Chain",
|
|
10
|
+
nativeCurrency: {
|
|
11
|
+
name: "Ether",
|
|
12
|
+
symbol: "ETH",
|
|
13
|
+
decimals: 18,
|
|
14
|
+
},
|
|
15
|
+
rpcUrls: ["https://rpc.mainnet.chain.robinhood.com"],
|
|
16
|
+
blockExplorerUrl: "https://robinhoodchain.blockscout.com",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/** Native-ETH sentinel accepted by the Switch quote API. */
|
|
20
|
+
export const ROBINHOOD_NATIVE_ETH =
|
|
21
|
+
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
22
|
+
|
|
23
|
+
/** Switch contracts deployed on Robinhood Chain. */
|
|
24
24
|
export const ROBINHOOD_SWITCH_CONTRACTS = {
|
|
25
|
-
/** ERC-20 approval target. Use quote.tx.to when submitting a swap. */
|
|
26
|
-
router: "0x8730C3e2cF2c8CDa8E6166837A1Ed26f46aa9E59",
|
|
27
|
-
routerView: "0xFF6b56d3F444eB5b7FA1db047F57140C84810376",
|
|
28
|
-
uniswapV2Adapter: "0x7a14d7A8509a66209D4332843b983b29bF5604A4",
|
|
29
|
-
uniswapV3Adapter: "0xbcA08f296d9Ba0dc19Aa0E05D355365cE29A3205",
|
|
25
|
+
/** ERC-20 approval target. Use quote.tx.to when submitting a swap. */
|
|
26
|
+
router: "0x8730C3e2cF2c8CDa8E6166837A1Ed26f46aa9E59",
|
|
27
|
+
routerView: "0xFF6b56d3F444eB5b7FA1db047F57140C84810376",
|
|
28
|
+
uniswapV2Adapter: "0x7a14d7A8509a66209D4332843b983b29bF5604A4",
|
|
29
|
+
uniswapV3Adapter: "0xbcA08f296d9Ba0dc19Aa0E05D355365cE29A3205",
|
|
30
30
|
uniswapV4Adapter: "0x754dDCD05aFbAd1cc7Bc42B9268EB586F579E7F6",
|
|
31
|
+
limitOrder: "0x752c50DDd3B426cAE3D7A995F313Ac74ac6B0230",
|
|
32
|
+
nativeEthFlow: "0x029FfC6aF9112eA078f1D6f4a98826DDB2136cf6",
|
|
33
|
+
limitOrderAdapter: "0x412F625072c10e58C619D1e0b3C95cd3d5689871",
|
|
34
|
+
limitOrderAdapterIndex: 3,
|
|
31
35
|
} as const;
|
|
32
36
|
|
|
33
|
-
/**
|
|
34
|
-
export const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/** Canonical Uniswap V4 singleton and periphery deployments. */
|
|
40
|
-
v4PoolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
|
|
41
|
-
v4PositionDescriptor: "0x9639443158e8c5efa35bd45287bf2effd3d8dc06",
|
|
42
|
-
v4PositionManager: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
|
|
43
|
-
v4Quoter: "0x8dc178efb8111bb0973dd9d722ebeff267c98f94",
|
|
44
|
-
v4StateView: "0xf3334192d15450cdd385c8b70e03f9a6bd9e673b",
|
|
45
|
-
v4UniversalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
|
|
46
|
-
permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
|
|
37
|
+
/** EIP-712 domain used for Robinhood ERC-20 limit-order signatures. */
|
|
38
|
+
export const ROBINHOOD_LIMIT_ORDER_EIP712_DOMAIN = {
|
|
39
|
+
name: "SwitchLimitOrder",
|
|
40
|
+
version: "2",
|
|
41
|
+
chainId: ROBINHOOD_CHAIN.id,
|
|
42
|
+
verifyingContract: ROBINHOOD_SWITCH_CONTRACTS.limitOrder,
|
|
47
43
|
} as const;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
44
|
+
|
|
45
|
+
/** Uniswap contracts used by the Robinhood Switch deployment. */
|
|
46
|
+
export const ROBINHOOD_UNISWAP_CONTRACTS = {
|
|
47
|
+
v2Factory: "0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f",
|
|
48
|
+
v3Factory: "0x1f7d7550B1b028f7571E69A784071F0205FD2EfA",
|
|
49
|
+
v3QuoterV2: "0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7",
|
|
50
|
+
v3SwapRouter02: "0xCaf681a66D020601342297493863E78C959E5cb2",
|
|
51
|
+
/** Canonical Uniswap V4 singleton and periphery deployments. */
|
|
52
|
+
v4PoolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
|
|
53
|
+
v4PositionDescriptor: "0x9639443158e8c5efa35bd45287bf2effd3d8dc06",
|
|
54
|
+
v4PositionManager: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
|
|
55
|
+
v4Quoter: "0x8dc178efb8111bb0973dd9d722ebeff267c98f94",
|
|
56
|
+
v4StateView: "0xf3334192d15450cdd385c8b70e03f9a6bd9e673b",
|
|
57
|
+
v4UniversalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
|
|
58
|
+
permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export interface RobinhoodToken {
|
|
62
|
+
readonly address: string;
|
|
63
|
+
readonly symbol: string;
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly decimals: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Tokens in the Switch Robinhood frontend's curated list.
|
|
70
|
+
*
|
|
71
|
+
* Community-token addresses must never be selected by symbol alone; clones
|
|
72
|
+
* can share the same symbol. The addresses, symbols, and decimals below were
|
|
73
|
+
* checked against Robinhood Chain on 2026-07-11.
|
|
74
|
+
*/
|
|
75
|
+
export const ROBINHOOD_TOKENS = {
|
|
76
|
+
WETH: {
|
|
77
|
+
address: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
|
|
78
|
+
symbol: "WETH",
|
|
79
|
+
name: "Wrapped Ether",
|
|
80
|
+
decimals: 18,
|
|
81
|
+
},
|
|
82
|
+
USDG: {
|
|
83
|
+
address: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
84
|
+
symbol: "USDG",
|
|
85
|
+
name: "Global Dollar",
|
|
86
|
+
decimals: 6,
|
|
87
|
+
},
|
|
88
|
+
VIRTUAL: {
|
|
89
|
+
address: "0xc6911796042b15d7Fa4F6CDe69e245DdCd3d9c31",
|
|
90
|
+
symbol: "VIRTUAL",
|
|
91
|
+
name: "Virtuals Protocol",
|
|
92
|
+
decimals: 18,
|
|
93
|
+
},
|
|
94
|
+
CASHCAT: {
|
|
95
|
+
address: "0x020bfC650A365f8BB26819deAAbF3E21291018b4",
|
|
96
|
+
symbol: "CASHCAT",
|
|
97
|
+
name: "Cash Cat",
|
|
98
|
+
decimals: 18,
|
|
99
|
+
},
|
|
100
|
+
WALLET: {
|
|
101
|
+
address: "0x0339f5459FC690aC85F1782e15782A151b4A9E1b",
|
|
102
|
+
symbol: "WALLET",
|
|
103
|
+
name: "Robinhood Wallet",
|
|
104
|
+
decimals: 18,
|
|
105
|
+
},
|
|
94
106
|
SEEDCOIN: {
|
|
95
|
-
address: "
|
|
96
|
-
symbol: "
|
|
97
|
-
name: "
|
|
98
|
-
decimals: 18,
|
|
99
|
-
},
|
|
100
|
-
JUGGERNAUT: {
|
|
101
|
-
address: "0xD7321801CAae694090694Ff55A9323139F043B88",
|
|
102
|
-
symbol: "JUGGERNAUT",
|
|
103
|
-
name: "The Juggernaut",
|
|
104
|
-
decimals: 18,
|
|
105
|
-
},
|
|
106
|
-
HOODRAT: {
|
|
107
|
-
address: "0x8e62F281f282686fCa6dCB39288069a93fC23F1c",
|
|
108
|
-
symbol: "HOODRAT",
|
|
109
|
-
name: "Hoodrat",
|
|
110
|
-
decimals: 18,
|
|
111
|
-
},
|
|
112
|
-
DIH: {
|
|
113
|
-
address: "0x17bb0C898254406b1Ea2e8E99B0C263e26c9E4a4",
|
|
114
|
-
symbol: "DIH",
|
|
115
|
-
name: "Dog In Hood",
|
|
116
|
-
decimals: 18,
|
|
117
|
-
},
|
|
118
|
-
KITSU: {
|
|
119
|
-
address: "0x8d4dFaaA4198b6486E0293Fec914C2B6a821D4DC",
|
|
120
|
-
symbol: "KITSU",
|
|
121
|
-
name: "KITSU",
|
|
122
|
-
decimals: 18,
|
|
123
|
-
},
|
|
124
|
-
WEN: {
|
|
125
|
-
address: "0xA80eb66b3E0CF66ccB46f8b8C9e7ff5803eEb820",
|
|
126
|
-
symbol: "WEN",
|
|
127
|
-
name: "Wen Lambo",
|
|
128
|
-
decimals: 18,
|
|
129
|
-
},
|
|
130
|
-
REPE: {
|
|
131
|
-
address: "0x5266eeafF092D6136AB63D18B975A60a0Cc0C8f7",
|
|
132
|
-
symbol: "REPE",
|
|
133
|
-
name: "Robinhood Pepe",
|
|
134
|
-
decimals: 18,
|
|
135
|
-
},
|
|
136
|
-
TENDIES: {
|
|
137
|
-
address: "0x45242320DBB855EeA8Fd36804C6487E10E97FCF9",
|
|
138
|
-
symbol: "TENDIES",
|
|
139
|
-
name: "TENDIES",
|
|
140
|
-
decimals: 18,
|
|
141
|
-
},
|
|
142
|
-
GME: {
|
|
143
|
-
address: "0x7e86381A763F0Ecca2bDF27C54eAC403ddD48123",
|
|
144
|
-
symbol: "GME",
|
|
145
|
-
name: "GameStop",
|
|
146
|
-
decimals: 18,
|
|
147
|
-
},
|
|
148
|
-
TOKEN_4663: {
|
|
149
|
-
address: "0xd4052415613B34Af236024B895574c467f65b6dD",
|
|
150
|
-
symbol: "4663",
|
|
151
|
-
name: "4663",
|
|
152
|
-
decimals: 18,
|
|
153
|
-
},
|
|
154
|
-
MARIAN: {
|
|
155
|
-
address: "0x01637b14B7378B99dE75A64d50656d98488D9a4d",
|
|
156
|
-
symbol: "MARIAN",
|
|
157
|
-
name: "Lady Marian",
|
|
158
|
-
decimals: 18,
|
|
159
|
-
},
|
|
160
|
-
} as const satisfies Readonly<Record<string, RobinhoodToken>>;
|
|
161
|
-
|
|
162
|
-
/** Exact ERC-20 ordering used by the Switch Robinhood token dropdown. */
|
|
163
|
-
export const ROBINHOOD_FRONTEND_TOKEN_LIST = [
|
|
164
|
-
ROBINHOOD_TOKENS.WETH,
|
|
165
|
-
ROBINHOOD_TOKENS.USDG,
|
|
166
|
-
ROBINHOOD_TOKENS.VIRTUAL,
|
|
167
|
-
ROBINHOOD_TOKENS.CASHCAT,
|
|
168
|
-
ROBINHOOD_TOKENS.WALLET,
|
|
169
|
-
ROBINHOOD_TOKENS.SEEDCOIN,
|
|
170
|
-
ROBINHOOD_TOKENS.JUGGERNAUT,
|
|
171
|
-
ROBINHOOD_TOKENS.HOODRAT,
|
|
172
|
-
ROBINHOOD_TOKENS.DIH,
|
|
173
|
-
ROBINHOOD_TOKENS.KITSU,
|
|
174
|
-
ROBINHOOD_TOKENS.WEN,
|
|
175
|
-
ROBINHOOD_TOKENS.REPE,
|
|
176
|
-
ROBINHOOD_TOKENS.TENDIES,
|
|
177
|
-
ROBINHOOD_TOKENS.GME,
|
|
178
|
-
ROBINHOOD_TOKENS.TOKEN_4663,
|
|
179
|
-
ROBINHOOD_TOKENS.MARIAN,
|
|
180
|
-
] as const satisfies readonly RobinhoodToken[];
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Tokens used to initialize the Robinhood swap UI.
|
|
184
|
-
*
|
|
185
|
-
* The current default pair is native ETH -> USDG. WETH remains in this set so
|
|
186
|
-
* native/wrapped lookup and token selection use the canonical deployment.
|
|
187
|
-
*/
|
|
188
|
-
export const ROBINHOOD_FRONTEND_DEFAULT_TOKENS = [
|
|
189
|
-
{
|
|
190
|
-
address: ROBINHOOD_NATIVE_ETH,
|
|
191
|
-
symbol: "ETH",
|
|
192
|
-
name: "Ether",
|
|
193
|
-
decimals: 18,
|
|
194
|
-
},
|
|
195
|
-
ROBINHOOD_TOKENS.WETH,
|
|
196
|
-
ROBINHOOD_TOKENS.USDG,
|
|
197
|
-
] as const satisfies readonly RobinhoodToken[];
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Production trusted-hop addresses, ordered by routing usefulness.
|
|
201
|
-
*
|
|
202
|
-
* The 2026-07-11 audit enumerated every pool creation from the Robinhood
|
|
203
|
-
* Uniswap V2/V3 factories, then required multiple meaningful counterparties
|
|
204
|
-
* and a successful zero-tax simulation. Keep this list synchronized with the
|
|
205
|
-
* Robinhood backend routing configuration.
|
|
206
|
-
*/
|
|
207
|
-
export const ROBINHOOD_TRUSTED_INTERMEDIATES = [
|
|
208
|
-
ROBINHOOD_TOKENS.WETH.address,
|
|
209
|
-
ROBINHOOD_TOKENS.USDG.address,
|
|
210
|
-
ROBINHOOD_TOKENS.VIRTUAL.address,
|
|
211
|
-
ROBINHOOD_TOKENS.CASHCAT.address,
|
|
212
|
-
] as const;
|
|
213
|
-
|
|
214
|
-
/** Preferred Robinhood fee tokens, from highest to lowest priority. */
|
|
215
|
-
export const ROBINHOOD_FEE_TOKEN_PRIORITY = [
|
|
216
|
-
ROBINHOOD_TOKENS.WETH.address,
|
|
217
|
-
ROBINHOOD_TOKENS.USDG.address,
|
|
218
|
-
ROBINHOOD_TOKENS.WALLET.address,
|
|
219
|
-
ROBINHOOD_TOKENS.SEEDCOIN.address,
|
|
220
|
-
ROBINHOOD_TOKENS.CASHCAT.address,
|
|
221
|
-
] as const;
|
|
222
|
-
|
|
223
|
-
const ROBINHOOD_FEE_PRIORITY_BY_ADDRESS = new Map<string, number>(
|
|
224
|
-
ROBINHOOD_FEE_TOKEN_PRIORITY.map((address, index) => [
|
|
225
|
-
address.toLowerCase(),
|
|
226
|
-
index + 1,
|
|
227
|
-
]),
|
|
228
|
-
);
|
|
229
|
-
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.set(
|
|
230
|
-
ROBINHOOD_NATIVE_ETH.toLowerCase(),
|
|
231
|
-
1,
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
/** Minimum directional tax data required for fee-mode selection. */
|
|
235
|
-
export interface RobinhoodTaxInfo {
|
|
236
|
-
isTaxToken: boolean;
|
|
237
|
-
buyTaxBps: number;
|
|
238
|
-
sellTaxBps: number;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Select the safer/preferred fee side for a Robinhood swap.
|
|
243
|
-
*
|
|
244
|
-
* Tax safety takes precedence over fee-token preference. Native ETH is treated
|
|
245
|
-
* as equivalent to WETH. `false` means fee on input; `true` means fee on output.
|
|
246
|
-
*/
|
|
247
|
-
export function selectRobinhoodFeeOnOutput(
|
|
248
|
-
from: string,
|
|
249
|
-
to: string,
|
|
250
|
-
fromTax: RobinhoodTaxInfo,
|
|
251
|
-
toTax: RobinhoodTaxInfo,
|
|
252
|
-
): boolean {
|
|
253
|
-
const inputHasSellTax = fromTax.isTaxToken && fromTax.sellTaxBps > 0;
|
|
254
|
-
const outputHasBuyTax = toTax.isTaxToken && toTax.buyTaxBps > 0;
|
|
255
|
-
|
|
256
|
-
if (inputHasSellTax && outputHasBuyTax) return false;
|
|
257
|
-
if (outputHasBuyTax) return false;
|
|
258
|
-
if (inputHasSellTax) return true;
|
|
259
|
-
|
|
260
|
-
const inputPriority =
|
|
261
|
-
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.get(from.toLowerCase()) ?? Infinity;
|
|
262
|
-
const outputPriority =
|
|
263
|
-
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.get(to.toLowerCase()) ?? Infinity;
|
|
264
|
-
|
|
265
|
-
if (outputPriority < inputPriority) return true;
|
|
266
|
-
if (inputPriority < outputPriority) return false;
|
|
267
|
-
if (inputPriority !== Infinity) return true;
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export interface RobinhoodQuoteParams {
|
|
272
|
-
from: string;
|
|
273
|
-
to: string;
|
|
274
|
-
/** Raw integer input amount in the input token's smallest unit. */
|
|
275
|
-
amount: string | bigint;
|
|
276
|
-
sender?: string;
|
|
277
|
-
receiver?: string;
|
|
278
|
-
/** Slippage in basis points. */
|
|
279
|
-
slippage?: number;
|
|
280
|
-
/** Partner fee in basis points. */
|
|
281
|
-
fee?: number;
|
|
282
|
-
partnerAddress?: string;
|
|
283
|
-
feeOnOutput?: boolean;
|
|
284
|
-
adapters?: string | readonly number[];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** Build a Robinhood quote URL with the required network value included. */
|
|
288
|
-
export function buildRobinhoodQuoteUrl(
|
|
289
|
-
params: RobinhoodQuoteParams,
|
|
290
|
-
apiBase: string = API_BASE,
|
|
291
|
-
): string {
|
|
292
|
-
const query = new URLSearchParams({
|
|
293
|
-
network: ROBINHOOD_NETWORK,
|
|
294
|
-
from: params.from,
|
|
295
|
-
to: params.to,
|
|
296
|
-
amount: params.amount.toString(),
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
if (params.sender) query.set("sender", params.sender);
|
|
300
|
-
if (params.receiver) query.set("receiver", params.receiver);
|
|
301
|
-
if (params.slippage !== undefined) {
|
|
302
|
-
query.set("slippage", params.slippage.toString());
|
|
303
|
-
}
|
|
304
|
-
if (params.fee !== undefined) query.set("fee", params.fee.toString());
|
|
305
|
-
if (params.partnerAddress) {
|
|
306
|
-
query.set("partnerAddress", params.partnerAddress);
|
|
307
|
-
}
|
|
308
|
-
if (params.feeOnOutput !== undefined) {
|
|
309
|
-
query.set("feeOnOutput", params.feeOnOutput.toString());
|
|
310
|
-
}
|
|
311
|
-
if (params.adapters !== undefined) {
|
|
312
|
-
query.set(
|
|
313
|
-
"adapters",
|
|
314
|
-
Array.isArray(params.adapters)
|
|
315
|
-
? params.adapters.join(",")
|
|
316
|
-
: String(params.adapters),
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return `${apiBase.replace(/\/$/, "")}/swap/quote?${query.toString()}`;
|
|
321
|
-
}
|
|
107
|
+
address: "0x58f693A30F124E59b125F7c7b837b0F6bbAF5a45",
|
|
108
|
+
symbol: "SEEDCOIN",
|
|
109
|
+
name: "Seedcoin",
|
|
110
|
+
decimals: 18,
|
|
111
|
+
},
|
|
112
|
+
JUGGERNAUT: {
|
|
113
|
+
address: "0xD7321801CAae694090694Ff55A9323139F043B88",
|
|
114
|
+
symbol: "JUGGERNAUT",
|
|
115
|
+
name: "The Juggernaut",
|
|
116
|
+
decimals: 18,
|
|
117
|
+
},
|
|
118
|
+
HOODRAT: {
|
|
119
|
+
address: "0x8e62F281f282686fCa6dCB39288069a93fC23F1c",
|
|
120
|
+
symbol: "HOODRAT",
|
|
121
|
+
name: "Hoodrat",
|
|
122
|
+
decimals: 18,
|
|
123
|
+
},
|
|
124
|
+
DIH: {
|
|
125
|
+
address: "0x17bb0C898254406b1Ea2e8E99B0C263e26c9E4a4",
|
|
126
|
+
symbol: "DIH",
|
|
127
|
+
name: "Dog In Hood",
|
|
128
|
+
decimals: 18,
|
|
129
|
+
},
|
|
130
|
+
KITSU: {
|
|
131
|
+
address: "0x8d4dFaaA4198b6486E0293Fec914C2B6a821D4DC",
|
|
132
|
+
symbol: "KITSU",
|
|
133
|
+
name: "KITSU",
|
|
134
|
+
decimals: 18,
|
|
135
|
+
},
|
|
136
|
+
WEN: {
|
|
137
|
+
address: "0xA80eb66b3E0CF66ccB46f8b8C9e7ff5803eEb820",
|
|
138
|
+
symbol: "WEN",
|
|
139
|
+
name: "Wen Lambo",
|
|
140
|
+
decimals: 18,
|
|
141
|
+
},
|
|
142
|
+
REPE: {
|
|
143
|
+
address: "0x5266eeafF092D6136AB63D18B975A60a0Cc0C8f7",
|
|
144
|
+
symbol: "REPE",
|
|
145
|
+
name: "Robinhood Pepe",
|
|
146
|
+
decimals: 18,
|
|
147
|
+
},
|
|
148
|
+
TENDIES: {
|
|
149
|
+
address: "0x45242320DBB855EeA8Fd36804C6487E10E97FCF9",
|
|
150
|
+
symbol: "TENDIES",
|
|
151
|
+
name: "TENDIES",
|
|
152
|
+
decimals: 18,
|
|
153
|
+
},
|
|
154
|
+
GME: {
|
|
155
|
+
address: "0x7e86381A763F0Ecca2bDF27C54eAC403ddD48123",
|
|
156
|
+
symbol: "GME",
|
|
157
|
+
name: "GameStop",
|
|
158
|
+
decimals: 18,
|
|
159
|
+
},
|
|
160
|
+
TOKEN_4663: {
|
|
161
|
+
address: "0xd4052415613B34Af236024B895574c467f65b6dD",
|
|
162
|
+
symbol: "4663",
|
|
163
|
+
name: "4663",
|
|
164
|
+
decimals: 18,
|
|
165
|
+
},
|
|
166
|
+
MARIAN: {
|
|
167
|
+
address: "0x01637b14B7378B99dE75A64d50656d98488D9a4d",
|
|
168
|
+
symbol: "MARIAN",
|
|
169
|
+
name: "Lady Marian",
|
|
170
|
+
decimals: 18,
|
|
171
|
+
},
|
|
172
|
+
} as const satisfies Readonly<Record<string, RobinhoodToken>>;
|
|
173
|
+
|
|
174
|
+
/** Exact ERC-20 ordering used by the Switch Robinhood token dropdown. */
|
|
175
|
+
export const ROBINHOOD_FRONTEND_TOKEN_LIST = [
|
|
176
|
+
ROBINHOOD_TOKENS.WETH,
|
|
177
|
+
ROBINHOOD_TOKENS.USDG,
|
|
178
|
+
ROBINHOOD_TOKENS.VIRTUAL,
|
|
179
|
+
ROBINHOOD_TOKENS.CASHCAT,
|
|
180
|
+
ROBINHOOD_TOKENS.WALLET,
|
|
181
|
+
ROBINHOOD_TOKENS.SEEDCOIN,
|
|
182
|
+
ROBINHOOD_TOKENS.JUGGERNAUT,
|
|
183
|
+
ROBINHOOD_TOKENS.HOODRAT,
|
|
184
|
+
ROBINHOOD_TOKENS.DIH,
|
|
185
|
+
ROBINHOOD_TOKENS.KITSU,
|
|
186
|
+
ROBINHOOD_TOKENS.WEN,
|
|
187
|
+
ROBINHOOD_TOKENS.REPE,
|
|
188
|
+
ROBINHOOD_TOKENS.TENDIES,
|
|
189
|
+
ROBINHOOD_TOKENS.GME,
|
|
190
|
+
ROBINHOOD_TOKENS.TOKEN_4663,
|
|
191
|
+
ROBINHOOD_TOKENS.MARIAN,
|
|
192
|
+
] as const satisfies readonly RobinhoodToken[];
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Tokens used to initialize the Robinhood swap UI.
|
|
196
|
+
*
|
|
197
|
+
* The current default pair is native ETH -> USDG. WETH remains in this set so
|
|
198
|
+
* native/wrapped lookup and token selection use the canonical deployment.
|
|
199
|
+
*/
|
|
200
|
+
export const ROBINHOOD_FRONTEND_DEFAULT_TOKENS = [
|
|
201
|
+
{
|
|
202
|
+
address: ROBINHOOD_NATIVE_ETH,
|
|
203
|
+
symbol: "ETH",
|
|
204
|
+
name: "Ether",
|
|
205
|
+
decimals: 18,
|
|
206
|
+
},
|
|
207
|
+
ROBINHOOD_TOKENS.WETH,
|
|
208
|
+
ROBINHOOD_TOKENS.USDG,
|
|
209
|
+
] as const satisfies readonly RobinhoodToken[];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Production trusted-hop addresses, ordered by routing usefulness.
|
|
213
|
+
*
|
|
214
|
+
* The 2026-07-11 audit enumerated every pool creation from the Robinhood
|
|
215
|
+
* Uniswap V2/V3 factories, then required multiple meaningful counterparties
|
|
216
|
+
* and a successful zero-tax simulation. Keep this list synchronized with the
|
|
217
|
+
* Robinhood backend routing configuration.
|
|
218
|
+
*/
|
|
219
|
+
export const ROBINHOOD_TRUSTED_INTERMEDIATES = [
|
|
220
|
+
ROBINHOOD_TOKENS.WETH.address,
|
|
221
|
+
ROBINHOOD_TOKENS.USDG.address,
|
|
222
|
+
ROBINHOOD_TOKENS.VIRTUAL.address,
|
|
223
|
+
ROBINHOOD_TOKENS.CASHCAT.address,
|
|
224
|
+
] as const;
|
|
225
|
+
|
|
226
|
+
/** Preferred Robinhood fee tokens, from highest to lowest priority. */
|
|
227
|
+
export const ROBINHOOD_FEE_TOKEN_PRIORITY = [
|
|
228
|
+
ROBINHOOD_TOKENS.WETH.address,
|
|
229
|
+
ROBINHOOD_TOKENS.USDG.address,
|
|
230
|
+
ROBINHOOD_TOKENS.WALLET.address,
|
|
231
|
+
ROBINHOOD_TOKENS.SEEDCOIN.address,
|
|
232
|
+
ROBINHOOD_TOKENS.CASHCAT.address,
|
|
233
|
+
] as const;
|
|
234
|
+
|
|
235
|
+
const ROBINHOOD_FEE_PRIORITY_BY_ADDRESS = new Map<string, number>(
|
|
236
|
+
ROBINHOOD_FEE_TOKEN_PRIORITY.map((address, index) => [
|
|
237
|
+
address.toLowerCase(),
|
|
238
|
+
index + 1,
|
|
239
|
+
]),
|
|
240
|
+
);
|
|
241
|
+
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.set(
|
|
242
|
+
ROBINHOOD_NATIVE_ETH.toLowerCase(),
|
|
243
|
+
1,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
/** Minimum directional tax data required for fee-mode selection. */
|
|
247
|
+
export interface RobinhoodTaxInfo {
|
|
248
|
+
isTaxToken: boolean;
|
|
249
|
+
buyTaxBps: number;
|
|
250
|
+
sellTaxBps: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Select the safer/preferred fee side for a Robinhood swap.
|
|
255
|
+
*
|
|
256
|
+
* Tax safety takes precedence over fee-token preference. Native ETH is treated
|
|
257
|
+
* as equivalent to WETH. `false` means fee on input; `true` means fee on output.
|
|
258
|
+
*/
|
|
259
|
+
export function selectRobinhoodFeeOnOutput(
|
|
260
|
+
from: string,
|
|
261
|
+
to: string,
|
|
262
|
+
fromTax: RobinhoodTaxInfo,
|
|
263
|
+
toTax: RobinhoodTaxInfo,
|
|
264
|
+
): boolean {
|
|
265
|
+
const inputHasSellTax = fromTax.isTaxToken && fromTax.sellTaxBps > 0;
|
|
266
|
+
const outputHasBuyTax = toTax.isTaxToken && toTax.buyTaxBps > 0;
|
|
267
|
+
|
|
268
|
+
if (inputHasSellTax && outputHasBuyTax) return false;
|
|
269
|
+
if (outputHasBuyTax) return false;
|
|
270
|
+
if (inputHasSellTax) return true;
|
|
271
|
+
|
|
272
|
+
const inputPriority =
|
|
273
|
+
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.get(from.toLowerCase()) ?? Infinity;
|
|
274
|
+
const outputPriority =
|
|
275
|
+
ROBINHOOD_FEE_PRIORITY_BY_ADDRESS.get(to.toLowerCase()) ?? Infinity;
|
|
276
|
+
|
|
277
|
+
if (outputPriority < inputPriority) return true;
|
|
278
|
+
if (inputPriority < outputPriority) return false;
|
|
279
|
+
if (inputPriority !== Infinity) return true;
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface RobinhoodQuoteParams {
|
|
284
|
+
from: string;
|
|
285
|
+
to: string;
|
|
286
|
+
/** Raw integer input amount in the input token's smallest unit. */
|
|
287
|
+
amount: string | bigint;
|
|
288
|
+
sender?: string;
|
|
289
|
+
receiver?: string;
|
|
290
|
+
/** Slippage in basis points. */
|
|
291
|
+
slippage?: number;
|
|
292
|
+
/** Partner fee in basis points. */
|
|
293
|
+
fee?: number;
|
|
294
|
+
partnerAddress?: string;
|
|
295
|
+
feeOnOutput?: boolean;
|
|
296
|
+
adapters?: string | readonly number[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Build a Robinhood quote URL with the required network value included. */
|
|
300
|
+
export function buildRobinhoodQuoteUrl(
|
|
301
|
+
params: RobinhoodQuoteParams,
|
|
302
|
+
apiBase: string = API_BASE,
|
|
303
|
+
): string {
|
|
304
|
+
const query = new URLSearchParams({
|
|
305
|
+
network: ROBINHOOD_NETWORK,
|
|
306
|
+
from: params.from,
|
|
307
|
+
to: params.to,
|
|
308
|
+
amount: params.amount.toString(),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (params.sender) query.set("sender", params.sender);
|
|
312
|
+
if (params.receiver) query.set("receiver", params.receiver);
|
|
313
|
+
if (params.slippage !== undefined) {
|
|
314
|
+
query.set("slippage", params.slippage.toString());
|
|
315
|
+
}
|
|
316
|
+
if (params.fee !== undefined) query.set("fee", params.fee.toString());
|
|
317
|
+
if (params.partnerAddress) {
|
|
318
|
+
query.set("partnerAddress", params.partnerAddress);
|
|
319
|
+
}
|
|
320
|
+
if (params.feeOnOutput !== undefined) {
|
|
321
|
+
query.set("feeOnOutput", params.feeOnOutput.toString());
|
|
322
|
+
}
|
|
323
|
+
if (params.adapters !== undefined) {
|
|
324
|
+
query.set(
|
|
325
|
+
"adapters",
|
|
326
|
+
Array.isArray(params.adapters)
|
|
327
|
+
? params.adapters.join(",")
|
|
328
|
+
: String(params.adapters),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return `${apiBase.replace(/\/$/, "")}/swap/quote?${query.toString()}`;
|
|
333
|
+
}
|