@zoralabs/protocol-sdk 0.9.6 → 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 +17 -0
- package/dist/anvil.d.ts +11 -1
- package/dist/anvil.d.ts.map +1 -1
- package/dist/apis/multicall3.d.ts +9 -0
- package/dist/apis/multicall3.d.ts.map +1 -0
- package/dist/apis/subgraph-getter.d.ts +9 -0
- package/dist/apis/subgraph-getter.d.ts.map +1 -0
- 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/create/contract-getter.d.ts +3 -6
- package/dist/create/contract-getter.d.ts.map +1 -1
- package/dist/fixtures/contract-setup.d.ts +16 -0
- package/dist/fixtures/contract-setup.d.ts.map +1 -0
- package/dist/fixtures/mint-query-results.d.ts +10 -0
- package/dist/fixtures/mint-query-results.d.ts.map +1 -0
- package/dist/fixtures/rewards-query-results.d.ts +6 -0
- package/dist/fixtures/rewards-query-results.d.ts.map +1 -0
- package/dist/index.cjs +989 -238
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1013 -238
- package/dist/index.js.map +1 -1
- package/dist/mint/subgraph-mint-getter.d.ts +3 -5
- package/dist/mint/subgraph-mint-getter.d.ts.map +1 -1
- package/dist/rewards/rewards-client.d.ts +34 -0
- package/dist/rewards/rewards-client.d.ts.map +1 -0
- package/dist/rewards/rewards-queries.d.ts +34 -0
- package/dist/rewards/rewards-queries.d.ts.map +1 -0
- package/dist/rewards/subgraph-queries.d.ts +20 -0
- package/dist/rewards/subgraph-queries.d.ts.map +1 -0
- package/dist/rewards/subgraph-rewards-getter.d.ts +15 -0
- package/dist/rewards/subgraph-rewards-getter.d.ts.map +1 -0
- package/dist/sdk.d.ts +9 -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 +19 -0
- package/src/apis/subgraph-getter.ts +33 -0
- package/src/create/1155-create-helper.test.ts +18 -18
- package/src/create/contract-getter.ts +7 -29
- package/src/fixtures/contract-setup.ts +57 -0
- package/src/fixtures/mint-query-results.ts +55 -0
- package/src/fixtures/rewards-query-results.ts +25 -0
- package/src/mint/mint-client.test.ts +18 -46
- package/src/mint/subgraph-mint-getter.ts +7 -27
- package/src/rewards/rewards-client.test.ts +310 -0
- package/src/rewards/rewards-client.ts +67 -0
- package/src/rewards/rewards-queries.ts +253 -0
- package/src/rewards/subgraph-queries.ts +49 -0
- package/src/rewards/subgraph-rewards-getter.ts +33 -0
- package/src/sdk.ts +29 -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,310 @@
|
|
|
1
|
+
import { describe, expect, vi } from "vitest";
|
|
2
|
+
import { encodeAbiParameters, erc20Abi, parseEther } from "viem";
|
|
3
|
+
import { zoraSepolia } from "viem/chains";
|
|
4
|
+
import {
|
|
5
|
+
forkUrls,
|
|
6
|
+
makeAnvilTest,
|
|
7
|
+
simulateAndWriteContractWithRetries,
|
|
8
|
+
} from "src/anvil";
|
|
9
|
+
import { createCollectorClient } from "src/sdk";
|
|
10
|
+
import { new1155ContractVersion } from "src/create/contract-setup";
|
|
11
|
+
import { ISubgraphQuerier } from "src/apis/subgraph-querier";
|
|
12
|
+
import { mockTimedSaleStrategyTokenQueryResult } from "src/fixtures/mint-query-results";
|
|
13
|
+
import {
|
|
14
|
+
secondarySwapABI,
|
|
15
|
+
secondarySwapAddress,
|
|
16
|
+
zoraCreator1155ImplABI,
|
|
17
|
+
} from "@zoralabs/protocol-deployments";
|
|
18
|
+
import { makeContractParameters } from "src/utils";
|
|
19
|
+
import { mockRewardsQueryResults } from "src/fixtures/rewards-query-results";
|
|
20
|
+
import { setupContractAndToken } from "src/fixtures/contract-setup";
|
|
21
|
+
import { advanceToSaleAndAndLaunchMarket } from "src/secondary/secondary-client.test";
|
|
22
|
+
|
|
23
|
+
describe("rewardsClient", () => {
|
|
24
|
+
makeAnvilTest({
|
|
25
|
+
forkBlockNumber: 13914833,
|
|
26
|
+
forkUrl: forkUrls.zoraSepolia,
|
|
27
|
+
anvilChainId: zoraSepolia.id,
|
|
28
|
+
})(
|
|
29
|
+
"it can view and withdraw rewards for mints",
|
|
30
|
+
async ({
|
|
31
|
+
viemClients: { publicClient, chain, walletClient, testClient },
|
|
32
|
+
}) => {
|
|
33
|
+
const creatorAccount = (await walletClient.getAddresses()!)[0]!;
|
|
34
|
+
const collectorAccount = (await walletClient.getAddresses()!)[1]!;
|
|
35
|
+
|
|
36
|
+
const { creatorClient, contractAddress, newTokenId, mintGetter } =
|
|
37
|
+
await setupContractAndToken({
|
|
38
|
+
chain,
|
|
39
|
+
publicClient,
|
|
40
|
+
creatorAccount,
|
|
41
|
+
walletClient,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await testClient.setBalance({
|
|
45
|
+
address: collectorAccount,
|
|
46
|
+
value: parseEther("10"),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const quantityToMint = 10n;
|
|
50
|
+
|
|
51
|
+
mintGetter.subgraphQuerier.query = vi
|
|
52
|
+
.fn<ISubgraphQuerier["query"]>()
|
|
53
|
+
.mockResolvedValue({
|
|
54
|
+
zoraCreateToken: mockTimedSaleStrategyTokenQueryResult({
|
|
55
|
+
chainId: chain.id,
|
|
56
|
+
tokenId: newTokenId,
|
|
57
|
+
contractAddress,
|
|
58
|
+
contractVersion:
|
|
59
|
+
new1155ContractVersion[
|
|
60
|
+
chain.id as keyof typeof new1155ContractVersion
|
|
61
|
+
],
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const collectorClient = createCollectorClient({
|
|
66
|
+
chainId: chain.id,
|
|
67
|
+
publicClient,
|
|
68
|
+
mintGetter,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const { parameters: collectParameters } = await collectorClient.mint({
|
|
72
|
+
minterAccount: collectorAccount,
|
|
73
|
+
mintType: "1155",
|
|
74
|
+
quantityToMint,
|
|
75
|
+
tokenId: newTokenId,
|
|
76
|
+
tokenContract: contractAddress,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await simulateAndWriteContractWithRetries({
|
|
80
|
+
parameters: collectParameters,
|
|
81
|
+
walletClient,
|
|
82
|
+
publicClient,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const rewardsBalance = await creatorClient.getRewardsBalances({
|
|
86
|
+
account: creatorAccount,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// creator reward is
|
|
90
|
+
const expectedRewardsBalance = quantityToMint * parseEther("0.0000555");
|
|
91
|
+
|
|
92
|
+
expect(rewardsBalance.protocolRewards).toEqual(expectedRewardsBalance);
|
|
93
|
+
|
|
94
|
+
const beforeBalance = await publicClient.getBalance({
|
|
95
|
+
address: creatorAccount,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const { parameters: withdrawParams } =
|
|
99
|
+
await creatorClient.withdrawRewards({
|
|
100
|
+
account: collectorAccount,
|
|
101
|
+
withdrawFor: creatorAccount,
|
|
102
|
+
claimSecondaryRoyalties: false,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await simulateAndWriteContractWithRetries({
|
|
106
|
+
parameters: withdrawParams,
|
|
107
|
+
walletClient,
|
|
108
|
+
publicClient,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const afterBalance = await publicClient.getBalance({
|
|
112
|
+
address: creatorAccount,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(afterBalance - beforeBalance).toBe(expectedRewardsBalance);
|
|
116
|
+
},
|
|
117
|
+
30_000,
|
|
118
|
+
);
|
|
119
|
+
makeAnvilTest({
|
|
120
|
+
forkBlockNumber: 13914833,
|
|
121
|
+
forkUrl: forkUrls.zoraSepolia,
|
|
122
|
+
anvilChainId: zoraSepolia.id,
|
|
123
|
+
})(
|
|
124
|
+
"it can view and withdraw rewards and secondary royalties for mints",
|
|
125
|
+
async ({
|
|
126
|
+
viemClients: { publicClient, chain, walletClient, testClient },
|
|
127
|
+
}) => {
|
|
128
|
+
const creatorAccount = (await walletClient.getAddresses()!)[0]!;
|
|
129
|
+
const collectorAccount = (await walletClient.getAddresses()!)[1]!;
|
|
130
|
+
|
|
131
|
+
const {
|
|
132
|
+
creatorClient,
|
|
133
|
+
contractAddress,
|
|
134
|
+
newTokenId,
|
|
135
|
+
mintGetter,
|
|
136
|
+
rewardsGetter,
|
|
137
|
+
} = await setupContractAndToken({
|
|
138
|
+
chain,
|
|
139
|
+
publicClient,
|
|
140
|
+
creatorAccount,
|
|
141
|
+
walletClient,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await testClient.setBalance({
|
|
145
|
+
address: collectorAccount,
|
|
146
|
+
value: parseEther("100"),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const quantityToMint = 10_000n;
|
|
150
|
+
|
|
151
|
+
mintGetter.subgraphQuerier.query = vi
|
|
152
|
+
.fn<ISubgraphQuerier["query"]>()
|
|
153
|
+
.mockResolvedValue({
|
|
154
|
+
zoraCreateToken: mockTimedSaleStrategyTokenQueryResult({
|
|
155
|
+
chainId: chain.id,
|
|
156
|
+
tokenId: newTokenId,
|
|
157
|
+
contractAddress,
|
|
158
|
+
contractVersion:
|
|
159
|
+
new1155ContractVersion[
|
|
160
|
+
chain.id as keyof typeof new1155ContractVersion
|
|
161
|
+
],
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const collectorClient = createCollectorClient({
|
|
166
|
+
chainId: chain.id,
|
|
167
|
+
publicClient,
|
|
168
|
+
mintGetter,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const { parameters: collectParameters } = await collectorClient.mint({
|
|
172
|
+
minterAccount: collectorAccount,
|
|
173
|
+
mintType: "1155",
|
|
174
|
+
quantityToMint,
|
|
175
|
+
tokenId: newTokenId,
|
|
176
|
+
tokenContract: contractAddress,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await simulateAndWriteContractWithRetries({
|
|
180
|
+
parameters: collectParameters,
|
|
181
|
+
walletClient,
|
|
182
|
+
publicClient,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await advanceToSaleAndAndLaunchMarket({
|
|
186
|
+
chainId: chain.id,
|
|
187
|
+
account: collectorAccount,
|
|
188
|
+
publicClient,
|
|
189
|
+
walletClient,
|
|
190
|
+
collectorClient,
|
|
191
|
+
testClient,
|
|
192
|
+
contractAddress,
|
|
193
|
+
tokenId: newTokenId,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const erc20z = (await collectorClient.getSecondaryInfo({
|
|
197
|
+
contract: contractAddress,
|
|
198
|
+
tokenId: newTokenId,
|
|
199
|
+
}))!.erc20z!;
|
|
200
|
+
|
|
201
|
+
// after market is launched, by 100 from the pool. there should be some rewards
|
|
202
|
+
// balances from secondary royalties
|
|
203
|
+
await simulateAndWriteContractWithRetries({
|
|
204
|
+
parameters: makeContractParameters({
|
|
205
|
+
abi: secondarySwapABI,
|
|
206
|
+
address:
|
|
207
|
+
secondarySwapAddress[chain.id as keyof typeof secondarySwapAddress],
|
|
208
|
+
functionName: "buy1155",
|
|
209
|
+
args: [
|
|
210
|
+
erc20z,
|
|
211
|
+
100n,
|
|
212
|
+
collectorAccount,
|
|
213
|
+
collectorAccount,
|
|
214
|
+
parseEther("1"),
|
|
215
|
+
0n,
|
|
216
|
+
],
|
|
217
|
+
account: collectorAccount,
|
|
218
|
+
value: parseEther("1"),
|
|
219
|
+
}),
|
|
220
|
+
walletClient,
|
|
221
|
+
publicClient,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const abiParameters = [
|
|
225
|
+
{ name: "recipient", internalType: "address payable", type: "address" },
|
|
226
|
+
{ name: "minEthToAcquire", internalType: "uint256", type: "uint256" },
|
|
227
|
+
{ name: "sqrtPriceLimitX96", internalType: "uint160", type: "uint160" },
|
|
228
|
+
] as const;
|
|
229
|
+
const sellData = encodeAbiParameters(abiParameters, [
|
|
230
|
+
collectorAccount,
|
|
231
|
+
0n,
|
|
232
|
+
0n,
|
|
233
|
+
]);
|
|
234
|
+
await simulateAndWriteContractWithRetries({
|
|
235
|
+
parameters: makeContractParameters({
|
|
236
|
+
functionName: "safeTransferFrom",
|
|
237
|
+
address: contractAddress,
|
|
238
|
+
abi: zoraCreator1155ImplABI,
|
|
239
|
+
account: collectorAccount,
|
|
240
|
+
args: [
|
|
241
|
+
collectorAccount,
|
|
242
|
+
secondarySwapAddress[chain.id as keyof typeof secondarySwapAddress],
|
|
243
|
+
newTokenId,
|
|
244
|
+
100n,
|
|
245
|
+
sellData,
|
|
246
|
+
],
|
|
247
|
+
}),
|
|
248
|
+
walletClient,
|
|
249
|
+
publicClient,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// now we should be able to get rewards balances for these royalties
|
|
253
|
+
|
|
254
|
+
// we need to stub the subgraph return
|
|
255
|
+
rewardsGetter.subgraphQuerier.query = vi
|
|
256
|
+
.fn<ISubgraphQuerier["query"]>()
|
|
257
|
+
.mockResolvedValue(
|
|
258
|
+
mockRewardsQueryResults({
|
|
259
|
+
erc20z: [erc20z],
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const rewardsBalance = await creatorClient.getRewardsBalances({
|
|
264
|
+
account: creatorAccount,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(rewardsBalance.secondaryRoyalties.eth).toBeGreaterThan(0);
|
|
268
|
+
expect(rewardsBalance.secondaryRoyalties.erc20[erc20z]).toBeGreaterThan(
|
|
269
|
+
0,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const beforeBalance = await publicClient.getBalance({
|
|
273
|
+
address: creatorAccount,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// it can withdraw all rewards
|
|
277
|
+
await simulateAndWriteContractWithRetries({
|
|
278
|
+
parameters: (
|
|
279
|
+
await creatorClient.withdrawRewards({
|
|
280
|
+
account: collectorAccount,
|
|
281
|
+
withdrawFor: creatorAccount,
|
|
282
|
+
claimSecondaryRoyalties: true,
|
|
283
|
+
})
|
|
284
|
+
).parameters,
|
|
285
|
+
publicClient,
|
|
286
|
+
walletClient,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const afterBalance = await publicClient.getBalance({
|
|
290
|
+
address: creatorAccount,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// make sure that some additional royalties were withdrawn, this is how we can do greater than
|
|
294
|
+
// we cant get exact precision
|
|
295
|
+
expect(afterBalance - beforeBalance).toBeGreaterThan(
|
|
296
|
+
rewardsBalance.protocolRewards,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const erc20balance = await publicClient.readContract({
|
|
300
|
+
abi: erc20Abi,
|
|
301
|
+
address: erc20z,
|
|
302
|
+
functionName: "balanceOf",
|
|
303
|
+
args: [creatorAccount],
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(erc20balance).toBeGreaterThan(0);
|
|
307
|
+
},
|
|
308
|
+
30_000,
|
|
309
|
+
);
|
|
310
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IPublicClient } from "src/types";
|
|
2
|
+
import { IRewardsGetter } from "./subgraph-rewards-getter";
|
|
3
|
+
import { Account, Address } from "viem";
|
|
4
|
+
import { getRewardsBalance, withdrawRewards } from "./rewards-queries";
|
|
5
|
+
|
|
6
|
+
export type WithdrawRewardsParams = {
|
|
7
|
+
// account is the address that is withdrawing the rewards
|
|
8
|
+
account: Address | Account;
|
|
9
|
+
// withdrawFor is the address that is receiving the rewards
|
|
10
|
+
withdrawFor: Address;
|
|
11
|
+
// claimSecondaryRoyalties is an optional flag to claim secondary royalties. Defaults to `true`.
|
|
12
|
+
claimSecondaryRoyalties?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type GetRewardsBalancesParams = {
|
|
16
|
+
// The address or account to get the rewards balance for
|
|
17
|
+
account: Address | Account;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class RewardsClient {
|
|
21
|
+
// Private properties to store chain ID, public client, and rewards getter
|
|
22
|
+
private readonly chainId: number;
|
|
23
|
+
private readonly publicClient: IPublicClient;
|
|
24
|
+
private readonly rewardsGetter: IRewardsGetter;
|
|
25
|
+
|
|
26
|
+
constructor({
|
|
27
|
+
chainId,
|
|
28
|
+
publicClient,
|
|
29
|
+
rewardsGetter,
|
|
30
|
+
}: {
|
|
31
|
+
chainId: number;
|
|
32
|
+
publicClient: IPublicClient;
|
|
33
|
+
rewardsGetter: IRewardsGetter;
|
|
34
|
+
}) {
|
|
35
|
+
// Initialize the private properties
|
|
36
|
+
this.chainId = chainId;
|
|
37
|
+
this.publicClient = publicClient;
|
|
38
|
+
this.rewardsGetter = rewardsGetter;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Withdraws rewards for a given account */
|
|
42
|
+
async withdrawRewards({
|
|
43
|
+
account,
|
|
44
|
+
withdrawFor,
|
|
45
|
+
claimSecondaryRoyalties,
|
|
46
|
+
}: WithdrawRewardsParams) {
|
|
47
|
+
return {
|
|
48
|
+
parameters: await withdrawRewards({
|
|
49
|
+
chainId: this.chainId,
|
|
50
|
+
rewardsGetter: this.rewardsGetter,
|
|
51
|
+
withdrawFor,
|
|
52
|
+
claimSecondaryRoyalties,
|
|
53
|
+
account,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Retrieves the rewards balances for a given account */
|
|
59
|
+
async getRewardsBalances(params: GetRewardsBalancesParams) {
|
|
60
|
+
return getRewardsBalance({
|
|
61
|
+
account: params.account,
|
|
62
|
+
chainId: this.chainId,
|
|
63
|
+
publicClient: this.publicClient,
|
|
64
|
+
rewardsGetter: this.rewardsGetter,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
protocolRewardsABI,
|
|
3
|
+
protocolRewardsAddress,
|
|
4
|
+
erc20ZRoyaltiesABI,
|
|
5
|
+
erc20ZRoyaltiesAddress,
|
|
6
|
+
wethAddress,
|
|
7
|
+
} from "@zoralabs/protocol-deployments";
|
|
8
|
+
import { makeContractParameters, PublicClient } from "src/utils";
|
|
9
|
+
import { PublicClient as PublicClientWithMulticall } from "viem";
|
|
10
|
+
import { Account, Address, encodeFunctionData, parseAbi } from "viem";
|
|
11
|
+
import { IRewardsGetter } from "./subgraph-rewards-getter";
|
|
12
|
+
import {
|
|
13
|
+
multicall3Abi,
|
|
14
|
+
multicall3Address,
|
|
15
|
+
Multicall3Call3,
|
|
16
|
+
} from "src/apis/multicall3";
|
|
17
|
+
|
|
18
|
+
// Aggregates unclaimed fees and separates ETH from other ERC20 tokens
|
|
19
|
+
function aggregateUnclaimedFees(
|
|
20
|
+
unclaimedFees: readonly {
|
|
21
|
+
token0: `0x${string}`;
|
|
22
|
+
token1: `0x${string}`;
|
|
23
|
+
token0Amount: bigint;
|
|
24
|
+
token1Amount: bigint;
|
|
25
|
+
}[],
|
|
26
|
+
wethAddress: Address,
|
|
27
|
+
) {
|
|
28
|
+
let ethBalance = 0n;
|
|
29
|
+
// Aggregate unclaimed fees by token address
|
|
30
|
+
const unclaimedFeesAggregate = unclaimedFees.reduce(
|
|
31
|
+
(acc, fee) => {
|
|
32
|
+
const addFee = (token: `0x${string}`, amount: bigint) => {
|
|
33
|
+
if (token === wethAddress) {
|
|
34
|
+
ethBalance += amount;
|
|
35
|
+
} else if (acc[token]) {
|
|
36
|
+
acc[token] += amount;
|
|
37
|
+
} else {
|
|
38
|
+
acc[token] = amount;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Apply 75% fee to each token amount
|
|
42
|
+
addFee(fee.token0, (fee.token0Amount * 75n) / 100n);
|
|
43
|
+
addFee(fee.token1, (fee.token1Amount * 75n) / 100n);
|
|
44
|
+
return acc;
|
|
45
|
+
},
|
|
46
|
+
{} as Record<string, bigint>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
eth: (ethBalance * 75n) / 100n, // Apply 75% fee to ETH balance
|
|
51
|
+
erc20: unclaimedFeesAggregate,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Define the return type for getRewardsBalance
|
|
56
|
+
type RewardsBalance = {
|
|
57
|
+
// The total balance, in eth of protocol rewards
|
|
58
|
+
protocolRewards: bigint;
|
|
59
|
+
// The secondary royalties balance.
|
|
60
|
+
secondaryRoyalties: {
|
|
61
|
+
// The balance, in eth, of secondary royalties
|
|
62
|
+
eth: bigint;
|
|
63
|
+
// The balance, aggregated by erc20 address, of secondary royalties
|
|
64
|
+
erc20: Record<Address, bigint>;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const getRewardsBalance = async ({
|
|
69
|
+
account, // The account to check rewards for (Address or Account object)
|
|
70
|
+
publicClient, // The public client for making blockchain queries
|
|
71
|
+
chainId, // The ID of the blockchain network
|
|
72
|
+
rewardsGetter, // Interface for getting ERC20Z tokens for a creator
|
|
73
|
+
}: {
|
|
74
|
+
account: Account | Address;
|
|
75
|
+
publicClient: PublicClient;
|
|
76
|
+
chainId: number;
|
|
77
|
+
rewardsGetter: IRewardsGetter;
|
|
78
|
+
}): Promise<RewardsBalance> => {
|
|
79
|
+
const address = typeof account === "string" ? account : account.address;
|
|
80
|
+
const erc20Zs = await rewardsGetter.getErc20ZzForCreator({ address });
|
|
81
|
+
|
|
82
|
+
// Perform multicall to get protocol rewards balance and unclaimed fees
|
|
83
|
+
const result = await (publicClient as PublicClientWithMulticall).multicall({
|
|
84
|
+
contracts: [
|
|
85
|
+
{
|
|
86
|
+
address:
|
|
87
|
+
protocolRewardsAddress[
|
|
88
|
+
chainId as keyof typeof protocolRewardsAddress
|
|
89
|
+
],
|
|
90
|
+
abi: protocolRewardsABI,
|
|
91
|
+
functionName: "balanceOf",
|
|
92
|
+
args: [address],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
address:
|
|
96
|
+
erc20ZRoyaltiesAddress[
|
|
97
|
+
chainId as keyof typeof erc20ZRoyaltiesAddress
|
|
98
|
+
],
|
|
99
|
+
abi: erc20ZRoyaltiesABI,
|
|
100
|
+
functionName: "getUnclaimedFeesBatch",
|
|
101
|
+
args: [erc20Zs],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
multicallAddress: multicall3Address,
|
|
105
|
+
allowFailure: false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const protocolRewardsBalance = result[0];
|
|
109
|
+
|
|
110
|
+
const wethAddressForChain = wethAddress[chainId as keyof typeof wethAddress];
|
|
111
|
+
|
|
112
|
+
// Aggregate unclaimed fees
|
|
113
|
+
const unclaimedFeesAggregate = aggregateUnclaimedFees(
|
|
114
|
+
result[1],
|
|
115
|
+
wethAddressForChain,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
protocolRewards: protocolRewardsBalance,
|
|
120
|
+
secondaryRoyalties: unclaimedFeesAggregate,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const withdrawProtocolRewards = ({
|
|
125
|
+
withdrawFor,
|
|
126
|
+
chainId,
|
|
127
|
+
}: {
|
|
128
|
+
// Account to execute the transaction
|
|
129
|
+
withdrawFor: Address;
|
|
130
|
+
chainId: number;
|
|
131
|
+
}) => {
|
|
132
|
+
return makeContractParameters({
|
|
133
|
+
abi: protocolRewardsABI,
|
|
134
|
+
functionName: "withdrawFor",
|
|
135
|
+
address:
|
|
136
|
+
protocolRewardsAddress[chainId as keyof typeof protocolRewardsAddress],
|
|
137
|
+
args: [withdrawFor, 0n],
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const makeClaimSecondaryRoyaltiesCalls = async ({
|
|
142
|
+
claimFor,
|
|
143
|
+
chainId,
|
|
144
|
+
rewardsGetter,
|
|
145
|
+
}: {
|
|
146
|
+
claimFor: Address;
|
|
147
|
+
chainId: number;
|
|
148
|
+
rewardsGetter: IRewardsGetter;
|
|
149
|
+
}) => {
|
|
150
|
+
const erc20z = await rewardsGetter.getErc20ZzForCreator({
|
|
151
|
+
address: claimFor,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const royaltiesAddress =
|
|
155
|
+
erc20ZRoyaltiesAddress[chainId as keyof typeof erc20ZRoyaltiesAddress];
|
|
156
|
+
|
|
157
|
+
if (erc20z.length === 0) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return erc20z.map((erc20z) => ({
|
|
162
|
+
target: royaltiesAddress,
|
|
163
|
+
callData: encodeFunctionData({
|
|
164
|
+
abi: erc20ZRoyaltiesABI,
|
|
165
|
+
functionName: "claimFor",
|
|
166
|
+
args: [erc20z],
|
|
167
|
+
}),
|
|
168
|
+
allowFailure: false,
|
|
169
|
+
}));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export async function withdrawSecondaryRoyalties({
|
|
173
|
+
claimFor,
|
|
174
|
+
chainId,
|
|
175
|
+
rewardsGetter,
|
|
176
|
+
}: {
|
|
177
|
+
claimFor: Address;
|
|
178
|
+
chainId: number;
|
|
179
|
+
rewardsGetter: IRewardsGetter;
|
|
180
|
+
}) {
|
|
181
|
+
const calls = await makeClaimSecondaryRoyaltiesCalls({
|
|
182
|
+
claimFor,
|
|
183
|
+
chainId,
|
|
184
|
+
rewardsGetter,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return makeContractParameters({
|
|
188
|
+
abi: multicall3Abi,
|
|
189
|
+
functionName: "aggregate3",
|
|
190
|
+
address: multicall3Address,
|
|
191
|
+
args: [calls],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Extract protocol rewards withdrawal call creation
|
|
196
|
+
const createProtocolRewardsCall = (
|
|
197
|
+
chainId: number,
|
|
198
|
+
withdrawFor: Address,
|
|
199
|
+
): Multicall3Call3 => ({
|
|
200
|
+
target:
|
|
201
|
+
protocolRewardsAddress[chainId as keyof typeof protocolRewardsAddress],
|
|
202
|
+
callData: encodeFunctionData({
|
|
203
|
+
abi: protocolRewardsABI,
|
|
204
|
+
functionName: "withdrawFor",
|
|
205
|
+
args: [withdrawFor, 0n],
|
|
206
|
+
}),
|
|
207
|
+
allowFailure: false,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Extract multicall parameters creation
|
|
211
|
+
const createMulticallParameters = (
|
|
212
|
+
calls: Multicall3Call3[],
|
|
213
|
+
account: Address | Account,
|
|
214
|
+
) =>
|
|
215
|
+
makeContractParameters({
|
|
216
|
+
abi: parseAbi(multicall3Abi),
|
|
217
|
+
functionName: "aggregate3",
|
|
218
|
+
address: multicall3Address,
|
|
219
|
+
args: [calls],
|
|
220
|
+
account,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Main withdrawRewards function
|
|
224
|
+
export const withdrawRewards = async ({
|
|
225
|
+
account,
|
|
226
|
+
withdrawFor,
|
|
227
|
+
claimSecondaryRoyalties = true,
|
|
228
|
+
chainId,
|
|
229
|
+
rewardsGetter,
|
|
230
|
+
}: {
|
|
231
|
+
account: Address | Account;
|
|
232
|
+
withdrawFor: Address;
|
|
233
|
+
claimSecondaryRoyalties?: boolean;
|
|
234
|
+
chainId: number;
|
|
235
|
+
rewardsGetter: IRewardsGetter;
|
|
236
|
+
}) => {
|
|
237
|
+
if (!claimSecondaryRoyalties) {
|
|
238
|
+
return {
|
|
239
|
+
...withdrawProtocolRewards({ chainId, withdrawFor }),
|
|
240
|
+
account,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const protocolRewardsCall = createProtocolRewardsCall(chainId, withdrawFor);
|
|
245
|
+
const secondaryRoyaltiesCalls = await makeClaimSecondaryRoyaltiesCalls({
|
|
246
|
+
chainId,
|
|
247
|
+
claimFor: withdrawFor,
|
|
248
|
+
rewardsGetter,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const allCalls = [protocolRewardsCall, ...secondaryRoyaltiesCalls];
|
|
252
|
+
return createMulticallParameters(allCalls, account);
|
|
253
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ISubgraphQuery } from "src/apis/subgraph-querier";
|
|
2
|
+
import { Address } from "viem";
|
|
3
|
+
|
|
4
|
+
export type RewardsToken = {
|
|
5
|
+
salesStrategies: [
|
|
6
|
+
{
|
|
7
|
+
zoraTimedMinter: {
|
|
8
|
+
erc20Z: {
|
|
9
|
+
id: Address;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CreatorERC20zQueryResult = {
|
|
17
|
+
zoraCreateTokens: RewardsToken[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function buildCreatorERC20zs({
|
|
21
|
+
address,
|
|
22
|
+
}: {
|
|
23
|
+
address: Address;
|
|
24
|
+
}): ISubgraphQuery<CreatorERC20zQueryResult["zoraCreateTokens"]> {
|
|
25
|
+
return {
|
|
26
|
+
query: `
|
|
27
|
+
query ($address: Bytes!) {
|
|
28
|
+
zoraCreateTokens(
|
|
29
|
+
where: { royalties_: { user: $address }, salesStrategies_: { type: "ZORA_TIMED" } }
|
|
30
|
+
) {
|
|
31
|
+
royalties {
|
|
32
|
+
user
|
|
33
|
+
}
|
|
34
|
+
salesStrategies {
|
|
35
|
+
zoraTimedMinter {
|
|
36
|
+
erc20Z {
|
|
37
|
+
id
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
variables: { address },
|
|
45
|
+
parseResponseData: (responseData: any | undefined) => {
|
|
46
|
+
return responseData?.zoraCreateTokens;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SubgraphGetter } from "src/apis/subgraph-getter";
|
|
2
|
+
import { ISubgraphQuerier } from "src/apis/subgraph-querier";
|
|
3
|
+
import { Address } from "viem";
|
|
4
|
+
import { buildCreatorERC20zs } from "./subgraph-queries";
|
|
5
|
+
|
|
6
|
+
export interface IRewardsGetter {
|
|
7
|
+
getErc20ZzForCreator: (params: { address: Address }) => Promise<Address[]>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SubgraphRewardsGetter
|
|
11
|
+
extends SubgraphGetter
|
|
12
|
+
implements IRewardsGetter
|
|
13
|
+
{
|
|
14
|
+
constructor(chainId: number, subgraphQuerier?: ISubgraphQuerier) {
|
|
15
|
+
super(chainId, subgraphQuerier);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getErc20ZzForCreator({
|
|
19
|
+
address,
|
|
20
|
+
}: {
|
|
21
|
+
address: Address;
|
|
22
|
+
}): Promise<Address[]> {
|
|
23
|
+
const queryResults = await this.querySubgraphWithRetries(
|
|
24
|
+
buildCreatorERC20zs({ address }),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
queryResults?.map(
|
|
29
|
+
(result) => result.salesStrategies[0].zoraTimedMinter.erc20Z.id,
|
|
30
|
+
) || []
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|