@dripfi/drip-sdk 1.0.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/DripApi.ts ADDED
@@ -0,0 +1,133 @@
1
+ import { BigNumber } from "ethers"
2
+ import { Vault } from "./types/Vault"
3
+ import { SwapInfo } from "./types/SwapInfo"
4
+ import { QLFastRedeem } from "./types/QLFastRedeem"
5
+
6
+ const WETH_TOKEN_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
7
+
8
+ export default class DripApi {
9
+
10
+ route: string
11
+
12
+ constructor(route: string) {
13
+ this.route = route
14
+ }
15
+
16
+ async fetchAllVaults(): Promise<Vault[]> {
17
+ const res = await fetch(`${this.route}/api-be/api/vault`)
18
+ const data: Vault[] = await res.json() as Vault[]
19
+ return data
20
+ }
21
+
22
+ async fetchVaultDetails(vaultAddress: string): Promise<Vault> {
23
+ const res = await fetch(`${this.route}/api-be/api/vault/${vaultAddress}`)
24
+ const data: Vault = await res.json() as Vault
25
+ return data
26
+ }
27
+
28
+
29
+ async getSwapInfo(fromTokenAddress: string, toTokenAddress: string, amount: BigNumber, fromAddress: string,): Promise<SwapInfo[]> {
30
+ if (fromTokenAddress === toTokenAddress && fromTokenAddress === WETH_TOKEN_ADDRESS) {
31
+ return []
32
+ }
33
+
34
+ const url = `${this.route}/oneinch?getRequest=/swap/v5.2/1/swap?fromTokenAddress=${fromTokenAddress}%26toTokenAddress=${toTokenAddress}%26amount=${amount.toString()}%26fromAddress=${fromAddress}%26slippage=0.1%26disableEstimate=true%26allowPartialFill=false%26includeTokensInfo=true`
35
+ const res = await fetch(url)
36
+ const data = await res.json() as any
37
+ return [
38
+ {
39
+ swapTarget: data.tx.to,
40
+ token: data.fromToken.address,
41
+ swapCallData: data.tx.data,
42
+ }
43
+ ]
44
+ }
45
+
46
+ async fetchUserSVTBalance(vaultAddress: string, walletAddress: string, token: string): Promise<BigNumber> {
47
+ const headers = new Headers()
48
+ headers.append('Authorization', token)
49
+ const res = await fetch(`${this.route}/api-be/api/spool/user/svtBalance/${walletAddress}/${vaultAddress}`, {
50
+ headers,
51
+ })
52
+
53
+ const data = await res.json() as BigNumber
54
+ return data
55
+ }
56
+
57
+ async fetchUserBalance(vaultAddress: string, walletAddress: string, token: string): Promise<Record<string, string>> {
58
+ const headers = new Headers()
59
+ headers.append('Authorization', token)
60
+ const res = await fetch(`${this.route}/api-be/api/spool/userBalance/${walletAddress}/${vaultAddress}`, {
61
+ headers,
62
+ })
63
+ const data = await res.json() as Record<string, string>
64
+ return data
65
+ }
66
+
67
+ async fetchEnrichedUserDNFTForVault(vaultAddress: string, walletAddress: string, token: string): Promise<any[]> {
68
+ const headers = new Headers()
69
+ headers.append('Authorization', token)
70
+ const res = await fetch(`${this.route}/api-be/api/spool/user/dNft/${walletAddress}/${vaultAddress}`, {
71
+ headers,
72
+ })
73
+ const data = await res.json()
74
+ return data as any[]
75
+ }
76
+
77
+ async fetchUserSVTFromNfts(vaultAddress: string, userAddress: string, dnfts: number[], token: string): Promise<BigNumber[]> {
78
+ const headers = new Headers()
79
+ headers.append('Authorization', token)
80
+ const res = await fetch(`${this.route}/api-be/api/spool/user/svtFromNft/${userAddress}/${vaultAddress}?dnfts=${dnfts.join(',')}`, {
81
+ headers: {
82
+ Authorization: token,
83
+ },
84
+ })
85
+
86
+ const data = await res.json() as BigNumber[]
87
+ return data
88
+ }
89
+
90
+ async fetchAllUserWNFTForVault(vaultAddress: string, walletAddress: string, token: string): Promise<any[]> {
91
+ const headers = new Headers()
92
+ headers.append('Authorization', token)
93
+ const res = await fetch(`${this.route}/api-be/api/spool/user/allWnft/${walletAddress}/${vaultAddress}`, {
94
+ headers
95
+ })
96
+ const data = await res.json() as any[]
97
+ return data
98
+ }
99
+
100
+ async fetchAllUserDNFTForVault(vaultAddress: string, walletAddress: string, token: string): Promise<any[]> {
101
+ const headers = new Headers()
102
+ headers.append('Authorization', token)
103
+ const res = await fetch(`${this.route}/api-be/api/spool/user/allDnft/${walletAddress}/${vaultAddress}`,{
104
+ headers
105
+ })
106
+ const data = await res.json() as any[]
107
+ return data
108
+ }
109
+
110
+ async fetchAssetPerSvtAtBlock(vaultAddress: string, blocknumber: number, token: string): Promise<BigNumber> {
111
+ const headers = new Headers()
112
+ headers.append('Authorization', token)
113
+ const res = await fetch(`${this.route}/api-be/api/spool/user/assetBalance/${blocknumber}/${vaultAddress}`, {
114
+ headers,
115
+ })
116
+ const data = await res.json() as BigNumber
117
+ return data
118
+ }
119
+
120
+ async fetchFastWithdrawNFTs(vaultAddress: string, walletAddress: string, token: string): Promise<QLFastRedeem[]> {
121
+ const headers = new Headers()
122
+ headers.append('Authorization', token)
123
+ const res = await fetch(`${this.route}/api-be/api/spool/user/fastWithdrawNft/${walletAddress}/${vaultAddress}`, {
124
+ headers,
125
+ })
126
+ const data = await res.json() as QLFastRedeem[]
127
+
128
+ return data
129
+ }
130
+
131
+ }
132
+
133
+
package/DripConfig.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { ContractAddresses, SDKConfig } from "@spool.fi/spool-v2-sdk";
2
+ import { Signer } from "ethers";
3
+
4
+ export class DripConfig {
5
+
6
+ internalConfig: SDKConfig
7
+ dripRoute: string
8
+ swapAndDepositContractAddress: string | undefined
9
+ smartVaultManagerAddress: string | undefined
10
+
11
+ constructor(subgraphUrl: string, priceFeedApiUrl: string, rewardsUrl: string, fastRedeemApi: string, contracts: Record<number, ContractAddresses>, dripRoute: string) {
12
+ this.internalConfig = new SDKConfig(subgraphUrl, priceFeedApiUrl, rewardsUrl, fastRedeemApi, contracts)
13
+ this.dripRoute = dripRoute
14
+ }
15
+
16
+ async getSwapAndDepositContractAddress(signer: Signer): Promise<string> {
17
+ if (!signer) throw Error('No signer provided')
18
+
19
+ if (!this.swapAndDepositContractAddress) {
20
+ const contract = await this.internalConfig.getChainAddresses(signer)
21
+ this.swapAndDepositContractAddress = contract.IDepositSwap
22
+ }
23
+
24
+ return this.swapAndDepositContractAddress
25
+ }
26
+
27
+ async getSmartVaultManagerAddress(signer: Signer): Promise<string> {
28
+ if (!signer) throw Error('No signer provided')
29
+
30
+ if (!this.smartVaultManagerAddress) {
31
+ const contract = await this.internalConfig.getChainAddresses(signer)
32
+ this.smartVaultManagerAddress = contract.ISmartVaultManager
33
+ }
34
+
35
+ return this.smartVaultManagerAddress
36
+ }
37
+
38
+ }
package/DripSdk.ts ADDED
@@ -0,0 +1,516 @@
1
+ import { Vault } from "./types/Vault";
2
+ import Web3Token from "web3-token";
3
+ import { DepositBagStruct, ERC20, ERC20__factory, EnrichedWNFT, RedeemBagStruct, SDKConfig, SpoolSdk } from '@spool.fi/spool-v2-sdk'
4
+ import { BigNumber, Bytes, Signer, ethers } from "ethers";
5
+ import { DripConfig } from "./DripConfig";
6
+ import { insertDot } from "./utils";
7
+ import { UserBalance } from "./types/UserBalance";
8
+ import DripApi from "./DripApi";
9
+
10
+ const KASU_USDC_VAULT_ADDRESS = "0xd8aa8099a53eddebe6a49c98ca12746ff19aa502"
11
+
12
+ export default class DripSdk {
13
+
14
+ private dripApi: DripApi
15
+ private spoolSdk?: SpoolSdk
16
+ private signer?: Signer
17
+ private dripConfig: DripConfig
18
+
19
+ constructor(dripConfig: DripConfig, signer?: Signer) {
20
+ this.signer = signer
21
+ this.dripConfig = dripConfig
22
+ if (signer) {
23
+ this.spoolSdk = new SpoolSdk(dripConfig.internalConfig, signer)
24
+ }
25
+ this.dripApi = new DripApi(dripConfig.dripRoute)
26
+
27
+ }
28
+
29
+ async getAllVaults(): Promise<Vault[]> {
30
+ return this.dripApi.fetchAllVaults()
31
+ }
32
+
33
+ async getVaultDetails(vaultAddress: string): Promise<Vault> {
34
+ return this.dripApi.fetchVaultDetails(vaultAddress)
35
+ }
36
+
37
+ async getUserBalance(vault: Vault): Promise<UserBalance> {
38
+ if (!this.signer) throw Error('No signer provided')
39
+ const userAddress = await this.signer.getAddress()
40
+
41
+ const token = await this.generateToken()
42
+ const dnfts = await this.dripApi.fetchAllUserDNFTForVault(vault.vaultAddress, userAddress, token)
43
+ const wnfts = await this.dripApi.fetchAllUserWNFTForVault(vault.vaultAddress, userAddress, token)
44
+ const decimals = await this.getERC20Precission(vault.depositToken.tokenAddress)
45
+
46
+ if (this.shouldUseNewWithdrawLogic(userAddress, vault)) {
47
+
48
+ const [estimatedPendingWithdrawalBalance, estimatedWithdrawableBalance] = await this.calculateAllWithdrawalBalances(
49
+ wnfts,
50
+ vault.vaultAddress,
51
+ decimals,
52
+ )
53
+ const hasWithdrawsToClaim = this.checkIfUserHasWithdrawsToClaim(wnfts)
54
+
55
+ const pendingDeposits: BigNumber = this.calculatePendingDeposits(dnfts, decimals)
56
+
57
+ const userBalance: BigNumber = this.calculateAllDeposits(dnfts, decimals).sub(pendingDeposits)
58
+ return {
59
+ hasWithdrawsToClaim,
60
+ userBalance: ethers.utils.formatUnits(userBalance, decimals),
61
+ pendingUserBalance: ethers.utils.formatUnits(pendingDeposits, decimals),
62
+ pendingWithdrawalBalance: ethers.utils.formatUnits(estimatedPendingWithdrawalBalance, decimals),
63
+ withdrawableBalance: ethers.utils.formatUnits(estimatedWithdrawableBalance, decimals),
64
+ }
65
+ } else {
66
+ const allDeposits = this.calculateDepositsOld(dnfts, decimals)
67
+ const pendingDeposits = this.calculatePendingDepositsOld(dnfts, decimals)
68
+ const [estimatedPendingWithdrawalBalance, estimatedWithdrawableBalance, estimatedWithdrawals] =
69
+ await this.calculateAllWithdrawalBalances(wnfts, vault.vaultAddress, decimals)
70
+ const fastWithdrawBalance = await this.calculateFastWithdrawBalancesOld(vault.vaultAddress, userAddress, decimals)
71
+
72
+ const hasWithdrawsToClaim = this.checkIfUserHasWithdrawsToClaim(wnfts)
73
+ let balance = allDeposits
74
+ .sub(estimatedWithdrawals)
75
+ .sub(estimatedWithdrawableBalance)
76
+ .sub(estimatedPendingWithdrawalBalance)
77
+ .sub(fastWithdrawBalance)
78
+ return {
79
+ hasWithdrawsToClaim,
80
+ userBalance: ethers.utils.formatUnits(balance, decimals),
81
+ pendingUserBalance: ethers.utils.formatUnits(pendingDeposits, decimals),
82
+ pendingWithdrawalBalance: ethers.utils.formatUnits(estimatedPendingWithdrawalBalance, decimals),
83
+ withdrawableBalance: ethers.utils.formatUnits(estimatedWithdrawableBalance, decimals),
84
+ }
85
+ }
86
+
87
+ }
88
+
89
+ private async fastWithdraw(vault: Vault, amountToWithdraw?: string) {
90
+ if (!this.signer) throw Error('No signer provided')
91
+ try {
92
+
93
+ const signerAddress = await this.signer.getAddress()
94
+ if (!signerAddress) throw Error('Error fetching address')
95
+
96
+ const redeemBagStruct: RedeemBagStruct = this.shouldUseNewWithdrawLogic(signerAddress, vault)
97
+ ? await this.generateNewRedeemBagStruct(vault, signerAddress, amountToWithdraw)
98
+ : await this.generateOldRedeemBagStruct(vault, signerAddress, amountToWithdraw)
99
+
100
+
101
+ const currentBlockNumber = await this.signer.provider?.getBlockNumber()
102
+
103
+ if (!currentBlockNumber) throw Error('Error fetching block number')
104
+
105
+ const redeemTx = await this.spoolSdk?.redeemFast(
106
+ redeemBagStruct,
107
+ signerAddress.toLowerCase(),
108
+ currentBlockNumber,
109
+ )
110
+
111
+ const redeemTxReceipt = await redeemTx!.wait()
112
+ } catch (error) {
113
+ console.log(error)
114
+ }
115
+ }
116
+
117
+ private async deposit(tokenAddress: string, vaultAddress: string, amount: string) {
118
+ if (!this.signer) throw Error('No signer provided')
119
+
120
+ const currentTokenAllowance = await this.getTokenAllowanceForDeposit(tokenAddress)
121
+ const decimals = await this.getERC20Precission(tokenAddress)
122
+ let amountToWithdrawFixedDecimals = parseFloat(amount).toFixed(decimals)
123
+ const amountToDeposit = ethers.utils.parseUnits(amountToWithdrawFixedDecimals, decimals)
124
+
125
+ if (amountToDeposit.gt(currentTokenAllowance)) {
126
+ const requiredTokenAllowance = amountToDeposit.sub(currentTokenAllowance)
127
+ await this.approveTokenForDeposit(tokenAddress, requiredTokenAllowance)
128
+ }
129
+ const signerAddress = await this.signer.getAddress()
130
+
131
+ if (!signerAddress) throw Error('Error fetching address')
132
+
133
+ const depositBagStruct: DepositBagStruct = {
134
+ smartVault: vaultAddress,
135
+ assets: [amountToDeposit],
136
+ receiver: signerAddress,
137
+ referral: ethers.constants.AddressZero,
138
+ doFlush: false,
139
+ }
140
+
141
+ const depositTx = await this.spoolSdk!.deposit(depositBagStruct)
142
+
143
+ await depositTx.wait()
144
+ }
145
+
146
+
147
+ private async swapAndDeposit(fromTokenAddress: string, toTokenAddress: string, fromTokenAmount: string, vaultAddress: string, ethAmount?: string) {
148
+ if (!this.signer) throw Error('No signer provided')
149
+
150
+
151
+ const decimals = await this.getERC20Precission(fromTokenAddress)
152
+ const amountToWithdrawFixedDecimals = parseFloat(fromTokenAmount).toFixed(decimals)
153
+ const fromToken = ethers.utils.parseUnits(amountToWithdrawFixedDecimals, decimals)
154
+
155
+ const signerAddress = await this.signer.getAddress()
156
+
157
+ if (fromToken.gt(BigNumber.from(0))) {
158
+ const currentTokenAllowance = await this.getTokenAllowanceForSwapAndDepositContractAddress(fromTokenAddress)
159
+
160
+ if (fromToken.gt(currentTokenAllowance)) {
161
+ await this.approveTokenForSwapAndDepositContract(fromTokenAddress, fromToken.sub(currentTokenAllowance))
162
+ }
163
+ }
164
+
165
+ const swapInfo = await this.dripApi.getSwapInfo(fromTokenAddress, toTokenAddress, fromToken, signerAddress)
166
+
167
+ const swapDepositBagStruct = {
168
+ inTokens: [fromTokenAddress],
169
+ inAmounts: [fromToken],
170
+ smartVault: vaultAddress,
171
+ swapInfo,
172
+ receiver: signerAddress,
173
+ referral: ethers.constants.AddressZero,
174
+ doFlush: false,
175
+ }
176
+
177
+ let swapAndDepositRequest: ethers.ContractTransaction | undefined
178
+
179
+ if (ethAmount) {
180
+ const eth = ethers.utils.parseEther(ethAmount)
181
+ swapAndDepositRequest = await this.spoolSdk?.swapAndDeposit(swapDepositBagStruct, { value: eth })
182
+ } else {
183
+ swapAndDepositRequest = await this.spoolSdk?.swapAndDeposit(swapDepositBagStruct)
184
+ }
185
+
186
+ await swapAndDepositRequest?.wait()
187
+ }
188
+
189
+ private async withdraw(vault: Vault, amountToWithdraw?: string) {
190
+ if (!this.signer) throw Error('No signer provided')
191
+
192
+ try {
193
+ const signerAddress = await this.signer.getAddress()
194
+
195
+ if (!signerAddress) throw Error('Error fetching address')
196
+
197
+ const redeemBagStruct: RedeemBagStruct = this.shouldUseNewWithdrawLogic(signerAddress, vault)
198
+ ? await this.generateNewRedeemBagStruct(vault, signerAddress, amountToWithdraw)
199
+ : await this.generateOldRedeemBagStruct(vault, signerAddress, amountToWithdraw)
200
+
201
+ const redeemTx = await this.spoolSdk!.redeem(redeemBagStruct, signerAddress.toLowerCase(), false)
202
+
203
+ const redeemTxReceipt = await redeemTx.wait()
204
+ } catch (error) {
205
+ console.log(error)
206
+ }
207
+ }
208
+
209
+
210
+ private async generateOldRedeemBagStruct(vault: Vault, signerAddress: string, amountToWithdraw?: string): Promise<RedeemBagStruct> {
211
+ const token = await this.generateToken()
212
+
213
+ const dnfts = await this.dripApi.fetchEnrichedUserDNFTForVault(vault.vaultAddress, signerAddress, token)
214
+
215
+ const userBalance = await this.dripApi.fetchUserBalance(vault.vaultAddress, signerAddress, token)
216
+ const totalTokenBalance = BigNumber.from(userBalance[vault.depositToken.tokenAddress])
217
+ const totalSharesFetched = await this.dripApi.fetchUserSVTBalance(vault.vaultAddress, signerAddress, token)
218
+ const totalShares = BigNumber.from(totalSharesFetched)
219
+
220
+ const decimals = await this.getERC20Precission(vault.depositToken.tokenAddress)
221
+
222
+ let sharesToWithdraw = BigNumber.from(0)
223
+
224
+ if (!amountToWithdraw) {
225
+ sharesToWithdraw = totalShares
226
+ } else {
227
+ //! Not a MAX Withdraw (calculate the shares to withdraw)
228
+ let amountToWithdrawFixedDecimals = parseFloat(amountToWithdraw).toFixed(decimals)
229
+
230
+ sharesToWithdraw = ethers.utils
231
+ .parseUnits(amountToWithdrawFixedDecimals, decimals)
232
+ .mul(totalShares)
233
+ .div(totalTokenBalance)
234
+
235
+ if (sharesToWithdraw.gt(totalShares)) {
236
+ sharesToWithdraw = totalShares
237
+ }
238
+ }
239
+
240
+ const nftIds = dnfts
241
+ .filter((item) => !item.isBurned && BigNumber.from(item.shares).gt(BigNumber.from('0')) && item.isDHWFinished)
242
+ .map((item) => item.nftId.toString())
243
+ const nftAmounts = dnfts
244
+ .filter((item) => !item.isBurned && BigNumber.from(item.shares).gt(BigNumber.from('0')) && item.isDHWFinished)
245
+ .map((item) => item.shares.toString())
246
+
247
+ return {
248
+ smartVault: vault.vaultAddress,
249
+ shares: sharesToWithdraw,
250
+ nftIds,
251
+ nftAmounts,
252
+ }
253
+ }
254
+
255
+
256
+ private async generateNewRedeemBagStruct(vault: Vault, signerAddress: string, amountToWithdraw?: string): Promise<RedeemBagStruct> {
257
+ const token = await this.generateToken()
258
+ const decimals = await this.getERC20Precission(vault.depositToken.tokenAddress.toLowerCase())
259
+ const withdrawAll: boolean = (!amountToWithdraw)
260
+ const initialAmountToWithdraw: BigNumber = ethers.utils.parseUnits(amountToWithdraw || '0', decimals)
261
+ let totalAmountToWithdraw: BigNumber = initialAmountToWithdraw
262
+ let dnfts: any[] = await this.dripApi.fetchEnrichedUserDNFTForVault(vault.vaultAddress.toLowerCase(), signerAddress, token)
263
+ dnfts = await this.getAllSvts(vault.vaultAddress.toLowerCase(), signerAddress, dnfts)
264
+ let shares: BigNumber = BigNumber.from(0)
265
+ let nftIds: BigNumber[] = []
266
+ let nftAmounts: BigNumber[] = []
267
+ let index = 0
268
+
269
+ while (dnfts[index] && (totalAmountToWithdraw.gt(0) || withdrawAll)) {
270
+
271
+ const dnft: { nftId: BigNumber, shares: BigNumber, svts: BigNumber, assets: number[] } = dnfts[index]
272
+
273
+ const userBalance = this.calculateBalanceForDNFT(dnft, decimals)
274
+
275
+ if (userBalance.gt(0)) {
276
+
277
+ let amountToWithdraw: BigNumber = totalAmountToWithdraw
278
+ const userSvts = dnft.svts
279
+
280
+ let svtsToWithdraw = BigNumber.from(0)
281
+ let nftAmount = BigNumber.from(0)
282
+ if (amountToWithdraw.gte(userBalance) || withdrawAll) {
283
+ nftAmount = dnft.shares
284
+ svtsToWithdraw = userSvts
285
+ if (!withdrawAll) totalAmountToWithdraw = totalAmountToWithdraw.sub(userBalance)
286
+ } else {
287
+ nftAmount = amountToWithdraw.mul(dnft.shares).div(userBalance)
288
+ svtsToWithdraw = nftAmount.mul(userSvts).div(dnft.shares)
289
+ totalAmountToWithdraw = totalAmountToWithdraw.sub(amountToWithdraw)
290
+ }
291
+ shares = shares.add(svtsToWithdraw)
292
+ nftIds.push(dnft.nftId)
293
+ nftAmounts.push(nftAmount)
294
+ }
295
+ index++
296
+ }
297
+
298
+ return {
299
+ smartVault: vault.vaultAddress.toLowerCase(),
300
+ shares: shares,
301
+ nftIds,
302
+ nftAmounts
303
+ }
304
+ }
305
+
306
+ private async getAllSvts(vaultAddress: string, userAddress: string, dnfts: any[]): Promise<{ nftId: BigNumber, shares: BigNumber, assets: number[], svts: BigNumber }[]> {
307
+ const token = await this.generateToken()
308
+ const svts: BigNumber[] = await this.dripApi.fetchUserSVTFromNfts(vaultAddress, userAddress, dnfts.map((element) => parseInt(element.nftId)), token)
309
+ const result = dnfts.map((element, index) => { return { nftId: element.nftId, assets: element.assets, shares: element.shares, svts: svts[index] } })
310
+ return result
311
+ }
312
+
313
+ private async getERC20Precission(tokenAddress: string): Promise<number> {
314
+ if (!this.signer) throw Error('No signer provided')
315
+
316
+ const signerAddress = await this.signer.getAddress()
317
+
318
+ if (!signerAddress) throw Error('Error fetching address')
319
+
320
+ const erc20Instance: ERC20 = ERC20__factory.connect(tokenAddress, this.signer)
321
+
322
+ const decimals = await erc20Instance.decimals()
323
+
324
+ return decimals
325
+ }
326
+
327
+ private async calculateFastWithdrawBalancesOld(vaultAddress: string, userAddress: string, decimals: number) {
328
+ const token = await this.generateToken()
329
+ const fastWithdrawNFTs = await this.dripApi.fetchFastWithdrawNFTs(vaultAddress, userAddress, token)
330
+
331
+ let fastWithdrawBalance = 0
332
+ for (const wnft of fastWithdrawNFTs) {
333
+ if (wnft.assetsWithdrawn[0].asset && wnft.assetsWithdrawn[0].claimed) {
334
+ const value = +insertDot(wnft.assetsWithdrawn[0].claimed, decimals)
335
+ fastWithdrawBalance += value
336
+ }
337
+ }
338
+ return ethers.utils.parseUnits(fastWithdrawBalance.toFixed(decimals), decimals)
339
+ }
340
+
341
+ private calculatePendingDepositsOld(dnfts: any[], decimals: number) {
342
+ let pendingDeposits = 0
343
+ for (const nft of dnfts) {
344
+ if (nft['assets'] && nft['assets'][0] && !nft.isDHWFinished) {
345
+ pendingDeposits += nft['assets'][0]
346
+ }
347
+ }
348
+ pendingDeposits = +pendingDeposits.toFixed(decimals)
349
+ return ethers.utils.parseUnits(pendingDeposits.toString(), decimals)
350
+ }
351
+
352
+ private calculateDepositsOld(dnfts: any[], decimals: number): BigNumber {
353
+ let deposits = 0
354
+ for (const nft of dnfts) {
355
+ if (nft['assets'] && nft['assets'][0] && nft.isDHWFinished) {
356
+ deposits += nft['assets'][0]
357
+ }
358
+ }
359
+ deposits = +deposits.toFixed(decimals)
360
+
361
+ return ethers.utils.parseUnits(deposits.toString(), decimals)
362
+ }
363
+
364
+ private calculatePendingDeposits(dnfts: any[], decimals: number): BigNumber {
365
+ let pendingDeposits: BigNumber = BigNumber.from(0)
366
+ for (const nft of dnfts) {
367
+ if (nft['assets'] && nft['assets'][0] && !nft.isDHWFinished) {
368
+ pendingDeposits = pendingDeposits.add(ethers.utils.parseUnits(nft['assets'][0].toString(), decimals))
369
+ }
370
+ }
371
+
372
+ return pendingDeposits
373
+ }
374
+
375
+
376
+ private calculateAllDeposits(dnfts: any[], decimals: number): BigNumber {
377
+ let deposits: BigNumber = BigNumber.from(0)
378
+ for (const nft of dnfts) {
379
+ deposits = deposits.add(this.calculateBalanceForDNFT(nft, decimals))
380
+ }
381
+ return deposits
382
+ }
383
+
384
+ private calculateBalanceForDNFT(dnft: { assets: number[], shares: BigNumber }, decimals: number): BigNumber {
385
+
386
+ const assets = BigNumber.from(ethers.utils.parseUnits(dnft.assets[0]!.toString(), decimals))
387
+ const shares = BigNumber.from(dnft.shares)
388
+ if (shares.eq(0)) {
389
+ return BigNumber.from(0)
390
+ }
391
+ return assets.mul(shares).div(1000000)
392
+ }
393
+
394
+ private checkIfUserHasWithdrawsToClaim(withdrawNFTS: any[]): boolean {
395
+ const nftIds = withdrawNFTS
396
+ //! Shares come as Strings instead of BigNumber from our Backend
397
+ .filter((item) => !item.isBurned && BigNumber.from(item.shares).gt(BigNumber.from('0')) && item.isDHWFinished)
398
+ .map((item) => item.nftId.toString())
399
+
400
+ const nftAmounts = withdrawNFTS
401
+ .filter((item) => !item.isBurned && BigNumber.from(item.shares).gt(BigNumber.from('0')) && item.isDHWFinished)
402
+ .map((item) => item.shares.toString())
403
+
404
+ return nftIds.length !== 0 || nftAmounts.length !== 0
405
+ }
406
+ private shouldUseNewWithdrawLogic(userAddress: string, vault: Vault) {
407
+
408
+ // Users that already withdrew using the old approach should keep using the same
409
+ const usersWhoAlreadyWithdraw: { [walletAddress: string]: string } = {
410
+ '0x5ae62d2bc40e9119aee9cd4ed50e3d9378c9a191': KASU_USDC_VAULT_ADDRESS
411
+ }
412
+ if (usersWhoAlreadyWithdraw[userAddress.toLowerCase()] && usersWhoAlreadyWithdraw[userAddress.toLowerCase()] === vault.vaultAddress.toLowerCase()) {
413
+ return false
414
+ }
415
+ return vault.newWithdraw
416
+ }
417
+
418
+ private async generateToken(): Promise<string> {
419
+ if (!this.signer) throw Error('No signer provided')
420
+ const token: string = await Web3Token.sign(async (msg: string | Bytes) => await this.signer!.signMessage(msg), '1d');
421
+ return token
422
+ }
423
+
424
+ private async calculateAllWithdrawalBalances(wnfts: EnrichedWNFT[], vaultAddress: string, decimals: number) {
425
+ if (!this.signer) throw Error('No signer provided')
426
+ const userAddress = await this.signer.getAddress()
427
+
428
+ let estimatedPendingWithdrawalBalance = BigNumber.from(0)
429
+ let estimatedWithdrawableBalance = BigNumber.from(0)
430
+ let withdrawals = 0
431
+
432
+ for (const wnft of wnfts) {
433
+ // Handle burned NFTs (already withdrawn by the user)
434
+ if (wnft.assets && wnft.assets[0] && wnft.isBurned) {
435
+ withdrawals += wnft.assets[0].amount
436
+ continue
437
+ }
438
+ const currentBlockNumber = wnft.blockNumber - 1
439
+ const token = await this.generateToken()
440
+ const assetsPerSvtAtBlock = await this.dripApi.fetchAssetPerSvtAtBlock(vaultAddress, currentBlockNumber, token)
441
+ const estimatedValueOfNFT = BigNumber.from(
442
+ parseInt(ethers.utils.formatUnits(BigNumber.from(wnft.svtWithdrawn).mul(assetsPerSvtAtBlock), 36)).toString()
443
+ )
444
+
445
+ if (wnft.isDHWFinished) {
446
+ // Processed and Withdrawable
447
+ estimatedWithdrawableBalance = estimatedWithdrawableBalance.add(estimatedValueOfNFT)
448
+ } else {
449
+ // Not processed, no DHW was ran => pending
450
+ estimatedPendingWithdrawalBalance = estimatedPendingWithdrawalBalance.add(estimatedValueOfNFT)
451
+ }
452
+ }
453
+ // return all values as BigNumber
454
+ withdrawals = +withdrawals.toFixed(decimals)
455
+ const estimatedWithdrawals = ethers.utils.parseUnits(withdrawals.toString(), decimals)
456
+ return [estimatedPendingWithdrawalBalance, estimatedWithdrawableBalance, estimatedWithdrawals]
457
+ }
458
+
459
+
460
+
461
+ private async getTokenAllowanceForSwapAndDepositContractAddress(tokenAddress: string): Promise<BigNumber> {
462
+ if (!this.signer) throw Error('No signer provided')
463
+
464
+ const signerAddress = await this.signer.getAddress()
465
+
466
+ if (!signerAddress) throw Error('Error fetching address')
467
+
468
+ const erc20Instance: ERC20 = ERC20__factory.connect(tokenAddress, this.signer)
469
+
470
+ const swapAndDepositAddress = await this.dripConfig.getSwapAndDepositContractAddress(this.signer)
471
+
472
+ const allowance = await erc20Instance.allowance(signerAddress, swapAndDepositAddress)
473
+
474
+ return allowance
475
+ }
476
+
477
+ private async approveTokenForSwapAndDepositContract(tokenAddress: string, amount: BigNumber) {
478
+ if (!this.signer) throw Error('No signer provided')
479
+
480
+ const swapAndDepositContractAddress = await this.dripConfig.getSwapAndDepositContractAddress(this.signer)
481
+
482
+ const erc20Instance: ERC20 = ERC20__factory.connect(tokenAddress, this.signer)
483
+ const approveTx = await erc20Instance.approve(swapAndDepositContractAddress, amount)
484
+
485
+ await approveTx.wait()
486
+ }
487
+
488
+ private async getTokenAllowanceForDeposit(tokenAddress: string): Promise<BigNumber> {
489
+ if (!this.signer) throw Error('No signer provided')
490
+
491
+ const signerAddress = await this.signer.getAddress()
492
+
493
+ if (!signerAddress) throw Error('Error fetching address')
494
+
495
+ const erc20Instance: ERC20 = ERC20__factory.connect(tokenAddress, this.signer)
496
+
497
+ const smartVaultManagerAddress = await this.dripConfig.getSmartVaultManagerAddress(this.signer)
498
+
499
+ const allowance = await erc20Instance.allowance(signerAddress, smartVaultManagerAddress)
500
+
501
+ return allowance
502
+ }
503
+
504
+ private async approveTokenForDeposit(tokenAddress: string, amount: BigNumber) {
505
+ if (!this.signer) throw Error('No signer provided')
506
+
507
+ const smartVaultManagerAddress = await this.dripConfig.getSmartVaultManagerAddress(this.signer)
508
+
509
+ const erc20Instance: ERC20 = ERC20__factory.connect(tokenAddress, this.signer)
510
+ const approveTx = await erc20Instance.approve(smartVaultManagerAddress, amount)
511
+
512
+ await approveTx.wait()
513
+ }
514
+
515
+ }
516
+
package/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import DripSdk from './DripSdk'
2
+ import { Vault, Strategy } from './types/Vault'
3
+ import { DripConfig } from './DripConfig'
4
+
5
+ export { Vault, Strategy, DripSdk, DripConfig }
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@dripfi/drip-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Drip SDK",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "dependencies": {
10
+ "@spool.fi/spool-v2-sdk": "1.0.14",
11
+ "web3-token": "^1.0.6",
12
+ "ethers": "^5.7.2"
13
+ },
14
+ "author": "",
15
+ "license": "ISC"
16
+ }
@@ -0,0 +1,15 @@
1
+ export interface QLFastRedeem {
2
+ assetsWithdrawn: {
3
+ asset: {
4
+ decimals: number
5
+ id: string
6
+ name: string
7
+ symbol: string
8
+ }
9
+ claimed: string
10
+ id: string
11
+ }[]
12
+ blockNumber: number
13
+ id: string
14
+ svtWithdrawn: string
15
+ }
@@ -0,0 +1,5 @@
1
+ export type SwapInfo = {
2
+ swapTarget: any
3
+ token: string
4
+ swapCallData: any
5
+ }
@@ -0,0 +1,7 @@
1
+ export type UserBalance = {
2
+ hasWithdrawsToClaim: boolean,
3
+ userBalance: string,
4
+ pendingUserBalance: string,
5
+ pendingWithdrawalBalance: string,
6
+ withdrawableBalance: string
7
+ }
package/types/Vault.ts ADDED
@@ -0,0 +1,68 @@
1
+ export type Vault = {
2
+ vaultName: string
3
+ vaultAddress: string
4
+ apy: number
5
+ tvr: number
6
+ protocols: string[]
7
+ projectName: string
8
+ depositToken: VaultDepositToken
9
+ type: VaultType
10
+ rewards: VaultReward[]
11
+ liveUntil: string
12
+ liveUntilFormatted: string
13
+ hasPoolEnded: boolean
14
+ boosters: NFTBoost[]
15
+ stretchGoals: StretchGoal[]
16
+ strategies: Strategy[]
17
+ newWithdraw: boolean
18
+ tgePrice?: number
19
+ maxAmountOfTokens?: number,
20
+ rewardsInformation: RewardsInformation
21
+ }
22
+
23
+ export type VaultType = 'launch' | 'earn' | 'airdrop'
24
+
25
+ export type VaultDepositToken = {
26
+ name: string
27
+ symbol: string
28
+ roundingDecimals: number
29
+ precisionDecimals: number
30
+ tokenAddress: string
31
+ }
32
+
33
+ export type VaultReward = {
34
+ type: 'token' | 'points'
35
+ name: string
36
+ symbol: string
37
+ decimals: number
38
+ tokenAddress: string
39
+ monthlyEmissionRate?: number
40
+ }
41
+
42
+ export type NFTBoost = {
43
+ url: string
44
+ tokenAddress: string
45
+ multiplier: number
46
+ nftAddress: string
47
+ network: string
48
+ initialBlock: number // needs to be the block when rewards was deployed
49
+ imagePath: string
50
+ }
51
+
52
+ export interface StretchGoal {
53
+ threshhold: number
54
+ threshholdDescription: string
55
+ rewardTooltip: string
56
+ rewardDescription: string
57
+ amountOfTokens: number
58
+ }
59
+
60
+ export type RewardsInformation = {
61
+ [tokenAddress: string]: { blockNumber: string, rewardrate: string, timestamp: string, endTime: string }
62
+ }
63
+
64
+ export type Strategy = {
65
+ address: string
66
+ lastDoHardWorkTime: number | null
67
+ lastDoHardWorkBlock: number | null
68
+ }
package/utils.ts ADDED
@@ -0,0 +1,12 @@
1
+
2
+ export function insertDot(numberString: string, decimals: number) {
3
+ if (numberString.length < decimals + 1) {
4
+ numberString = '0'.repeat(decimals + 1 - numberString.length) + numberString
5
+ }
6
+ const indexToInsertDot = numberString.length - decimals
7
+ // Ensure there is a part before the dot, even if it's 0
8
+ const beforeDecimal = indexToInsertDot > 0 ? numberString.slice(0, indexToInsertDot) : '0'
9
+ const afterDecimal = numberString.slice(indexToInsertDot)
10
+ return `${beforeDecimal}.${afterDecimal}`
11
+ }
12
+