From 42d108c641f757253dc69e27997dc015e0b10ecb Mon Sep 17 00:00:00 2001 From: Melisa Anabella Rossi Date: Tue, 16 Jul 2024 17:18:15 -0300 Subject: [PATCH 1/8] feat: add accept trade flow --- webapp/package-lock.json | 41 ++++++- webapp/package.json | 2 +- .../AssetPage/YourOffer/YourOffer.tsx | 7 +- .../Bid/AcceptButton/AcceptButton.tsx | 14 ++- .../Bid/AcceptButton/AcceptButton.types.ts | 4 +- webapp/src/components/Bid/Bid.container.ts | 7 +- webapp/src/components/Bid/Bid.tsx | 111 ++++++++++++++---- webapp/src/components/Bid/Bid.types.ts | 9 +- webapp/src/components/Bid/utils.ts | 20 ++++ webapp/src/modules/bid/actions.ts | 4 +- webapp/src/modules/bid/sagas.ts | 85 +++++++++----- .../src/modules/translation/locales/en.json | 8 ++ .../src/modules/translation/locales/es.json | 8 ++ .../src/modules/translation/locales/zh.json | 8 ++ .../modules/vendor/decentraland/BidService.ts | 8 ++ .../vendor/decentraland/marketplace/api.ts | 17 ++- .../vendor/decentraland/marketplace/types.ts | 16 +++ webapp/src/utils/trades.ts | 58 ++++++--- 18 files changed, 340 insertions(+), 87 deletions(-) create mode 100644 webapp/src/components/Bid/utils.ts diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 6a2076cf5d..a2ddd3504e 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -12,7 +12,7 @@ "@0xsquid/squid-types": "^0.1.29", "@covalenthq/client-sdk": "^0.6.4", "@dcl/crypto": "^3.0.0", - "@dcl/schemas": "^11.10.5", + "@dcl/schemas": "^12.0.0", "@dcl/single-sign-on-client": "^0.1.0", "@dcl/ui-env": "^1.5.0", "@ethersproject/providers": "^5.6.2", @@ -1743,9 +1743,9 @@ "integrity": "sha512-Cg+MoIOn+BYmQV2q8zSFnNYY+GldlnUazwBnfgrq3i66ZxOaZ65h01btd8OUtSAlfWG4VTNIOHDjtKqmuwJNBg==" }, "node_modules/@dcl/schemas": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.10.5.tgz", - "integrity": "sha512-/SqtrVh68lQF4iwe0HIU/egR076PPFpmp6NF6HpikKcHywS6xSi+IdMyI6uYdbT2jV3C9QmIB9foVBI0qsIsSA==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-12.0.0.tgz", + "integrity": "sha512-wQ5acIFHRsian6HPuYpFxJxNyhAW30iobInoo3vR5RCTyKa6XJR+dafaSYuj+CwOihwl5BWV9mp9oZ5c6XOG3Q==", "dependencies": { "ajv": "^8.11.0", "ajv-errors": "^3.0.0", @@ -9110,6 +9110,17 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, + "node_modules/decentraland-connect/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decentraland-connect/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -9258,6 +9269,17 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, + "node_modules/decentraland-dapps/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decentraland-dapps/node_modules/@formatjs/intl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.2.1.tgz", @@ -9514,6 +9536,17 @@ } } }, + "node_modules/decentraland-ui/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", diff --git a/webapp/package.json b/webapp/package.json index 564c66e337..93c15a9fff 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -7,7 +7,7 @@ "@0xsquid/squid-types": "^0.1.29", "@covalenthq/client-sdk": "^0.6.4", "@dcl/crypto": "^3.0.0", - "@dcl/schemas": "^11.10.5", + "@dcl/schemas": "^12.0.0", "@dcl/single-sign-on-client": "^0.1.0", "@dcl/ui-env": "^1.5.0", "@ethersproject/providers": "^5.6.2", diff --git a/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx b/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx index 77f4d6a655..763802776a 100644 --- a/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx +++ b/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx @@ -127,7 +127,12 @@ const YourOffer = (props: Props) => { - diff --git a/webapp/src/components/Bid/AcceptButton/AcceptButton.tsx b/webapp/src/components/Bid/AcceptButton/AcceptButton.tsx index 75f93f618d..7ce5d53efe 100644 --- a/webapp/src/components/Bid/AcceptButton/AcceptButton.tsx +++ b/webapp/src/components/Bid/AcceptButton/AcceptButton.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { Button, Popup } from 'decentraland-ui' +import { isNFT } from '../../../modules/asset/utils' import { isInsufficientMANA, checkFingerprint } from '../../../modules/bid/utils' import { useFingerprint } from '../../../modules/nft/hooks' import { isLandLocked } from '../../../modules/rental/utils' @@ -8,11 +9,11 @@ import { LandLockedPopup } from '../../LandLockedPopup' import { Props } from './AcceptButton.types' const AcceptButton = (props: Props) => { - const { nft, bid, onClick, rental, userAddress } = props + const { asset, bid, onClick, rental, userAddress } = props - const [fingerprint, isLoadingFingerprint] = useFingerprint(nft) + const [fingerprint, isLoadingFingerprint] = useFingerprint(asset && isNFT(asset) ? asset : null) const [hasInsufficientMANA, setHasInsufficientMANA] = useState(false) - const isCurrentlyLocked = rental && nft && isLandLocked(userAddress, rental, nft) + const isCurrentlyLocked = rental && asset && isLandLocked(userAddress, rental, asset) useEffect(() => { isInsufficientMANA(bid) @@ -21,9 +22,10 @@ const AcceptButton = (props: Props) => { }, [bid]) const isValidFingerprint = checkFingerprint(bid, fingerprint) - const isValidSeller = !!nft && nft.owner === bid.seller + const assetOwner = !!asset && (isNFT(asset) ? asset.owner : asset?.creator) + const isValidSeller = assetOwner && assetOwner === userAddress - const isDisabled = isCurrentlyLocked || !nft || isLoadingFingerprint || hasInsufficientMANA || !isValidFingerprint || !isValidSeller + const isDisabled = isCurrentlyLocked || !asset || isLoadingFingerprint || hasInsufficientMANA || !isValidFingerprint || !isValidSeller let button = ( + {'bidAddress' in bid && ( + + )} ) : null} {isSeller ? ( <> - + {(asset, _order, rental) => ( )} @@ -155,17 +154,13 @@ const Bid = (props: Props) => { {isBidder ? ( - - {nft => } + + {asset => } ) : null} {showConfirmationModal ? ( - + {asset => asset && ( { - const { nft, bid } = props + const { asset, bid } = props - const [fingerprint] = useFingerprint(nft) + const [fingerprint] = useFingerprint(asset && isNFT(asset) ? asset : null) const [hasInsufficientMANA, setHasInsufficientMANA] = useState(false) useEffect(() => { diff --git a/webapp/src/components/Bid/WarningMessage/WarningMessage.types.ts b/webapp/src/components/Bid/WarningMessage/WarningMessage.types.ts index 3d82949e8c..6d8d3de0ca 100644 --- a/webapp/src/components/Bid/WarningMessage/WarningMessage.types.ts +++ b/webapp/src/components/Bid/WarningMessage/WarningMessage.types.ts @@ -1,7 +1,7 @@ import { Bid } from '@dcl/schemas' -import { NFT } from '../../../modules/nft/types' +import { Asset } from '../../../modules/asset/types' export type Props = { - nft: NFT | null + asset: Asset | null bid: Bid } diff --git a/webapp/src/components/Bid/utils.ts b/webapp/src/components/Bid/utils.ts index 3c3788e13f..f21e740d73 100644 --- a/webapp/src/components/Bid/utils.ts +++ b/webapp/src/components/Bid/utils.ts @@ -1,17 +1,17 @@ import { ethers } from 'ethers' +import { ChainId } from '@dcl/schemas' import { getNetworkProvider } from 'decentraland-dapps/dist/lib' import ERC721ABI from '../../contracts/ERC721.json' -import { Contract } from '../../modules/vendor/services' -export const fetchContractName = async (contract: Contract | null) => { +export const fetchContractName = async (address: string, chainId: ChainId) => { try { - if (!contract) { + if (!address || !chainId) { return null } - const provider = await getNetworkProvider(contract.chainId) + const provider = await getNetworkProvider(chainId) - const erc721 = new ethers.Contract(contract.address, ERC721ABI, new ethers.providers.Web3Provider(provider)) + const erc721 = new ethers.Contract(address, ERC721ABI, new ethers.providers.Web3Provider(provider)) return (await erc721.name()) as string } catch (e) { diff --git a/webapp/src/modules/bid/sagas.spec.ts b/webapp/src/modules/bid/sagas.spec.ts index f72c284fe5..4df8f283fb 100644 --- a/webapp/src/modules/bid/sagas.spec.ts +++ b/webapp/src/modules/bid/sagas.spec.ts @@ -1,9 +1,12 @@ import { call, select } from 'redux-saga/effects' import { expectSaga } from 'redux-saga-test-plan' import { throwError } from 'redux-saga-test-plan/providers' -import { Bid, ChainId, Network, RentalListing, RentalStatus, TradeAssetType, TradeCreation, TradeType } from '@dcl/schemas' +import { Bid, ChainId, Network, RentalListing, RentalStatus, TradeAssetType, TradeCreation, TradeType, Trade } from '@dcl/schemas' import { waitForTx } from 'decentraland-dapps/dist/modules/transaction/utils' import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' +import { sendTransaction } from 'decentraland-dapps/dist/modules/wallet/utils' +import { ContractData, ContractName, getContract as getDCLContract } from 'decentraland-transactions' +import { getTradeToAccept } from '../../utils/trades' import { Asset } from '../asset/types' import { getContract } from '../contract/selectors' import { getIsBidsOffChainEnabled } from '../features/selectors' @@ -388,5 +391,114 @@ describe('when handling the accepting a bid action', () => { }) }) - describe('and offchain bids are enabled', () => {}) + describe('and offchain bids are enabled', () => { + let marketplaceAPIMock: jest.Mocked + let bid: Bid + + describe('and getting the trade by id fails', () => { + let error: string + + beforeEach(() => { + error = 'Some error' + bid = { + contractAddress: '0x123', + tradeId: 'atrade-id' + } as Bid + marketplaceAPIMock = { fetchTrade: jest.fn().mockRejectedValue(new Error(error)) } as unknown as jest.Mocked + }) + + it('should dispatch an action signaling the failure of the action handling', () => { + return expectSaga(bidSaga, marketplaceAPIMock) + .provide([[select(getIsBidsOffChainEnabled), true]]) + .put(acceptBidFailure(bid, error)) + .dispatch(acceptBidRequest(bid)) + .run({ silenceTimeout: true }) + }) + }) + + describe('and the trade acceptance transaction was sent successfully', () => { + let trade: Trade + let txHash: string + let offchainMarketplaceContract: ContractData + + beforeEach(() => { + trade = { + id: 'atrade-id', + signer: '0x123', + signature: '0x123123', + type: TradeType.BID, + network: Network.ETHEREUM, + chainId: ChainId.ETHEREUM_SEPOLIA, + createdAt: Date.now(), + checks: { + expiration: Date.now() + 100000000000, + effective: Date.now(), + uses: 1, + salt: '', + allowedRoot: '0x', + contractSignatureIndex: 0, + externalChecks: [], + signerSignatureIndex: 0 + }, + sent: [ + { + assetType: TradeAssetType.ERC20, + contractAddress: '0x1231', + amount: '2', + extra: '' + } + ], + received: [ + { + assetType: TradeAssetType.ERC721, + contractAddress: '0x12321', + tokenId: '1', + extra: '', + beneficiary: '0x123' + } + ] + } + + bid = { + contractAddress: '0x123', + tradeId: 'atrade-id', + price: '123' + } as Bid + + offchainMarketplaceContract = { + address: '0x234', + abi: [], + chainId: ChainId.ETHEREUM_SEPOLIA, + name: 'OffChainMarketplace', + version: '1.0.0' + } + + txHash = '0x12312412' + + marketplaceAPIMock = { fetchTrade: jest.fn().mockResolvedValue(trade) } as unknown as jest.Mocked + }) + + it.only('should dispatch an action signaling the success of the action handling', () => { + return expectSaga(bidSaga, marketplaceAPIMock) + .provide([ + [select(getIsBidsOffChainEnabled), true], + [call(getDCLContract, ContractName.OffChainMarketplace, trade.chainId), offchainMarketplaceContract], + [ + call( + sendTransaction as (contract: ContractData, contractMethodName: string, ...contractArguments: any[]) => Promise, + offchainMarketplaceContract, + 'function accept(Trade[] calldata _trades) external;', + [getTradeToAccept(trade)] + ), + Promise.resolve(txHash) + ], + [select(getCurrentNFT), null], + [call(waitForTx, txHash), Promise.resolve()] + ]) + .put(acceptBidSuccess(bid)) + .dispatch(acceptBidRequest(bid)) + .run({ silenceTimeout: true }) + }) + }) + }) }) diff --git a/webapp/src/modules/bid/sagas.ts b/webapp/src/modules/bid/sagas.ts index bd8a35ba62..169995fc16 100644 --- a/webapp/src/modules/bid/sagas.ts +++ b/webapp/src/modules/bid/sagas.ts @@ -1,5 +1,7 @@ -import { takeEvery, put, select, call, all } from 'redux-saga/effects' +import { History } from 'history' +import { takeEvery, put, select, call, all, getContext } from 'redux-saga/effects' import { Bid, RentalStatus, Trade, TradeCreation } from '@dcl/schemas' +import { showToast } from 'decentraland-dapps/dist/modules/toast/actions' import { waitForTx } from 'decentraland-dapps/dist/modules/transaction/utils' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { sendTransaction } from 'decentraland-dapps/dist/modules/wallet/utils' @@ -12,6 +14,8 @@ import { getIsBidsOffChainEnabled } from '../features/selectors' import { getCurrentNFT } from '../nft/selectors' import { getRentalById } from '../rental/selectors' import { isRentalListingOpen, waitUntilRentalChangesStatus } from '../rental/utils' +import { locations } from '../routing/locations' +import { getBidPlacedSuccessToast } from '../toast/toasts' import { MarketplaceAPI } from '../vendor/decentraland/marketplace/api' import { VendorName } from '../vendor/types' import { VendorFactory } from '../vendor/VendorFactory' @@ -59,9 +63,14 @@ export function* bidSaga(marketplaceAPI: MarketplaceAPI) { const isBidsOffchainEnabled: boolean = yield select(getIsBidsOffChainEnabled) if (isBidsOffchainEnabled) { + const history: History = yield getContext('history') const trade: TradeCreation = yield call([bidUtils, 'createBidTrade'], asset, price, expiresAt, fingerprint) yield call([marketplaceAPI, 'addTrade'], trade) yield put(placeBidSuccess(asset, price, expiresAt, asset.chainId, wallet.address, fingerprint)) + yield put(showToast(getBidPlacedSuccessToast(asset))) + history.push( + isNFT(asset) ? locations.nft(asset.contractAddress, asset.tokenId) : locations.item(asset.contractAddress, asset.itemId) + ) } else { if (isNFT(asset)) { const { bidService } = VendorFactory.build(asset.vendor) @@ -93,15 +102,15 @@ export function* bidSaga(marketplaceAPI: MarketplaceAPI) { if (isBidsOffchainEnabled) { const trade: Trade = yield call([marketplaceAPI, 'fetchTrade'], bid.tradeId) const tradeToAccept = tradeUtils.getTradeToAccept(trade) - const offchainMarketplaceContract = getDCLContract(ContractName.OffChainMarketplace, trade.chainId) + const offchainMarketplaceContract: ContractData = yield call(getDCLContract, ContractName.OffChainMarketplace, trade.chainId) txHash = yield call( sendTransaction as (contract: ContractData, contractMethodName: string, ...contractArguments: any[]) => Promise, offchainMarketplaceContract, - 'accept', + 'function accept(Trade[] calldata _trades) external;', [tradeToAccept] ) } else { - console.error('Not able to accept bid') + throw new Error('not able to accept offchain bids') } } else { const contract = (yield select(getContract, { diff --git a/webapp/src/modules/toast/toasts.tsx b/webapp/src/modules/toast/toasts.tsx index e9cc145a62..a9e7087c73 100644 --- a/webapp/src/modules/toast/toasts.tsx +++ b/webapp/src/modules/toast/toasts.tsx @@ -6,7 +6,7 @@ import { T, t } from 'decentraland-dapps/dist/modules/translation/utils' import { Button, Icon, ToastType } from 'decentraland-ui' import { config } from '../../config' import { builderUrl } from '../../lib/environment' -import { AssetType } from '../asset/types' +import { Asset, AssetType } from '../asset/types' import { getAssetName } from '../asset/utils' import { bulkPickUnpickRequest } from '../favorites/actions' import { List } from '../favorites/types' @@ -311,3 +311,15 @@ export function getCrossChainTransactionSuccessToast(txLink: string): Omit { + // TODO: This will be removed when final design is ready + return { + type: ToastType.INFO, + title: 'Bid placed', + body: `Bid for ${asset.name} has been placed successfully`, + closable: true, + timeout: DEFAULT_TIMEOUT, + icon: + } +} diff --git a/webapp/src/modules/vendor/decentraland/marketplace/api.spec.ts b/webapp/src/modules/vendor/decentraland/marketplace/api.spec.ts index 48553f5bc1..008162a3df 100644 --- a/webapp/src/modules/vendor/decentraland/marketplace/api.spec.ts +++ b/webapp/src/modules/vendor/decentraland/marketplace/api.spec.ts @@ -1,4 +1,4 @@ -import { TradeCreation } from '@dcl/schemas' +import { Bid, BidSortBy, Trade, TradeCreation } from '@dcl/schemas' import { MARKETPLACE_SERVER_URL, MarketplaceAPI } from './api' describe('when adding a new trade', () => { @@ -48,3 +48,61 @@ describe('when adding a new trade', () => { }) }) }) + +describe('when fetching a trade by id', () => { + let marketplaceApi: MarketplaceAPI + let fetchMock: jest.SpyInstance + + beforeEach(() => { + marketplaceApi = new MarketplaceAPI(MARKETPLACE_SERVER_URL) + fetchMock = jest.spyOn(marketplaceApi as any, 'fetch').mockResolvedValue({} as Trade) + }) + + it('should call the marketplace api with the right parameters', async () => { + await marketplaceApi.fetchTrade('tradeId') + + expect(fetchMock).toHaveBeenCalledWith('/v1/trades/tradeId', { method: 'GET' }) + }) +}) + +describe('when fetching bids', () => { + let marketplaceApi: MarketplaceAPI + let fetchMock: jest.SpyInstance + + beforeEach(() => { + marketplaceApi = new MarketplaceAPI(MARKETPLACE_SERVER_URL) + fetchMock = jest.spyOn(marketplaceApi as any, 'fetch').mockResolvedValue([] as Bid[]) + }) + + describe('when no parameters are passed', () => { + it('should call the marketplace api with the right parameters', async () => { + await marketplaceApi.fetchBids() + + expect(fetchMock).toHaveBeenCalledWith('/v1/bids', { method: 'GET' }) + }) + }) + + describe('when the limit and offset params are defined', () => { + it('should call the marketplace api with correct limit and offset parameters', async () => { + await marketplaceApi.fetchBids({ limit: 1, offset: 0 }) + + expect(fetchMock).toHaveBeenCalledWith('/v1/bids?limit=1&offset=0', { method: 'GET' }) + }) + }) + + describe('when the bidder param is defined', () => { + it('should call the marketplace api with correct bidder parameters', async () => { + await marketplaceApi.fetchBids({ bidder: '0x1234' }) + + expect(fetchMock).toHaveBeenCalledWith('/v1/bids?bidder=0x1234', { method: 'GET' }) + }) + }) + + describe('when the sortBy parameter is defined', () => { + it('should call the marketplace api with limit and offset parameters', async () => { + await marketplaceApi.fetchBids({ sortBy: BidSortBy.MOST_EXPENSIVE }) + + expect(fetchMock).toHaveBeenCalledWith('/v1/bids?sortBy=most_expensive', { method: 'GET' }) + }) + }) +}) diff --git a/webapp/src/modules/vendor/decentraland/marketplace/api.ts b/webapp/src/modules/vendor/decentraland/marketplace/api.ts index f312d47744..4a557de300 100644 --- a/webapp/src/modules/vendor/decentraland/marketplace/api.ts +++ b/webapp/src/modules/vendor/decentraland/marketplace/api.ts @@ -38,12 +38,13 @@ export class MarketplaceAPI extends BaseClient { } fetchBids = async (queryParams: GetBidsParameters = {}) => { + const params = Object.entries(queryParams) const searchParams = new URLSearchParams() - Object.entries(queryParams).forEach(([key, value]) => { + params.forEach(([key, value]) => { searchParams.append(key, value.toString()) }) - return this.fetch>(`/v1/bids${searchParams.size ? `?${searchParams.toString()}` : ''}`, { method: 'GET' }) + return this.fetch>(`/v1/bids${params.length ? `?${searchParams.toString()}` : ''}`, { method: 'GET' }) } } diff --git a/webapp/src/utils/trades.ts b/webapp/src/utils/trades.ts index 3e40b79c8e..26525abcbe 100644 --- a/webapp/src/utils/trades.ts +++ b/webapp/src/utils/trades.ts @@ -1,6 +1,6 @@ import { TypedDataDomain, TypedDataField, ethers } from 'ethers' import { ChainId, Trade, TradeAsset, TradeAssetType, TradeCreation } from '@dcl/schemas' -import { getConnectedProvider, getSigner } from 'decentraland-dapps/dist/lib/eth' +import { getNetworkProvider, getSigner } from 'decentraland-dapps/dist/lib/eth' import { ContractData, ContractName, getContract } from 'decentraland-transactions' import { fromMillisecondsToSeconds } from '../lib/time' @@ -42,7 +42,7 @@ export const OFFCHAIN_MARKETPLACE_TYPES: Record = { } export async function getOffChainMarketplaceContract(chainId: ChainId) { - const provider = await getConnectedProvider() + const provider = await getNetworkProvider(chainId) if (!provider) { throw new Error('Could not get connected provider') } From d453ac61e82c2f408d462f9ce37711d75bba4356 Mon Sep 17 00:00:00 2001 From: Melisa Anabella Rossi Date: Thu, 18 Jul 2024 18:09:29 -0300 Subject: [PATCH 5/8] commit to undo --- .../ActivityPage/Transaction/Transaction.tsx | 81 +++++-------------- .../AssetPage/BidsTable/BidsTable.tsx | 3 +- webapp/src/modules/bid/utils.ts | 5 ++ 3 files changed, 28 insertions(+), 61 deletions(-) diff --git a/webapp/src/components/ActivityPage/Transaction/Transaction.tsx b/webapp/src/components/ActivityPage/Transaction/Transaction.tsx index 6bf9708eff..ced7c4f863 100644 --- a/webapp/src/components/ActivityPage/Transaction/Transaction.tsx +++ b/webapp/src/components/ActivityPage/Transaction/Transaction.tsx @@ -220,73 +220,34 @@ const Transaction = (props: Props) => { ) } - case PLACE_BID_SUCCESS: { - const { tokenId, contractAddress, price } = tx.payload as { tokenId: string; contractAddress: string; price: number } - - return ( - - {nft => ( - {nft ? getAssetName(nft) : ''}, - price: ( - - {price.toLocaleString()} - - ) - }} - /> - } - tx={tx} - /> - )} - - ) - } - case ACCEPT_BID_TRANSACTION_SUBMITTED: { - const { tokenId, contractAddress, price } = tx.payload as { tokenId: string; contractAddress: string; price: number } - return ( - - {nft => ( - {nft ? getAssetName(nft) : ''}, - price: ( - - {price.toLocaleString()} - - ) - }} - /> - } - tx={tx} - /> - )} - - ) - } + case PLACE_BID_SUCCESS: + case ACCEPT_BID_TRANSACTION_SUBMITTED: case CANCEL_BID_SUCCESS: { - const { tokenId, contractAddress, price } = tx.payload as { tokenId: string; contractAddress: string; price: number } + const payload = tx.payload as { contractAddress: string; price: number } & ({ tokenId: string } | { itemId: string }) + const { contractAddress, price } = payload + + const tokenId = 'tokenId' in payload ? payload.tokenId : payload.itemId + const assetType = 'tokenId' in payload ? AssetType.NFT : AssetType.ITEM + const link = assetType === AssetType.NFT ? locations.nft(contractAddress, tokenId) : locations.item(contractAddress, tokenId) + const translationKey = + tx.actionType === PLACE_BID_SUCCESS + ? 'transaction.detail.place_bid' + : tx.actionType === ACCEPT_BID_TRANSACTION_SUBMITTED + ? 'transaction.detail.accept_bid' + : 'transaction.detail.cancel_bid' + return ( - - {nft => ( + + {asset => ( {nft ? getAssetName(nft) : ''}, + name: {asset ? getAssetName(asset) : ''}, price: ( - + {price.toLocaleString()} ) diff --git a/webapp/src/components/AssetPage/BidsTable/BidsTable.tsx b/webapp/src/components/AssetPage/BidsTable/BidsTable.tsx index f96b9beb48..7e7bb4e9fa 100644 --- a/webapp/src/components/AssetPage/BidsTable/BidsTable.tsx +++ b/webapp/src/components/AssetPage/BidsTable/BidsTable.tsx @@ -6,6 +6,7 @@ import { Mana, useTabletAndBelowMediaQuery } from 'decentraland-ui' import { formatWeiMANA } from '../../../lib/mana' import { AssetType } from '../../../modules/asset/types' import { getAssetName } from '../../../modules/asset/utils' +import { isBidTrade } from '../../../modules/bid/utils' import { bidAPI } from '../../../modules/vendor/decentraland' import { AssetProvider } from '../../AssetProvider' import { ConfirmInputValueModal } from '../../ConfirmInputValueModal' @@ -108,7 +109,7 @@ const BidsTable = (props: Props) => { handleSortByChange={(value: string) => setSortBy(value as BidSortBy)} sortBy={sortBy} /> - {showConfirmationModal.bid && showConfirmationModal.display && 'tokenId' in showConfirmationModal.bid ? ( + {showConfirmationModal.bid && showConfirmationModal.display && !isBidTrade(showConfirmationModal.bid) ? ( Date: Thu, 18 Jul 2024 18:24:54 -0300 Subject: [PATCH 6/8] fix comments --- webapp/src/modules/bid/sagas.spec.ts | 2 +- webapp/src/modules/bid/sagas.ts | 10 ++- .../vendor/decentraland/marketplace/types.ts | 1 + webapp/src/utils/trades.spec.ts | 78 ++++++++++++++++++- 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/webapp/src/modules/bid/sagas.spec.ts b/webapp/src/modules/bid/sagas.spec.ts index 4df8f283fb..5ae66b5f47 100644 --- a/webapp/src/modules/bid/sagas.spec.ts +++ b/webapp/src/modules/bid/sagas.spec.ts @@ -478,7 +478,7 @@ describe('when handling the accepting a bid action', () => { marketplaceAPIMock = { fetchTrade: jest.fn().mockResolvedValue(trade) } as unknown as jest.Mocked }) - it.only('should dispatch an action signaling the success of the action handling', () => { + it('should dispatch an action signaling the success of the action handling', () => { return expectSaga(bidSaga, marketplaceAPIMock) .provide([ [select(getIsBidsOffChainEnabled), true], diff --git a/webapp/src/modules/bid/sagas.ts b/webapp/src/modules/bid/sagas.ts index 169995fc16..f314184fdf 100644 --- a/webapp/src/modules/bid/sagas.ts +++ b/webapp/src/modules/bid/sagas.ts @@ -180,10 +180,12 @@ export function* bidSaga(marketplaceAPI: MarketplaceAPI) { const isBidsOffchainEnabled: boolean = yield select(getIsBidsOffChainEnabled) if (isBidsOffchainEnabled) { - sellerBids = ((yield call([marketplaceAPI, 'fetchBids'])) as Awaited>).results // TODO: add seller filter - bidderBids = ( - (yield call([marketplaceAPI, 'fetchBids'], { bidder: address })) as Awaited> - ).results + const bids = (yield all([ + call([marketplaceAPI, 'fetchBids'], { seller: address }), + call([marketplaceAPI, 'fetchBids'], { bidder: address }) + ])) as [Awaited>, Awaited>] + sellerBids = bids[0].results + bidderBids = bids[1].results } else { for (const vendorName of Object.values(VendorName)) { const { bidService } = VendorFactory.build(vendorName) diff --git a/webapp/src/modules/vendor/decentraland/marketplace/types.ts b/webapp/src/modules/vendor/decentraland/marketplace/types.ts index 386f51fd84..383582708a 100644 --- a/webapp/src/modules/vendor/decentraland/marketplace/types.ts +++ b/webapp/src/modules/vendor/decentraland/marketplace/types.ts @@ -10,6 +10,7 @@ export type GetBidsParameters = { limit?: number offset?: number bidder?: string + seller?: string sortBy?: BidSortBy } diff --git a/webapp/src/utils/trades.spec.ts b/webapp/src/utils/trades.spec.ts index 9b54d64d0a..b0bf9d1029 100644 --- a/webapp/src/utils/trades.spec.ts +++ b/webapp/src/utils/trades.spec.ts @@ -4,6 +4,7 @@ import { CollectionItemTradeAsset, ERC20TradeAsset, ERC721TradeAsset, + Trade, TradeAsset, TradeCreation, TradeType @@ -11,7 +12,7 @@ import { import * as ethUtils from 'decentraland-dapps/dist/lib/eth' import { ContractData, ContractName, getContract } from 'decentraland-transactions' import { fromMillisecondsToSeconds } from '../lib/time' -import { OFFCHAIN_MARKETPLACE_TYPES, getTradeSignature, getValueForTradeAsset } from './trades' +import { OFFCHAIN_MARKETPLACE_TYPES, getTradeSignature, getTradeToAccept, getValueForTradeAsset } from './trades' jest.mock('decentraland-dapps/dist/lib/eth', () => { const module = jest.requireActual('decentraland-dapps/dist/lib/eth') @@ -169,3 +170,78 @@ describe('when getting the trade signature', () => { }) }) }) + +describe('when getting the trade to accept', () => { + let trade: Trade + + beforeEach(() => { + trade = { + id: 'an-id', + createdAt: Date.now(), + signature: '123123123', + signer: '0x123', + type: TradeType.BID, + network: Network.ETHEREUM, + chainId: ChainId.ETHEREUM_SEPOLIA, + checks: { + expiration: Date.now() + 100000000000, + effective: Date.now(), + uses: 1, + salt: '', + allowedRoot: '0x', + contractSignatureIndex: 0, + externalChecks: [], + signerSignatureIndex: 0 + }, + sent: [ + { + assetType: TradeAssetType.ERC20, + contractAddress: '0x123', + amount: '2', + extra: '' + } + ], + received: [ + { + assetType: TradeAssetType.ERC721, + contractAddress: '0x123', + tokenId: '1', + extra: '', + beneficiary: '0x123123' + } + ] + } + }) + + it('should return the trade with the correct structure', () => { + expect(getTradeToAccept(trade)).toEqual({ + signer: trade.signer, + signature: trade.signature, + checks: { + expiration: fromMillisecondsToSeconds(trade.checks.expiration), + effective: fromMillisecondsToSeconds(trade.checks.effective), + uses: trade.checks.uses, + salt: ethers.utils.hexZeroPad(ethers.utils.hexlify(trade.chainId), 32), + allowedRoot: ethers.utils.hexZeroPad(trade.checks.allowedRoot, 32), + allowedProof: [], + contractSignatureIndex: trade.checks.contractSignatureIndex, + signerSignatureIndex: trade.checks.signerSignatureIndex, + externalChecks: trade.checks.externalChecks + }, + sent: trade.sent.map(asset => ({ + assetType: asset.assetType, + contractAddress: asset.contractAddress, + value: getValueForTradeAsset(asset), + extra: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(asset.extra)), + beneficiary: asset.contractAddress + })), + received: trade.received.map(asset => ({ + assetType: asset.assetType, + contractAddress: asset.contractAddress, + value: getValueForTradeAsset(asset), + extra: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(asset.extra)), + beneficiary: asset.beneficiary + })) + }) + }) +}) From 117d9f2d66f9626b737b950b86a2dd439b69c704 Mon Sep 17 00:00:00 2001 From: Melisa Anabella Rossi Date: Thu, 18 Jul 2024 19:17:08 -0300 Subject: [PATCH 7/8] fix comments --- .../components/AssetPage/YourOffer/YourOffer.tsx | 14 ++++++-------- webapp/src/components/Bid/Bid.tsx | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx b/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx index 763802776a..e459b3fcaa 100644 --- a/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx +++ b/webapp/src/components/AssetPage/YourOffer/YourOffer.tsx @@ -10,6 +10,7 @@ import iconListings from '../../../images/iconListings.png' import infoIcon from '../../../images/infoIcon.png' import { formatDistanceToNow } from '../../../lib/date' import { formatWeiMANA } from '../../../lib/mana' +import { isBidTrade } from '../../../modules/bid/utils' import { locations } from '../../../modules/routing/locations' import { bidAPI } from '../../../modules/vendor/decentraland' import Mana from '../../Mana/Mana' @@ -127,14 +128,11 @@ const YourOffer = (props: Props) => { - + {!isBidTrade(bid) && ( + + )} diff --git a/webapp/src/components/Bid/Bid.tsx b/webapp/src/components/Bid/Bid.tsx index d8281fa890..e7c1b327df 100644 --- a/webapp/src/components/Bid/Bid.tsx +++ b/webapp/src/components/Bid/Bid.tsx @@ -12,6 +12,7 @@ import { formatDistanceToNow } from '../../lib/date' import { formatWeiMANA } from '../../lib/mana' import { AssetType } from '../../modules/asset/types' import { getAssetName } from '../../modules/asset/utils' +import { isBidTrade } from '../../modules/bid/utils' import { locations } from '../../modules/routing/locations' import { addressEquals } from '../../modules/wallet/utils' import { AssetImage } from '../AssetImage' @@ -124,7 +125,7 @@ const Bid = (props: Props) => {
{isBidder ? ( <> - {'bidAddress' in bid && ( + {!isBidTrade(bid) && ( From 67fe1703b72a11b8e80ba44e4c7d2ce0027155b6 Mon Sep 17 00:00:00 2001 From: Melisa Anabella Rossi Date: Thu, 18 Jul 2024 20:10:20 -0300 Subject: [PATCH 8/8] fix isBid trade calculation --- webapp/src/modules/bid/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/modules/bid/utils.ts b/webapp/src/modules/bid/utils.ts index 2ca3a21d87..9923d73493 100644 --- a/webapp/src/modules/bid/utils.ts +++ b/webapp/src/modules/bid/utils.ts @@ -89,5 +89,5 @@ export async function createBidTrade(asset: NFT | Item, price: number, expiresAt } export function isBidTrade(bid: Bid): bid is BidTrade { - return 'trade_id' in bid + return 'tradeId' in bid }