-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
273 lines (232 loc) · 8.36 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import { config as dotenv } from "dotenv";
import {
createWalletClient,
http,
getContract,
erc20Abi,
parseUnits,
maxUint256,
publicActions,
concat,
numberToHex,
size,
} from "viem";
import type { Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { scroll } from "viem/chains";
import { wethAbi } from "./abi/weth-abi";
/* For the 0x Challenge on Scroll, implement the following
1. Display the percentage breakdown of liquidity sources
2. Monetize your app with affiliate fees and surplus collection
3. Display buy/sell tax for tokens with tax
4. Display all sources of liquidity on Scroll
*/
const qs = require("qs");
// Load environment variables
dotenv();
const { PRIVATE_KEY, ZERO_EX_API_KEY, ALCHEMY_HTTP_TRANSPORT_URL } =
process.env;
// Validate requirements
if (!PRIVATE_KEY) throw new Error("missing PRIVATE_KEY.");
if (!ZERO_EX_API_KEY) throw new Error("missing ZERO_EX_API_KEY.");
if (!ALCHEMY_HTTP_TRANSPORT_URL)
throw new Error("missing ALCHEMY_HTTP_TRANSPORT_URL.");
// Fetch headers
const headers = new Headers({
"Content-Type": "application/json",
"0x-api-key": ZERO_EX_API_KEY,
"0x-version": "v2",
});
// Setup wallet client
const client = createWalletClient({
account: privateKeyToAccount(`0x${PRIVATE_KEY}` as `0x${string}`),
chain: scroll,
transport: http(ALCHEMY_HTTP_TRANSPORT_URL),
}).extend(publicActions); // Extend wallet client with publicActions for public client
const [address] = await client.getAddresses();
// Set up contracts
const weth = getContract({
address: "0x5300000000000000000000000000000000000004",
abi: wethAbi,
client,
});
const wsteth = getContract({
address: "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32",
abi: erc20Abi,
client,
});
// Function to display the percentage breakdown of liquidity sources
function displayLiquiditySources(route: any) {
const fills = route.fills;
const totalBps = fills.reduce((acc: number, fill: any) => acc + parseInt(fill.proportionBps), 0);
console.log(`${fills.length} Sources`);
fills.forEach((fill: any) => {
const percentage = (parseInt(fill.proportionBps) / 100).toFixed(2);
console.log(`${fill.source}: ${percentage}%`);
});
}
// Function to display the buy/sell taxes for tokens
function displayTokenTaxes(tokenMetadata: any) {
const buyTokenBuyTax = (parseInt(tokenMetadata.buyToken.buyTaxBps) / 100).toFixed(2);
const buyTokenSellTax = (parseInt(tokenMetadata.buyToken.sellTaxBps) / 100).toFixed(2);
const sellTokenBuyTax = (parseInt(tokenMetadata.sellToken.buyTaxBps) / 100).toFixed(2);
const sellTokenSellTax = (parseInt(tokenMetadata.sellToken.sellTaxBps) / 100).toFixed(2);
if (buyTokenBuyTax > 0 || buyTokenSellTax > 0) {
console.log(`Buy Token Buy Tax: ${buyTokenBuyTax}%`);
console.log(`Buy Token Sell Tax: ${buyTokenSellTax}%`);
}
if (sellTokenBuyTax > 0 || sellTokenSellTax > 0) {
console.log(`Sell Token Buy Tax: ${sellTokenBuyTax}%`);
console.log(`Sell Token Sell Tax: ${sellTokenSellTax}%`);
}
}
// Function to display all liquidity sources on Scroll
const getLiquiditySources = async () => {
const chainId = client.chain.id.toString(); // Ensure this is the correct ID for Scroll
const sourcesParams = new URLSearchParams({
chainId: chainId,
});
const sourcesResponse = await fetch(
`https://api.0x.org/swap/v1/sources?${sourcesParams.toString()}`,
{
headers,
}
);
const sourcesData = await sourcesResponse.json();
const sources = Object.keys(sourcesData.sources);
console.log("Liquidity sources for Scroll chain:");
console.log(sources.join(", "));
};
const main = async () => {
// 4. Display all liquidity sources on Scroll
await getLiquiditySources();
// Specify sell amount
const decimals = (await weth.read.decimals()) as number;
const sellAmount = parseUnits("0.1", decimals);
// 2. Add parameters for affiliate fees and surplus collection
const affiliateFeeBps = "100"; // 1%
const surplusCollection = "true";
// 1. Fetch price with monetization parameters
const priceParams = new URLSearchParams({
chainId: client.chain.id.toString(),
sellToken: weth.address,
buyToken: wsteth.address,
sellAmount: sellAmount.toString(),
taker: client.account.address,
affiliateFee: affiliateFeeBps, // Parameter for affiliate fees
surplusCollection: surplusCollection, // Parameter for surplus collection
});
const priceResponse = await fetch(
"https://api.0x.org/swap/permit2/price?" + priceParams.toString(),
{
headers,
}
);
const price = await priceResponse.json();
console.log("Fetching price to swap 0.1 WETH for wstETH");
console.log(
`https://api.0x.org/swap/permit2/price?${priceParams.toString()}`
);
console.log("priceResponse: ", price);
// 2. Check if taker needs to set an allowance for Permit2
if (price.issues.allowance !== null) {
try {
const { request } = await weth.simulate.approve([
price.issues.allowance.spender,
maxUint256,
]);
console.log("Approving Permit2 to spend WETH...", request);
// Set approval
const hash = await weth.write.approve(request.args);
console.log(
"Approved Permit2 to spend WETH.",
await client.waitForTransactionReceipt({ hash })
);
} catch (error) {
console.log("Error approving Permit2:", error);
}
} else {
console.log("WETH already approved for Permit2");
}
// 3. Fetch quote with monetization parameters
const quoteParams = new URLSearchParams();
for (const [key, value] of priceParams.entries()) {
quoteParams.append(key, value);
}
const quoteResponse = await fetch(
"https://api.0x.org/swap/permit2/quote?" + quoteParams.toString(),
{
headers,
}
);
const quote = await quoteResponse.json();
console.log("Fetching quote to swap 0.1 WETH for wstETH");
console.log("quoteResponse: ", quote);
// 1. Display the percentage breakdown of liquidity sources
if (quote.route) {
displayLiquiditySources(quote.route);
}
// 3. Display the buy/sell taxes for tokens
if (quote.tokenMetadata) {
displayTokenTaxes(quote.tokenMetadata);
}
// 2. Display monetization information
if (quote.affiliateFeeBps) {
const affiliateFee = (parseInt(quote.affiliateFeeBps) / 100).toFixed(2);
console.log(`Affiliate Fee: ${affiliateFee}%`);
}
if (quote.tradeSurplus && parseFloat(quote.tradeSurplus) > 0) {
console.log(`Trade Surplus Collected: ${quote.tradeSurplus}`);
}
// 4. Sign permit2.eip712 returned from quote
let signature: Hex | undefined;
if (quote.permit2?.eip712) {
try {
signature = await client.signTypedData(quote.permit2.eip712);
console.log("Signed permit2 message from quote response");
} catch (error) {
console.error("Error signing permit2 coupon:", error);
}
// 5. Append sig length and sig data to transaction.data
if (signature && quote?.transaction?.data) {
const signatureLengthInHex = numberToHex(size(signature), {
signed: false,
size: 32,
});
const transactionData = quote.transaction.data as Hex;
const sigLengthHex = signatureLengthInHex as Hex;
const sig = signature as Hex;
quote.transaction.data = concat([transactionData, sigLengthHex, sig]);
} else {
throw new Error("Failed to obtain signature or transaction data");
}
}
// 6. Submit transaction with permit2 signature
if (signature && quote.transaction.data) {
const nonce = await client.getTransactionCount({
address: client.account.address,
});
const signedTransaction = await client.signTransaction({
account: client.account,
chain: client.chain,
gas: quote?.transaction.gas ? BigInt(quote.transaction.gas) : undefined,
to: quote?.transaction.to,
data: quote.transaction.data,
value: quote?.transaction.value
? BigInt(quote.transaction.value)
: undefined, // value is used for native tokens
gasPrice: quote?.transaction.gasPrice
? BigInt(quote.transaction.gasPrice)
: undefined,
nonce: nonce,
});
const hash = await client.sendRawTransaction({
serializedTransaction: signedTransaction,
});
console.log("Transaction hash:", hash);
console.log(`See tx details at https://scrollscan.com/tx/${hash}`);
} else {
console.error("Failed to obtain a signature, transaction not sent.");
}
};
main();