• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

decentraland / marketplace / 10167911878

30 Jul 2024 06:36PM UTC coverage: 66.622% (-0.1%) from 66.726%
10167911878

Pull #2277

github

meelrossi
fix tests
Pull Request #2277: feat: add ui fixes for bids offchain flow

2655 of 5176 branches covered (51.29%)

Branch coverage included in aggregate %.

3 of 7 new or added lines in 4 files covered. (42.86%)

12 existing lines in 5 files now uncovered.

7778 of 10484 relevant lines covered (74.19%)

78.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.97
/webapp/src/modules/rental/sagas.ts
1
import { ethers } from 'ethers'
32✔
2
import { call, delay, put, select, take, takeEvery } from 'redux-saga/effects'
32✔
3
import { NFT, NFTCategory, Network, PeriodCreation, RentalListing, RentalListingCreation, RentalStatus } from '@dcl/schemas'
32✔
4
import { getConnectedProvider } from 'decentraland-dapps/dist/lib/eth'
32✔
5
import { CloseModalAction, CLOSE_MODAL } from 'decentraland-dapps/dist/modules/modal/actions'
32✔
6
import { waitForTx } from 'decentraland-dapps/dist/modules/transaction/utils'
32✔
7
import { sendTransaction } from 'decentraland-dapps/dist/modules/wallet/utils'
32✔
8
import { AuthIdentity } from 'decentraland-crypto-fetch'
9
import { ContractData, ContractName, getContract, Provider } from 'decentraland-transactions'
32✔
10
import { getContract as getContractByQuery } from '../contract/selectors'
32✔
11
import { getIdentity } from '../identity/utils'
32✔
12
import { fetchNFTRequest, FETCH_NFT_SUCCESS } from '../nft/actions'
32✔
13
import { getFingerprint } from '../nft/estate/utils'
32✔
14
import { getCurrentNFT } from '../nft/selectors'
32✔
15
import { rentalsAPI } from '../vendor/decentraland/rentals/api'
32✔
16
import { getAddress } from '../wallet/selectors'
32✔
17
import { addressEquals } from '../wallet/utils'
32✔
18
import {
19
  claimAssetFailure,
20
  ClaimAssetRequestAction,
21
  claimAssetTransactionSubmitted,
22
  claimAssetSuccess,
23
  CLAIM_ASSET_REQUEST,
24
  clearRentalErrors,
25
  upsertRentalFailure,
26
  UpsertRentalRequestAction,
27
  upsertRentalSuccess,
28
  UPSERT_RENTAL_REQUEST,
29
  RemoveRentalRequestAction,
30
  removeRentalFailure,
31
  removeRentalSuccess,
32
  removeRentalTransactionSubmitted,
33
  REMOVE_RENTAL_REQUEST,
34
  ACCEPT_RENTAL_LISTING_REQUEST,
35
  AcceptRentalListingRequestAction,
36
  acceptRentalListingSuccess,
37
  acceptRentalListingFailure,
38
  acceptRentalListingTransactionSubmitted
39
} from './actions'
32✔
40
import { daysByPeriod, generateECDSASignatureWithValidV, getNonces, getSignature, waitUntilRentalChangesStatus } from './utils'
32✔
41

42
export function* rentalSaga() {
279✔
43
  yield takeEvery(UPSERT_RENTAL_REQUEST, handleCreateOrEditRentalRequest)
279✔
44
  yield takeEvery(CLAIM_ASSET_REQUEST, handleClaimLandRequest)
279✔
45
  yield takeEvery(CLOSE_MODAL, handleModalClose)
279✔
46
  yield takeEvery(REMOVE_RENTAL_REQUEST, handleRemoveRentalRequest)
279✔
47
  yield takeEvery(ACCEPT_RENTAL_LISTING_REQUEST, handleAcceptRentalListingRequest)
279✔
48
}
49

