From 1db1f5fefb2dfeb7cc30a54233c48c65f110617b Mon Sep 17 00:00:00 2001 From: Richard Irala Date: Thu, 13 Mar 2025 02:33:56 -0300 Subject: [PATCH 1/4] Update custom marketplace end to end guide --- guides/custom-marketplace.mdx | 782 ++++++---------------------------- 1 file changed, 129 insertions(+), 653 deletions(-) diff --git a/guides/custom-marketplace.mdx b/guides/custom-marketplace.mdx index e001d33..0c9e96f 100644 --- a/guides/custom-marketplace.mdx +++ b/guides/custom-marketplace.mdx @@ -1,7 +1,6 @@ --- - -title: "Orderbook Transactions" -description: This guide covers the creation of a custom marketplace using tools from the Sequence stack. It includes steps for minting tokens, wallet authentication, blockchain queries, multi-wallet types, request creation, order accepting, and optional integration of an embedded wallet. +title: "Build a Custom Marketplace" +description: This guide walks you through building a custom marketplace using the Sequence stack. It covers token minting, wallet authentication, displaying collections, creating and purchasing listings, making and accepting offers, and managing inventory—all seamlessly integrated with an embedded wallet and Sequence Kit. sidebarTitle: Build a Custom Marketplace --- @@ -11,11 +10,9 @@ The tools will enable you to perform: 1. [Minting](/guides/custom-marketplace#1-minting): Minting of tokens to your wallet from the Sequence Builder 2. [Wallet Authentication](/guides/custom-marketplace#2-wallet-authentication): Use of Sequence Kit to authenticate a user -3. [Blockchain Queries](/guides/custom-marketplace#3-blockchain-queries): Querying of token balances using the Indexer -4. [Multi-wallet Types](/guides/custom-marketplace#4-multi-wallet-types): Allow users to either use a Sequence Wallet or an EOA -5. [Request Creation](/guides/custom-marketplace#5-request-creation): Creation of sell listing requests on the Sequence Market Protocol -6. [Order Accepting](/guides/custom-marketplace#6-order-accepting): Accepting of top orders from the Marketplace -7. [(Optional) Enable Embedded Wallet](/guides/custom-marketplace#7-optional-integrate-embedded-wallet-into-sequence-kit): Add a more seamless UX experience with no-confirmation transactions +3. [Displaying collections](/guides/custom-marketplace#3-displaying-collections): ((((Querying of token balances using the Indexer)))) +4. [Listings and Offers](/guides/custom-marketplace#4-listings-and-offers): (((Allow users to either use a Sequence Wallet or an EOA))) +5. [(Optional) Inventory](/guides/custom-marketplace#5-inventory-optional): Add a inventory for your users See an example [simplified marketplace dapp](https://simple-marketplace-boilerplate.pages.dev/) that enables users to mint collectibles, sell the collectibles with the Sequence Marketplace Protocol, and make purchases with USDC on `base-sepolia` by getting a top order from the Marketplace. @@ -32,722 +29,201 @@ The first step is to create a collectible from the Sequence Builder and mint a f For your project, you'll need a way to authenticate your user with a wallet. -Your choice from the Sequence stack is to use either an [Embedded Wallet](/sdk/headless-wallet/quickstart) for headless and web2-like UX, or a [Ecosystem Wallet](/sdk/web/ecosystem) with [Sequence Kit](/sdk/web/overview) to reach more types of wallets. - -For this guide we'll use an `Universal Sequence Wallet` with `Sequence Kit` connector (with an option for an `Embedded Wallet`) which can authenticate users using Google or Apple auth, in addition to user brought wallets like Coinbase or Metamask. +For this guide we'll use an `Embedded Wallet` with `Sequence Kit` connector which can authenticate users using Google or Apple auth, in addition to user brought wallets like Coinbase or Metamask. ### Install Packages -Either you can create a vanilla js/html/css project from a [template like this](https://github.com/moskalyk/vanilla-js-sequence-kit-starter) for a templated setup, or we will walk you through how to use react from scratch here. +Either you can start from a great foundation with our [Marketplace Boilerplate](/solutions/marketplaces/orderbook/starter#quickstart%3A-marketplace-boilerplate), which already includes all of this integrated with a great UI, or we will walk you through how to use React from scratch here. Start by creating a project in a folder of your name choosing: +```bash npm +npx create-next-app ``` -mkdir -cd -npx create-react-app . --template=typescript -``` - -Then, begin by installing the required packages in the ` folder` - -``` -pnpm install @0xsequence/kit @0xsequence/kit-connectors wagmi ethers viem 0xsequence @tanstack/react-query -``` - -Then in `src` next to `index.tsx` in the folder, create a `config.ts` file with the following contents: - -```js -import { arbitrumSepolia, Chain } from 'wagmi/chains' -import { getDefaultConnectors } from '@0xsequence/kit-connectors' -import { createConfig, http } from 'wagmi' - -const chains = [arbitrumSepolia] as [Chain, ...Chain[]] - -const projectAccessKey = process.env.REACT_APP_PROJECTACCESSKEY!; -const walletConnectProjectId = process.env.REACT_APP_WALLETCONNECTID!; - -const connectors = getDefaultConnectors( "universal", { - walletConnectProjectId: walletConnectProjectId, - defaultChainId: 421614, - appName: 'demo app', - projectAccessKey -}) - -const transports: any = {} - -chains.forEach(chain => { - transports[chain.id] = http() -}) - -const config = createConfig({ - transports, - connectors, - chains -}) - -export { config } - -``` - - - Be sure to include a `.env` file in the root of your project to include client - secrets - - -Next, import the `config` to be consumed by the `WagmiProvider` in the `index.tsx` - -```js -import ReactDOM from "react-dom/client"; -import { KitProvider } from "@0xsequence/kit"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { WagmiProvider } from "wagmi"; -import App from './App' - -import { config } from "./config"; - -const root = ReactDOM.createRoot( - document.getElementById("root") as HTMLElement, -); - -const queryClient = new QueryClient(); - -function Dapp() { - return ( - - - - - - - - ); -} -root.render( - -); +```fix +Recommended versions: +* next@14.2.24 +* react@18.3.1 ``` -And finally, add a button in the `App.tsx` to make the Sequence Kit modal appear - -```js -import { useOpenConnectModal, useKitWallets } from "@0xsequence/kit"; - -function App() { - const { address } = useAccount(); - const { setOpenConnectModal } = useOpenConnectModal(); - const { - wallets, // Array of connected wallets - linkedWallets, // Array of linked wallets (for embedded wallets) - setActiveWallet, // Function to set a wallet as active - disconnectWallet, // Function to disconnect a wallet - } = useKitWallets(); - - const isConnected = wallets.length; - - const connect = async () => { - setOpenConnectModal(true); - }; - - return ( - <> - {!isConnected && } - {address && address} - - ); -} +Then, begin by installing the required packages in the ` folder` -export default App; +```bash pnpm +pnpm add @0xsequence/kit @0xsequence/kit-checkout @0xsequence/kit-wallet @0xsequence/marketplace-sdk @0xsequence/design-system@^1 @0xsequence/network wagmi ethers@^6 viem 0xsequence @tanstack/react-query @tanstack/react-query-devtools @legendapp/state framer-motion@^8.5.2 pino-pretty ``` -Great! You should have an application that can authorize a user and return a wallet address. +### Layout -You can now test it with: +Now we should create a Layout to use our 'Providers' component. First, we'll create a folder named 'config' inside the 'src' directory. Within it, we'll add a 'marketplace-sdk' folder containing two files: config.ts and ssr.ts. -``` -pnpm run start -``` - -## 3. Blockchain Queries +#### Setting Up config.ts -Once you have one or a few collectibles minted, you can query the data from the contract address from your deployment, which can be found here: - -![copy contract address](/images/marketplace/copy_contract.png) - -You can query data using the indexer, using this code where an account address and contract address (retrieved from the Sequence Builder deployed contract) are inputted into the indexer api +In this file, we will define and manage essential configuration settings for our Marketplace SDK, including environment variables. This centralized approach ensures that configuration values are easily accessible and maintainable. -This will be important when you're determining a `tokenID` to create a request on the marketplace, for this demo we'll assume you're dealing with a single `tokenID` +You can find the code in this [GitHub repository](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-config-ts) -```js -// Works in both a Webapp (browser) or Node.js: -import { SequenceIndexer } from "@0xsequence/indexer"; +#### Setting Up ssr.ts -const indexer = new SequenceIndexer( - "https://arbitrum-sepolia-indexer.sequence.app", - "" -); +This SSR Client serves as an entry point for initializing the marketplace SDK on the Next.js server, enabling efficient data fetching and configuration setup before rendering the UI. -// try any contract and account address you'd like :), as an example -const contractAddress = " - -- `contractType` (string) - the type of contract type (i.e. ERC20, ERC721, or ERC1155) -- `contractAddress` (string) - the contract address of the token -- `accountAddress` (string) - the deploying account address -- `tokenID` (string) - the tokenID of the token (always 0 if ERC20) -- `balance` (string) - the balance of the token -- `blockHash` (string) - the transaction merkle hash of the block when the token was deployed -- `blockNumber` (number) - the blocknumber the token was deployed -- `chainId` (number) - the chain id of the token -- `contractType` - - `chainId` (number) - the chain id of the token - - `address` (string) - the address of the token - - `name` (string) - contract level name of the token - - `type` (string) - the type of contract type (i.e. ERC20, ERC721, or ERC1155) - - `symbol` (string) - the symbol of the token - - `decimals` (number) - the number of decimals the token has - - `logoURI` (string) - the logo of the token displayed in sequence.app - - `deployed` (boolean) - whether the token is deployed - - `bytecodeHash` (string) - hash of the bytecode of a smart contract deployed on the blockchain - - `extensions` - - `link` (string) - the adjoining website to link to the project - - `description` (string) - the metadata description of the token - - `ogImage` (string) - the banner image for the token, rendered in sequence.app - - `originChainId` (number) - the originating chain id the token represents - - `originAddress` (string) - the originating contract address the token represents - - `verified` (boolean) - whether the token is verified and trusted - - `verifiedBy` (string) - the verifing source as to why this is not spam -- `updatedAt` (date) - the last time the indexer was updated -- `tokenMetadata` - - - `tokenId` (string) - the tokenID of the token (always 0 if ERC20) - - `contractAddress` (string) - the contract address of the token - - `name` (string) - token level name - - `description` (string) - the description of the token - - `image` (string) - the image as a url of the token - - `decimals` (string) - the number of decimals for the token - - `properties` (object) - an object containing the properties of the token metadata - - `external_url` (string) - an external url for where to find the token or more details - - `updatedAt` (date) - the last time the token metadata was updated - - - -## 4. Multi-wallet Types - -Due to the fact that we're using `Sequence Kit` for this example that allows you to use a Sequence wallet, in addition to your own brought `EOA wallet`, sending transactions to the blockchain will differ due to the fact that with a `Sequence wallet`, you can send batch transactions to optimize gas costs, whereas with `wagmi` using an EOA you can only send 1 transaction at a time. - -To accomplish this, we take a few steps to create a local state variable that checks for the authorized wallet +This setup ensures a seamless integration of the marketplace SDK with server-side rendering, improving performance and user experience. -```ts -import { useEffect } from "react"; -import { useConnect, useAccount } from "wagmi"; - -function App() { - const { isConnected } = useAccount(); - const { connectors } = useConnect(); - const [isSequence, setIsSequence] = useState(false); - - useEffect(() => { - connectors.map(async (connector) => { - if ((await connector.isAuthorized()) && connector.id === "sequence") { - setIsSequence(true); - } - }); - }, [isConnected]); -} +```tsx +const { getMarketplaceConfig, config, getInitialState } = await ssrClient(); ``` + + + Retrieves the marketplace configuration in builder mode, providing necessary settings for customization. + + + Contains the resolved configuration based on the environment and server context. + - - In the Sequence Market protocol, when you create a listing, it's referred to - as a `request`, and when you accept a request it's called an `order`. - - -## 5. Request Creation - -For this example, we'll be using `Arbitrum Sepolia USDC` from the [community faucet](https://faucet.circle.com/) + + Fetches the initial state required for preloading marketplace data before rendering. + + -Head over there to first get some tokens, so that you can make listing with your request +#### Setting Up layout.tsx ---- +Now that we have the minimum requirements in place, the next step is to create a layout.tsx file inside the src folder. This file will define the structure of our application. You can find the code in this [GitHub repository](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-providers-tsx) -Then, in order to create a request for the orderbook, we'll need to first make sure we enable the marketplace orderbook contract with approval to transfer your tokens +### Authentication -First, we check that the marketplace is approved for the contract, with some logic +Great! We now have the foundation of our application. Next, we’ll create our homepage for connect wallet and disconnect functionality. To achieve this, we will add two new components to our repository. Inside the src/app/components directory, we will create [Connect.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-connect-tsx) and [Disconnect.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-disconnect-tsx) files. -```js -const ERC1155Contract = '0x1693ffc74edbb50d6138517fe5cd64fd1c917709' -const MarketPlaceContract = '0xB537a160472183f2150d42EB1c3DD6684A55f74c' +#### Setting Up LandingPage.tsx -function App() { +Create a new file in src/app named [LandingPage.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-landingpage-tsx). This file will define the homepage, integrating both wallet connection and disconnection functionality. - async function checkERC1155Approval(ownerAddress: string, operatorAddress: string) { - const abi = [ - "function isApprovedForAll(address account, address operator) external view returns (bool)" - ]; - const provider = new ethers.providers.JsonRpcProvider(`https://nodes.sequence.app/arbitrum-sepolia/${process.env.REACT_APP_PROJECT_ACCESSKEY}`); - const contract = new ethers.Contract(ERC1155Contract, abi, provider); - return await contract.isApprovedForAll(ownerAddress, operatorAddress); - } - - const createRequest = async () => { - ... - if(await checkERC1155Approval(address!,MarketPlaceContract)){ - // is approved and only requires a single transaction - ... - } else { // is not approved, so requires multiple transactions - - if(isSequence) { .. perform multi-batch transactions - ... - } else { // is not a sequence wallet - ... - } - } - }; - -} -``` - -Next, we'll need to craft the transaction with the correct ABI to generate the expected calldata for the various paths of: not being approved versus approved, and if it is a sequence wallet or not. +We're almost there! To complete the authentication integration, we will create a page.tsx file inside src/app. This file will integrate our LandingPage component with the following code: ```ts -const [requestData, setRequestData] = useState(null); - -const createRequest = async () => { - const sequenceMarketInterface = new ethers.Interface([ - "function createRequest(tuple(bool isListing, bool isERC1155, address tokenContract, uint256 tokenId, uint256 quantity, uint96 expiry, address currency, uint256 pricePerToken)) external nonReentrant returns (uint256 requestId)", - ]); - - const amountBigNumber = ethers.parseUnits(String("0.01"), 6); // ensure to use the proper decimals - - const request = { - isListing: true, - isERC1155: true, - tokenContract: ERC1155Contract, - tokenId: 1, - quantity: 1, - expiry: Date.now() + 7 * 24 * 60 * 60 * 1000, // 1 day - currency: ArbSepoliaUSDCContract, - pricePerToken: amountBigNumber, - }; +import { ssrClient } from "@/config/marketplace-sdk/ssr"; +import LandingPage from "./LandingPage"; - const data = sequenceMarketInterface.encodeFunctionData("createRequest", [ - request, - ]); +const Page = async () => { + const { getMarketplaceConfig } = await ssrClient(); + const { collections } = await getMarketplaceConfig(); - setRequestData(data); // we'll need this in the next step - - if (await checkERC1155Approval(address!, MarketPlaceContract)) { - // is approved and only requires a single transaction - - sendTransaction({ - to: MarketPlaceContract, - data: `0x${data.slice(2, data.length)}`, - gas: null, - }); - } else { - // is not approved, so requires multiple transactions - - const erc1155Interface = new ethers.Interface([ - "function setApprovalForAll(address _operator, bool _approved) returns ()", - ]); - - // is not approved - const dataApprove = erc1155Interface.encodeFunctionData( - "setApprovalForAll", - ["0xB537a160472183f2150d42EB1c3DD6684A55f74c", true] - ); - - const txApprove = { - to: ERC1155Contract, - data: dataApprove, - }; - - const tx = { - to: MarketPlaceContract, - data: data, - }; - - if (isSequence) { - const wallet = sequence.getWallet(); - const signer = wallet.getSigner(421614); - - try { - const res = signer.sendTransaction([txApprove, tx]); - console.log(res); - } catch (err) { - console.log(err); - console.log("user closed the wallet, or, an error occured"); - } - } else { - // is not a sequence wallet - // todo: implement mutex - - sendTransaction({ - to: ERC1155Contract, - data: `0x${dataApprove.slice(2, data.length)}`, - gas: null, - }); - // still need to send acceptRequest transaction - } - } + if (collections) return ; }; -``` - -Finally, for the path where the transaction does not take place from a sequence wallet and is not approved, we must submit a transaction once there is a transaction receipt from the `useSendTransaction` hook using a mutex to confirm which transaction the hash came from. This is done in a react `useEffect` function. - - - In computer programming, a mutual exclusion (mutex) is a program object that - prevents multiple threads from accessing the same shared resource - simultaneously. - - -```ts -import { useSendTransaction } from 'wagmi' -import { useMutex } from 'react-context-mutex'; - -function App() { - ... - const [requestData, setRequestData] = useState(null) - const { data: hash, sendTransaction } = useSendTransaction() - const MutexRunner = useMutex(); - const mutexApproveERC1155 = new MutexRunner('sendApproveERC1155'); - - const createRequest = async () => { - ... - if(await checkERC1155Approval(address!,MarketPlaceContract)){ - ... - } else { - if (isSequence) { // is a sequence wallet - ... - } else { // is not a sequence wallet - mutexApproveERC1155.lock() - sendTransaction({ - to: ERC1155Contract, - data: `0x${dataApprove.slice(2,data.length)}`, - gas: null - }) - } - } - }; - useEffect(() => { - if (mutexApproveERC1155.isLocked() && hash) { - sendTransaction({ - to: MarketPlaceContract, - data: `0x${requestData.slice(2, requestData.length)}`, - gas: null, - }); - mutexApproveERC1155.unlock(); - } - }, [requestData, hash]); +export default Page; ``` -Great you're done creating requests to the Sequence Market protocol, now you can implement a button and try the flow. +#### Run and Test -## 6. Order Accepting - -Now that we have an order on the marketplace, we need to do a few things: - -- `Query the Marketplace`: query the marketplace for an `orderId` that you want to accept an order for -- `Currency Balance`: check for currency balance using the indexer -- `Token Approval`: check for currency approval for the marketplace to transfer tokens - -#### Query the Marketplace - -Lets query the marketplace orderbook to get the `pricePerToken` and `orderId` the order is for - -```ts - const getTopOrder = async (tokenID: string) => { - const res = await fetch( - "https://marketplace-api.sequence.app/arbitrum-sepolia/rpc/Marketplace/GetTopOrders", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - collectionAddress: ERC1155Contract, - currencyAddresses: [ArbSepoliaUSDCContract], - orderbookContractAddress: MarketPlaceContract, - tokenIDs: [tokenID], - isListing: true, - priceSort: "DESC", // descending based on price to get lowest offer first - }), - }, - ); - const result = await res.json(); - return result.orders[0] // getting the first order from the list - } - - const acceptOrder = async () => { - const tokenID = '1' - const topOrder: any = await getTopOrder(tokenID) - const requiredAmount = topOrder.pricePerToken - ... - if(await checkERC20Balance(requiredAmount)){ - ... - } else { - ... - } - } -``` - -#### Currency Balance - -We'll use the indexer to query the balance and see if the user has enough token to pay for the order. This can be accomplished with the following code: +Congratulations on making it this far! You can now run pnpm dev to test your authentication and everything you've implemented so far. - You'll need to make sure when you perform an equality check on the token - contract address, that it is inputted as all lowercase + Be sure to include a `.env` file in the root of your project to include client secrets -```js -import { SequenceIndexer } from '@0xsequence/indexer' -... -const checkERC20Balance = async (requiredAmount: any) => { - const indexer = new SequenceIndexer('https://arbitrum-sepolia-indexer.sequence.app', process.env.REACT_APP_PROJECT_ACCESSKEY) - - const contractAddress = ArbSepoliaUSDCContract - const accountAddress = address - - const tokenBalances = await indexer.getTokenBalances({ - contractAddress: contractAddress, - accountAddress: accountAddress, - }) +## 3. Displaying collections - let hasEnoughBalance = false +To display the marketplace collections we need to create a folder named 'Collections' inside the 'src/app/components' directory. Within this folder, we will create two files: [index.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-index-collections-tsx) and [Collection.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-collection-tsx). - tokenBalances.balances.map((token) => { - const tokenBalanceBN = ethers.BigNumber.from(token.balance); - const requiredAmountBN = ethers.BigNumber.from(requiredAmount); - if(token.contractAddress == ArbSepoliaUSDCContract && tokenBalanceBN.gte(requiredAmountBN)){ - hasEnoughBalance = true - } - }) +To finish we will add the Collections component in the LandingPage.tsx - return hasEnoughBalance +```ts +"use client"; -} +import { ConnectButton } from "./components/Connect"; +import { useAccount } from "wagmi"; +import { Disconnect } from "./components/Disconnect"; +import { MarketplaceCollection } from "@0xsequence/marketplace-sdk"; +import { Collections } from "./components/Collections"; -const acceptOrder = async () => { - const tokenID = '1' - const topOrder: any = await getTopOrder(tokenID) - const requiredAmount = topOrder.pricePerToken - ... - if(await checkERC20Balance(requiredAmount)){ - ... - } else { - ... // provide prompt on screen that user does not have balance - } +interface LandingPageProps { + collections: MarketplaceCollection[]; } -``` - -#### Token Approval - -Next, we'll check for token approval for the Marketplace to be able to transfer the currency token - -```ts - const checkERC20Approval = async (ownerAddress: string, spenderAddress: string, tokenContractAddress: string, requiredAmount: string) => { - const abi = [ - "function allowance(address owner, address spender) external view returns (uint256)" - ]; +const LandingPage = ({ collections }: LandingPageProps) => { + const { isConnected, address } = useAccount(); - const provider = new ethers.providers.JsonRpcProvider(`https://nodes.sequence.app/arbitrum-sepolia/${process.env.REACT_APP_PROJECT_ACCESSKEY}`); - const contract = new ethers.Contract(tokenContractAddress, abi, provider); - const allowance = await contract.allowance(ownerAddress, spenderAddress); - - const requiredAmountBN = ethers.BigNumber.from(requiredAmount); - const allowanceBN = ethers.BigNumber.from(allowance); - - return allowanceBN.gte(requiredAmountBN); + if (isConnected && address) { + return ( +
+

User address: {address}

+ + +
+ ); } - const acceptOrder = async () => { - const tokenID = '1' - const topOrder: any = await getTopOrder(tokenID) - const requiredAmount = topOrder.pricePerToken + return ; +}; - if(await checkERC20Balance(requiredAmount)){ - if(!(await checkERC20Approval(address!,MarketPlaceContract,ArbSepoliaUSDCContract,requiredAmount))){ - ... - } else { +export default LandingPage; +``` - } - else { +## 4. Listings and Offers - } - } - } -``` +This section is for integrating the listings and offers from our marketplace into our UI. -Finally, we'll complete the needed logic with actually sending a transaction to the blockchain +To create the Items page and its components, we need to add a new folder named Collectibles inside the src/app/components directory. Within this folder, we will create two files: [index.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-index-collectibles) and [Collectible.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-collectible-tsx). -We begin with the same flow as before, accounting for sending multi-batch transaction if it's a sequence wallet and not approved, or, if the Marketplace is approved to spend your tokens, only submitting a single transaction +Finally, create a page.tsx file inside src/app/collection/[chainParam]/[collectionId] and add the following code snippet: ```ts - ... - const mutexApproveERC20 = new MutexRunner('sendApproveERC20'); - ... - const acceptOrder = async () => { - const topOrder: any = await getTopOrder('1') - const requiredAmount = topOrder.pricePerToken - - const sequenceMarketInterface = new ethers.Interface([ - "function acceptRequest(uint256 requestId, uint256 quantity, address recipient, uint256[] calldata additionalFees, address[] calldata additionalFeeRecipients)", - ]); - - const quantity = 1 - const data = sequenceMarketInterface.encodeFunctionData( - "acceptRequest", - [topOrder.orderId, quantity, address, [], []], - ); +"use client"; + +import { getChainId } from "@/app/utils/getChain"; +import { Collectibles } from "@/app/components/Collectibles"; +import { Address } from "viem"; +import { useAccount } from "wagmi"; +import { ConnectButton } from "@/app/components/Connect"; +import { Disconnect } from "@/app/components/Disconnect"; + +type CollectionBuyPageParams = { + params: { + collectionId: string; + chainParam: string; + }; +}; - setAcceptData(data) // we'll need this later, only for sequence kit enabled transactions - - const tx = { - to: MarketPlaceContract, // 0xB537a160472183f2150d42EB1c3DD6684A55f74c - data: data - } - - if(await checkERC20Balance(requiredAmount)){ - if((await checkERC20Approval(address!,MarketPlaceContract,ArbSepoliaUSDCContract,requiredAmount))){ - sendTransaction({ - to: MarketPlaceContract, - data: `0x${data.slice(2,data.length)}`, - gas: null - }) - } else { - ... - const erc20Interface = new ethers.Interface([ - "function approve(address spender, uint256 amount) external returns (bool)" - ]); - - const spenderAddress = "0xB537a160472183f2150d42EB1c3DD6684A55f74c"; - const maxUint256 = ethers.constants.MaxUint256; - const dataApprove = erc20Interface.encodeFunctionData("approve", [spenderAddress, maxUint256]); - - if(isSequence){ - const wallet = sequence.getWallet() - const signer = wallet.getSigner(421614) - - const txApprove = { - to: ArbSepoliaUSDCContract, // The contract address of the ERC-20 token, replace with actual contract address - data: dataApprove - }; - - try { - const res = await signer.sendTransaction([txApprove, tx]) - console.log(res) - } catch (err) { - console.log(err) - console.log('user closed the wallet, or, an error occured') - } - } else { - mutexApproveERC20.lock() - - sendTransaction({ - to: ArbSepoliaUSDCContract, - data: `0x${dataApprove.slice(2,dataApprove.length)}`, - gas: null - }) - } - } - } -``` +const CollectionBuyPage = ({ params }: CollectionBuyPageParams) => { + const { address, isConnected } = useAccount(); + const chainId = getChainId(params.chainParam)!; + const { collectionId } = params; -Then in the flow for not being a sequence wallet and requiring approval, we'll include another `useEffect` with the mutex check implemented like before + return ( +
+ {address && isConnected ? ( +
+

User address: {address}

+ +
+ ) : ( + + )} + +
+ ); +}; -```ts - ... - const { data: hash, sendTransaction } = useSendTransaction() - ... - useEffect(() => { - if (acceptData && mutexApproveERC20.isLocked()) { - sendTransaction({ - to: MarketPlaceContract, - data: `0x${acceptData.slice(2, acceptData.length)}`, - gas: null, - }); - mutexApproveERC20.unlock(); - } - }, [hash, acceptData]); +export default CollectionBuyPage; ``` -Great, everything is complete if you add the function click handler attached to a button - -## 7. (Optional) Integrate Embedded Wallet into Sequence Kit +### Run and test -In order to make your sequence kit connector as [Embedded Wallet](/sdk/headless-wallet/quickstart) enabled, we'll need to install a few package version and update our `config.ts` we used at the beginning of the guide +You have successfully added the items to your new page, and they should now be visible in the UI. To view them, run pnpm dev. From the landing page, select any of your collections to be redirected to the new items page. Ensure that everything functions as expected by testing the listing, purchasing, offer-making, and offer-accepting processes for an NFT. -The Embedded Wallet feature allows no-confirmation transactions, which can create a smoother UX +## 5. Inventory (Optional) -``` -pnpm i @0xsequence/kit@2.0.5-beta.9 @0xsequence/kit-connectors@2.0.5-beta.9 -``` - -```ts -// config.ts -import { arbitrumSepolia, Chain } from "wagmi/chains"; -import { getDefaultWaasConnectors } from "@0xsequence/kit-connectors"; // updated -import { createConfig, http } from "wagmi"; -import { getKitConnectWallets } from "@0xsequence/kit"; // updated - -const chains = [arbitrumSepolia] as [Chain, ...Chain[]]; - -// added environment variables -const projectAccessKey = process.env.REACT_APP_PROJECTACCESSKEY!; -const waasConfigKey = process.env.REACT_APP_WAASCONFIGKEY!; -const googleClientId = process.env.REACT_APP_GOOGLECLIENTID!; -const appleClientId = process.env.REACT_APP_APPLECLIENTID!; -const walletConnectProjectId = process.env.REACT_APP_WALLETCONNECTID!; -const appleRedirectURI = "https://" + window.location.host; // note: update slug to include correct homepage - -const connectors = [ - ...getDefaultWaasConnectors({ - // updated connector type - walletConnectProjectId: walletConnectProjectId, - defaultChainId: 421614, - waasConfigKey, - googleClientId, - appleClientId, - appleRedirectURI, - appName: "demo app", - projectAccessKey, - enableConfirmationModal: false, - }), - ...getKitConnectWallets(projectAccessKey, []), -]; - -const transports: any = {}; - -chains.forEach((chain) => { - transports[chain.id] = http(); -}); - -const config = createConfig({ - transports, - connectors, - chains, -}); - -export { config }; -``` +To access the user's inventory, you can refer to the following documentation: [Sequence Inventory Modal](https://docs.sequence.xyz/solutions/wallets/sequence-kit/quickstart_examples/inventory). Everything is set up, and you only need to invoke the inventory modal. -The last step, is to make sure to update our team with the Google and Apple authorized URLs (e.g. http://localhost:3000) to call the Embeded Wallet login flow from +If you prefer to build a custom UI using APIs, you can retrieve token balances through the following endpoint: [Get Token Balances API](https://docs.sequence.xyz/api/indexer/endpoints#tag/public/POST/rpc/Indexer/GetTokenBalances). \ No newline at end of file From 4180139deb9fcf93c3e1835d8ed747350a2af842 Mon Sep 17 00:00:00 2001 From: Richard Irala Date: Thu, 13 Mar 2025 23:55:02 -0300 Subject: [PATCH 2/4] Add step 'Core files for Development' in custom marketplace docs --- guides/custom-marketplace.mdx | 120 ++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/guides/custom-marketplace.mdx b/guides/custom-marketplace.mdx index 0c9e96f..72662da 100644 --- a/guides/custom-marketplace.mdx +++ b/guides/custom-marketplace.mdx @@ -10,9 +10,10 @@ The tools will enable you to perform: 1. [Minting](/guides/custom-marketplace#1-minting): Minting of tokens to your wallet from the Sequence Builder 2. [Wallet Authentication](/guides/custom-marketplace#2-wallet-authentication): Use of Sequence Kit to authenticate a user -3. [Displaying collections](/guides/custom-marketplace#3-displaying-collections): ((((Querying of token balances using the Indexer)))) -4. [Listings and Offers](/guides/custom-marketplace#4-listings-and-offers): (((Allow users to either use a Sequence Wallet or an EOA))) -5. [(Optional) Inventory](/guides/custom-marketplace#5-inventory-optional): Add a inventory for your users +3. [Core Files for Development](/guides/custom-marketplace#3-core-files-for-development): Create the necessary files before proceeding. These files are essential for maintaining structure and improving the development process. +4. [Displaying collections](/guides/custom-marketplace#4-displaying-collections): Retrieve and display the marketplace's available collections. +5. [Listings and Offers](/guides/custom-marketplace#5-listings-and-offers): Enable users to list tokens for sale or make offers +6. [(Optional) Inventory](/guides/custom-marketplace#6-inventory-optional): Add a inventory for your users See an example [simplified marketplace dapp](https://simple-marketplace-boilerplate.pages.dev/) that enables users to mint collectibles, sell the collectibles with the Sequence Marketplace Protocol, and make purchases with USDC on `base-sepolia` by getting a top order from the Marketplace. @@ -129,7 +130,114 @@ Congratulations on making it this far! You can now run pnpm dev to test your aut Be sure to include a `.env` file in the root of your project to include client secrets -## 3. Displaying collections +## 3. Core Files for Development + +To continue with the next steps follow these instructions to create essential files required for development. + +* Create a file named getChain.ts inside the src/app/utils directory and paste the following code snippet. This utility will assist in the integration of our listings page. + +```ts +import { networks } from '@0xsequence/network'; + +export type ChainNameOrId = string | number; + +export const getChain = (nameOrId: ChainNameOrId) => { + for (const network of Object.values(networks)) { + if ( + network.name === String(nameOrId).toLowerCase() || + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + network.chainId === Number(nameOrId) + ) { + return network; + } + } +}; + +export const getChainName = (nameOrId: ChainNameOrId) => + getChain(nameOrId)?.name; +export const getChainId = (nameOrId: ChainNameOrId) => + getChain(nameOrId)?.chainId; +export const getPresentableChainName = (nameOrId: ChainNameOrId) => + getChain(nameOrId)?.title; + +export const getCurrencyIconUrl = (chainId: number): string => { + return `https://assets.sequence.info/images/networks/medium/${chainId}.webp`; +}; +``` + +* Create a file named types.ts inside the src/app/utils directory and paste the following code snippet. These types will help maintain strong typing in the code. + +```ts +import { Order, OrderbookKind } from "@0xsequence/marketplace-sdk"; +import { Hash, Hex } from "viem"; + +export type ModalCallbacks = { + onSuccess?: ({ hash, orderId }: { hash?: Hash; orderId?: string }) => void; + onError?: (error: Error) => void; +}; + +export type ShowBuyModalArgs = { + chainId: string; + collectionAddress: Hex; + tokenId: string; + order: Order; +}; + +export type OpenCreateListingModalArgs = { + collectionAddress: Hex; + chainId: string; + collectibleId: string; + orderbookKind?: OrderbookKind; + callbacks?: ModalCallbacks; +}; + +export type ShowCreateListingModalArgs = Exclude; + +export type OpenMakeOfferModalArgs = { + collectionAddress: Hex; + chainId: string; + collectibleId: string; + orderbookKind?: OrderbookKind; + callbacks?: ModalCallbacks; +}; + +export type ShowMakeOfferModalArgs = Exclude; + +export type OpenSellModalArgs = { + collectionAddress: Hex; + chainId: string; + tokenId: string; + order: Order; + callbacks?: ModalCallbacks; +}; + +export type ShowSellModalArgs = Exclude; +``` + +* Create a file named Button.tsx inside the src/app/components directory and paste the following code snippet. This Button component ensures the buttons are displayed clearly in this guide. + +```ts +export const Button = ({ + onClick, + children, +}: React.ButtonHTMLAttributes) => ( + +); +``` + +## 4. Displaying collections To display the marketplace collections we need to create a folder named 'Collections' inside the 'src/app/components' directory. Within this folder, we will create two files: [index.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-index-collections-tsx) and [Collection.tsx](https://gist.github.com/RichardIrala/79239f44c86e01fe465092ab765ee9fd#file-collection-tsx). @@ -167,7 +275,7 @@ const LandingPage = ({ collections }: LandingPageProps) => { export default LandingPage; ``` -## 4. Listings and Offers +## 5. Listings and Offers This section is for integrating the listings and offers from our marketplace into our UI. @@ -222,7 +330,7 @@ export default CollectionBuyPage; You have successfully added the items to your new page, and they should now be visible in the UI. To view them, run pnpm dev. From the landing page, select any of your collections to be redirected to the new items page. Ensure that everything functions as expected by testing the listing, purchasing, offer-making, and offer-accepting processes for an NFT. -## 5. Inventory (Optional) +## 6. Inventory (Optional) To access the user's inventory, you can refer to the following documentation: [Sequence Inventory Modal](https://docs.sequence.xyz/solutions/wallets/sequence-kit/quickstart_examples/inventory). Everything is set up, and you only need to invoke the inventory modal. From 443ff5fb4646813af6adc8bbf66794a568584005 Mon Sep 17 00:00:00 2001 From: Richard Irala Date: Thu, 20 Mar 2025 20:48:37 -0300 Subject: [PATCH 3/4] Add Marketplace SDK documentation --- docs.json | 15 + sdk/marketplace-sdk/getting-started.mdx | 188 ++++++++++++ .../hooks/marketplace-actions.mdx | 223 ++++++++++++++ sdk/marketplace-sdk/hooks/overview.mdx | 18 ++ .../hooks/use-list-collectibles.mdx | 272 ++++++++++++++++++ sdk/marketplace-sdk/overview.mdx | 20 ++ 6 files changed, 736 insertions(+) create mode 100644 sdk/marketplace-sdk/getting-started.mdx create mode 100644 sdk/marketplace-sdk/hooks/marketplace-actions.mdx create mode 100644 sdk/marketplace-sdk/hooks/overview.mdx create mode 100644 sdk/marketplace-sdk/hooks/use-list-collectibles.mdx create mode 100644 sdk/marketplace-sdk/overview.mdx diff --git a/docs.json b/docs.json index ed01e17..5d1c5cc 100644 --- a/docs.json +++ b/docs.json @@ -203,6 +203,21 @@ "sdk/web/on-ramp" ] }, + { + "group": "Marketplace SDK", + "pages": [ + "sdk/marketplace-sdk/overview", + "sdk/marketplace-sdk/getting-started", + { + "group": "Hooks", + "pages": [ + "sdk/marketplace-sdk/hooks/overview", + "sdk/marketplace-sdk/hooks/use-list-collectibles", + "sdk/marketplace-sdk/hooks/marketplace-actions" + ] + } + ] + }, { "group": "Game Engine", "pages": [ diff --git a/sdk/marketplace-sdk/getting-started.mdx b/sdk/marketplace-sdk/getting-started.mdx new file mode 100644 index 0000000..f72a197 --- /dev/null +++ b/sdk/marketplace-sdk/getting-started.mdx @@ -0,0 +1,188 @@ +--- +title: "Getting Started with Marketplace SDK" +description: Learn how to get started with Marketplace SDK by installing the necessary packages and setting up configs +sidebarTitle: Getting Started +--- + +Prior to beginning this integration, ensure you have installed and configured our Web SDK. For setup instructions, please refer to the [Web SDK - Getting started](/sdk/web/getting-started) documentation. Once complete, return here to proceed. + +Otherwise, we will walk you through the process of installing Marketplace SDK, instantiating the SDK and providing you some hooks to work with our marketplace + +## Installing Marketplace SDK Packages + +Marketplace SDK is modular, allowing you to install only the necessary packages. To get started, install the `@0xsequence/marketplace-sdk` core package, as well as install other dependencies necessary dependencies. + +```bash +npm install @0xsequence/kit @0xsequence/kit-checkout @0xsequence/kit-wallet @0xsequence/marketplace-sdk @0xsequence/design-system@^1 @0xsequence/network wagmi ethers@^6 viem 0xsequence @tanstack/react-query @tanstack/react-query-devtools @legendapp/state framer-motion@^8.5.2 pino-pretty +# or +pnpm add @0xsequence/kit @0xsequence/kit-checkout @0xsequence/kit-wallet @0xsequence/marketplace-sdk @0xsequence/design-system@^1 @0xsequence/network wagmi ethers@^6 viem 0xsequence @tanstack/react-query @tanstack/react-query-devtools @legendapp/state framer-motion@^8.5.2 pino-pretty +# or +yarn add @0xsequence/kit @0xsequence/kit-checkout @0xsequence/kit-wallet @0xsequence/marketplace-sdk @0xsequence/design-system@^1 @0xsequence/network wagmi ethers@^6 viem 0xsequence @tanstack/react-query @tanstack/react-query-devtools @legendapp/state framer-motion@^8.5.2 pino-pretty +``` + +# Setting Up your Dapp + +To utilize the core marketplace-sdk wrapper to interact with your marketplace from your application, follow these steps: + + + + +Ensuring that your Web SDK is properly integrated is crucial before getting started with Marketplace SDK. To verify this, simply check if you can log in and log out successfully. + + + + + +Next, a configuration variable for Marketplace SDK will need to be created. In this file, we will define and manage essential configuration settings for our Marketplace SDK, including environment variables. This centralized approach ensures that configuration values are easily accessible and maintainable. + +#### config.ts + + + +```ts nextjs +import type { SdkConfig } from '@0xsequence/marketplace-sdk'; + +export function getConfig(host: string) { + const appleClientId = process.env.NEXT_PUBLIC_APPLE_CLIENT_ID; + + const embedded = process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY + ? ({ + waasConfigKey: process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY, + googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + appleClientId, + appleRedirectURI: appleClientId ? host : '', + } satisfies NonNullable['embedded']) + : undefined; + + const config = { + projectId: process.env.NEXT_PUBLIC_SEQUENCE_PROJECT_ID!, + projectAccessKey: process.env.NEXT_PUBLIC_SEQUENCE_ACCESS_KEY!, + wallet: { + walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, + embedded, + }, + } satisfies SdkConfig; + return config; +} +``` +```ts vite +import type { SdkConfig } from '@0xsequence/marketplace-sdk'; + +export function getConfig(host: string) { + const appleClientId = import.meta.env.VITE_APPLE_CLIENT_ID; + + const embedded = import.meta.env.VITE_WAAS_CONFIG_KEY + ? ({ + waasConfigKey: import.meta.env.VITE_WAAS_CONFIG_KEY, + googleClientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, + appleClientId, + appleRedirectURI: appleClientId ? host : '', + } satisfies NonNullable['embedded']) + : undefined; + + const config = { + projectId: import.meta.env.VITE_SEQUENCE_PROJECT_ID!, + projectAccessKey: import.meta.env.VITE_SEQUENCE_ACCESS_KEY!, + wallet: { + walletConnectProjectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID, + embedded, + }, + } satisfies SdkConfig; + + return config; +} +``` + + + + + +Next, a Ssr Client for Marketplace SDK will need to be created. This SSR Client serves as an entry point for initializing the marketplace SDK on the Next.js server, enabling efficient data fetching and configuration setup before rendering the UI. + +#### Understanding the SSR Client + +The SSR Client allows you to access key marketplace data and configurations, which are essential for properly initializing the SDK on the server side. The following data can be retrieved: + +This setup ensures a seamless integration of the marketplace SDK with server-side rendering, improving performance and user experience. + +#### ssrClient.ts + + + +```ts nextjs +import { getConfig } from './config'; +import { createSSRClient } from '@0xsequence/marketplace-sdk/react/ssr'; +import { QueryClient } from '@tanstack/react-query'; +import { headers } from 'next/headers'; + +export const ssrClient = async () => { + const headersList = headers(); + + return createSSRClient({ + cookie: headersList.get('cookie') || '', + config: getConfig(`https://${headersList.get('x-forwarded-host') || headersList.get('host')}/`), + queryClient: new QueryClient(), + }); +}; +``` + +```ts vite +// You can omit this step with VITE +``` + + + + + + + +Open your `Providers.tsx` file, where the Web SDK is configured, and integrate the Marketplace SDK providers. Additionally, replace the existing `createWagmiConfig` setup with the updated configuration provided below. + +```ts +import { + MarketplaceProvider, + ModalProvider, + createWagmiConfig, + marketplaceConfigOptions, +} from "@0xsequence/marketplace-sdk/react"; + +// Into your React component: + +const { data: marketplaceConfig } = useQuery( + marketplaceConfigOptions(sdkConfig), + queryClient +); + +if (!marketplaceConfig) { + return null; +} + +// If you already have this configuration from Web SDK please replace it for this new config +const wagmiConfig: Config = createWagmiConfig( + marketplaceConfig, + sdkConfig, + !!sdkInitialState +); + +return ( + /* Your other providers should be placed here (they should wrap MarketplaceProvider) */ + + + {children} + + + + /* Your other providers should close here */ +); +``` + + + + + +Congratulations! Now you’re ready to explore the available hooks in our Marketplace SDK. Interested? Check out the [Marketplace SDK hooks](/sdk/marketplace-sdk/hooks/overview) documentation to learn more. + + + + + \ No newline at end of file diff --git a/sdk/marketplace-sdk/hooks/marketplace-actions.mdx b/sdk/marketplace-sdk/hooks/marketplace-actions.mdx new file mode 100644 index 0000000..830850d --- /dev/null +++ b/sdk/marketplace-sdk/hooks/marketplace-actions.mdx @@ -0,0 +1,223 @@ +--- +title: "Marketplace Actions" +description: The action hooks are essential for interacting with the Marketplace in your application. Useful for making listings, making offers, and performing buy and sell actions. +sidebarTitle: "Marketplace Actions" +--- + +## useCreateListingModal + +The useCreateListingModal hook is used to list an item for sale on the Marketplace. It provides the necessary functionality to create and manage a new listing. + +```ts +import { useCreateListingModal } from "@0xsequence/marketplace-sdk/react"; + +## Into your React component: + +const { show: showListModal } = useCreateListingModal({ onError }); + +const onClickList = () => { + showListModal({ + collectionAddress, + chainId, + collectibleId, + orderbookKind, + }); +}; + +return +``` + + + + + ```ts + interface useCreateListingModal { + onSuccess?: ({ hash, orderId }: { + hash?: Hash; + orderId?: string; + }) => void; + onError?: (error: Error) => void; + } + ``` + + + + + ```ts + interface ShowCreateListingModalArgs { + collectionAddress: Hex; + chainId: string; + collectibleId: string; + orderbookKind?: OrderbookKind; + callbacks?: ModalCallbacks; + } + ``` + + + + + +## useBuyModal + +The useBuyModal hook allows users to purchase an NFT that is listed for sale on the Marketplace. It handles the buying process and transaction execution. + +```ts +import { useBuyModal } from "@0xsequence/marketplace-sdk/react"; + +## Into your React component: + +const { show: showBuyModal } = useBuyModal({ + onSuccess(hash) { + console.log("Buy transaction sent with hash: ", hash); + }, + onError, +}); + +const onClickBuy = () => { + showBuyModal({ + chainId, + collectionAddress, + tokenId, + order, + }); +}; + +return +``` + + + + + ```ts + interface useBuyModal { + onSuccess?: ({ hash, orderId }: { + hash?: Hash; + orderId?: string; + }) => void; + onError?: (error: Error) => void; + } + ``` + + + + + ```ts + interface ShowBuyModalArgs { + chainId: string; + collectionAddress: Hex; + tokenId: string; + order: Order; + } + ``` + + + + + +## useMakeOfferModal + +The useMakeOfferModal hook enables users to place an offer on an NFT. It facilitates creating and submitting offers within the Marketplace. + +```ts +import { useMakeOfferModal } from "@0xsequence/marketplace-sdk/react"; + +## Into your React component: + +const { show: showOfferModal } = useMakeOfferModal({ + onError, +}); + +const onClickOffer = () => { + showOfferModal({ + collectionAddress, + chainId, + collectibleId, + orderbookKind, + }); +}; + +return +``` + + + + + ```ts + interface useMakeOfferModal { + onSuccess?: ({ hash, orderId }: { + hash?: Hash; + orderId?: string; + }) => void; + onError?: (error: Error) => void; + } + ``` + + + + + ```ts + interface ShowMakeOfferModalArgs { + collectionAddress: Hex; + chainId: string; + collectibleId: string; + orderbookKind?: OrderbookKind; + callbacks?: ModalCallbacks; + } + ``` + + + + + +## useSellModal + +The useSellModal hook is used to sell an NFT by accepting an existing offer. It provides the necessary functionality to complete the selling process. + +```ts +import { useSellModal } from "@0xsequence/marketplace-sdk/react"; + +## Into your React component: + +const { show: showSellModal } = useSellModal({ onError }); + +const onAcceptOffer = () => { + showSellModal({ + collectionAddress, + chainId, + tokenId, + order, + }); +}; + +return +``` + + + + + ```ts + interface useSellModal { + onSuccess?: ({ hash, orderId }: { + hash?: Hash; + orderId?: string; + }) => void; + onError?: (error: Error) => void; + } + ``` + + + + + ```ts + interface ShowSellModalArgs { + collectionAddress: Hex; + chainId: string; + tokenId: string; + order: Order; + callbacks?: ModalCallbacks; + } + ``` + + + + \ No newline at end of file diff --git a/sdk/marketplace-sdk/hooks/overview.mdx b/sdk/marketplace-sdk/hooks/overview.mdx new file mode 100644 index 0000000..9adeb7b --- /dev/null +++ b/sdk/marketplace-sdk/hooks/overview.mdx @@ -0,0 +1,18 @@ +--- +title: "Marketplace SDK Hooks" +description: Marketplace SDK provides a collection of optimized hooks to seamlessly and efficiently integrate marketplace functionality into your applications. These hooks enable smooth and secure user experience management. +sidebarTitle: Overview +--- + +It includes hooks for both retrieving marketplace data and performing actions such as buying, selling, creating, and accepting offers. + +Refer to the documentation to learn how to use them and maximize your integration. + + + + Collection of GET hooks for retrieving key Marketplace data. Useful for fetching and managing Marketplace information in your UI + + + Manage listings, offers, and purchases by integrating Marketplace Actions. + + \ No newline at end of file diff --git a/sdk/marketplace-sdk/hooks/use-list-collectibles.mdx b/sdk/marketplace-sdk/hooks/use-list-collectibles.mdx new file mode 100644 index 0000000..4a640a7 --- /dev/null +++ b/sdk/marketplace-sdk/hooks/use-list-collectibles.mdx @@ -0,0 +1,272 @@ +--- +title: "Marketplace Data Hooks" +description: Collection of GET hooks for retrieving key Marketplace data. Useful for fetching and managing Marketplace information in your UI. +sidebarTitle: Marketplace Data Hooks +--- + +## useListCollectibles + +The useListCollectibles hook retrieves the current listings and offers in a collection from your Marketplace. Useful for accessing and managing listings and offers efficiently. + +```ts +import { OrderSide } from '@0xsequence/marketplace-sdk'; +import { useListCollectibles } from '@0xsequence/marketplace-sdk/react'; + +## Into your React component: + +const { + data: collectibles, + isLoading: collectiblesLoading, + fetchNextPage: fetchNextCollectibles, +} = useListCollectibles({ + chainId, + collectionAddress, + filter: { + // # Optional filters + includeEmpty, + searchText, + properties, + }, + side: OrderSide.listing, +}); + +const collectiblesFlat = + collectibles?.pages.flatMap((p) => p.collectibles) ?? []; + +return ( +
+ {collectiblesFlat?.map((collectible) => ( + // Your Collectibles component + ))} +
+); +``` + + + + + ```ts + interface UseListCollectiblesArgs { + chainId: string; + side: OrderSide; + collectionAddress: `0x${string}`; + page?: { + page: number; + pageSize: number; + sort?: { + order: SortOrder$1; + column: string; + }[]; + more?: boolean; + }; + filter?: { + includeEmpty: boolean; + searchText?: string; + properties?: { + type: PropertyType; + name: string; + values?: any[]; + max?: number; + min?: number; + }[]; + marketplaces?: MarketplaceKind[]; + inAccounts?: string[]; + notInAccounts?: string[]; + ordersCreatedBy?: string[]; + ordersNotCreatedBy?: string[]; + }; + query?: { + enabled?: boolean; + }; + } + ``` + + + + + + Contains the paginated collectible orders data. + + + + List of collectible orders returned in pages. + + + + Indicates whether the data is currently loading. + + + + This function allows you to fetch the next "page" of results. + + + + +## useListCollectiblesPaginated + +The useListCollectiblesPaginated hook efficiently retrieves and manages current listings and offers from your Marketplace, making it ideal for displaying paginated NFTs within a collection. + +```ts +import { OrderSide } from '@0xsequence/marketplace-sdk'; +import { useListCollectiblesPaginated } from '@0xsequence/marketplace-sdk/react'; + +const chainId = 137; +const searchText = ""; +const enabled = true; +const includeEmpty = true; +const properties = []; +const pageSize = 30; +const currentPage = 1; +const collectionAddress = "0x0e5566a108e617baedbebb44e3fcc7bf03e3a839"; + +## Into your React component: + +const { + data: collectiblesResponse, + isLoading: collectiblesLoading, +} = useListCollectiblesPaginated({ + chainId: String(chainId), + collectionAddress, + side: OrderSide.listing, + filter: { + // # Optional filters + includeEmpty, + searchText, + properties, + }, + page: { + page: currentPage, + pageSize, + }, + query: { + page: currentPage, + pageSize, + enabled, + }, +}); + +const collectiblesList = collectiblesResponse?.collectibles ?? []; + +return ( +
+ {collectiblesList?.map((collectible) => ( + // Your Collectibles component + ))} +
+); +``` + + + + ```ts + interface UseListCollectiblesPaginatedArgs { + chainId: string; + side: OrderSide; + collectionAddress: `0x${string}`; + page?: { + page: number; + pageSize: number; + sort?: { + order: SortOrder$1; + column: string; + }[]; + more?: boolean; + }; + filter?: { + includeEmpty: boolean; + searchText?: string; + properties?: { + type: PropertyType; + name: string; + values?: any[]; + max?: number; + min?: number; + }[]; + marketplaces?: MarketplaceKind[]; + inAccounts?: string[]; + notInAccounts?: string[]; + ordersCreatedBy?: string[]; + ordersNotCreatedBy?: string[]; + }; + query: { + page: number; + pageSize: number; + enabled?: boolean; + }; + } + ``` + + + + + Contains the collectible orders data. + + + + List of collectible orders. + + + + Indicates whether the data is currently loading. + + + + +## useCountOfCollectables + +The useCountOfCollectables hook returns the number of NFTs in a collection. + +```ts +import { OrderSide } from '@0xsequence/marketplace-sdk'; +import { useCountOfCollectables } from '@0xsequence/marketplace-sdk/react'; + +const countOfCollectables = useCountOfCollectables({ + chainId: String(chainId), + collectionAddress: collectionId, + side: OrderSide.listing, + filter: { + searchText: text, + includeEmpty, + properties, + }, +}); +``` + + + + ```ts + interface UseCountOfCollectables { + chainId: ChainId; + collectionAddress: CollectionAddress; + query?: { enabled?: boolean }; + filter?: { + includeEmpty: boolean; + searchText?: string; + properties?: { + name: string; + type: PropertyType; + min?: number; + max?: number; + values?: any[]; + }[]; + marketplaces?: MarketplaceKind[]; + inAccounts?: string[]; + notInAccounts?: string[]; + ordersCreatedBy?: string[]; + ordersNotCreatedBy?: string[]; + }; + side?: OrderSide; + } + ``` + + + + + Numeric response data. + + + + Indicates whether the data is currently loading. + + + \ No newline at end of file diff --git a/sdk/marketplace-sdk/overview.mdx b/sdk/marketplace-sdk/overview.mdx new file mode 100644 index 0000000..ae3ef71 --- /dev/null +++ b/sdk/marketplace-sdk/overview.mdx @@ -0,0 +1,20 @@ +--- +title: "Marketplace SDK" +description: Marketplace SDK is a comprehensive toolkit that seamlessly integrates our Marketplaces into applications. +sidebarTitle: Overview +--- + +Why use the Marketplace SDK? It provides a seamless way to integrate Sequence's marketplace with minimal effort. Leverage its powerful hooks to retrieve marketplace data and execute essential actions such as listings, purchases, offers, and offer acceptance. + + + + Learn about the prerequisites and setup required to work with the Marketplace SDK. + + + Explore how to integrate your marketplace using our available hooks. + + + +## Next Steps + +Ready to integrate Marketplace SDK into your application? Check out our [Quickstart guide](/sdk/marketplace-sdk/getting-started). \ No newline at end of file From 0acb3ed5f2b814b41a057ee08092b9b06da3afae Mon Sep 17 00:00:00 2001 From: Richard Irala Date: Mon, 31 Mar 2025 22:24:14 -0300 Subject: [PATCH 4/4] Update marketplace sdk set up doc --- sdk/marketplace-sdk/getting-started.mdx | 170 ++++++++++++++++++------ 1 file changed, 131 insertions(+), 39 deletions(-) diff --git a/sdk/marketplace-sdk/getting-started.mdx b/sdk/marketplace-sdk/getting-started.mdx index f72a197..8cb591d 100644 --- a/sdk/marketplace-sdk/getting-started.mdx +++ b/sdk/marketplace-sdk/getting-started.mdx @@ -38,57 +38,104 @@ Next, a configuration variable for Marketplace SDK will need to be created. In t #### config.ts +```ts vite +import type { SdkConfig } from '@0xsequence/marketplace-sdk'; + +const projectAccessKey = import.meta.env.VITE_PROJECT_ACCESS_KEY; +const projectId = import.meta.env.VITE_PROJECT_ID!; +const waasConfigKey = import.meta.env.VITE_WAAS_CONFIG_KEY; +const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID; +const appleClientId = import.meta.env.VITE_APPLE_CLIENT_ID; +const appleRedirectURI = window.location.origin + window.location.pathname; +const walletConnectId = import.meta.env.VITE_WALLET_CONNECT_ID; + +export function getConfig() { + const embedded = waasConfigKey + ? ({ + waasConfigKey, + googleClientId, + appleClientId, + appleRedirectURI, + } satisfies NonNullable["embedded"]) + : undefined; -```ts nextjs + const config = { + projectId, + projectAccessKey, + wallet: { + walletConnectProjectId: walletConnectId, + embedded, + }, + } satisfies SdkConfig; + + return config; +} +``` + +```ts nextjs server side rendering import type { SdkConfig } from '@0xsequence/marketplace-sdk'; -export function getConfig(host: string) { - const appleClientId = process.env.NEXT_PUBLIC_APPLE_CLIENT_ID; +const projectAccessKey = process.env.NEXT_PUBLIC_PROJECT_ACCESS_KEY!; +const projectId = process.env.NEXT_PUBLIC_PROJECT_ID!; +const waasConfigKey = process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY!; +const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; +const appleClientId = process.env.NEXT_PUBLIC_APPLE_CLIENT_ID; +const walletConnectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_ID; - const embedded = process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY +export function getConfig(host: string) { + const embedded = waasConfigKey ? ({ - waasConfigKey: process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY, - googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + waasConfigKey, + googleClientId, appleClientId, appleRedirectURI: appleClientId ? host : '', } satisfies NonNullable['embedded']) : undefined; const config = { - projectId: process.env.NEXT_PUBLIC_SEQUENCE_PROJECT_ID!, - projectAccessKey: process.env.NEXT_PUBLIC_SEQUENCE_ACCESS_KEY!, + projectId, + projectAccessKey, wallet: { - walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, + walletConnectProjectId: walletConnectId, embedded, }, } satisfies SdkConfig; + return config; } ``` -```ts vite -import type { SdkConfig } from '@0xsequence/marketplace-sdk'; -export function getConfig(host: string) { - const appleClientId = import.meta.env.VITE_APPLE_CLIENT_ID; +```ts nextjs client side rendering +import type { SdkConfig } from '@0xsequence/marketplace-sdk'; - const embedded = import.meta.env.VITE_WAAS_CONFIG_KEY +const projectAccessKey = process.env.NEXT_PUBLIC_PROJECT_ACCESS_KEY!; +const projectId = process.env.NEXT_PUBLIC_PROJECT_ID!; +const waasConfigKey = process.env.NEXT_PUBLIC_WAAS_CONFIG_KEY!; +const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; +const appleClientId = process.env.NEXT_PUBLIC_APPLE_CLIENT_ID; +const appleRedirectURI = + typeof window !== "undefined" ? `https://${window.location.host}` : ""; +const walletConnectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_ID; + +export function getConfig() { + const embedded = waasConfigKey ? ({ - waasConfigKey: import.meta.env.VITE_WAAS_CONFIG_KEY, - googleClientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, + waasConfigKey, + googleClientId, appleClientId, - appleRedirectURI: appleClientId ? host : '', + appleRedirectURI, } satisfies NonNullable['embedded']) : undefined; const config = { - projectId: import.meta.env.VITE_SEQUENCE_PROJECT_ID!, - projectAccessKey: import.meta.env.VITE_SEQUENCE_ACCESS_KEY!, + projectId, + projectAccessKey, wallet: { - walletConnectProjectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID, + walletConnectProjectId: walletConnectId, embedded, }, } satisfies SdkConfig; - + return config; } ``` @@ -108,8 +155,11 @@ This setup ensures a seamless integration of the marketplace SDK with server-sid #### ssrClient.ts +```ts vite +// You can omit this step with VITE +``` -```ts nextjs +```ts nextjs server side rendering import { getConfig } from './config'; import { createSSRClient } from '@0xsequence/marketplace-sdk/react/ssr'; import { QueryClient } from '@tanstack/react-query'; @@ -126,8 +176,8 @@ export const ssrClient = async () => { }; ``` -```ts vite -// You can omit this step with VITE +```ts nextjs client side rendering +// You can omit this step in NEXT.js if your layout uses 'use client' instead of server-side rendering ``` @@ -136,33 +186,74 @@ export const ssrClient = async () => { -Open your `Providers.tsx` file, where the Web SDK is configured, and integrate the Marketplace SDK providers. Additionally, replace the existing `createWagmiConfig` setup with the updated configuration provided below. +Open your `Providers.tsx` file, where the Web SDK is configured, and integrate the Marketplace SDK providers. -```ts + +```ts vite import { MarketplaceProvider, ModalProvider, - createWagmiConfig, - marketplaceConfigOptions, } from "@0xsequence/marketplace-sdk/react"; +import { getConfig } from "./config"; + +const sdkConfig = getConfig(); // Into your React component: -const { data: marketplaceConfig } = useQuery( - marketplaceConfigOptions(sdkConfig), - queryClient +return ( + /* Your other providers should be placed here (they should wrap MarketplaceProvider) */ + + + {children} + + + + /* Your other providers should close here */ ); +``` -if (!marketplaceConfig) { - return null; -} +```ts nextjs server side rendering +import { + MarketplaceProvider, + ModalProvider, + marketplaceConfigOptions, +} from "@0xsequence/marketplace-sdk/react"; + +import { + QueryClient, + QueryClientProvider, + useQuery, +} from '@tanstack/react-query'; + +import { ssrClient } from '../ssrClient.ts'; + +// Into your React component: -// If you already have this configuration from Web SDK please replace it for this new config -const wagmiConfig: Config = createWagmiConfig( - marketplaceConfig, - sdkConfig, - !!sdkInitialState +const { getInitialState, config: sdkConfig } = ssrClient(); +const sdkInitialState = await getInitialState(); + +return ( + /* Your other providers should be placed here (they should wrap MarketplaceProvider) */ + + + {children} + + + + /* Your other providers should close here */ ); +``` + +```ts nextjs client side rendering +import { + MarketplaceProvider, + ModalProvider, +} from "@0xsequence/marketplace-sdk/react"; +import { getConfig } from "./config"; + +const sdkConfig = getConfig(); + +// Into your React component: return ( /* Your other providers should be placed here (they should wrap MarketplaceProvider) */ @@ -175,6 +266,7 @@ return ( /* Your other providers should close here */ ); ``` +