@switch-win/sdk 1.2.9 → 1.3.0
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/CHANGELOG.md +26 -0
- package/GASLESS.md +407 -0
- package/LIMIT-ORDERS.md +213 -205
- package/README.md +131 -105
- package/ROBINHOOD.md +201 -198
- package/abi/GaslessSwitchV2ABI.json +1095 -0
- package/dist/constants.d.ts +276 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +403 -0
- package/dist/constants.js.map +1 -0
- package/dist/gasless-client.d.ts +81 -0
- package/dist/gasless-client.d.ts.map +1 -0
- package/dist/gasless-client.js +467 -0
- package/dist/gasless-client.js.map +1 -0
- package/dist/gasless-index.d.ts +5 -0
- package/dist/gasless-index.d.ts.map +1 -0
- package/dist/gasless-index.js +5 -0
- package/dist/gasless-index.js.map +1 -0
- package/dist/gasless-operators.d.ts +32 -0
- package/dist/gasless-operators.d.ts.map +1 -0
- package/dist/gasless-operators.js +132 -0
- package/dist/gasless-operators.js.map +1 -0
- package/dist/gasless-store.d.ts +18 -0
- package/dist/gasless-store.d.ts.map +1 -0
- package/dist/gasless-store.js +83 -0
- package/dist/gasless-store.js.map +1 -0
- package/dist/gasless-types.d.ts +218 -0
- package/dist/gasless-types.d.ts.map +1 -0
- package/dist/gasless-types.js +2 -0
- package/dist/gasless-types.js.map +1 -0
- package/dist/gasless.d.ts +135 -0
- package/dist/gasless.d.ts.map +1 -0
- package/dist/gasless.js +513 -0
- package/dist/gasless.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/limit-orders.d.ts +440 -0
- package/dist/limit-orders.d.ts.map +1 -0
- package/dist/limit-orders.js +387 -0
- package/dist/limit-orders.js.map +1 -0
- package/dist/networks/index.d.ts +3 -0
- package/dist/networks/index.d.ts.map +1 -0
- package/dist/networks/index.js +2 -0
- package/dist/networks/index.js.map +1 -0
- package/dist/networks/robinhood.d.ts +536 -0
- package/dist/networks/robinhood.d.ts.map +1 -0
- package/dist/networks/robinhood.js +489 -0
- package/dist/networks/robinhood.js.map +1 -0
- package/dist/types.d.ts +436 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/examples/gasless-nextjs-proxy.ts +87 -0
- package/examples/gasless-operator.ts +76 -0
- package/examples/gasless-swap.ts +170 -0
- package/examples/limit-order-ethers.ts +195 -0
- package/examples/nextjs-proxy.ts +82 -0
- package/examples/react-hooks.tsx +396 -0
- package/examples/swap-ethers.ts +209 -0
- package/examples/swap-web3py.py +267 -0
- package/package.json +53 -12
- package/src/constants.ts +184 -182
- package/src/gasless-client.ts +823 -0
- package/src/gasless-index.ts +4 -0
- package/src/gasless-operators.ts +227 -0
- package/src/gasless-store.ts +137 -0
- package/src/gasless-types.ts +258 -0
- package/src/gasless.ts +756 -0
- package/src/index.ts +69 -63
- package/src/limit-orders.ts +206 -195
- package/src/types.ts +80 -55
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Switch DEX Aggregator — Complete Swap Example (web3.py)
|
|
3
|
+
|
|
4
|
+
Demonstrates the full swap lifecycle:
|
|
5
|
+
1. Fetch adapters (optional — for DEX filtering)
|
|
6
|
+
2. Check token taxes (determines feeOnOutput mode)
|
|
7
|
+
3. Get quote with exact expectedOutputAmount
|
|
8
|
+
4. Approve ERC-20 (if needed)
|
|
9
|
+
5. Send swap transaction
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
pip install web3 requests
|
|
13
|
+
export SWITCH_API_KEY=your_key
|
|
14
|
+
export PRIVATE_KEY=your_private_key
|
|
15
|
+
python examples/swap-web3py.py
|
|
16
|
+
|
|
17
|
+
Prerequisites:
|
|
18
|
+
pip install web3 requests
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import requests
|
|
24
|
+
from web3 import Web3
|
|
25
|
+
|
|
26
|
+
# ── Constants --
|
|
27
|
+
|
|
28
|
+
API_BASE = "https://quote.switch.win"
|
|
29
|
+
SWITCH_ROUTER = "0x69033829f50244FD1be7BDC8e74aE0fF97E47126"
|
|
30
|
+
NATIVE_PLS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
|
|
31
|
+
WPLS = "0xA1077a294dDE1B09bB078844df40758a5D0f9a27"
|
|
32
|
+
RPC_URL = os.environ.get("RPC_URL", "https://rpc.pulsechain.com")
|
|
33
|
+
|
|
34
|
+
ERC20_ABI = [
|
|
35
|
+
{
|
|
36
|
+
"constant": True,
|
|
37
|
+
"inputs": [
|
|
38
|
+
{"name": "owner", "type": "address"},
|
|
39
|
+
{"name": "spender", "type": "address"},
|
|
40
|
+
],
|
|
41
|
+
"name": "allowance",
|
|
42
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
43
|
+
"type": "function",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"constant": False,
|
|
47
|
+
"inputs": [
|
|
48
|
+
{"name": "spender", "type": "address"},
|
|
49
|
+
{"name": "amount", "type": "uint256"},
|
|
50
|
+
],
|
|
51
|
+
"name": "approve",
|
|
52
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
53
|
+
"type": "function",
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
# ── Configuration --
|
|
58
|
+
|
|
59
|
+
API_KEY = os.environ.get("SWITCH_API_KEY")
|
|
60
|
+
PRIVATE_KEY = os.environ.get("PRIVATE_KEY")
|
|
61
|
+
|
|
62
|
+
if not API_KEY:
|
|
63
|
+
print("Error: Set SWITCH_API_KEY env var")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
if not PRIVATE_KEY:
|
|
66
|
+
print("Error: Set PRIVATE_KEY env var")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
# ── Swap parameters --
|
|
70
|
+
|
|
71
|
+
FROM_TOKEN = "0xA1077a294dDE1B09bB078844df40758a5D0f9a27" # WPLS
|
|
72
|
+
TO_TOKEN = "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab" # PLSX
|
|
73
|
+
AMOUNT = 1000 * 10**18 # 1000 WPLS in wei
|
|
74
|
+
SLIPPAGE_BPS = 100 # 1%
|
|
75
|
+
FEE_BPS = 10 # 0.10%
|
|
76
|
+
PARTNER_ADDRESS = None # your partner wallet or None
|
|
77
|
+
RECEIVER_ADDRESS = None # custom recipient address or None to send to sender
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── Helpers --
|
|
81
|
+
|
|
82
|
+
def api_fetch(path: str, params: dict | None = None) -> dict:
|
|
83
|
+
"""Make an authenticated API call to Switch."""
|
|
84
|
+
resp = requests.get(
|
|
85
|
+
f"{API_BASE}{path}",
|
|
86
|
+
params=params,
|
|
87
|
+
headers={"x-api-key": API_KEY},
|
|
88
|
+
)
|
|
89
|
+
resp.raise_for_status()
|
|
90
|
+
return resp.json()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def determine_fee_on_output(
|
|
94
|
+
from_token: str,
|
|
95
|
+
to_token: str,
|
|
96
|
+
from_tax: dict,
|
|
97
|
+
to_tax: dict,
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Determine feeOnOutput based on tax info and PLS involvement.
|
|
100
|
+
|
|
101
|
+
- Selling a tax token → fee on output (avoid triggering sell-tax on fee)
|
|
102
|
+
- Buying a tax token → fee on input (avoid triggering buy-tax on fee)
|
|
103
|
+
- Buying PLS/WPLS → fee on output (collect PLS)
|
|
104
|
+
- Selling PLS/WPLS → fee on input (collect PLS)
|
|
105
|
+
- Default → False
|
|
106
|
+
"""
|
|
107
|
+
pls_addresses = [NATIVE_PLS.lower(), WPLS.lower()]
|
|
108
|
+
from_addr = from_token.lower()
|
|
109
|
+
to_addr = to_token.lower()
|
|
110
|
+
|
|
111
|
+
if from_tax.get("isTaxToken") and from_tax.get("sellTaxBps", 0) > 0:
|
|
112
|
+
return True
|
|
113
|
+
if to_tax.get("isTaxToken") and to_tax.get("buyTaxBps", 0) > 0:
|
|
114
|
+
return False
|
|
115
|
+
if to_addr in pls_addresses:
|
|
116
|
+
return True
|
|
117
|
+
if from_addr in pls_addresses:
|
|
118
|
+
return False
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def swap(
|
|
123
|
+
w3: Web3,
|
|
124
|
+
account,
|
|
125
|
+
from_token: str,
|
|
126
|
+
to_token: str,
|
|
127
|
+
amount_wei: int,
|
|
128
|
+
slippage_bps: int = 50,
|
|
129
|
+
fee_bps: int = 0,
|
|
130
|
+
partner: str | None = None,
|
|
131
|
+
receiver: str | None = None,
|
|
132
|
+
):
|
|
133
|
+
"""Execute a full swap: adapters → checkTax → quote → approve → send."""
|
|
134
|
+
sender = account.address
|
|
135
|
+
print(f"Sender: {sender}")
|
|
136
|
+
print(f"Swapping {amount_wei} wei of {from_token} → {to_token}")
|
|
137
|
+
|
|
138
|
+
# ── Step 1: Fetch available adapters (optional — cache this) ─
|
|
139
|
+
print("\n── Step 1: Fetching adapters ──")
|
|
140
|
+
adapters_data = api_fetch("/swap/adapters")
|
|
141
|
+
adapter_names = [a["name"] for a in adapters_data["adapters"]]
|
|
142
|
+
print(f"Available DEXes: {', '.join(adapter_names)}")
|
|
143
|
+
|
|
144
|
+
# ── Step 2: Check token taxes --
|
|
145
|
+
print("\n── Step 2: Checking token taxes ──")
|
|
146
|
+
from_tax = api_fetch("/swap/checkTax", {"token": from_token, "network": "pulsechain"})
|
|
147
|
+
to_tax = api_fetch("/swap/checkTax", {"token": to_token, "network": "pulsechain"})
|
|
148
|
+
|
|
149
|
+
if from_tax.get("isTaxToken"):
|
|
150
|
+
print(f"From token: TAX (sell={from_tax['sellTaxBps']}bps, buy={from_tax['buyTaxBps']}bps)")
|
|
151
|
+
else:
|
|
152
|
+
print("From token: not a tax token")
|
|
153
|
+
|
|
154
|
+
if to_tax.get("isTaxToken"):
|
|
155
|
+
print(f"To token: TAX (sell={to_tax['sellTaxBps']}bps, buy={to_tax['buyTaxBps']}bps)")
|
|
156
|
+
else:
|
|
157
|
+
print("To token: not a tax token")
|
|
158
|
+
|
|
159
|
+
# ── Determine fee mode --
|
|
160
|
+
fee_on_output = determine_fee_on_output(from_token, to_token, from_tax, to_tax)
|
|
161
|
+
print(f"Fee mode: fee on {'output' if fee_on_output else 'input'}")
|
|
162
|
+
|
|
163
|
+
# ── Step 3: Fetch quote --
|
|
164
|
+
print("\n── Step 3: Fetching quote ──")
|
|
165
|
+
params = {
|
|
166
|
+
"network": "pulsechain",
|
|
167
|
+
"from": from_token,
|
|
168
|
+
"to": to_token,
|
|
169
|
+
"amount": str(amount_wei),
|
|
170
|
+
"sender": sender,
|
|
171
|
+
"slippage": str(slippage_bps),
|
|
172
|
+
"feeOnOutput": str(fee_on_output).lower(),
|
|
173
|
+
}
|
|
174
|
+
if fee_bps:
|
|
175
|
+
params["fee"] = str(fee_bps)
|
|
176
|
+
if partner:
|
|
177
|
+
params["partnerAddress"] = partner
|
|
178
|
+
if receiver:
|
|
179
|
+
params["receiver"] = receiver
|
|
180
|
+
|
|
181
|
+
quote = api_fetch("/swap/quote", params)
|
|
182
|
+
|
|
183
|
+
if "error" in quote:
|
|
184
|
+
raise Exception(f"Quote error: {quote['error']}")
|
|
185
|
+
|
|
186
|
+
tx_data = quote.get("tx")
|
|
187
|
+
tx_data_fee_on_output = quote.get("txFeeOnOutput")
|
|
188
|
+
if not tx_data:
|
|
189
|
+
raise Exception("No tx object in response — did you provide sender?")
|
|
190
|
+
|
|
191
|
+
# Choose the matching tx variant for the fee mode we determined
|
|
192
|
+
chosen_tx = tx_data_fee_on_output if fee_on_output and tx_data_fee_on_output else tx_data
|
|
193
|
+
|
|
194
|
+
print(f"Raw DEX output: {quote['totalAmountOut']}")
|
|
195
|
+
print(f"Expected received: {quote['expectedOutputAmount']}")
|
|
196
|
+
print(f"Min output: {quote['minAmountOut']}")
|
|
197
|
+
print(f"Eff. slippage: {quote['effectiveSlippagePercent']}%")
|
|
198
|
+
print(f"Paths: {len(quote['paths'])}")
|
|
199
|
+
|
|
200
|
+
# ── Step 4: Approve ERC-20 if needed --
|
|
201
|
+
if from_token.lower() != NATIVE_PLS.lower():
|
|
202
|
+
token = w3.eth.contract(
|
|
203
|
+
address=Web3.to_checksum_address(from_token), abi=ERC20_ABI
|
|
204
|
+
)
|
|
205
|
+
current_allowance = token.functions.allowance(
|
|
206
|
+
sender, chosen_tx["to"]
|
|
207
|
+
).call()
|
|
208
|
+
|
|
209
|
+
if current_allowance < amount_wei:
|
|
210
|
+
print("\nApproving SwitchRouter to spend tokens...")
|
|
211
|
+
approve_tx = token.functions.approve(
|
|
212
|
+
chosen_tx["to"], amount_wei
|
|
213
|
+
).build_transaction(
|
|
214
|
+
{
|
|
215
|
+
"from": sender,
|
|
216
|
+
"nonce": w3.eth.get_transaction_count(sender),
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
signed = account.sign_transaction(approve_tx)
|
|
220
|
+
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
221
|
+
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
|
|
222
|
+
print(f"Approved in tx: {receipt['transactionHash'].hex()}")
|
|
223
|
+
else:
|
|
224
|
+
print("\nSufficient allowance already granted")
|
|
225
|
+
|
|
226
|
+
# ── Step 5: Send swap --
|
|
227
|
+
print("\nSending swap transaction...")
|
|
228
|
+
swap_tx = {
|
|
229
|
+
"from": sender,
|
|
230
|
+
"to": Web3.to_checksum_address(chosen_tx["to"]),
|
|
231
|
+
"data": chosen_tx["data"],
|
|
232
|
+
"value": int(chosen_tx["value"]),
|
|
233
|
+
"nonce": w3.eth.get_transaction_count(sender),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
signed = account.sign_transaction(swap_tx)
|
|
237
|
+
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
238
|
+
print(f"Tx hash: {tx_hash.hex()}")
|
|
239
|
+
|
|
240
|
+
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
|
|
241
|
+
print(f"\nSwap confirmed in block {receipt['blockNumber']}")
|
|
242
|
+
return receipt
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def main():
|
|
246
|
+
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
|
247
|
+
if not w3.is_connected():
|
|
248
|
+
print("Error: Could not connect to RPC")
|
|
249
|
+
sys.exit(1)
|
|
250
|
+
|
|
251
|
+
account = w3.eth.account.from_key(PRIVATE_KEY)
|
|
252
|
+
|
|
253
|
+
swap(
|
|
254
|
+
w3=w3,
|
|
255
|
+
account=account,
|
|
256
|
+
from_token=FROM_TOKEN,
|
|
257
|
+
to_token=TO_TOKEN,
|
|
258
|
+
amount_wei=AMOUNT,
|
|
259
|
+
slippage_bps=SLIPPAGE_BPS,
|
|
260
|
+
fee_bps=FEE_BPS,
|
|
261
|
+
partner=PARTNER_ADDRESS,
|
|
262
|
+
receiver=RECEIVER_ADDRESS,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@switch-win/sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Switch swaps, limit orders, and GaslessSwitch V2 integrations on PulseChain and Robinhood Chain",
|
|
5
5
|
"author": "BuildTheTech",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -21,26 +21,67 @@
|
|
|
21
21
|
"sdk"
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
|
-
"main": "
|
|
25
|
-
"types": "
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
26
|
"exports": {
|
|
27
|
-
".":
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"./
|
|
32
|
-
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./types": {
|
|
32
|
+
"types": "./dist/types.d.ts",
|
|
33
|
+
"default": "./dist/types.js"
|
|
34
|
+
},
|
|
35
|
+
"./constants": {
|
|
36
|
+
"types": "./dist/constants.d.ts",
|
|
37
|
+
"default": "./dist/constants.js"
|
|
38
|
+
},
|
|
39
|
+
"./networks": {
|
|
40
|
+
"types": "./dist/networks/index.d.ts",
|
|
41
|
+
"default": "./dist/networks/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./networks/robinhood": {
|
|
44
|
+
"types": "./dist/networks/robinhood.d.ts",
|
|
45
|
+
"default": "./dist/networks/robinhood.js"
|
|
46
|
+
},
|
|
47
|
+
"./limit-orders": {
|
|
48
|
+
"types": "./dist/limit-orders.d.ts",
|
|
49
|
+
"default": "./dist/limit-orders.js"
|
|
50
|
+
},
|
|
51
|
+
"./gasless": {
|
|
52
|
+
"types": "./dist/gasless-index.d.ts",
|
|
53
|
+
"default": "./dist/gasless-index.js"
|
|
54
|
+
},
|
|
55
|
+
"./gasless/operators": {
|
|
56
|
+
"types": "./dist/gasless-operators.d.ts",
|
|
57
|
+
"default": "./dist/gasless-operators.js"
|
|
58
|
+
},
|
|
33
59
|
"./abi": "./abi/SwitchRouterABI.json",
|
|
34
|
-
"./abi/limit-order": "./abi/SwitchLimitOrderABI.json"
|
|
60
|
+
"./abi/limit-order": "./abi/SwitchLimitOrderABI.json",
|
|
61
|
+
"./abi/gasless": "./abi/GaslessSwitchV2ABI.json"
|
|
35
62
|
},
|
|
36
63
|
"files": [
|
|
64
|
+
"dist/",
|
|
37
65
|
"src/",
|
|
38
66
|
"abi/",
|
|
67
|
+
"examples/",
|
|
68
|
+
"CHANGELOG.md",
|
|
39
69
|
"README.md",
|
|
40
70
|
"ROBINHOOD.md",
|
|
41
|
-
"LIMIT-ORDERS.md"
|
|
71
|
+
"LIMIT-ORDERS.md",
|
|
72
|
+
"GASLESS.md"
|
|
42
73
|
],
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsc -p tsconfig.json",
|
|
76
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.examples.json --noEmit",
|
|
77
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
78
|
+
"check:gasless-live": "npm run build && node scripts/check-gasless-live.mjs",
|
|
79
|
+
"prepack": "npm run build"
|
|
80
|
+
},
|
|
43
81
|
"devDependencies": {
|
|
44
82
|
"typescript": "^5.9.3"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"ethers": "6.17.0"
|
|
45
86
|
}
|
|
46
87
|
}
|