Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Premint and CreateListing buttons #774

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/react-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"cids": "^1.1.9",
"dayjs": "1.11.7",
"eth-revert-reason": "^1.0.3",
"ethers-v6": "npm:ethers@^6.10.0",
"formik": "2.2.9",
"graphql-request": "5.2.0",
"jotai": "^1.13.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/react-kit/src/components/config/ConfigContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ProtocolConfig } from "@bosonprotocol/core-sdk";
import { createContext, useContext } from "react";
import { Signer } from "ethers";
import { Signer as SignerV6 } from "ethers-v6";

export type ConfigContextProps = {
config: ProtocolConfig;
Expand All @@ -17,6 +18,7 @@ export type ConfigContextProps = {
externalConnectedChainId?: number;
externalConnectedAccount?: string;
externalConnectedSigner?: Signer;
externalConnectedSignerV6?: SignerV6;
withExternalConnectionProps?: boolean;
withWeb3React: boolean;
withCustomReduxContext?: boolean;
Expand Down
214 changes: 214 additions & 0 deletions packages/react-kit/src/components/cta/offer/CreateListing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React, { useState } from "react";
import { BigNumberish } from "ethers";
import { ChainId } from "@bosonprotocol/common";
import { Chain as OSChain, OpenSeaSDK } from "opensea-js";
import { Signer as SignerV6 } from "ethers-v6";
import { API_BASE_MAINNET, API_BASE_TESTNET } from "opensea-js/lib/constants";

import { Button } from "../../buttons/Button";
import { ButtonTextWrapper, ExtraInfo, LoadingWrapper } from "../common/styles";
import { CtaButtonProps } from "../common/types";
import { Loading } from "../../ui/loading/Loading";
import { ButtonSize } from "../../ui/buttonSize";
import { useCoreSdkOverrides } from "../../../hooks/core-sdk/useCoreSdkOverrides";
import { withQueryClientProvider } from "../../queryClient/withQueryClientProvider";
import { useSignerV6 } from "../../../hooks";
import {
Listing,
MarketplaceType
} from "@bosonprotocol/core-sdk/dist/cjs/marketplaces/types";
import {
ConfigProvider,
ConfigProviderProps
} from "../../config/ConfigProvider";

type Props = {
tokenId: BigNumberish;
price: BigNumberish;
} & CtaButtonProps<{
tokenId: BigNumberish;
price: BigNumberish;
}> & {
providerProps: Omit<ConfigProviderProps, "children">;
};

function getOpenSeaChain(chainId: ChainId): OSChain {
switch (chainId) {
case 1: {
return OSChain.Mainnet;
}
case 137: {
return OSChain.Polygon;
}
case 80002: {
return OSChain.Amoy;
}
case 11155111: {
return OSChain.Sepolia;
}
case 31337: {
return "hardhat" as OSChain;
}
default: {
throw new Error(`Chain ${chainId} not supported`);
}
}
}

function createOpenSeaSDK(
signerV6: SignerV6,
chainId: ChainId,
OPENSEA_API_KEY: string
): OpenSeaSDK {
let openseaUrl;
switch (chainId) {
case 1:
case 137: {
openseaUrl = API_BASE_MAINNET;
break;
}
default: {
openseaUrl = API_BASE_TESTNET;
}
}
const openseaSdk = new OpenSeaSDK(
signerV6 as any,
{
chain: getOpenSeaChain(chainId),
apiKey: OPENSEA_API_KEY,
apiBaseUrl: openseaUrl
},
(line) => console.info(`SEPOLIA OS: ${line}`)
);
(openseaSdk.api as any).apiBaseUrl = openseaUrl; // << force the API URL
return openseaSdk;
}

const withConfigProvider = <
P extends { providerProps: Omit<ConfigProviderProps, "children"> }
>(
WrappedComponent: React.ComponentType<P>
) => {
// Define the props type for the wrapped component
type Props = P;

// Return a new component
const WithConfigProvider: React.FC<Props> = (props) => {
return (
<ConfigProvider {...props.providerProps}>
<WrappedComponent {...props} />
</ConfigProvider>
);
};

// Set display name for debugging purposes
const displayName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
WithConfigProvider.displayName = `withQueryClientProviderCustom(${displayName})`;

return WithConfigProvider;
};

export const CreateListingButton = withConfigProvider(
withQueryClientProvider(
({
tokenId,
price,
disabled = false,
showLoading = false,
extraInfo,
onSuccess,
onError,
onPendingSignature,
onPendingTransaction,
waitBlocks = 1,
size = ButtonSize.Large,
variant = "secondaryFill",
children,
coreSdkConfig,
providerProps,
...rest
}: Props) => {
const coreSdk = useCoreSdkOverrides({ coreSdkConfig });
const [isLoading, setIsLoading] = useState<boolean>(false);
const signerV6 = useSignerV6();
const OPENSEA_API_KEY = "56a6c19ed53b48f3aab9eff2c2e91468";
const OPENSEA_FEE_RECIPIENT =
"0x0000a26b00c1F0DF003000390027140000fAa719"; // On Real OpenSea

return (
<Button
variant={variant}
disabled={disabled || !signerV6 || !tokenId}
size={size}
onClick={async () => {
if (!isLoading && tokenId) {
try {
setIsLoading(true);
onPendingSignature?.();

const { offerId, exchangeId } = coreSdk.parseTokenId(tokenId);
console.log({
offerId: offerId.toString(),
exchangeId: exchangeId.toString()
});
const openseaSdk = coreSdk.marketplace(
MarketplaceType.OPENSEA,
createOpenSeaSDK(
signerV6 as SignerV6,
(await coreSdk.web3Lib.getChainId()) as ChainId,
OPENSEA_API_KEY
),
OPENSEA_FEE_RECIPIENT
);
const offer = await coreSdk.getOfferById(offerId);
const nftContract = offer.collection.collectionContract.address;
const { wrapped, wrapper } = await openseaSdk.isVoucherWrapped(
nftContract,
tokenId.toString()
);
const listing: Listing = {
asset: {
contract: wrapped ? (wrapper as string) : nftContract,
tokenId: tokenId.toString()
},
offerer: await (signerV6 as SignerV6).getAddress(),
price: price.toString(),
expirationTime: Math.floor(Date.now() / 1000) + 3600, // should be greater than now + 10 mins
exchangeToken: {
address: offer.exchangeToken.address, // can't be anything else than WETH on testnet
decimals: Number(offer.exchangeToken.decimals)
},
auction: true
};
const listingOrder = await openseaSdk.createListing(listing);
console.log("Listing Order:", listingOrder);
onSuccess?.(undefined as any, { tokenId, price });
} catch (error) {
onError?.(error as Error, { txResponse: undefined });
} finally {
setIsLoading(false);
}
}
}}
{...rest}
>
<ButtonTextWrapper>
{children || "Create Listing"}
{extraInfo && ((!isLoading && showLoading) || !showLoading) ? (
<ExtraInfo>{extraInfo}</ExtraInfo>
) : (
<>
{isLoading && showLoading && (
<LoadingWrapper>
<Loading />
</LoadingWrapper>
)}
</>
)}
</ButtonTextWrapper>
</Button>
);
}
)
);
Loading
Loading