@zoralabs/protocol-sdk 0.10.0 → 0.11.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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +11 -0
- package/dist/apis/multicall3.d.ts +1 -1
- package/dist/apis/multicall3.d.ts.map +1 -1
- package/dist/create/1155-create-helper.test.d.ts +3 -0
- package/dist/create/1155-create-helper.test.d.ts.map +1 -0
- package/dist/fixtures/contract-setup.d.ts +16 -0
- package/dist/fixtures/contract-setup.d.ts.map +1 -0
- package/dist/index.cjs +538 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +556 -28
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +4 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/secondary/conversions.d.ts +7 -0
- package/dist/secondary/conversions.d.ts.map +1 -0
- package/dist/secondary/secondary-client.d.ts +67 -0
- package/dist/secondary/secondary-client.d.ts.map +1 -0
- package/dist/secondary/slippage.d.ts +25 -0
- package/dist/secondary/slippage.d.ts.map +1 -0
- package/dist/secondary/types.d.ts +37 -0
- package/dist/secondary/types.d.ts.map +1 -0
- package/dist/secondary/uniswap/abis.d.ts +152 -0
- package/dist/secondary/uniswap/abis.d.ts.map +1 -0
- package/dist/secondary/uniswap/uniswapQuote.d.ts +13 -0
- package/dist/secondary/uniswap/uniswapQuote.d.ts.map +1 -0
- package/dist/secondary/uniswap/uniswapQuoteExact.d.ts +37 -0
- package/dist/secondary/uniswap/uniswapQuoteExact.d.ts.map +1 -0
- package/dist/secondary/uniswap/uniswapReserves.d.ts +3 -0
- package/dist/secondary/uniswap/uniswapReserves.d.ts.map +1 -0
- package/dist/secondary/utils.d.ts +10 -0
- package/dist/secondary/utils.d.ts.map +1 -0
- package/dist/sparks/sparks-contracts.d.ts.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/apis/multicall3.ts +1 -1
- package/src/fixtures/contract-setup.ts +57 -0
- package/src/rewards/rewards-client.test.ts +15 -111
- package/src/sdk.ts +11 -0
- package/src/secondary/conversions.ts +20 -0
- package/src/secondary/secondary-client.test.ts +276 -0
- package/src/secondary/secondary-client.ts +374 -0
- package/src/secondary/slippage.ts +42 -0
- package/src/secondary/types.ts +64 -0
- package/src/secondary/uniswap/abis.ts +20 -0
- package/src/secondary/uniswap/uniswapQuote.ts +134 -0
- package/src/secondary/uniswap/uniswapQuoteExact.ts +114 -0
- package/src/secondary/uniswap/uniswapReserves.ts +34 -0
- package/src/secondary/utils.ts +40 -0
- package/src/sparks/sparks-contracts.ts +1 -3
- package/src/types.ts +6 -7
- package/src/utils.ts +7 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { Account, Address, encodeAbiParameters } from "viem";
|
|
2
|
+
import {
|
|
3
|
+
secondarySwapAddress,
|
|
4
|
+
zoraCreator1155ImplABI,
|
|
5
|
+
safeTransferSwapAbiParameters,
|
|
6
|
+
secondarySwapABI,
|
|
7
|
+
} from "@zoralabs/protocol-deployments";
|
|
8
|
+
import { makeContractParameters, PublicClient } from "src/utils";
|
|
9
|
+
import { getUniswapQuote } from "./uniswap/uniswapQuote";
|
|
10
|
+
import { calculateSlippageUp, calculateSlippageDown } from "./slippage";
|
|
11
|
+
import { getSecondaryInfo } from "./utils";
|
|
12
|
+
import { addressOrAccountAddress } from "src/utils";
|
|
13
|
+
import { SimulateContractParametersWithAccount } from "src/types";
|
|
14
|
+
import {
|
|
15
|
+
QuotePrice,
|
|
16
|
+
BuyWithSlippageInput,
|
|
17
|
+
SellWithSlippageInput,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
// uniswap's auto slippage for L2s is 0.5% -> 0.005
|
|
21
|
+
const UNISWAP_SLIPPAGE = 0.005;
|
|
22
|
+
|
|
23
|
+
// Error constants
|
|
24
|
+
const ERROR_INSUFFICIENT_WALLET_FUNDS = "Insufficient wallet funds";
|
|
25
|
+
const ERROR_INSUFFICIENT_POOL_SUPPLY = "Insufficient pool supply";
|
|
26
|
+
const ERROR_SECONDARY_NOT_CONFIGURED =
|
|
27
|
+
"Secondary not configured for given contract and token";
|
|
28
|
+
export const ERROR_SECONDARY_NOT_STARTED = "Secondary market has not started";
|
|
29
|
+
|
|
30
|
+
// Helper function to create error objects
|
|
31
|
+
function makeError(errorMessage: string) {
|
|
32
|
+
return { error: errorMessage };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Call =
|
|
36
|
+
| {
|
|
37
|
+
// Parameters for the buy or sell transaction
|
|
38
|
+
parameters: SimulateContractParametersWithAccount;
|
|
39
|
+
error?: undefined;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
parameters?: undefined;
|
|
43
|
+
// Error message if the buy or sell operation cannot be performed
|
|
44
|
+
error: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
async function makeBuy({
|
|
48
|
+
erc20z,
|
|
49
|
+
poolBalance,
|
|
50
|
+
amount,
|
|
51
|
+
quantity,
|
|
52
|
+
account,
|
|
53
|
+
recipient,
|
|
54
|
+
chainId,
|
|
55
|
+
slippage,
|
|
56
|
+
publicClient,
|
|
57
|
+
}: {
|
|
58
|
+
erc20z: Address;
|
|
59
|
+
poolBalance: { erc20z: bigint };
|
|
60
|
+
amount: bigint;
|
|
61
|
+
quantity: bigint;
|
|
62
|
+
account: Address | Account;
|
|
63
|
+
recipient?: Address;
|
|
64
|
+
chainId: number;
|
|
65
|
+
slippage: number;
|
|
66
|
+
publicClient: PublicClient;
|
|
67
|
+
}): Promise<Call> {
|
|
68
|
+
const costWithSlippage = calculateSlippageUp(amount, slippage);
|
|
69
|
+
|
|
70
|
+
// we cannot buy all the available tokens in a pool (the quote fails if we try doing that)
|
|
71
|
+
const availableToBuy = poolBalance.erc20z / BigInt(1e18) - 1n;
|
|
72
|
+
|
|
73
|
+
const accountAddress = addressOrAccountAddress(account);
|
|
74
|
+
|
|
75
|
+
const availableToSpend = await publicClient.getBalance({
|
|
76
|
+
address: accountAddress,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (costWithSlippage > availableToSpend) {
|
|
80
|
+
return makeError(ERROR_INSUFFICIENT_WALLET_FUNDS);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (availableToBuy < BigInt(quantity)) {
|
|
84
|
+
return makeError(ERROR_INSUFFICIENT_POOL_SUPPLY);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
parameters: makeContractParameters({
|
|
89
|
+
abi: secondarySwapABI,
|
|
90
|
+
address:
|
|
91
|
+
secondarySwapAddress[chainId as keyof typeof secondarySwapAddress],
|
|
92
|
+
functionName: "buy1155",
|
|
93
|
+
args: [
|
|
94
|
+
erc20z,
|
|
95
|
+
quantity,
|
|
96
|
+
recipient || accountAddress,
|
|
97
|
+
accountAddress,
|
|
98
|
+
costWithSlippage,
|
|
99
|
+
0n,
|
|
100
|
+
],
|
|
101
|
+
account: account,
|
|
102
|
+
value: costWithSlippage,
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type BuyOrSellWithSlippageResult =
|
|
108
|
+
| ({
|
|
109
|
+
// Price to buy or sell the quantity of 1155s
|
|
110
|
+
price: QuotePrice;
|
|
111
|
+
} & Call)
|
|
112
|
+
| {
|
|
113
|
+
error: string;
|
|
114
|
+
price?: undefined;
|
|
115
|
+
parameters?: undefined;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export async function buyWithSlippage({
|
|
119
|
+
contract,
|
|
120
|
+
tokenId,
|
|
121
|
+
publicClient,
|
|
122
|
+
quantity,
|
|
123
|
+
chainId,
|
|
124
|
+
account,
|
|
125
|
+
slippage = UNISWAP_SLIPPAGE,
|
|
126
|
+
recipient,
|
|
127
|
+
}: BuyWithSlippageInput & {
|
|
128
|
+
chainId: number;
|
|
129
|
+
publicClient: PublicClient;
|
|
130
|
+
}): Promise<BuyOrSellWithSlippageResult> {
|
|
131
|
+
const secondaryInfo = await getSecondaryInfo({
|
|
132
|
+
contract,
|
|
133
|
+
tokenId,
|
|
134
|
+
publicClient,
|
|
135
|
+
chainId,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!secondaryInfo) {
|
|
139
|
+
return makeError(ERROR_SECONDARY_NOT_CONFIGURED);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { erc20z, pool, secondaryActivated } = secondaryInfo;
|
|
143
|
+
|
|
144
|
+
if (!secondaryActivated) {
|
|
145
|
+
return makeError(ERROR_SECONDARY_NOT_STARTED);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { poolBalance, amount, price } = await getUniswapQuote(
|
|
149
|
+
{
|
|
150
|
+
type: "buy",
|
|
151
|
+
quantity,
|
|
152
|
+
poolAddress: pool,
|
|
153
|
+
erc20z,
|
|
154
|
+
chainId,
|
|
155
|
+
},
|
|
156
|
+
publicClient,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const call = await makeBuy({
|
|
160
|
+
erc20z,
|
|
161
|
+
poolBalance,
|
|
162
|
+
amount,
|
|
163
|
+
quantity,
|
|
164
|
+
account,
|
|
165
|
+
recipient,
|
|
166
|
+
chainId,
|
|
167
|
+
slippage,
|
|
168
|
+
publicClient,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...call,
|
|
173
|
+
price,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function makeSell({
|
|
178
|
+
contract,
|
|
179
|
+
tokenId,
|
|
180
|
+
poolBalance,
|
|
181
|
+
amount,
|
|
182
|
+
quantity,
|
|
183
|
+
account,
|
|
184
|
+
recipient,
|
|
185
|
+
chainId,
|
|
186
|
+
slippage,
|
|
187
|
+
publicClient,
|
|
188
|
+
}: {
|
|
189
|
+
contract: Address;
|
|
190
|
+
tokenId: bigint;
|
|
191
|
+
poolBalance: { weth: bigint };
|
|
192
|
+
amount: bigint;
|
|
193
|
+
quantity: bigint;
|
|
194
|
+
account: Address | Account;
|
|
195
|
+
recipient?: Address;
|
|
196
|
+
chainId: number;
|
|
197
|
+
slippage: number;
|
|
198
|
+
publicClient: PublicClient;
|
|
199
|
+
}): Promise<Call> {
|
|
200
|
+
const accountAddress = addressOrAccountAddress(account);
|
|
201
|
+
|
|
202
|
+
const tokenCount = await publicClient.readContract({
|
|
203
|
+
abi: zoraCreator1155ImplABI,
|
|
204
|
+
address: contract,
|
|
205
|
+
functionName: "balanceOf",
|
|
206
|
+
args: [accountAddress, tokenId],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const availableToSell = tokenCount ?? 0n;
|
|
210
|
+
const availableToReceive = poolBalance.weth;
|
|
211
|
+
|
|
212
|
+
if (quantity > availableToSell) {
|
|
213
|
+
return makeError(ERROR_INSUFFICIENT_WALLET_FUNDS);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (amount > availableToReceive) {
|
|
217
|
+
return makeError(ERROR_INSUFFICIENT_POOL_SUPPLY);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const receivedWithSlippage = calculateSlippageDown(amount, slippage);
|
|
221
|
+
|
|
222
|
+
const data = encodeAbiParameters(safeTransferSwapAbiParameters, [
|
|
223
|
+
recipient || accountAddress,
|
|
224
|
+
receivedWithSlippage,
|
|
225
|
+
0n,
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
parameters: makeContractParameters({
|
|
230
|
+
abi: zoraCreator1155ImplABI,
|
|
231
|
+
address: contract,
|
|
232
|
+
functionName: "safeTransferFrom",
|
|
233
|
+
account,
|
|
234
|
+
args: [
|
|
235
|
+
accountAddress,
|
|
236
|
+
secondarySwapAddress[chainId as keyof typeof secondarySwapAddress],
|
|
237
|
+
tokenId,
|
|
238
|
+
quantity,
|
|
239
|
+
data,
|
|
240
|
+
],
|
|
241
|
+
}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function sellWithSlippage({
|
|
246
|
+
contract,
|
|
247
|
+
tokenId,
|
|
248
|
+
publicClient,
|
|
249
|
+
quantity,
|
|
250
|
+
chainId,
|
|
251
|
+
account,
|
|
252
|
+
slippage = UNISWAP_SLIPPAGE,
|
|
253
|
+
recipient,
|
|
254
|
+
}: SellWithSlippageInput & {
|
|
255
|
+
chainId: number;
|
|
256
|
+
publicClient: PublicClient;
|
|
257
|
+
}): Promise<BuyOrSellWithSlippageResult> {
|
|
258
|
+
const secondaryInfo = await getSecondaryInfo({
|
|
259
|
+
contract,
|
|
260
|
+
tokenId,
|
|
261
|
+
publicClient,
|
|
262
|
+
chainId,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!secondaryInfo) {
|
|
266
|
+
return makeError(ERROR_SECONDARY_NOT_CONFIGURED);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { pool, secondaryActivated, erc20z } = secondaryInfo;
|
|
270
|
+
|
|
271
|
+
if (!secondaryActivated) {
|
|
272
|
+
return makeError(ERROR_SECONDARY_NOT_STARTED);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { poolBalance, amount, price } = await getUniswapQuote(
|
|
276
|
+
{ type: "sell", quantity, poolAddress: pool, chainId, erc20z },
|
|
277
|
+
publicClient,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const call = await makeSell({
|
|
281
|
+
contract,
|
|
282
|
+
tokenId,
|
|
283
|
+
poolBalance,
|
|
284
|
+
amount,
|
|
285
|
+
quantity,
|
|
286
|
+
account,
|
|
287
|
+
recipient,
|
|
288
|
+
chainId,
|
|
289
|
+
slippage,
|
|
290
|
+
publicClient,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
...call,
|
|
295
|
+
price,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* A client for handling secondary market operations.
|
|
301
|
+
*/
|
|
302
|
+
export class SecondaryClient {
|
|
303
|
+
private publicClient: PublicClient;
|
|
304
|
+
private chainId: number;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Creates a new SecondaryClient instance.
|
|
308
|
+
* @param publicClient - The public client for interacting with the blockchain.
|
|
309
|
+
* @param chainId - The ID of the blockchain network.
|
|
310
|
+
*/
|
|
311
|
+
constructor({
|
|
312
|
+
publicClient,
|
|
313
|
+
chainId,
|
|
314
|
+
}: {
|
|
315
|
+
publicClient: PublicClient;
|
|
316
|
+
chainId: number;
|
|
317
|
+
}) {
|
|
318
|
+
this.publicClient = publicClient;
|
|
319
|
+
this.chainId = chainId;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get the secondary info for a given contract and token ID.
|
|
324
|
+
* @param contract - The address of the contract.
|
|
325
|
+
* @param tokenId - The ID of the token.
|
|
326
|
+
* @returns A promise that resolves to the secondary info.
|
|
327
|
+
*/
|
|
328
|
+
async getSecondaryInfo({
|
|
329
|
+
contract,
|
|
330
|
+
tokenId,
|
|
331
|
+
}: {
|
|
332
|
+
contract: Address;
|
|
333
|
+
tokenId: bigint;
|
|
334
|
+
}) {
|
|
335
|
+
return getSecondaryInfo({
|
|
336
|
+
contract,
|
|
337
|
+
tokenId,
|
|
338
|
+
publicClient: this.publicClient,
|
|
339
|
+
chainId: this.chainId,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Prepares a buy operation with slippage protection for purchasing a quantity of ERC1155 tokens with ETH on the secondary market.
|
|
345
|
+
* @param input - The input parameters for the buy operation.
|
|
346
|
+
* @returns A promise that resolves to the result of the buy operation, including price breakdown and transaction parameters.
|
|
347
|
+
*/
|
|
348
|
+
async buy1155OnSecondary(
|
|
349
|
+
input: BuyWithSlippageInput,
|
|
350
|
+
): Promise<BuyOrSellWithSlippageResult> {
|
|
351
|
+
// Call the buyWithSlippage function with the provided input and client details
|
|
352
|
+
return buyWithSlippage({
|
|
353
|
+
...input,
|
|
354
|
+
publicClient: this.publicClient,
|
|
355
|
+
chainId: this.chainId,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Prepares a sell operation with slippage protection for selling a quantity of ERC1155 tokens for ETH on the secondary market.
|
|
361
|
+
* @param input - The input parameters for the sell operation.
|
|
362
|
+
* @returns A promise that resolves to the result of the sell operation, including price breakdown and transaction parameters.
|
|
363
|
+
*/
|
|
364
|
+
async sell1155OnSecondary(
|
|
365
|
+
input: SellWithSlippageInput,
|
|
366
|
+
): Promise<BuyOrSellWithSlippageResult> {
|
|
367
|
+
// Call the sellWithSlippage function with the provided input and client details
|
|
368
|
+
return sellWithSlippage({
|
|
369
|
+
...input,
|
|
370
|
+
publicClient: this.publicClient,
|
|
371
|
+
chainId: this.chainId,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const SCALE = 1e18;
|
|
2
|
+
const ONE_SCALED = BigInt(SCALE);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Increase a base amount by a slippage percentage
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This is useful when trying to calculate how much a user would at maximum pay,
|
|
9
|
+
* when allowing for the specified amount of slippage (i.e. when buying 1155s).
|
|
10
|
+
*
|
|
11
|
+
* @param amount - the amount to calculate with slippage (if a bigint is passed we assume wei, otherwise we assume ether/usd/etc)
|
|
12
|
+
* @param slippage - the slippage percantage as a fraction (i.e. 5% -> 0.05)
|
|
13
|
+
* @returns the amount increased by the slippage
|
|
14
|
+
*/
|
|
15
|
+
export function calculateSlippageUp<
|
|
16
|
+
T extends bigint | number | string,
|
|
17
|
+
R = T extends bigint ? bigint : number,
|
|
18
|
+
>(amount: T, slippage: number): R {
|
|
19
|
+
return typeof amount === "bigint"
|
|
20
|
+
? (((amount * (ONE_SCALED + BigInt(slippage * SCALE))) / ONE_SCALED) as R)
|
|
21
|
+
: ((parseFloat(String(amount)) * (1 + slippage)) as R);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decrease a base amount by a slippage percentage
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* This is useful when trying to calculate how much a user would at minimum receive,
|
|
29
|
+
* when allowing for the specified amount of slippage (i.e. when selling 1155s).
|
|
30
|
+
*
|
|
31
|
+
* @param amount - the amount to calculate with slippage (if a bigint is passed we assume wei, otherwise we assume ether/usd/etc)
|
|
32
|
+
* @param slippage - the slippage percantage as a fraction (i.e. 5% -> 0.05)
|
|
33
|
+
* @returns the amount decreased by the slippage
|
|
34
|
+
*/
|
|
35
|
+
export function calculateSlippageDown<
|
|
36
|
+
T extends bigint | number | string,
|
|
37
|
+
R = T extends bigint ? bigint : number,
|
|
38
|
+
>(amount: T, slippage: number): R {
|
|
39
|
+
return typeof amount === "bigint"
|
|
40
|
+
? (((amount * (ONE_SCALED - BigInt(slippage * SCALE))) / ONE_SCALED) as R)
|
|
41
|
+
: ((parseFloat(String(amount)) * (1 - slippage)) as R);
|
|
42
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Address, Account } from "viem";
|
|
2
|
+
|
|
3
|
+
export type PriceBreakdown = {
|
|
4
|
+
// Price per individual token
|
|
5
|
+
perToken: bigint;
|
|
6
|
+
// Total price for all tokens
|
|
7
|
+
total: bigint;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type QuotePrice = {
|
|
11
|
+
// Price breakdown in wei
|
|
12
|
+
wei: PriceBreakdown;
|
|
13
|
+
// Price breakdown in sparks
|
|
14
|
+
sparks: PriceBreakdown;
|
|
15
|
+
// Function to get price breakdown in USDC
|
|
16
|
+
usdc: () => Promise<{
|
|
17
|
+
// Price per individual token in USDC
|
|
18
|
+
perToken: number;
|
|
19
|
+
// Total price in USDC
|
|
20
|
+
total: number;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type GetQuoteOutput = {
|
|
25
|
+
// Amount needed to pay for the swap
|
|
26
|
+
amount: bigint;
|
|
27
|
+
poolBalance: {
|
|
28
|
+
// Balance of ERC20Z tokens in the pool
|
|
29
|
+
erc20z: bigint;
|
|
30
|
+
// Balance of WETH in the pool
|
|
31
|
+
weth: bigint;
|
|
32
|
+
};
|
|
33
|
+
// Detailed price information
|
|
34
|
+
price: QuotePrice;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BuyWithSlippageInput = {
|
|
38
|
+
// Address of the 1155 contract to interact with
|
|
39
|
+
contract: Address;
|
|
40
|
+
// 1155 token is to buy or sell
|
|
41
|
+
tokenId: bigint;
|
|
42
|
+
// Amount of tokens to buy or sell
|
|
43
|
+
quantity: bigint;
|
|
44
|
+
// Account to use for the transaction
|
|
45
|
+
account: Address | Account;
|
|
46
|
+
// Slippage percentage (optional), defaults to 0.005 (0.5%)
|
|
47
|
+
slippage?: number;
|
|
48
|
+
// Optional recipient address (if different from buyer/seller)
|
|
49
|
+
recipient?: Address;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Same structure as BuyWithSlippageInput
|
|
53
|
+
export type SellWithSlippageInput = BuyWithSlippageInput;
|
|
54
|
+
|
|
55
|
+
export type SecondaryInfo = {
|
|
56
|
+
// Whether the secondary market is activated
|
|
57
|
+
secondaryActivated: boolean;
|
|
58
|
+
// Address of the liquidity pool for the erc20z to WETH pair
|
|
59
|
+
pool: Address;
|
|
60
|
+
// Address of the erc20z token
|
|
61
|
+
erc20z: Address;
|
|
62
|
+
// Timestamp when the secondary market will end
|
|
63
|
+
saleEnd?: bigint;
|
|
64
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseAbi } from "viem";
|
|
2
|
+
|
|
3
|
+
export const uniswapQuoterABI = parseAbi([
|
|
4
|
+
"struct QuoteExactInputSingleParams { address tokenIn; address tokenOut; uint256 amountIn; uint24 fee; uint160 sqrtPriceLimitX96; }",
|
|
5
|
+
"struct QuoteExactOutputSingleParams { address tokenIn; address tokenOut; uint256 amount; uint24 fee; uint160 sqrtPriceLimitX96; }",
|
|
6
|
+
"function quoteExactInputSingle(QuoteExactInputSingleParams calldata params) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)",
|
|
7
|
+
"function quoteExactOutputSingle(QuoteExactOutputSingleParams calldata params) external returns (uint256 amountIn, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const uniswapV3PoolAbi = parseAbi([
|
|
11
|
+
"function fee() external view returns (uint24)",
|
|
12
|
+
"function liquidity() external view returns (uint128)",
|
|
13
|
+
"function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)",
|
|
14
|
+
"function token0() external view returns (address)",
|
|
15
|
+
"function token1() external view returns (address)",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export const uniswapV2USDCAbi = parseAbi([
|
|
19
|
+
"function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast)",
|
|
20
|
+
]);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Address, erc20Abi } from "viem";
|
|
2
|
+
import { PublicClient as PublicClientWithMulticall } from "viem";
|
|
3
|
+
import { PublicClient } from "src/utils";
|
|
4
|
+
import { getEthPriceInUSDC } from "./uniswapReserves";
|
|
5
|
+
import { convertWeiToSparks, convertWeiToUSD } from "../conversions";
|
|
6
|
+
|
|
7
|
+
import { uniswapV3PoolAbi } from "./abis";
|
|
8
|
+
import { wethAddress as wethAddresses } from "@zoralabs/protocol-deployments";
|
|
9
|
+
|
|
10
|
+
import { GetQuoteOutput } from "../types";
|
|
11
|
+
import {
|
|
12
|
+
quoteExactInputSingle,
|
|
13
|
+
quoteExactOutputSingle,
|
|
14
|
+
} from "./uniswapQuoteExact";
|
|
15
|
+
import { multicall3Address } from "src/apis/multicall3";
|
|
16
|
+
|
|
17
|
+
async function getPoolInfo({
|
|
18
|
+
poolAddress,
|
|
19
|
+
client,
|
|
20
|
+
WETH,
|
|
21
|
+
erc20z,
|
|
22
|
+
}: {
|
|
23
|
+
poolAddress: Address;
|
|
24
|
+
WETH: Address;
|
|
25
|
+
erc20z: Address;
|
|
26
|
+
client: PublicClient;
|
|
27
|
+
}) {
|
|
28
|
+
const [fee, wethBalance, erc20zBalance] = await (
|
|
29
|
+
client as PublicClientWithMulticall
|
|
30
|
+
).multicall({
|
|
31
|
+
contracts: [
|
|
32
|
+
{
|
|
33
|
+
abi: uniswapV3PoolAbi,
|
|
34
|
+
address: poolAddress,
|
|
35
|
+
functionName: "fee",
|
|
36
|
+
},
|
|
37
|
+
// get WETH and erc20z balance for pool
|
|
38
|
+
{
|
|
39
|
+
abi: erc20Abi,
|
|
40
|
+
address: WETH,
|
|
41
|
+
functionName: "balanceOf",
|
|
42
|
+
args: [poolAddress],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
abi: erc20Abi,
|
|
46
|
+
address: erc20z,
|
|
47
|
+
functionName: "balanceOf",
|
|
48
|
+
args: [poolAddress],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
multicallAddress: multicall3Address,
|
|
52
|
+
allowFailure: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
wethBalance,
|
|
57
|
+
erc20zBalance,
|
|
58
|
+
fee,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type GetQuoteInput = {
|
|
63
|
+
// Indicates whether the quote is for buying or selling
|
|
64
|
+
type: "buy" | "sell";
|
|
65
|
+
// The amount of tokens to buy or sell
|
|
66
|
+
quantity: bigint;
|
|
67
|
+
poolAddress: Address;
|
|
68
|
+
erc20z: Address;
|
|
69
|
+
chainId: number;
|
|
70
|
+
};
|
|
71
|
+
export async function getUniswapQuote(
|
|
72
|
+
input: GetQuoteInput,
|
|
73
|
+
client: PublicClient,
|
|
74
|
+
): Promise<GetQuoteOutput> {
|
|
75
|
+
const { type, quantity, poolAddress, chainId, erc20z } = input;
|
|
76
|
+
|
|
77
|
+
const WETH = wethAddresses[chainId as keyof typeof wethAddresses];
|
|
78
|
+
|
|
79
|
+
const pool = await getPoolInfo({ poolAddress, WETH, erc20z, client });
|
|
80
|
+
|
|
81
|
+
const { fee, wethBalance, erc20zBalance } = pool;
|
|
82
|
+
|
|
83
|
+
// 1 unit always comes to 1e18 erc20 equivalent
|
|
84
|
+
const amountWithDecimals = input.quantity * 10n ** 18n;
|
|
85
|
+
|
|
86
|
+
const amount =
|
|
87
|
+
type === "buy"
|
|
88
|
+
? await quoteExactOutputSingle({
|
|
89
|
+
tokenIn: WETH,
|
|
90
|
+
tokenOut: erc20z,
|
|
91
|
+
amountOut: amountWithDecimals,
|
|
92
|
+
fee,
|
|
93
|
+
chainId,
|
|
94
|
+
client,
|
|
95
|
+
})
|
|
96
|
+
: await quoteExactInputSingle({
|
|
97
|
+
tokenIn: erc20z,
|
|
98
|
+
tokenOut: WETH,
|
|
99
|
+
amountIn: amountWithDecimals,
|
|
100
|
+
fee,
|
|
101
|
+
chainId,
|
|
102
|
+
client,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const price = amount;
|
|
106
|
+
const pricePerToken = amount / BigInt(quantity);
|
|
107
|
+
|
|
108
|
+
const getUsdPrice = async () => {
|
|
109
|
+
const ethPriceInUSD = await getEthPriceInUSDC();
|
|
110
|
+
return {
|
|
111
|
+
perToken: convertWeiToUSD({ amount: pricePerToken, ethPriceInUSD }),
|
|
112
|
+
total: convertWeiToUSD({ amount: price, ethPriceInUSD }),
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
amount,
|
|
118
|
+
poolBalance: {
|
|
119
|
+
erc20z: erc20zBalance,
|
|
120
|
+
weth: wethBalance,
|
|
121
|
+
},
|
|
122
|
+
price: {
|
|
123
|
+
wei: {
|
|
124
|
+
perToken: pricePerToken,
|
|
125
|
+
total: price,
|
|
126
|
+
},
|
|
127
|
+
sparks: {
|
|
128
|
+
perToken: convertWeiToSparks(pricePerToken),
|
|
129
|
+
total: convertWeiToSparks(price),
|
|
130
|
+
},
|
|
131
|
+
usdc: getUsdPrice,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|