50
function* handleCreateOrEditRentalRequest(action: UpsertRentalRequestAction) {
51
  const { nft, pricePerDay, expiresAt, operationType } = action.payload
6✔
52

53
  const periods: PeriodCreation[] = action.payload.periods.map(period => ({
6✔
54
    maxDays: daysByPeriod[period],
55
    minDays: daysByPeriod[period],
56
    pricePerDay: ethers.utils.parseUnits(pricePerDay.toString()).toString()
57
  }))
58

59
  try {
6✔
60
    const address: string | undefined = yield select(getAddress)
6✔
61
    if (!address) {
6✔
62
      throw new Error(`Invalid address`)
1✔
63
    }
64

65
    const nonces: string[] = yield call(getNonces, nft.chainId, nft.contractAddress, nft.tokenId, address)
5✔
66

67
    let signature: string = yield call(getSignature, nft.chainId, nft.contractAddress, nft.tokenId, nonces, periods, expiresAt)
4✔
68

69
    signature = yield call(generateECDSASignatureWithValidV, signature)
3✔
70

71
    const rentalsContract: ContractData = getContract(ContractName.Rentals, nft.chainId)
3✔
72

73
    const rentalListingCreation: RentalListingCreation = {
3✔
74
      chainId: nft.chainId,
75
      contractAddress: nft.contractAddress,
76
      tokenId: nft.tokenId,
77
      network: nft.network as Network.ETHEREUM,
78
      expiration: expiresAt,
79
      rentalContractAddress: rentalsContract.address,
80
      nonces,
81
      periods,
82
      signature,
83
      target: ethers.constants.AddressZero // For now, all rent listing will be "public", for all addresses to use.
84
    }
85

86
    const identity: AuthIdentity = yield getIdentity()
3✔
87

88
    const rental: RentalListing = yield call([rentalsAPI, 'createRentalListing'], rentalListingCreation, identity)
3✔
89

90
    yield put(upsertRentalSuccess(nft, rental, operationType))
3✔
91
  } catch (error) {
92
    yield put(upsertRentalFailure(nft, pricePerDay, action.payload.periods, expiresAt, (error as Error).message))
3✔
93
  }
94
}
95

96
function* handleClaimLandRequest(action: ClaimAssetRequestAction) {
97
  const { nft, rental } = action.payload
6✔
98

99
  try {
6✔
100
    const provider: Provider | null = yield call(getConnectedProvider)
6✔
101
    if (!provider) {
6✔
102
      throw new Error('A provider is required to claim LAND')
1✔
103
    }
104

105
    const address: string | undefined = yield select(getAddress)
5✔
106
    if (!address) {
5✔
107
      throw new Error('An address is required to claim LAND')
1✔
108
    }
109

110
    const rentalsContract: ContractData = yield call(getContract, ContractName.Rentals, nft.chainId)
4✔
111

112
    const txHash: string = yield call(
3✔
113
      sendTransaction as (contract: ContractData, contractMethodName: string, ...contractArguments: any[]) => Promise<string>,
114
      rentalsContract,
115
      'claim(address[],uint256[])',
116
      [nft.contractAddress],
117
      [nft.tokenId]
118
    )
119
    yield put(claimAssetTransactionSubmitted(nft, txHash, rentalsContract.address))
2✔
120
    yield call(waitForTx, txHash)
2✔
121
    yield call(waitUntilRentalChangesStatus, nft, RentalStatus.CLAIMED)
1✔
122
    let hasAssetBack = addressEquals(nft.owner, rental.lessor!)
1✔
123
    while (!hasAssetBack) {
1✔
124
      yield put(fetchNFTRequest(nft.contractAddress, nft.tokenId))
1✔
125
      yield take(FETCH_NFT_SUCCESS)
1✔
126
      const nftUpdated: NFT = yield select(getCurrentNFT)
1✔
127
      hasAssetBack = addressEquals(nftUpdated.owner, rental.lessor!)
1✔
128
      yield delay(5000)
1✔
129
    }
130
    yield put(claimAssetSuccess(nft, rental))
1✔
131
  } catch (error) {
132
    yield put(claimAssetFailure((error as Error).message))
5✔
133
  }
134
}
135

136
function* handleModalClose(action: CloseModalAction) {
137
  if (action.payload.name === 'ClaimLandModal' || action.payload.name === 'RemoveRentalModal') {
2✔
138
    yield put(clearRentalErrors())
2✔
139
  }
140
}
141

