@switch-win/sdk 1.2.9 → 1.3.1
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 +34 -0
- package/GASLESS.md +434 -0
- package/LIMIT-ORDERS.md +213 -205
- package/README.md +134 -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 +82 -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 +222 -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 +173 -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 +824 -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 +262 -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,173 @@
|
|
|
1
|
+
import { BrowserProvider, type Eip1193Provider } from "ethers";
|
|
2
|
+
import {
|
|
3
|
+
GaslessSwapClient,
|
|
4
|
+
createGaslessBrowserStore,
|
|
5
|
+
type GaslessOrderOutcome,
|
|
6
|
+
type GaslessTrade,
|
|
7
|
+
type GaslessEligibility,
|
|
8
|
+
type PreparedGaslessSwap,
|
|
9
|
+
} from "../src/gasless-index.js";
|
|
10
|
+
import type { LimitOrderNetwork } from "../src/limit-orders.js";
|
|
11
|
+
|
|
12
|
+
/** Opt-in gasless flow: fixed 0.1% protocol fee plus up to 0.05% operator payout
|
|
13
|
+
* including gas. To offer regular swaps only, leave this flow out and use the
|
|
14
|
+
* regular swap example instead. See GASLESS.md for fee policy and integration steps.
|
|
15
|
+
* Wire these callbacks to your existing swap form/modal. No action runs on import. */
|
|
16
|
+
export async function createGaslessSwapFlow(
|
|
17
|
+
wallet: Eip1193Provider,
|
|
18
|
+
network: LimitOrderNetwork,
|
|
19
|
+
ui: {
|
|
20
|
+
/** Main amount: prepared.expectedOutput. Separate minimum: prepared.minAmountOut.
|
|
21
|
+
* Format both with output-token decimals. The SDK signs the exact raw minimum. */
|
|
22
|
+
showQuote(prepared: PreparedGaslessSwap): void;
|
|
23
|
+
showRegular(reason: string): void;
|
|
24
|
+
/** Optional PLS/ETH payout, gas reserve and reason-code display. */
|
|
25
|
+
showEligibility?(eligibility: GaslessEligibility | undefined): void;
|
|
26
|
+
showPending(): void;
|
|
27
|
+
showOutcome(result: GaslessOrderOutcome): void;
|
|
28
|
+
showError(message: string): void;
|
|
29
|
+
},
|
|
30
|
+
) {
|
|
31
|
+
const provider = new BrowserProvider(wallet, "any");
|
|
32
|
+
const signer = await provider.getSigner();
|
|
33
|
+
const maker = await signer.getAddress();
|
|
34
|
+
const client = new GaslessSwapClient({
|
|
35
|
+
network,
|
|
36
|
+
provider,
|
|
37
|
+
quoteEndpoint: "/api/switch/swap/quote",
|
|
38
|
+
taxEndpoint: "/api/switch/swap/checkTax",
|
|
39
|
+
ordersEndpoint: "/api/switch/limit-orders",
|
|
40
|
+
});
|
|
41
|
+
const store = createGaslessBrowserStore(network, maker);
|
|
42
|
+
let displayed: PreparedGaslessSwap | undefined;
|
|
43
|
+
let busy = false;
|
|
44
|
+
let quotesPaused = Boolean(await store.load());
|
|
45
|
+
let quoteRequest: AbortController | undefined;
|
|
46
|
+
let signingRequest: AbortController | undefined;
|
|
47
|
+
let intent = 0;
|
|
48
|
+
let unlimited = true; // Explicit user toggle can set false.
|
|
49
|
+
|
|
50
|
+
async function quote(trade: Omit<GaslessTrade, "maker">) {
|
|
51
|
+
if (busy || quotesPaused || (await store.load())) return;
|
|
52
|
+
quoteRequest?.abort();
|
|
53
|
+
const controller = (quoteRequest = new AbortController());
|
|
54
|
+
const current = ++intent;
|
|
55
|
+
try {
|
|
56
|
+
const result = await client.quote({ ...trade, maker }, controller.signal);
|
|
57
|
+
if (controller.signal.aborted || current !== intent) return;
|
|
58
|
+
displayed = result.eligible ? result.prepared : undefined;
|
|
59
|
+
ui.showEligibility?.(result.eligibility);
|
|
60
|
+
if (result.eligible) ui.showQuote(result.prepared);
|
|
61
|
+
else ui.showRegular(result.reason);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!controller.signal.aborted && current === intent)
|
|
64
|
+
ui.showError(
|
|
65
|
+
error instanceof Error ? error.message : "Quote unavailable",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function sign(trade: Omit<GaslessTrade, "maker">) {
|
|
71
|
+
if (busy || (await store.load())) return;
|
|
72
|
+
busy = true;
|
|
73
|
+
quotesPaused = true;
|
|
74
|
+
quoteRequest?.abort();
|
|
75
|
+
const controller = (signingRequest = new AbortController());
|
|
76
|
+
try {
|
|
77
|
+
await client.signAndSubmit({
|
|
78
|
+
trade: { ...trade, maker },
|
|
79
|
+
signer,
|
|
80
|
+
store,
|
|
81
|
+
displayed,
|
|
82
|
+
signal: controller.signal,
|
|
83
|
+
// Refresh both amounts before the wallet opens: main expected output
|
|
84
|
+
// stays on prepared.expectedOutput; the SDK signs prepared.minAmountOut.
|
|
85
|
+
onPrepared(prepared) {
|
|
86
|
+
displayed = prepared;
|
|
87
|
+
ui.showQuote(prepared);
|
|
88
|
+
ui.showEligibility?.(
|
|
89
|
+
prepared.quote.gaslessEligibility ?? prepared.preview.eligibility,
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
onPending() {
|
|
93
|
+
ui.showPending();
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
ui.showError(
|
|
98
|
+
error instanceof Error ? error.message : "Gasless submission failed.",
|
|
99
|
+
);
|
|
100
|
+
} finally {
|
|
101
|
+
busy = false;
|
|
102
|
+
signingRequest = undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function approve(
|
|
107
|
+
trade: Omit<GaslessTrade, "maker">,
|
|
108
|
+
resetToZero = false,
|
|
109
|
+
) {
|
|
110
|
+
if (busy || (await store.load())) return;
|
|
111
|
+
busy = true;
|
|
112
|
+
quoteRequest?.abort();
|
|
113
|
+
try {
|
|
114
|
+
const transaction = await client.approve({ ...trade, maker }, signer, {
|
|
115
|
+
unlimited,
|
|
116
|
+
resetToZero,
|
|
117
|
+
quoteGasPriceWei: displayed?.gasPriceWei,
|
|
118
|
+
});
|
|
119
|
+
const receipt = await transaction.wait();
|
|
120
|
+
if (!receipt || receipt.status !== 1)
|
|
121
|
+
throw Error("Token approval failed.");
|
|
122
|
+
// Approval time can stale the quote. Fetch on the next explicit quote/sign action.
|
|
123
|
+
displayed = undefined;
|
|
124
|
+
} finally {
|
|
125
|
+
busy = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function checkStatus() {
|
|
130
|
+
const result = await client.reconcile(store);
|
|
131
|
+
if (result) ui.showOutcome(result);
|
|
132
|
+
if (result && result.status !== "pending") {
|
|
133
|
+
displayed = undefined;
|
|
134
|
+
quotesPaused = true;
|
|
135
|
+
// Show settlementTxUrl when filled. Do not automatically fetch another quote.
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Call on accountsChanged/chainChanged and any user edit, including slippage/recipient.
|
|
141
|
+
// Recreate this flow for a new account/network; maker, signer and store are scoped to them.
|
|
142
|
+
function invalidateIntent() {
|
|
143
|
+
intent++;
|
|
144
|
+
displayed = undefined;
|
|
145
|
+
ui.showEligibility?.(undefined);
|
|
146
|
+
quoteRequest?.abort();
|
|
147
|
+
signingRequest?.abort();
|
|
148
|
+
// A signature already obtained retains its pending lock even after abort.
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
quote,
|
|
153
|
+
sign,
|
|
154
|
+
approve,
|
|
155
|
+
checkStatus,
|
|
156
|
+
invalidateIntent,
|
|
157
|
+
setUnlimited(value: boolean) {
|
|
158
|
+
unlimited = value;
|
|
159
|
+
},
|
|
160
|
+
async assertNoPending() {
|
|
161
|
+
// Call before Regular/Wrap actions too; closing the gasless modal is not cancellation.
|
|
162
|
+
if (busy || (await store.load()))
|
|
163
|
+
throw Error("A gasless order can still execute.");
|
|
164
|
+
},
|
|
165
|
+
async startAnotherSwap() {
|
|
166
|
+
if (busy || (await store.load()))
|
|
167
|
+
throw Error("The previous order can still execute.");
|
|
168
|
+
quotesPaused = false;
|
|
169
|
+
},
|
|
170
|
+
// Use a non-overlapping timer (await checkStatus, then setTimeout >= 5000ms).
|
|
171
|
+
// A close/dismiss button hides the modal; it does not cancel the signed order.
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Limit Orders — Complete Example (ethers.js v6)
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the full limit order lifecycle:
|
|
5
|
+
* 1. Build order parameters
|
|
6
|
+
* 2. Approve the selected network's current contract to spend input tokens
|
|
7
|
+
* 3. Sign the order via EIP-712
|
|
8
|
+
* 4. Submit the signed order to the Switch backend
|
|
9
|
+
* 5. Query your active orders
|
|
10
|
+
* 6. Cancel an order on-chain
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx tsx examples/limit-order-ethers.ts
|
|
14
|
+
*
|
|
15
|
+
* Prerequisites:
|
|
16
|
+
* npm install ethers
|
|
17
|
+
* Set PRIVATE_KEY environment variable.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { ethers } from "ethers";
|
|
21
|
+
import {
|
|
22
|
+
buildLimitOrder,
|
|
23
|
+
getNetworkEIP712SigningParams,
|
|
24
|
+
getLimitOrderApprovalTarget,
|
|
25
|
+
fetchLimitOrderConfig,
|
|
26
|
+
submitLimitOrder,
|
|
27
|
+
fetchLimitOrders,
|
|
28
|
+
fetchLimitOrder,
|
|
29
|
+
fetchLimitOrderPairs,
|
|
30
|
+
fetchLimitOrderStats,
|
|
31
|
+
} from "../src/limit-orders";
|
|
32
|
+
import {
|
|
33
|
+
ERC20_ABI,
|
|
34
|
+
LIMIT_ORDER_ABI,
|
|
35
|
+
} from "../src/constants";
|
|
36
|
+
import type { LimitOrderNetwork } from "../src/limit-orders";
|
|
37
|
+
|
|
38
|
+
// ── Configuration ───────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
|
|
41
|
+
const NETWORK = (process.env.SWITCH_NETWORK ?? "pulsechain") as LimitOrderNetwork;
|
|
42
|
+
if (NETWORK !== "pulsechain" && NETWORK !== "robinhood") {
|
|
43
|
+
throw new Error("SWITCH_NETWORK must be pulsechain or robinhood");
|
|
44
|
+
}
|
|
45
|
+
const RPC_URL = process.env.RPC_URL ?? (
|
|
46
|
+
NETWORK === "robinhood"
|
|
47
|
+
? "https://rpc.mainnet.chain.robinhood.com"
|
|
48
|
+
: "https://pulsechain-rpc.publicnode.com"
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (!PRIVATE_KEY) throw new Error("Set PRIVATE_KEY env var");
|
|
52
|
+
|
|
53
|
+
// ── Order parameters ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const TOKEN_IN = process.env.TOKEN_IN ?? (
|
|
56
|
+
NETWORK === "robinhood"
|
|
57
|
+
? "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168" // USDG
|
|
58
|
+
: "0xA1077a294dDE1B09bB078844df40758a5D0f9a27" // WPLS
|
|
59
|
+
);
|
|
60
|
+
const TOKEN_OUT = process.env.TOKEN_OUT ?? (
|
|
61
|
+
NETWORK === "robinhood"
|
|
62
|
+
? "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73" // WETH
|
|
63
|
+
: "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab" // PLSX
|
|
64
|
+
);
|
|
65
|
+
const AMOUNT_IN = process.env.AMOUNT_IN ?? "1000000";
|
|
66
|
+
const MIN_AMOUNT_OUT = process.env.MIN_AMOUNT_OUT ?? "1";
|
|
67
|
+
|
|
68
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
const provider = new ethers.JsonRpcProvider(RPC_URL);
|
|
72
|
+
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
73
|
+
const maker = await signer.getAddress();
|
|
74
|
+
const liveConfig = await fetchLimitOrderConfig({ network: NETWORK });
|
|
75
|
+
|
|
76
|
+
console.log(`Maker: ${maker}`);
|
|
77
|
+
console.log(`Limit order: ${AMOUNT_IN} wei of ${TOKEN_IN} → min ${MIN_AMOUNT_OUT} wei of ${TOKEN_OUT}\n`);
|
|
78
|
+
|
|
79
|
+
// ── Step 1: Build the order ───────────────────────────────────────────
|
|
80
|
+
console.log("── Step 1: Building order ──");
|
|
81
|
+
const order = buildLimitOrder({
|
|
82
|
+
maker,
|
|
83
|
+
tokenIn: TOKEN_IN,
|
|
84
|
+
tokenOut: TOKEN_OUT,
|
|
85
|
+
amountIn: AMOUNT_IN,
|
|
86
|
+
minAmountOut: MIN_AMOUNT_OUT,
|
|
87
|
+
deadline: Math.floor(Date.now() / 1000) + 86400, // 24-hour expiry
|
|
88
|
+
// nonce: auto-generated from Date.now()
|
|
89
|
+
// feeOnOutput: false (default)
|
|
90
|
+
// recipient: maker (default)
|
|
91
|
+
// unwrapOutput: false (default)
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log(`Nonce: ${order.nonce}`);
|
|
95
|
+
console.log(`Deadline: ${new Date(order.deadline * 1000).toISOString()}`);
|
|
96
|
+
|
|
97
|
+
// ── Step 2: Approve the correct contract for this fee mode ────────────
|
|
98
|
+
console.log("\n── Step 2: Approving token spend ──");
|
|
99
|
+
const approvalTarget = getLimitOrderApprovalTarget(
|
|
100
|
+
NETWORK,
|
|
101
|
+
order.feeOnOutput,
|
|
102
|
+
{ limitOrderContract: liveConfig.limitOrderContract },
|
|
103
|
+
);
|
|
104
|
+
const token = new ethers.Contract(TOKEN_IN, ERC20_ABI, signer);
|
|
105
|
+
const currentAllowance: bigint = await token.allowance(maker, approvalTarget);
|
|
106
|
+
|
|
107
|
+
if (currentAllowance < BigInt(AMOUNT_IN)) {
|
|
108
|
+
console.log(`Approving ${approvalTarget} to spend ${AMOUNT_IN} wei...`);
|
|
109
|
+
const approveTx = await token.approve(approvalTarget, AMOUNT_IN);
|
|
110
|
+
const approveReceipt = await approveTx.wait();
|
|
111
|
+
console.log(`Approved in tx: ${approveReceipt.hash}`);
|
|
112
|
+
} else {
|
|
113
|
+
console.log("Sufficient allowance already granted");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Step 3: Sign the order via EIP-712 ────────────────────────────────
|
|
117
|
+
console.log("\n── Step 3: Signing order (EIP-712) ──");
|
|
118
|
+
const { domain, types } = getNetworkEIP712SigningParams(
|
|
119
|
+
NETWORK,
|
|
120
|
+
liveConfig.limitOrderContract,
|
|
121
|
+
);
|
|
122
|
+
const signature = await signer.signTypedData(domain, types, order);
|
|
123
|
+
console.log(`Signature: ${signature.slice(0, 20)}...`);
|
|
124
|
+
|
|
125
|
+
// ── Step 4: Submit signedorder to the Switch backend ─────────────────
|
|
126
|
+
console.log("\n── Step 4: Submitting order to backend ──");
|
|
127
|
+
const result = await submitLimitOrder(
|
|
128
|
+
{ ...order, signature, limitOrderContract: liveConfig.limitOrderContract },
|
|
129
|
+
{ network: NETWORK },
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if ("error" in result) {
|
|
133
|
+
throw new Error(`Submit failed: ${result.error}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(`Order created! ID: ${result.order.id}`);
|
|
137
|
+
console.log(`Status: ${result.order.status}`);
|
|
138
|
+
|
|
139
|
+
// ── Step 5: Query orders ──────────────────────────────────────────────
|
|
140
|
+
console.log("\n── Step 5: Querying orders ──");
|
|
141
|
+
|
|
142
|
+
// Fetch all active orders for this maker
|
|
143
|
+
const { orders, total } = await fetchLimitOrders({
|
|
144
|
+
maker,
|
|
145
|
+
status: "ACTIVE",
|
|
146
|
+
network: NETWORK,
|
|
147
|
+
});
|
|
148
|
+
console.log(`Active orders for ${maker}: ${total}`);
|
|
149
|
+
for (const o of orders) {
|
|
150
|
+
console.log(` [nonce=${o.nonce}] ${o.tokenIn} → ${o.tokenOut} | ${o.amountIn} → min ${o.minAmountOut}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Fetch the specific order we just created
|
|
154
|
+
const fetched = await fetchLimitOrder(maker, order.nonce, { network: NETWORK });
|
|
155
|
+
if ("error" in fetched) {
|
|
156
|
+
console.log(` Lookup failed: ${fetched.error}`);
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`\nFetched order: nonce=${fetched.nonce}, status=${fetched.status}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check active pairs
|
|
162
|
+
const pairs = await fetchLimitOrderPairs({ network: NETWORK });
|
|
163
|
+
console.log(`\nActive pairs: ${pairs.length}`);
|
|
164
|
+
for (const p of pairs) {
|
|
165
|
+
console.log(` ${p.pairKey} — ${p.activeOrders} orders`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Global stats
|
|
169
|
+
const stats = await fetchLimitOrderStats({ network: NETWORK });
|
|
170
|
+
console.log(`\nStats: active=${stats.active}, filled=${stats.filled}, cancelled=${stats.cancelled}, expired=${stats.expired}`);
|
|
171
|
+
|
|
172
|
+
// ── Step 6: Cancel the order ──────────────────────────────────────────
|
|
173
|
+
console.log("\n── Step 6: Cancelling order ──");
|
|
174
|
+
|
|
175
|
+
// Cancel on-chain (prevents the filler bot from executing it). Use the
|
|
176
|
+
// per-order contract so older orders remain cancellable after upgrades.
|
|
177
|
+
console.log("Invalidating nonce on-chain...");
|
|
178
|
+
const limitOrderContract = new ethers.Contract(
|
|
179
|
+
result.order.limitOrderContract ?? liveConfig.limitOrderContract,
|
|
180
|
+
LIMIT_ORDER_ABI,
|
|
181
|
+
signer,
|
|
182
|
+
);
|
|
183
|
+
const cancelTx = await limitOrderContract.invalidateNonce(order.nonce);
|
|
184
|
+
const cancelReceipt = await cancelTx.wait();
|
|
185
|
+
console.log(`Nonce invalidated in tx: ${cancelReceipt.hash}`);
|
|
186
|
+
|
|
187
|
+
// The backend indexer observes NonceCancelled and updates order status.
|
|
188
|
+
|
|
189
|
+
console.log("\nDone!");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main().catch((err) => {
|
|
193
|
+
console.error(err);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch DEX Aggregator — Next.js API Route Proxy
|
|
3
|
+
*
|
|
4
|
+
* Keeps your API key server-side. Your frontend calls YOUR endpoint,
|
|
5
|
+
* which attaches the key and forwards the request to Switch.
|
|
6
|
+
*
|
|
7
|
+
* File: app/api/swap/[...path]/route.ts (Next.js App Router catch-all)
|
|
8
|
+
*
|
|
9
|
+
* Proxied routes:
|
|
10
|
+
* /api/swap/quote → /swap/quote (get a swap quote)
|
|
11
|
+
* /api/swap/adapters → /swap/adapters (list DEX adapters)
|
|
12
|
+
* /api/swap/checkTax → /swap/checkTax (check token buy/sell tax)
|
|
13
|
+
*
|
|
14
|
+
* Frontend usage:
|
|
15
|
+
* const quote = await fetch("/api/swap/quote?network=pulsechain&from=0x...&to=0x...&amount=1000&sender=0x...");
|
|
16
|
+
* const adapters = await fetch("/api/swap/adapters?network=pulsechain");
|
|
17
|
+
* const tax = await fetch("/api/swap/checkTax?token=0x...&network=pulsechain");
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const SWITCH_API_KEY = process.env.SWITCH_API_KEY;
|
|
21
|
+
const SWITCH_API_BASE =
|
|
22
|
+
process.env.SWITCH_API_BASE ?? "https://quote.switch.win";
|
|
23
|
+
|
|
24
|
+
/** Allowed upstream paths (whitelist to prevent open-proxy abuse). */
|
|
25
|
+
const ALLOWED_PATHS = new Set(["quote", "adapters", "checkTax"]);
|
|
26
|
+
|
|
27
|
+
if (!SWITCH_API_KEY) {
|
|
28
|
+
console.warn(
|
|
29
|
+
"⚠️ SWITCH_API_KEY is not set — swap proxy will not work.",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function GET(
|
|
34
|
+
request: Request,
|
|
35
|
+
{ params }: { params: { path: string[] } },
|
|
36
|
+
) {
|
|
37
|
+
if (!SWITCH_API_KEY) {
|
|
38
|
+
return Response.json(
|
|
39
|
+
{ error: "Server misconfiguration: API key not set" },
|
|
40
|
+
{ status: 500 },
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Resolve the sub-path, e.g. ["quote"] or ["checkTax"]
|
|
45
|
+
const subPath = params.path?.join("/") ?? "";
|
|
46
|
+
|
|
47
|
+
if (!ALLOWED_PATHS.has(subPath)) {
|
|
48
|
+
return Response.json(
|
|
49
|
+
{ error: `Unknown endpoint: /swap/${subPath}` },
|
|
50
|
+
{ status: 404 },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { searchParams } = new URL(request.url);
|
|
55
|
+
|
|
56
|
+
// Build the upstream URL and forward all query params
|
|
57
|
+
const url = new URL(`/swap/${subPath}`, SWITCH_API_BASE);
|
|
58
|
+
searchParams.forEach((value, key) => url.searchParams.set(key, value));
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const upstream = await fetch(url.toString(), {
|
|
62
|
+
headers: { "x-api-key": SWITCH_API_KEY },
|
|
63
|
+
// Don't cache on the edge — quote freshness matters
|
|
64
|
+
cache: "no-store",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const data = await upstream.json();
|
|
68
|
+
|
|
69
|
+
return Response.json(data, {
|
|
70
|
+
status: upstream.status,
|
|
71
|
+
headers: {
|
|
72
|
+
"Cache-Control": "no-store, max-age=0",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error("Switch API proxy error:", err);
|
|
77
|
+
return Response.json(
|
|
78
|
+
{ error: "Failed to reach Switch API" },
|
|
79
|
+
{ status: 502 },
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|