Skip to content
This repository was archived by the owner on Jun 18, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/bot/src/offer/post-escrow-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { sendToThread } from '@echo/bot/helpers/send-to-thread'
import { buildOfferLinkButton } from '@echo/bot/offer/build-offer-link-button'
import { getOfferThreadOnEchoChannel } from '@echo/bot/offer/get-offer-thread-on-echo-channel'
import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot'
import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot'
import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { getUserByUsername } from '@echo/firestore/crud/user/get-user-by-username'
import { OfferError } from '@echo/model/constants/errors/offer-error'
import { offerReceiverNftItems } from '@echo/model/helpers/offer/offer-receiver-nft-items'
Expand All @@ -22,7 +22,7 @@ async function getUserDiscordId(username: string) {
}

async function isNftFromItemInEscrow(item: NftItem): Promise<boolean> {
const nftSnapshot = await getNftSnapshot(item.token)
const nftSnapshot = await getNftSnapshotByIndex(item.token)
if (!isNil(nftSnapshot)) {
return pipe(prop('id'), getEscrowedNftSnapshot, andThen(complement(isNil)), otherwise(always(false)))(nftSnapshot)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { ListingError } from '@echo/firestore-functions/constants/errors/listing
import { error } from '@echo/firestore-functions/constants/logger'
import type { ExpireListingTaskData } from '@echo/firestore-functions/tasks/expire-listing-task'
import { expireListing } from '@echo/firestore/crud/listing/expire-listing'
import { getListing } from '@echo/firestore/crud/listing/get-listing'
import type { Listing } from '@echo/model/types/listing'
import { withSlugSchema } from '@echo/model/validators/slug-schema'
import { otherwise, pipe } from 'ramda'
import type { Nullable } from '@echo/utils/types/nullable'
import { always, isNil, otherwise, pipe } from 'ramda'

export async function expireListingTaskRequestHandler(data: ExpireListingTaskData) {
const { slug } = withSlugSchema.parse(data)
await pipe(
expireListing,
otherwise((err) => {
error({ err, listing: { slug } }, ListingError.Expire)
})
)(slug)
const listing = await pipe(getListing, otherwise(always<Nullable<Listing>>(undefined)))(slug)
if (!isNil(listing) && !listing.locked) {
await pipe(
expireListing,
otherwise((err) => {
error({ err, listing: { slug } }, ListingError.Expire)
})
)(slug)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { OfferError } from '@echo/firestore-functions/constants/errors/offer-err
import { error } from '@echo/firestore-functions/constants/logger'
import type { ExpireOfferTaskData } from '@echo/firestore-functions/tasks/expire-offer-task'
import { expireOffer } from '@echo/firestore/crud/offer/expire-offer'
import { getOffer } from '@echo/firestore/crud/offer/get-offer'
import type { Offer } from '@echo/model/types/offer'
import { withSlugSchema } from '@echo/model/validators/slug-schema'
import { otherwise, pipe } from 'ramda'
import type { Nullable } from '@echo/utils/types/nullable'
import { always, isNil, otherwise, pipe } from 'ramda'

export async function expireOfferTaskRequestHandler(data: ExpireOfferTaskData) {
const { slug } = withSlugSchema.parse(data)
await pipe(
expireOffer,
otherwise((err) => {
error({ err, offer: { slug } }, OfferError.Expire)
})
)(slug)
const offer = await pipe(getOffer, otherwise(always<Nullable<Offer>>(undefined)))(slug)
if (!isNil(offer) && !offer.locked) {
await pipe(
expireOffer,
otherwise((err) => {
error({ err, offer: { slug } }, OfferError.Expire)
})
)(slug)
}
}
4 changes: 3 additions & 1 deletion app/frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const nextConfig = {
},
swcMinify: true,
webpack: (config) => {
config.devtool = 'hidden-source-map'
if (process.env.NODE_ENV === 'production') {
config.devtool = 'hidden-source-map'
}
return config
}
}
Expand Down
1 change: 1 addition & 0 deletions app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"next": "14.2.20",
"next-auth": "5.0.0-beta.25",
"next-intl": "3.25.3",
"p-filter": "4.1.0",
"ramda": "0.30.1",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
6 changes: 4 additions & 2 deletions app/frontend/src/app/me/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isOfferRedeemable } from '@echo/backend/helpers/offer/is-offer-redeemable'
import { getListingsForCreator } from '@echo/firestore/crud/listing/get-listings-for-creator'
import { getPendingListingsForUser } from '@echo/firestore/crud/listing/get-pending-listings-for-user'
import { getNftsForOwner } from '@echo/firestore/crud/nft/get-nfts-for-owner'
Expand All @@ -9,11 +10,12 @@ import { toListingsWithRole } from '@echo/frontend/lib/helpers/listing/to-listin
import { toOffersWithRole } from '@echo/frontend/lib/helpers/offer/to-offers-with-role'
import { otherwiseEmptyArray } from '@echo/frontend/lib/helpers/otherwise-empty-array'
import { toSwapsWithRole } from '@echo/frontend/lib/helpers/swap/to-swaps-with-role'
import { getRedeemableOffers } from '@echo/model/helpers/offer/get-redeemable-offers'
import type { User } from '@echo/model/types/user'
import type { SwapDetailsSearchParams } from '@echo/routing/types/frontend/search-params/swap-details-search-params'
import { swapDetailsSearchParamsTransformSchema } from '@echo/routing/validators/frontend/swap/swap-details-search-params-transform-schema'
import { ProfilePage } from '@echo/ui/pages/profile/profile-page'
import type { OfferWithRole } from '@echo/ui/types/offer-with-role'
import pFilter from 'p-filter'
import { always, andThen, otherwise, pipe, prop } from 'ramda'

interface Props {
Expand Down Expand Up @@ -41,7 +43,7 @@ async function render({ user, searchParams }: Props) {
andThen(toOffersWithRole(user)),
otherwiseEmptyArray
)(user)
const redeemableOffers = getRedeemableOffers(offers)
const redeemableOffers = await pFilter<OfferWithRole>(offers, isOfferRedeemable(user.username))
const offersCount = await pipe(prop('username'), getUserOffersCount, otherwise(always(0)))(user)
const swaps = await pipe(prop('username'), getSwapsForUser, andThen(toSwapsWithRole(user)), otherwiseEmptyArray)(user)
const selection = swapDetailsSearchParamsTransformSchema.parse({ swaps, searchParams })
Expand Down
2 changes: 0 additions & 2 deletions app/storybook/.storybook/manager-head.html

This file was deleted.

54 changes: 54 additions & 0 deletions app/storybook/src/swap/swap-card.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// noinspection JSUnusedGlobalSymbols

import { TokenType } from '@echo/model/constants/token-type'
import { nftMockPx2 } from '@echo/model/mocks/nft-mock'
import { swapMock } from '@echo/model/mocks/swap-mock'
import type { Erc721Item, Item } from '@echo/model/types/item'
import type { Swap } from '@echo/model/types/swap'
import { SwapCard } from '@echo/ui/components/swap/card/swap-card'
import { type Meta, type StoryObj } from '@storybook/react'
import { append, modify } from 'ramda'
import { type FunctionComponent } from 'react'

type ComponentType = FunctionComponent<{
stack: boolean
}>

const metadata: Meta<ComponentType> = {
title: 'Swap/Card',
args: {
stack: false
},
argTypes: {
stack: {
control: 'boolean'
}
}
}
export default metadata
export const Default: StoryObj<ComponentType> = {
render: ({ stack }) => {
const swap = stack
? modify(
'senderItems',
append<Item>({
token: {
contract: nftMockPx2.collection.contract,
collection: {
name: nftMockPx2.collection.name,
slug: nftMockPx2.collection.slug,
totalSupply: nftMockPx2.collection.totalSupply
},
name: nftMockPx2.name,
pictureUrl: nftMockPx2.pictureUrl,
tokenId: nftMockPx2.tokenId,
type: TokenType.Erc721
}
} as Erc721Item),
swapMock
)
: swapMock

return <SwapCard swap={swap as Swap} />
}
}
47 changes: 47 additions & 0 deletions lib/backend/src/helpers/offer/is-offer-redeemable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot'
import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import type { EscrowedNftDocument } from '@echo/firestore/types/model/escrowed-nft-document'
import type { QueryDocumentSnapshot } from '@echo/firestore/types/query-document-snapshot'
import { OfferState } from '@echo/model/constants/offer-state'
import { itemToken } from '@echo/model/helpers/item/item-token'
import { offerReceiverNftItems } from '@echo/model/helpers/offer/offer-receiver-nft-items'
import { offerSenderNftItems } from '@echo/model/helpers/offer/offer-sender-nft-items'
import type { NftItem } from '@echo/model/types/item'
import type { Offer } from '@echo/model/types/offer'
import { unlessNil } from '@echo/utils/helpers/unless-nil'
import type { Nullable } from '@echo/utils/types/nullable'
import { always, andThen, ifElse, isNil, otherwise, pathEq, pipe, prop } from 'ramda'

export function isOfferRedeemable(username: string) {
return async function (offer: Offer): Promise<boolean> {
const { state } = offer
if (state === OfferState.Cancelled || state === OfferState.Rejected || state === OfferState.Expired) {
const nftItems = ifElse<[Offer], NftItem[], NftItem[]>(
pathEq(username, ['sender', 'username']),
offerSenderNftItems,
ifElse(pathEq(username, ['receiver', 'username']), offerReceiverNftItems, always<NftItem[]>([]))
)(offer)
for (const item of nftItems) {
const escrowedNftSnapshot = await pipe(
itemToken,
getNftSnapshotByIndex,
andThen(
unlessNil(
pipe(
prop('id'),
getEscrowedNftSnapshot,
otherwise(always<Nullable<QueryDocumentSnapshot<EscrowedNftDocument>>>(undefined))
)
)
),
otherwise(always<Nullable<QueryDocumentSnapshot<EscrowedNftDocument>>>(undefined))
)(item)
if (!isNil(escrowedNftSnapshot)) {
return true
}
}
return false
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { userDocumentToModel } from '@echo/firestore/converters/user-document-to-model'
import { getCollection } from '@echo/firestore/crud/collection/get-collection'
import { escrowNft } from '@echo/firestore/crud/nft/escrow-nft'
import { getNftByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot'
import { getNftByIndex, getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { removeNftOwner } from '@echo/firestore/crud/nft/remove-nft-owner'
import { setNftOwner } from '@echo/firestore/crud/nft/set-nft-owner'
import { unescrowNft } from '@echo/firestore/crud/nft/unescrow-nft'
Expand All @@ -27,7 +26,7 @@ export async function erc721TransferEventHandler({ contract, from, to, tokenId }
}
if (from === echoAddress) {
const nftSnapshot = await pipe(
getNftSnapshot,
getNftSnapshotByIndex,
otherwise(always<Nullable<QueryDocumentSnapshot<NftDocument>>>(undefined))
)({ collection, tokenId })
if (isNil(nftSnapshot)) {
Expand Down
9 changes: 9 additions & 0 deletions lib/backend/test/mappers/echo-offer-to-base-offer.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { echoOfferToBaseOffer } from '@echo/backend/mappers/echo-offer-to-base-offer'
import { getCollectionByContract } from '@echo/firestore/crud/collection/get-collection-by-contract'
import { getNftByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { getUserByWallet } from '@echo/firestore/crud/user/get-user-by-wallet'
import type { UserDocument } from '@echo/firestore/types/model/user-document'
import { eqNft } from '@echo/model/helpers/nft/eq-nft'
import { collectionMocks } from '@echo/model/mocks/collection-mock'
import { nftMocks } from '@echo/model/mocks/nft-mock'
import { baseOfferMockFromJohnnycage } from '@echo/model/mocks/offer-mock'
import { userMocks } from '@echo/model/mocks/user-mock'
import type { Collection } from '@echo/model/types/collection'
import type { Nft } from '@echo/model/types/nft'
import { toPromise } from '@echo/utils/helpers/to-promise'
Expand All @@ -15,6 +18,7 @@ import { find, pipe, propEq } from 'ramda'

jest.mock('@echo/firestore/crud/collection/get-collection-by-contract')
jest.mock('@echo/firestore/crud/nft/get-nft-by-index')
jest.mock('@echo/firestore/crud/user/get-user-by-wallet')

describe('mappers - contractOfferToBaseOffer', () => {
jest
Expand All @@ -23,6 +27,11 @@ describe('mappers - contractOfferToBaseOffer', () => {
jest
.mocked(getNftByIndex)
.mockImplementation((index) => pipe(find<Nft>(eqNft(index)), toPromise<Nullable<Nft>>)(nftMocks))
jest
.mocked(getUserByWallet)
.mockImplementation((wallet) =>
pipe(find<UserDocument>(propEq(wallet, 'wallet')), toPromise)(userMocks as UserDocument[])
)

beforeEach(() => {
jest.clearAllMocks()
Expand Down
4 changes: 2 additions & 2 deletions lib/firestore/src/crud/nft/escrow-nft.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot'
import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot'
import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { removeNftOwner } from '@echo/firestore/crud/nft/remove-nft-owner'
import { escrowedNftsCollection } from '@echo/firestore/helpers/collection/collections'
import { setReference } from '@echo/firestore/helpers/reference/set-reference'
Expand All @@ -9,7 +9,7 @@ import type { Nft, OwnedNftIndex } from '@echo/model/types/nft'
import { isNil } from 'ramda'

export async function escrowNft(nft: OwnedNftIndex): Promise<string> {
const snapshot = await getNftSnapshot(nft)
const snapshot = await getNftSnapshotByIndex(nft)
if (isNil(snapshot)) {
return Promise.reject(Error(NftError.NotFound))
}
Expand Down
11 changes: 0 additions & 11 deletions lib/firestore/src/crud/nft/get-nft-snapshot.ts

This file was deleted.

4 changes: 2 additions & 2 deletions lib/firestore/src/crud/nft/remove-nft-owner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot'
import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { nftsCollection } from '@echo/firestore/helpers/collection/collections'
import { updateReference } from '@echo/firestore/helpers/reference/update-reference'
import type { NftDocument } from '@echo/firestore/types/model/nft-document'
Expand All @@ -8,7 +8,7 @@ import { FieldValue } from 'firebase-admin/firestore'
import { isNil } from 'ramda'

export async function removeNftOwner(nft: NftIndex): Promise<NftDocument> {
const snapshot = await getNftSnapshot(nft)
const snapshot = await getNftSnapshotByIndex(nft)
if (isNil(snapshot)) {
return Promise.reject(Error(NftError.NotFound))
}
Expand Down
4 changes: 2 additions & 2 deletions lib/firestore/src/crud/nft/set-nft-owner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot'
import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index'
import { nftsCollection } from '@echo/firestore/helpers/collection/collections'
import { updateReference } from '@echo/firestore/helpers/reference/update-reference'
import type { NftDocument } from '@echo/firestore/types/model/nft-document'
Expand All @@ -12,7 +12,7 @@ interface SetNftOwnerArgs {
}

export async function setNftOwner({ nft, owner }: SetNftOwnerArgs): Promise<NftDocument> {
const snapshot = await getNftSnapshot(nft)
const snapshot = await getNftSnapshotByIndex(nft)
if (isNil(snapshot)) {
return Promise.reject(Error(NftError.NotFound))
}
Expand Down
14 changes: 0 additions & 14 deletions lib/model/src/helpers/offer/get-redeemable-offers.ts

This file was deleted.

30 changes: 13 additions & 17 deletions lib/ui/src/components/offer/card/offer-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { OfferCardPicture } from '@echo/ui/components/offer/card/offer-card-pict
import { OfferStackPicture } from '@echo/ui/components/offer/card/offer-stack-picture'
import { buildNftStack } from '@echo/ui/helpers/nft/build-nft-stack'
import { nftLabel } from '@echo/ui/helpers/nft/nft-label'
import { isNonEmptyArray } from '@echo/utils/helpers/is-non-empty-array'
import { head } from 'ramda'
import { type FunctionComponent, useCallback } from 'react'

Expand All @@ -23,23 +22,20 @@ export const OfferCard: FunctionComponent<Props> = ({ offer, onSelect }) => {
onSelect?.(offer.slug)
}, [offer.slug, onSelect])

if (isNonEmptyArray(items)) {
if (items.length > 1) {
const stack = buildNftStack(items, offer.sender)
return (
<StackLayout onClick={select}>
<OfferStackPicture pictureUrl={stack.pictureUrl} label={stack.label} state={offer.state} />
<CardFooter title={stack.collection.name} subtitle={stack.label} />
</StackLayout>
)
}
const item = head(items)
if (items.length > 1) {
const stack = buildNftStack(items, offer.sender)
return (
<CardLayout onClick={select}>
<OfferCardPicture pictureUrl={item.token.pictureUrl} label={nftLabel(item.token)} state={offer.state} />
<CardFooter title={item.token.collection.name} subtitle={nftLabel(item.token)} />
</CardLayout>
<StackLayout onClick={select}>
<OfferStackPicture pictureUrl={stack.pictureUrl} label={stack.label} state={offer.state} />
<CardFooter title={stack.collection.name} subtitle={stack.label} />
</StackLayout>
)
}
return null
const item = head(items)
return (
<CardLayout onClick={select}>
<OfferCardPicture pictureUrl={item.token.pictureUrl} label={nftLabel(item.token)} state={offer.state} />
<CardFooter title={item.token.collection.name} subtitle={nftLabel(item.token)} />
</CardLayout>
)
}
Loading
Loading