Skip to content

Commit

Permalink
Merge pull request #40 from ArkProjectNFTs/yohan/token-offers
Browse files Browse the repository at this point in the history
feat: token offers
  • Loading branch information
kwiss authored Jul 5, 2024
2 parents 150f7d2 + 51e264a commit 9e30739
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,38 @@ import { useAccount } from "@starknet-react/core";
import { areAddressesEqual } from "@ark-market/ui";
import { Button } from "@ark-market/ui/button";

import type { Offer, Token, TokenMarketData } from "~/types";
import { env } from "~/env";

interface AcceptOfferProps {
token: Token;
tokenMarketData: TokenMarketData;
offer: Offer;
offerOrderHash: string;
offerAmount: string;

tokenOwner: string;
tokenContractAddress: string;
tokenId: string;
tokenIsListed: boolean;
tokenListingOrderHash: string;
}

const AcceptOffer: React.FC<AcceptOfferProps> = ({
token,
tokenMarketData,
offer,
offerAmount,
offerOrderHash,
tokenIsListed,
tokenListingOrderHash,
tokenContractAddress,
tokenId,
tokenOwner,
}) => {
const { address, account } = useAccount();
const { fulfillOffer, status } = useFulfillOffer();
const { fulfill: fulfillAuction, status: statusAuction } =
useFulfillAuction();
const type = useOrderType({
orderHash: BigInt(tokenMarketData.order_hash),
orderHash: BigInt(tokenListingOrderHash),
});
const isAuction = type === "AUCTION";
const isOwner = areAddressesEqual(token.owner, address);
const isListed = tokenMarketData.is_listed;
const isOwner = areAddressesEqual(tokenOwner, address);
const isListed = tokenIsListed;

if (!account || !isOwner) {
return null;
Expand All @@ -46,19 +54,19 @@ const AcceptOffer: React.FC<AcceptOfferProps> = ({
await fulfillAuction({
starknetAccount: account,
brokerId: env.NEXT_PUBLIC_BROKER_ID,
tokenAddress: token.contract_address,
tokenId: token.token_id,
orderHash: tokenMarketData.order_hash,
relatedOrderHash: offer.order_hash,
startAmount: offer.offer_amount,
tokenAddress: tokenContractAddress,
tokenId,
orderHash: tokenListingOrderHash,
relatedOrderHash: offerOrderHash,
startAmount: offerAmount,
});
} else {
await fulfillOffer({
starknetAccount: account,
brokerId: env.NEXT_PUBLIC_BROKER_ID,
tokenAddress: token.contract_address,
tokenId: token.token_id,
orderHash: offer.order_hash,
tokenAddress: tokenContractAddress,
tokenId,
orderHash: offerOrderHash,
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { useAccount } from "@starknet-react/core";
import { Button } from "@ark-market/ui/button";
import { toast } from "@ark-market/ui/toast";

import type { Offer, Token } from "~/types";

interface CancelOfferProps {
token: Token;
offer: Offer;
tokenContractAddress: string;
tokenId: string;
offerOrderHash: string;
}

const CancelOffer: React.FC<CancelOfferProps> = ({ token, offer }) => {
const CancelOffer = ({
offerOrderHash,
tokenContractAddress,
tokenId,
}: CancelOfferProps) => {
const { account } = useAccount();
const { cancel, status } = useCancel();

Expand All @@ -32,9 +35,9 @@ const CancelOffer: React.FC<CancelOfferProps> = ({ token, offer }) => {

await cancel({
starknetAccount: account,
tokenAddress: token.contract_address,
tokenId: BigInt(token.token_id),
orderHash: BigInt(offer.order_hash),
tokenAddress: tokenContractAddress,
tokenId: BigInt(tokenId),
orderHash: BigInt(offerOrderHash),
});
};

Expand All @@ -45,7 +48,7 @@ const CancelOffer: React.FC<CancelOfferProps> = ({ token, offer }) => {
const isLoading = ["loading", "cancelling"].includes(status);

return (
<Button size="sm" onClick={handleClick} disabled={isLoading}>
<Button size="xl" onClick={handleClick} disabled={isLoading}>
{status === "loading" ? (
<ReloadIcon className="animate-spin" />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,21 @@ const TokenOffers: React.FC<TokenOffersProps> = ({
<div className="flex space-x-2">
{isOwner && tokenMarketData && (
<AcceptOffer
token={token}
tokenMarketData={tokenMarketData}
offer={offer}
offerAmount={offer.offer_amount}
offerOrderHash={offer.order_hash}
tokenContractAddress={token.contract_address}
tokenId={token.token_id}
tokenIsListed={tokenMarketData.is_listed}
tokenListingOrderHash={tokenMarketData.order_hash}
tokenOwner={token.owner}
/>
)}
{areAddressesEqual(offer.offer_maker, address) && (
<CancelOffer token={token} offer={offer} />
<CancelOffer
tokenId={token.token_id}
offerOrderHash={offer.order_hash}
tokenContractAddress={token.contract_address}
/>
)}
</div>
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ export default function TokenAbout({
}, [contractAddress]);

const ownerShortenedAddress = useMemo(() => {
if (tokenInfos.owner === null) {
return undefined;
}
return `${tokenInfos.owner.slice(0, 4)}...${tokenInfos.owner.slice(-4)}`;
}, [tokenInfos.owner]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useAccount } from "@starknet-react/core";
import { validateAndParseAddress } from "starknet";

import { Button } from "@ark-market/ui/button";

import AcceptOffer from "~/app/assets/[contract_address]/[token_id]/components/accept-offer";
import CancelOffer from "~/app/assets/[contract_address]/[token_id]/components/cancel-offer";
import ConnectWalletModal from "~/components/connect-wallet-modal";

interface TokenOffersTableActionProps {
owner: string;
offerSourceAddress: string | null;
offerOrderHash: string;
tokenId: string;
tokenContractAddress: string;
offerAmount: string;
tokenIsListed: boolean;
tokenListingOrderHash: string | null;
}

export default function TokenOffersTableAction({
offerAmount,
offerOrderHash,
offerSourceAddress,
owner,
tokenContractAddress,
tokenId,
tokenIsListed,
tokenListingOrderHash,
}: TokenOffersTableActionProps) {
const { address } = useAccount();

const isOwner =
address !== undefined &&
validateAndParseAddress(address) === validateAndParseAddress(owner);

if (!address) {
return (
<ConnectWalletModal>
<Button size="xl">Connect wallet</Button>
</ConnectWalletModal>
);
}

if (isOwner && tokenListingOrderHash !== null) {
return (
<AcceptOffer
offerOrderHash={offerOrderHash}
tokenId={tokenId}
tokenContractAddress={tokenContractAddress}
tokenOwner={owner}
offerAmount={offerAmount}
tokenIsListed={tokenIsListed}
tokenListingOrderHash={tokenListingOrderHash}
/>
);
}

if (
validateAndParseAddress(address) ===
validateAndParseAddress(offerSourceAddress ?? "")
)
return (
<CancelOffer
offerOrderHash={offerOrderHash}
tokenId={tokenId}
tokenContractAddress={tokenContractAddress}
/>
);

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { getRoundedRemainingTime, shortAddress } from "@ark-market/ui";
import { PriceTag } from "@ark-market/ui/price-tag";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@ark-market/ui/table";

import type { TokenOffer } from "../queries/getTokenData";
import type { TokenMarketData } from "~/types";
import TokenOffersTableAction from "./token-offers-table-action";

interface TokenOffersTableProps {
tokenOffers: TokenOffer[];
tokenContractAdress: string;
tokenId: string;
owner: string;
tokenMarketData: TokenMarketData | null;
}

export default function TokenOffersTable({
tokenOffers,
owner,
tokenContractAdress,
tokenId,
tokenMarketData,
}: TokenOffersTableProps) {
return (
<Table>
<TableHeader className="hover:bg-background">
<TableRow className="grid w-full grid-cols-5 items-center">
<TableHead className="sticky top-0 flex items-center">
Price
</TableHead>
<TableHead className="sticky top-0 flex items-center">
Floor difference
</TableHead>
<TableHead className="sticky top-0 flex items-center">From</TableHead>
<TableHead className="sticky top-0 flex items-center">
Expiration
</TableHead>
<TableHead className="sticky top-0 flex items-center">
Action
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="block max-h-[25.5rem] overflow-auto text-sm font-semibold">
{tokenOffers.map((offer) => {
return (
<TableRow
key={offer.offer_id}
className="grid h-[4.625rem] w-full grid-cols-5 items-center"
>
<TableCell>
<PriceTag price={offer.price} />
</TableCell>
{/* TODO @YohanTz: Check how this one looks */}
<TableCell>{offer.floor_difference ?? "_"}</TableCell>
<TableCell>
{offer.source ? shortAddress(offer.source) : "_"}
</TableCell>
<TableCell>
In {getRoundedRemainingTime(offer.expire_at)}
</TableCell>
<TableCell>
<TokenOffersTableAction
owner={owner}
offerSourceAddress={offer.source}
offerOrderHash={offer.hash}
tokenContractAddress={tokenContractAdress}
tokenId={tokenId}
offerAmount={offer.price}
tokenIsListed={tokenMarketData?.is_listed ?? false}
tokenListingOrderHash={tokenMarketData?.order_hash ?? null}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}
Loading

0 comments on commit 9e30739

Please sign in to comment.