142
function* handleRemoveRentalRequest(action: RemoveRentalRequestAction) {
143
  const { nft } = action.payload
7✔
144

145
  try {
7✔
146
    if (!nft.openRentalId) {
7✔
147
      throw new Error('The provided NFT does not have an open rental')
1✔
148
    }
149

150
    const provider: Provider | null = yield call(getConnectedProvider)
6✔
151
    if (!provider) {
6✔
152
      throw new Error('A provider is required to remove a rental')
1✔
153
    }
154

155
    const address: string | undefined = yield select(getAddress)
5✔
156
    if (!address) {
5✔
157
      throw new Error('An address is required to remove a rental')
1✔
158
    }
159

160
    const rentalsContract: ContractData = yield call(getContract, ContractName.Rentals, nft.chainId)
4✔
161

162
    const txHash: string = yield call(
3✔
163
      sendTransaction as (contract: ContractData, contractMethodName: string, ...contractArguments: any[]) => Promise<string>,
164
      rentalsContract,
165
      'bumpAssetIndex(address,uint256)',
166
      nft.contractAddress,
167
      nft.tokenId
168
    )
169
    yield put(removeRentalTransactionSubmitted(nft, txHash))
2✔
170
    yield call(waitForTx, txHash)
2✔
171
    yield call(waitUntilRentalChangesStatus, nft, RentalStatus.CANCELLED)
1✔
172
    yield put(removeRentalSuccess(nft))
1✔
173
  } catch (error) {
174
    yield put(removeRentalFailure((error as Error).message))
6✔
175
  }
176
}
177

178
function* handleAcceptRentalListingRequest(action: AcceptRentalListingRequestAction) {
179
  const { nft, rental, periodIndexChosen, addressOperator } = action.payload
8✔
180

181
  try {
8✔
182
    if (!nft.openRentalId) {
8✔
183
      throw new Error('The provided NFT does not have an open rental')
1✔
184
    }
185

186
    const provider: Provider | null = yield call(getConnectedProvider)
7✔
187
    if (!provider) {
7✔
188
      throw new Error('A provider is required to remove a rental')
1✔
189
    }
190

191
    const address: string | undefined = yield select(getAddress)
6✔
192
    if (!address) {
6✔
193
      throw new Error('An address is required to remove a rental')
1✔
194
    }
195

196
    const rentalsContract: ContractData = yield call(getContract, ContractName.Rentals, nft.chainId)
5✔
197

198
    // the contract expects these as arrays of values
199
    const [pricePerDay, maxDays, minDays] = rental.periods.reduce(
4✔
200
      (acc, curr) => {
201
        acc[0].push(curr.pricePerDay)
4✔
202
        acc[1].push(curr.maxDays)
4✔
203
        acc[2].push(curr.minDays)
4✔
204
        return acc
4✔
205
      },
206
      [[], [], []] as [string[], number[], number[]]
207
    )
208

209
    const signature: string = yield call(generateECDSASignatureWithValidV, rental.signature)
4✔
210

211
    const listing = {
4✔
212
      signer: rental.lessor,
213
      contractAddress: rental.contractAddress,
214
      tokenId: rental.tokenId,
215
      expiration: (rental.expiration / 1000).toString(),
216
      indexes: rental.nonces,
217
      pricePerDay,
218
      maxDays,
219
      minDays,
220
      target: ethers.constants.AddressZero,
221
      signature
222
    }
223

224
    let fingerprint = ethers.utils.randomBytes(32).map(() => 0)
128✔
225
    if (nft.category === NFTCategory.ESTATE) {
4!
226
      const estateContract: ReturnType<typeof getContractByQuery> = yield select(getContractByQuery, {
×
227
        category: NFTCategory.ESTATE
228
      })
229
      if (estateContract) {
×
NEW
230
        fingerprint = yield call(getFingerprint, nft.tokenId, estateContract, nft.chainId)
×
231
      }
232
    }
233

234
    const txParams = [Object.values(listing), addressOperator, periodIndexChosen, rental.periods[periodIndexChosen].maxDays, fingerprint]
4✔
235

236
    const txHash: string = yield call(
4✔
237
      sendTransaction as (contract: ContractData, contractMethodName: string, ...contractArguments: any[]) => Promise<string>,
238
      rentalsContract,
239
      'acceptListing((address,address,uint256,uint256,uint256[3],uint256[],uint256[],uint256[],address,bytes),address,uint256,uint256,bytes32)',
240
      ...txParams
241
    )
242
    yield put(acceptRentalListingTransactionSubmitted(nft, rental, txHash, periodIndexChosen))
3✔
243
    yield call(waitForTx, txHash)
3✔
244
    const rentalListingUpdated: RentalListing = yield call(waitUntilRentalChangesStatus, nft, RentalStatus.EXECUTED)
2✔
245
    yield put(acceptRentalListingSuccess(nft, rentalListingUpdated, periodIndexChosen))
2✔
246
  } catch (error) {
247
    yield put(acceptRentalListingFailure((error as Error).message))
6✔
248
  }
249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc