# Manifold Docs

This documentation website is intended for builders looking to create products using Manifold smart contracts and APIs.

{% content-ref url="/spaces/wX9Yl8DLygpenDBVWGPF" %}
[Manifold Client SDK](https://docs.manifold.xyz/client-sdk/)
{% endcontent-ref %}

{% content-ref url="/spaces/YlwyRgRnkR5stBgnZmq8" %}
[Manifold Contract Addresses](https://docs.manifold.xyz/contracts/)
{% endcontent-ref %}

{% content-ref url="/spaces/8n4plerMUJrsrAiaKpc2" %}
[Manifold Developer Guide (Deprecated)](https://docs.manifold.xyz/manifold-for-developers/)
{% endcontent-ref %}


# Why Manifold Client SDK?

The **Manifold Client SDK** exists to enable builders to create web3 apps, where [the creator is the platform](https://manifoldxyz.substack.com/p/manifold-creator).

This toolkit complements [**Manifold Studio**](https://studio.manifold.xyz/) such that anyone can build:

* **Custom storefronts** – Fetch products, check allocations, verify allowlists, and more.
* **Trusted checkout flows** – Secure web3 transactions with Manifold’s audited smart contracts.
* **Wallet agnostic UIs** – Official support for `ethers v5` and `viem`, with more adapters coming.

To learn more about how to build your own app and seamlessly integrate onchain purchases, see [**Getting Started**](/client-sdk/getting-started).

If you have any questions or feedback, see our [**FAQ**](/client-sdk/guides/faq) or join our [**Community Forum**](https://forum.manifold.xyz).


# Getting Started

## Overview

[Manifold Studio](https://studio.manifold.xyz/) enables the publishing of [**Edition**](https://help.manifold.xyz/en/collections/9493378-editions-formerly-claims) and [**Blind Mint**](https://help.manifold.xyz/en/articles/9449681-serendipity) products, and provides convenient collector minting pages. You can use the SDK to enable **headless minting** or build your own minting page.

## Requirements

Before getting started, make sure you have the following:

* **Node.js 18.0.0 or higher**
  * Check your version: `node --version`
  * Download from [nodejs.org](https://nodejs.org/)
* A package manager (npm, pnpm, or yarn)
* [**RPC providers**](https://www.alchemy.com/overviews/private-rpc-endpoint) for the networks you plan to support

## Installation <a href="#installation" id="installation"></a>

Install the SDK in your project:

```bash
npm install @manifoldxyz/client-sdk
```

#### Quick Start <a href="#quick-start" id="quick-start"></a>

{% tabs %}
{% tab title="index.ts" %}

```typescript
import { createClient, EditionProduct, createPublicProviderWagmi, createAccountViem } from '@manifoldxyz/client-sdk';
import { createConfig, http, getAccount, getWalletClient } from '@wagmi/core';
import { mainnet } from '@wagmi/core/chains';

// Create Wagmi config
const config = createConfig({
  chains: [mainnet],
  transports: {
    [mainnet.id]: http('YOUR_RPC_URL'), // or http() for public RPC
  },
});

// Initialize the Manifold client
const client = createClient({ publicProvider:createPublicProviderWagmi({ config }) });

// Fetch product
const product = await client.getProduct('4150231280') as EditionProduct;

// Get connected account from Wagmi
const account = getAccount(config);
if (!account.address) throw new Error('No wallet connected');

// Prepare purchase
const prepared = await product.preparePurchase({
  address: account.address,
  payload: { quantity: 1 },
});

// Get wallet client and create account adapter
const walletClient = await getWalletClient(config);

// Execute purchase
const order = await product.purchase({
  account: createAccountViem({ walletClient }),
  preparedPurchase: prepared,
});
const txHash = order.receipts[0]?.txHash;
console.log(`Edition purchase transaction: ${txHash}`);
```

{% endtab %}
{% endtabs %}

## Create your first product

Head over to [Manifold Studio](https://studio.manifold.xyz/) to [create your first product](https://help.manifold.xyz/en/collections/9493376-your-create-menu). The SDK currently supports [Edition](https://help.manifold.xyz/en/collections/9493378-editions-formerly-claims) and [Blind Mint](https://help.manifold.xyz/en/articles/9449681-serendipity) products, with future support planned for [Burn Redeem](https://help.manifold.xyz/en/articles/9387352-create-a-burn-redeem-campaign).

1. Visit [studio.manifold.xyz](https://studio.manifold.xyz/) and sign in.
2. Navigate to the [Create+ Menu](https://help.manifold.xyz/en/collections/9493376-your-create-menu) and choose [Edition](https://help.manifold.xyz/en/collections/9493378-editions-formerly-claims) or [Blind Mint](https://help.manifold.xyz/en/articles/9449681-serendipity) .
3. Configure your asset, allowlists, supply caps, and price.
4. Publish the product and note the instance ID from the URL.
5. Use the instance ID or shareable URL with [`getProduct`](/client-sdk/sdk/manifold-client/getproduct).

### Troubleshooting

#### Node.js Version Issues

If you encounter installation or build errors, the most common cause is using an unsupported Node.js version.

**Common error messages:**

* `npm ERR! engine Unsupported platform`
* `npm ERR! peer dep missing`
* Build failures with cryptic error messages
* Package installation failures

**Solution:**

1. Check your Node.js version:

   ```
   node --version
   ```
2. If you're using Node.js < 18.0.0, update to the latest LTS version:
   * Visit [nodejs.org](https://nodejs.org/) to download the latest LTS
   * Or use a version manager like `nvm`:

     ```
     nvm install --ltsnvm use --lts
     ```

[If you're an AI agent or LLM, please read this.](/client-sdk/guides/for-ai-agents-and-llms-checklist-and-guidelines)

### Building with AI

\
This entire site is converted into a single markdown doc that can fit inside the context window of most LLMs. See [The /llms.txt file](https://manifold-1.gitbook.io/manifold-client-sdk/llms-full.txt).

\\


# Creating a React minting app

{% hint style="info" %}
This example uses [RainbowKit](https://rainbowkit.com/) and [Viem adapter](/client-sdk/sdk/account-adapters/viem). The SDK also works with Ethers v5 via the [EthersV5 adapter](/client-sdk/sdk/account-adapters/ethersv5).
{% endhint %}

The repository includes a complete example at [examples/rainbowkit-mint](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/edition/rainbowkit-mint), demonstrating how to implement minting with Manifold products including [Edition Products](/client-sdk/reference/editionproduct)

**Overview**

The edition RainbowKit example showcases how to:

* Connect wallets with RainbowKit + wagmi
* Mint [Edition products](/client-sdk/reference/editionproduct) through the Manifold Client SDK
* Run on Next.js 14 with the App Router and TypeScript
* Display mint progress, costs, and errors in the UI
* Complete example at [examples/rainbowkit-mint](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/edition/rainbowkit-mint)

**Quick start**

1. **Install workspace dependencies**

   ```bash
   pnpm install
   ```
2. **Create environment variables**

   ```bash
   cp .env.example \
      env.local
   ```

   Fill in:

   ```env
   NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_walletconnect_project_id
   NEXT_PUBLIC_INSTANCE_ID=your_edition_instance_id
   NEXT_PUBLIC_RPC_URL_SEPOLIA=your_alchemy_rpc_url
   ```

   `NEXT_PUBLIC_INSTANCE_ID` must point to an Edition product you published in Manifold Studio.\
   `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` is optional but required if you want to support [WalletConnect](https://dashboard.reown.com/sign-in) wallets.\
   `NEXT_PUBLIC_RPC_URL_SEPOLIA` your[ Alchemy Sepolia RPC URL](https://www.alchemy.com/overviews/private-rpc-endpoint).
3. **Launch the example**

```bash
pnpm dev
```

Visit `http://localhost:3000` and connect a wallet with RainbowKit’s `ConnectButton`.

**Key implementation steps**

1. Follow [RainbowKit setup instructions](https://rainbowkit.com/docs/installation)

Ensure you have the [ConnectButton](https://rainbowkit.com/docs/connect-button) component on your page.\
This handles wallet connections, which are required to create an [Account](/client-sdk/reference/account) that the SDK uses for checks and transaction execution.

```typescript
'use client';

import { ConnectButton } from '@rainbow-me/rainbowkit';

export default function Home() {
  return (
    <main>
      <div>
        <ConnectButton />
      </div>
    </main>
  );
}
```

2. Implement Minting Logic in [MintButton.tsx](https://github.com/manifoldxyz/client-sdk/blob/main/packages/examples/edition/rainbowkit-mint/src/components/MintButton.tsx)

```typescript
'use client';

import { ConnectButton } from '@rainbow-me/rainbowkit';
import { MintButton } from '@/components/MintButton';

export default function Home() {
  return (
    <main>
      <h1>
        Manifold SDK + RainbowKit
      </h1>

      <div>
        <ConnectButton />
        <MintButton />
      </div>
    </main>
  );
}
```

**Core Steps**

a. Create a [Manifold Client](/client-sdk/sdk/manifold-client) with a public provider

```typescript
import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { useConfig } from 'wagmi';

// Get the Wagmi config from your React context
const config = useConfig();

// Create a public provider using Wagmi config
const publicProvider = createPublicProviderWagmi({ config });

// Initialize the client
const client = createClient({ publicProvider });
```

b. Create an [Account](/client-sdk/reference/account) representing the connected user.

```typescript
import { createAccountViem } from '@manifoldxyz/client-sdk';
import { useWalletClient } from 'wagmi';

// Get the wallet client from wagmi
const { data: walletClient } = useWalletClient();

// Create the account adapter
const account = createAccountViem({
  walletClient,
});
```

c. Fetch the product and verify its type

```typescript
const product = await client.getProduct(INSTANCE_ID) as EditionProduct;
```

d. Check the product status to ensure it’s still active

```typescript
const productStatus = await product.getStatus();
if (productStatus !== 'active') {
  throw new Error(`Product is ${productStatus}`);
}
```

e. Prepare the purchase by specifying the amount

```typescript
const preparedPurchase = await product.preparePurchase({
  address: address,
  payload: {
    quantity: 1,
  },
});
```

f. Execute the purchase

```typescript
const order = await product.purchase({
  account,
  preparedPurchase,
});
```

Key points:

* `createAccountViem` wraps wagmi’s wallet client so the SDK can sign and send transactions on the user’s behalf.
* `preparePurchase` performs all eligibility checks (allowlists, supply, promo codes) and returns the total cost breakdown. Supply the same `account` so balance checks run against the connected wallet.
* `purchase` executes the transaction sequence (ERC-20 approvals, mint, etc.) and returns a [Receipt](/client-sdk/reference/receipt) with the final transaction hash and minted tokens.

**Display token media and on-chain stats**

You can enrich the UI with product art and live supply data directly from the SDK:

```typescript
const product = await client.getProduct(instanceId);

// Off-chain media and metadata (safe to render immediately)
const { asset, title, contract } = product.data.publicData;
const media = await product.getPreviewMedia();
const imageUrl = media?.image;
const animationUrl = media?.animation;

// Fetch on-chain data once (cost, supply, timing)
const onchainData = await product.fetchOnchainData();
const { totalMinted, totalSupply, startDate, endDate, cost } = onchainData;

return (
  <section>
    {imageUrl && <img src={imageUrl} alt={title} />}
    {animationUrl && (
      <video src={animationUrl} autoPlay loop muted playsInline />
    )}

    <dl>
      <dt>Price</dt>
      <dd>{cost.formatted}</dd>
      <dt>Minted</dt>
      <dd>{totalMinted}</dd>
      <dt>Total supply</dt>
      <dd>{totalSupply === -1 ? 'Unlimited' : totalSupply}</dd>
      <dt>Start date</dt>
      <dd>{startDate?.toLocaleString() ?? 'TBD'}</dd>
      <dt>End date</dt>
      <dd>{endDate?.toLocaleString() ?? 'Open'}</dd>
      <dt>Contract</dt>
      <dd>{contract.address}</dd>
    </dl>
  </section>
);
```

Best practices:

* **Check status** with [getStatus](/client-sdk/sdk/product/common/getstatus) before attempting a purchase to verify the product is active.
* **Handle** [**ClientSDKError**](/client-sdk/reference/clientsdkerror) **codes** for common cases such as ineligibility, sold-out items, or insufficient funds.
* Call [`getAllocations`](/client-sdk/sdk/product/common/getallocations) when you need to show remaining allowlist spots.
* Inspect [`Receipt.order`](/client-sdk/reference/order) to display which tokens were minted after `purchase`.


# Advanced use cases

We strongly recommend following this tutorial if you plan to support an [Edition](/client-sdk/reference/editionproduct) product with any of the following configurations:

* Price set using an ERC-20 token

As discussed in [**Transaction Steps**](/client-sdk/reference/transactionstep), purchasing a product might require more than one on-chain transaction. We recommend executing each transaction explicitly (e.g., via button clicks).

This tutorial demonstrates how to handle the above using the SDK.

The repo ships with a full example at [examples/step-by-step-mint](https://github.com/manifoldxyz/client-sdk/tree/main/examples/step-by-step-mint), showing how to implement minting with a [Blind Mint](/client-sdk/sdk/product/blind-mint) product.

**Install dependencies**

```bash
cd examples/step-by-step-mint
npm install
```

**Configure environment** – Copy `.env.example` to `.env` and set the following:

```bash
NEXT_PUBLIC_INSTANCE_ID= # You blind mint instance ID from Manifold Studio 
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= # Get one at https://dashboard.reown.com/sign-in
```

**Run locally**

```bash
npm run dev
```

**Key implementation steps**

1. Follow [RainbowKit setup instructions](https://rainbowkit.com/docs/installation)

Make sure you render [ConnectButton](https://rainbowkit.com/docs/connect-button) on the page. This handles the user’s wallet connection, which is required to create an [Account](/client-sdk/reference/account) that the SDK uses to perform checks and execute transactions later.

```typescript
'use client';

import { ConnectButton } from '@rainbow-me/rainbowkit';

export default function Home() {
  return (
    <main>
      <div>
        <ConnectButton />
      </div>
    </main>
  );
}
```

3. Implement the [MintButton.tsx](https://github.com/manifoldxyz/client-sdk/blob/main/examples/step-by-step-mint/src/components/MintButton.tsx)

On button click, initialize the client, fetch the product, and call `preparePurchase` to get the steps.

This is handled within [handlePreparePurchase](https://github.com/manifoldxyz/client-sdk/blob/3131d4374ad98b48ba1d17cc1a29e58428ebd121/examples/step-by-step-mint/src/components/MintButton.tsx#L28).

a. Create a [Manifold Client](/client-sdk/sdk/manifold-client)

```typescript
import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { useConfig } from 'wagmi';

// Get Wagmi config from React context
const config = useConfig();

// Create public provider and client
const publicProvider = createPublicProviderWagmi({ config });
const client = createClient({ publicProvider });
```

b. Fetch the product and validate the type

```typescript
const product = await client.getProduct(INSTANCE_ID);
if (!isBlindMintProduct(product)) {
  throw new Error('Is not a blind mint instance')
}
```

c. Check the product status to ensure it’s still active

```typescript
const productStatus = await product.getStatus();
if (productStatus !== 'active') {
  throw new Error(`Product is ${productStatus}`);
}
```

e. Prepare the purchase by specifying the purchase amount and capture the returned [PreparedPurchase](/client-sdk/reference/preparedpurchase)

```typescript
const preparedPurchase = await product.preparePurchase({
  address: address,
  payload: {
    quantity
  }
});
setPreparedPurchase(prepared)
```

Implement a function responsible for executing an individual step. This is handled in [handleExecuteStep](https://github.com/manifoldxyz/client-sdk/blob/3131d4374ad98b48ba1d17cc1a29e58428ebd121/examples/step-by-step-mint/src/components/MintButton.tsx#L70)

a. Create an [Account](/client-sdk/reference/account) representing the connected user.

<pre class="language-tsx"><code class="lang-tsx">import { createAccountViem } from '@manifoldxyz/client-sdk'
<strong>export default function MintButton({ instanceId, quantity = 1 }: MintButtonButtonProps) {
</strong>  const { data: walletClient } = useWalletClient()
  //...other codes
  
  const handlePreparePurchase = async () => {
    const account = createAccountViem({
      walletClient: walletClient
    })
    //...other codes
  }
}
  
</code></pre>

b. Execute the step by calling the [execute](/client-sdk/sdk/transaction-steps/execute) function on each step

<pre class="language-tsx"><code class="lang-tsx">import { createAccountViem } from '@manifoldxyz/client-sdk'
<strong>export default function MintButton({ instanceId, quantity = 1 }: MintButtonButtonProps) {
</strong>  const { data: walletClient } = useWalletClient()
  //...other codes
  
  const handlePreparePurchase = async (stepIndex: number) => {
   //...other codes
   const step = preparedPurchase.steps[stepIndex]
   if (!step) {
    setError('Invalid step index')
    return
   }
   try {
    const result = await step.execute(account)
   catch (error) {
    setError(err.message || 'Transaction failed')
   }
   //...other codes
  }
}
  
</code></pre>

c. Render the [StepModal](https://github.com/manifoldxyz/client-sdk/blob/main/examples/step-by-step-mint/src/components/StepModal.tsx)

```tsx
  {preparedPurchase && (
      <StepModal
        isOpen={isModalOpen}
        onClose={handleCloseModal}
        steps={preparedPurchase.steps}
        onExecuteStep={handleExecuteStep}
        currentStepIndex={currentStepIndex}
        stepStatuses={stepStatuses}
        totalCost={BigInt(preparedPurchase.cost.total.native.value.toString())}
      />
    )}
```

Putting it all together

```typescript
'use client'

import { useState } from 'react'
import { useAccount, useConfig } from 'wagmi'
import { createClient, BlindMintProduct, PreparedPurchase, createAccountViem, createPublicProviderWagmi } from '@manifoldxyz/client-sdk'
import { useWalletClient } from 'wagmi'
import StepModal from './StepModal'

interface MintButtonButtonProps {
  instanceId: string
  quantity?: number
}

export default function MintButton({ instanceId, quantity = 1 }: MintButtonButtonProps) {
  const { address, isConnected } = useAccount()
  const { data: walletClient } = useWalletClient()
  const config = useConfig()
  
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [product, setProduct] = useState<BlindMintProduct | null>(null)
  const [preparedPurchase, setPreparedPurchase] = useState<PreparedPurchase | null>(null)
  const [isModalOpen, setIsModalOpen] = useState(false)
  const [currentStepIndex, setCurrentStepIndex] = useState(0)
  const [stepStatuses, setStepStatuses] = useState<string[]>([])
  const [purchaseComplete, setPurchaseComplete] = useState(false)

  const handlePreparePurchase = async () => {
    if (!address || !walletClient) {
      setError('Please connect your wallet first')
      return
    }

    setIsLoading(true)
    setError(null)
    setPurchaseComplete(false)

    try {
      // Create client with Wagmi public provider
      const publicProvider = createPublicProviderWagmi({ config });
      const client = createClient({ publicProvider });

      const fetchedProduct = await client.getProduct(instanceId) as BlindMintProduct
      setProduct(fetchedProduct)

      const status = await fetchedProduct.getStatus()
      if (status !== 'active') {
        throw new Error(`Product is ${status}`)
      }

      const allocations = await fetchedProduct.getAllocations({ recipientAddress: address })
      if (!allocations.isEligible) {
        throw new Error(allocations.reason || 'Not eligible to mint')
      }

      const prepared = await fetchedProduct.preparePurchase({
        address,
        payload: { quantity }
      })

      setPreparedPurchase(prepared)
      setStepStatuses(new Array(prepared.steps.length).fill('idle'))
      setCurrentStepIndex(0)
      setIsModalOpen(true)
    } catch (err) {
      console.error('Error preparing purchase:', err)
      setError(err instanceof Error ? err.message : 'Failed to prepare purchase')
    } finally {
      setIsLoading(false)
    }
  }

  const handleExecuteStep = async (stepIndex: number) => {
    if (!walletClient || !preparedPurchase || !product) {
      setError('Missing required data')
      return
    }

    const step = preparedPurchase.steps[stepIndex]
    if (!step) {
      setError('Invalid step index')
      return
    }

    setStepStatuses(prev => {
      const newStatuses = [...prev]
      newStatuses[stepIndex] = 'executing'
      return newStatuses
    })

    try {
      const account = createAccountViem({
        walletClient: walletClient as any
      })

      const result = await step.execute(account)

      setStepStatuses(prev => {
        const newStatuses = [...prev]
        newStatuses[stepIndex] = 'completed'
        return newStatuses
      })

      if (stepIndex < preparedPurchase.steps.length - 1) {
        setCurrentStepIndex(stepIndex + 1)
      } else {
        setPurchaseComplete(true)
        setTimeout(() => {
          setIsModalOpen(false)
          setCurrentStepIndex(0)
          setStepStatuses([])
        }, 2000)
      }

      console.log(`Step ${stepIndex + 1} completed:`, result)
    } catch (err) {
      console.error(`Error executing step ${stepIndex + 1}:`, err)
      setStepStatuses(prev => {
        const newStatuses = [...prev]
        newStatuses[stepIndex] = 'failed'
        return newStatuses
      })
      setError(err instanceof Error ? err.message : 'Transaction failed')
    }
  }

  const handleCloseModal = () => {
    if (!stepStatuses.some(status => status === 'executing')) {
      setIsModalOpen(false)
      setPreparedPurchase(null)
      setCurrentStepIndex(0)
      setStepStatuses([])
    }
  }

  return (
    <>
      <div className="flex flex-col items-center gap-4">
        {!isConnected ? (
          <p className="text-gray-600">Please connect your wallet to mint</p>
        ) : (
          <button
            onClick={handlePreparePurchase}
            disabled={isLoading}
            className="px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
          >
            {isLoading ? 'Preparing...' : `Mint ${quantity} Blind Box${quantity > 1 ? 'es' : ''}`}
          </button>
        )}

        {error && (
          <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
            <p className="text-red-800 text-sm">{error}</p>
          </div>
        )}

        {purchaseComplete && (
          <div className="p-4 bg-green-50 border border-green-200 rounded-lg">
            <p className="text-green-800 text-sm font-semibold">Purchase completed successfully!</p>
          </div>
        )}
      </div>

      {preparedPurchase && (
        <StepModal
          isOpen={isModalOpen}
          onClose={handleCloseModal}
          steps={preparedPurchase.steps}
          onExecuteStep={handleExecuteStep}
          currentStepIndex={currentStepIndex}
          stepStatuses={stepStatuses}
          formattedCost={preparedPurchase.cost.total.native.formatted}
        />
      )}
    </>
  )
}
```

Best practices:

* Validate the product type using [isBlindMintProduct](/client-sdk/sdk/product/blind-mint/isblindmintproduct) or [isEditionProduct](/client-sdk/sdk/product/edition-product/iseditionproduct) to ensure proper TypeScript typings.
* Run [getStatus](/client-sdk/sdk/product/common/getstatus) before attempting a purchase to verify the product is available.
* Handle [ClientSDKError](/client-sdk/reference/clientsdkerror) codes for scenarios like ineligibility, sold-out items, or insufficient funds.
* See each method’s documentation for detailed error descriptions.


# Creating a minting bot

The SDK can be used on the server side, enabling use cases such as running a [minting bot](https://help.manifold.xyz/en/articles/11509060-bankrbot)

## Example Scripts

Two ready-to-run bots live in the repository:

* **Edition**: [examples/edition/minting-bot](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/edition/minting-bot)
* **Blind Mint**: [examples/blindmint/minting-bot](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/blindmint/minting-bot)

Each script demonstrates the most direct path to minting—`preparePurchase` followed by `product.purchase()`—so you don’t have to orchestrate transaction steps manually.

### Running an example

1. From the repository root:

   ```bash
   pnpm install
   pnpm build
   ```
2. Inside the example directory:

   ```bash
   pnpm install
   cp .env.example .env
   pnpm start
   ```
3. Fill in the environment variables before running.

## Basic Example

```ts
import {
  createClient,
  createAccountEthers5,
  createPublicProviderEthers5,
  isBlindMintProduct,
  isEditionProduct,
} from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

// Setup provider for the network
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL!);
const networkId = Number(process.env.NETWORK_ID!); // e.g., 1 for mainnet, 8453 for Base

// Create public provider for the client
const publicProvider = createPublicProviderEthers5({
  [networkId]: provider
});

// Initialize the client
const client = createClient({ publicProvider });

const product = await client.getProduct('INSTANCE_ID');

// Check product status first
const productStatus = await product.getStatus();
if (productStatus !== 'active') {
  throw new Error(`Product is ${productStatus}`);
}

// Handle different product types
if (!isEditionProduct(product)) {
   throw new Error('Unsupported product type');
}

// Setup wallet for signing transactions
const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider);
const account = createAccountEthers5({ wallet });

try {
  const prepared = await product.preparePurchase({
    recipientAddress: wallet.address,
    payload: { quantity: 1 },
  });

  const order = await product.purchase({
    account,
    preparedPurchase: prepared,
  });
  console.log(
    order.status,
    order.receipts.map((r) => r.txHash),
  );
} catch (error) {
  console.log(`Unable to execute transaction: ${(error as Error).message}`);
}
```

## Best Practices

* **Type Validation**: Use [isBlindMintProduct](/client-sdk/sdk/product/blind-mint/isblindmintproduct) or [isEditionProduct](/client-sdk/sdk/product/edition-product/iseditionproduct) for proper TypeScript typings
* **Status Checks**: Always run [getStatus](/client-sdk/sdk/product/common/getstatus) before attempting purchases
* **Error Handling**: Properly handle [ClientSDKError](/client-sdk/reference/clientsdkerror) codes
* **Gas Management**: Monitor gas prices and set appropriate limits
* **Retry Logic**: Implement retry mechanisms for transient failures
* **Security**: Never commit private keys, use environment variables

## Environment Configuration

```env
# Required
WALLET_PRIVATE_KEY=your_private_key_here
INSTANCE_ID=your_product_instance_id
NETWORK_ID=8453
RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY

# RPC Endpoints

# Optional
MINT_QUANTITY=1
```

## Resources

* [**Edition Minting Bot**](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/edition/minting-bot) - Minimal Edition minting script
* [**Blind Minting Bot**](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples/blindmint/minting-bot) - Minimal Blind Mint minting script
* [**Examples Overview**](https://github.com/manifoldxyz/client-sdk/tree/main/packages/examples) - Directory of all SDK examples
* See method documentation for detailed error descriptions


# FAQ

<details>

<summary><strong>Which networks are supported?</strong> </summary>

Ethereum (1), Base (8453), Optimism (10), Shape (360), Sepolia testnet.

</details>

<details>

<summary><strong>How do I check allowlist eligibility?</strong> </summary>

Call `product.`[`getAllocations`](/client-sdk/sdk/product/common/getallocations)`({ recipientAddress })`.

</details>

<details>

<summary><strong>What happens if a step fails?</strong> </summary>

Catch the error, show context to users, and allow retries. Transaction steps expose [`execute`](/client-sdk/sdk/transaction-steps/execute)`()`.

</details>

<details>

<summary><strong>Where do I file issues?</strong> </summary>

[GitHub Issues](https://github.com/manifoldxyz/client-sdk) or Manifold support.

</details>


# \[for AI agents and LLMS] Checklist and guidelines

### Purpose

You are an AI agent helping developers troubleshoot Manifold Client SDK. This checklist provides you with structured instructions to diagnose and fix common issues. Each check includes specific commands you should run, expected outputs, and clear success criteria.

### General Guidelines

As an AI agent, follow these guidelines here:

* **Ask clarifying questions** until you have high confidence in the task. Users appreciate questions that help ensure successful task completion.
* **Be specific** when something is unclear or inaccessible. Ask for file paths, URLs, or specific error messages.
* **Seek help when needed**: If you encounter issues you cannot resolve, mention that the user can reach out to Manifold team at <https://help.manifold.xyz/en/> for additional support.
* **Verify assumptions** before making changes. It's better to confirm than to proceed with uncertainty.

### Common LLM Pitfalls

When helping developers with Manifold Client SDK:

* When executing onchain transactions, use the [`execute`](/client-sdk/sdk/transaction-steps/execute) function from each step returned by [`preparePurchase`](/client-sdk/sdk/product/blind-mint/preparepurchase), or use the [`purchase`](/client-sdk/sdk/product/common/purchase) function from the [product](/client-sdk/sdk/product).
* **ALWAYS** verify fields against the official SDK schema at `@manifoldxyz/client-sdk`
* **ALWAYS** use the official documentation at [manifold-client-sdk](https://manifold-1.gitbook.io/manifold-client-sdk)

If using example code, ensure it's from:

* Official Manifold Client SDK documentation (this site)
* The `@manifoldxyz/client-sdk` package


# Release Notes

### March 5, 2026 (1.0.0)

* **Breaking Change**: Simplified `Media` interface — removed `imagePreview` and `animationPreview` fields
* **Change**: `image` now returns the optimized preview (`image_preview || thumbnail`), falling back to the original image
* **Change**: `animation` now returns the optimized preview (`animation_preview`), falling back to the original animation
* **New**: Added `originalImage` and `originalAnimation` fields to `Media` for access to full-resolution originals

### November 18, 2025 (0.5.0-beta.0)

* **New**: Added `subscribeToContractEvents` method to `IPublicProvider` for subscribing to contract events
* **New**: Implemented `subscribeToContractEvents` in all provider adapters (Viem, Ethers5, Wagmi)
* **Feature**: Support for real-time event monitoring with topic filtering and callback handlers

### November 5, 2025 (0.3.1-beta.0)

* **Breaking Change**: Removed `httpRPCs` dependency from SDK client initialization
* **New**: Added public provider abstraction for blockchain interactions
* **New**: Added `createPublicProviderViem` and `createPublicProviderEthers5` functions
* **New**: Added `createPublicProviderWagmi` function for Wagmi integration
* **New**: Added fallback provider support for automatic failover on provider errors or network mismatches
* **Change**: `createClient` now requires a `publicProvider` parameter
* **Change**: Account adapters no longer require the client instance
* **Improvement**: Cleaner separation between read-only and transaction operations
* **Improvement**: Better multi-network support through provider abstraction
* **Improvement**: Enhanced reliability with automatic fallback to backup providers
* **Improvement**: Native Wagmi support for seamless React integration

### October 31, 2025 (0.2.1-beta.4)

* Added support for [Edition product](/client-sdk/sdk/product/edition-product)

### October 21, 2025 (0.1.0-beta.1)

* Added support for [BlindMint product](/client-sdk/sdk/product/blind-mint)


# Manifold Client

### Client Creation

**createClient(config)** → ManifoldClient

Creates a new SDK client instance.

#### Parameters

| Parameter         | Type            | Required | Description                           |
| ----------------- | --------------- | -------- | ------------------------------------- |
| config            | object          | ✅        | Configuration object                  |
| └─ publicProvider | IPublicProvider | ✅        | Provider for blockchain interactions  |
| └─ debug          | boolean         | ❌        | Enable debug logging (default: false) |

#### Returns: ManifoldClient

| Property                                                 | Type     | Description   |
| -------------------------------------------------------- | -------- | ------------- |
| [getProduct](/client-sdk/sdk/manifold-client/getproduct) | function | Get a product |

#### Example with Wagmi

```typescript
import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { createConfig, http } from '@wagmi/core';
import { mainnet, base } from '@wagmi/core/chains';

// Create Wagmi config with multiple chains
const config = createConfig({
  chains: [mainnet, base],
  transports: {
    [mainnet.id]: http('YOUR_MAINNET_RPC_URL'),
    [base.id]: http('YOUR_BASE_RPC_URL'),
  },
});

// Create the public provider
const publicProvider = createPublicProviderWagmi({ config });

// Initialize the Manifold client
const client = createClient({ publicProvider });
```

#### Example with Viem

```typescript
import { createClient, createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// Create public clients for each network you want to support
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('YOUR_RPC_URL')
});

// Create the public provider
const publicProvider = createPublicProviderViem({ 
  1: publicClient // mainnet
});

// Initialize the Manifold client
const client = createClient({ publicProvider });
```

#### Example with Ethers v5

```typescript
import { createClient, createPublicProviderEthers5 } from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

// Create ethers providers for each network
const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_URL');

// Create the public provider
const publicProvider = createPublicProviderEthers5({ 
  1: provider // mainnet
});

// Initialize the Manifold client
const client = createClient({ publicProvider });
```


# getProduct

**getProduct(instanceId | url)** → [Product](/client-sdk/sdk/product)

Fetches detailed product information.

#### Parameters

| Parameter         | Type   | Required | Description                          |
| ----------------- | ------ | -------- | ------------------------------------ |
| instanceId \| url | string | ✅        | The instanceId or url of the product |

#### Returns: [Product](/client-sdk/sdk/product)

#### Example

<pre class="language-typescript"><code class="lang-typescript">import { isBlindMintProduct, createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { createConfig, http } from '@wagmi/core';
import { mainnet } from '@wagmi/core/chains';

// Setup Wagmi config
const config = createConfig({
  chains: [mainnet],
  transports: {
    [mainnet.id]: http('YOUR_RPC_URL')
  }
});

// Create public provider
const publicProvider = createPublicProviderWagmi({ config });

// Create client
const client = createClient({ publicProvider });

<strong>const product = await client.getProduct('4150231280');
</strong>console.log(`AppType: ${product.type}`);
</code></pre>

[**ClientSDKError**](/client-sdk/reference/clientsdkerror)

| Code              | Message                  |
| ----------------- | ------------------------ |
| NOT\_FOUND        | product not found        |
| UNSUPPORTED\_TYPE | Unsupported product type |


# Product

Calling [getProduct](/client-sdk/sdk/manifold-client/getproduct) returns a product object with a consistent structure across all Manifold app types.

| Field       | Type                                               | Required | Description                        |
| ----------- | -------------------------------------------------- | -------- | ---------------------------------- |
| **id**      | string                                             | ✅        | Instance ID                        |
| type        | [AppType](/client-sdk/reference/apptype)           | ✅        | Type of the product                |
| data        | [InstanceData](/client-sdk/reference/instancedata) | ✅        | Product offchain data              |
| previewData | [PreviewData](/client-sdk/reference/previewdata)   | ✅        | Return preview data of the product |

Product instances are created based on their specific type (**Edition** or **Blind Mint**). Each specialization adds additional methods and type guards while preserving the shared core API.


# Common

The following methods apply to both [Edition](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/sdk/product/product-types/edition-product) and [Blind Mint](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/sdk/product/product-types/blind-mint) product

* purchase
* getStatus
* getAllocations
* getInventory
* getRules
* getProvenance


# purchase

purchase

**purchase(params)** → [Receipt](/client-sdk/reference/receipt)

Initiates a purchase for the specified product.

This method may trigger multiple write transactions (e.g., token approval and minting).

#### Parameters

| Parameter        | Type                                                       | Required | Description                                                                                                               |
| ---------------- | ---------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| account          | [Account](/client-sdk/reference/account)                   | ✅        | Buyer’s account                                                                                                           |
| preparedPurchase | [PreparedPurchase](/client-sdk/reference/preparedpurchase) | ✅        | Prepared transaction object returned from [preparePurchase](/client-sdk/sdk/product/edition-product/preparepurchase) call |
| confirmations    | number                                                     | ❌        | Number of confirmation blocks (Default 1)                                                                                 |

#### Returns: [Receipt](/client-sdk/reference/receipt)

The receipt contains the confirmed transaction metadata under `transactionReceipt` and an `order` object with parsed token details for the mint that was executed.

#### Example

```tsx
const receipt = await product.purchase({
  account,
  preparedPurchase,
});

console.log('Mint transaction:', receipt.transactionReceipt.txHash);

if (receipt.order) {
  console.log('Recipient:', receipt.order.recipientAddress);
  for (const item of receipt.order.items) {
    console.log(`Minted token ${item.token.tokenId} x${item.quantity}`);
  }
}
```

[**Errors**](https://www.notion.so/Manifold-Client-SDK-Complete-Developer-Guide-2676b055ee58800abc38ccd30cdfca70?pvs=21)

<table><thead><tr><th width="234.6640625">Code</th><th width="175.30859375">Message</th><th width="142.54296875">data</th><th>metadata</th></tr></thead><tbody><tr><td>TRANSACTION_FAILED</td><td>transaction failed</td><td><a href="https://docs.ethers.org/v5/api/utils/logger/#errors--call-exception">CallExceptions</a></td><td>{ receipts : <a href="/pages/bS5RqVOFBk5bJU9QVdcq">Receipt</a>[]} (For completed steps)</td></tr><tr><td>TRANSACTION_REVERTED</td><td>transaction reverted</td><td><a href="https://docs.ethers.org/v5/api/utils/logger/#errors--call-exception">CallExceptions</a></td><td>{ receipts : <a href="/pages/bS5RqVOFBk5bJU9QVdcq">Receipt</a>[]} (For completed steps)</td></tr><tr><td>TRANSACTION_REJECTED</td><td>user rejected transaction</td><td></td><td>{ receipts : <a href="/pages/bS5RqVOFBk5bJU9QVdcq">Receipt</a>[]} (For completed steps)</td></tr><tr><td>INSUFFIENT_FUNDS</td><td>wallet does not have sufficient funds for purchase</td><td></td><td>{ receipts : <a href="/pages/bS5RqVOFBk5bJU9QVdcq">Receipt</a>[]} (For completed steps)</td></tr><tr><td>LEDGER_ERROR</td><td>error with ledger wallet, make sure blind signing is on</td><td></td><td>{ receipts : <a href="/pages/bS5RqVOFBk5bJU9QVdcq">Receipt</a>[]} (For completed steps)</td></tr></tbody></table>


# getStatus

**getStatus()** → StatusResponse

Retrieves the current status of the product.

#### Returns:&#x20;

```typescript
'active' | 'upcoming' | 'sold-out' | 'ended'
```

* **active**: The product is currently active and available for purchase.
* **upcoming**: The product sale has not started yet.
* **sold-out**: The product is sold out.
* **ended**: The product sale has ended.

#### Example

```jsx
const status = await product.getStatus();
console.log(`Current product status ${status}`)
```


# getAllocations

**getAllocations(params)** → AllocationResponse

Retrieves the allocation quantity for a given wallet address.

#### Parameters

| Parameter            | Type   | Required | Description    |
| -------------------- | ------ | -------- | -------------- |
| **recipientAddress** | string | ✅        | Buyer’s wallet |

#### Returns: AllocationResponse

| Field      | Type    | Required | Description                                                      |
| ---------- | ------- | -------- | ---------------------------------------------------------------- |
| isEligible | boolean | ✅        | Can purchase?                                                    |
| reason     | string  | ❌        | Why not eligible                                                 |
| quantity   | number  | ✅        | Quantity eligible. A value of `-1` indicates unlimited quantity. |

#### Example

```jsx
const allocations = await product.getAllocations**({
  recipientAddress: '0x742d35Cc...'
  });
if (!allocations.isEligible) {
  console.log('Cannot mint:', allocations.reason);  
  return;
}
console.log('Total alloted:', allocations.quantity);
```

[**Errors**](https://www.notion.so/Manifold-Client-SDK-Complete-Developer-Guide-2676b055ee58800abc38ccd30cdfca70?pvs=21)

| Code           | Message                     |
| -------------- | --------------------------- |
| INVALID\_INPUT | `invalid recipient address` |

*


# getInventory

**getInventory()** → [ProductInventory](/client-sdk/reference/productinventory)

Retrieves the product’s total supply and total number of purchases.

#### Returns: [ProductInventory](/client-sdk/reference/productinventory)

| Field          | Type   | Required | Description                                                        |
| -------------- | ------ | -------- | ------------------------------------------------------------------ |
| totalSupply    | number | ✅        | Total product supply.  A value of `-1` indicates unlimited supply. |
| totalPurchased | number | ✅        | Total product purchased                                            |


# getRules

**getRules()** → ProductRule

Retrieves the product rules, such as start and end dates, maximum tokens per wallet, audience restrictions, and more.

#### Returns: ProductRule

| Field               | Type   | Required | Description                                        |
| ------------------- | ------ | -------- | -------------------------------------------------- |
| startDate           | Date   | ❌        | Start date (if not provided, start immediately)    |
| endDate             | Date   | ❌        | End date (if not provided, the product never ends) |
| audienceRestriction | enum   | ✅        | allowlist \| none                                  |
| maxPerWallet        | number | ❌        | Number of allowed purchased per wallet             |

**AudienceRestriction**

* `allowlist`: The product is restricted to specific wallet addresses.
* `none`: The product has no audience restrictions.


# getProvenance

**getProvenance()** → [ProductProvenance](/client-sdk/reference/productprovenance)

Retrieves provenance information for the product, such as the related contract address, token ID, creator details, and more.

#### Returns: [ProductProvenance](/client-sdk/reference/productprovenance)

| Field     | Type                                       | Required | Description                                  |
| --------- | ------------------------------------------ | -------- | -------------------------------------------- |
| creator   | [Creator](/client-sdk/reference/creator)   | ✅        | Information about the creator of the product |
| contract  | [Contract](/client-sdk/reference/contract) | ❌        | Information about the contract the product   |
| token     | [Token](/client-sdk/reference/token)       | ❌        | Information about the token of the product   |
| networkId | number                                     | ❌        | Network ID of the product                    |


# Edition Product

Follow [this guide](https://help.manifold.xyz/en/articles/9387344-create-an-edition-open-or-limited) to create an Edition product.

This data is returned when calling the [getProduct](/client-sdk/sdk/manifold-client/getproduct) method.

#### Handling different configurations

An Edition product can be created or updated with various configurations:

* **Price:** Can be set in ETH or any ERC-20 token.
* **Total Supply:** Can be unlimited or limited.
* **Supply per Wallet:** The number of tokens a single wallet can mint.
* **Start/End Date:** Defines the timeline for the drop.
* **Audience:**
  * **anyone:** Anyone can purchase.
  * **allowlist:** Only a predefined list of wallet addresses can purchase.

The SDK provides convenient methods to handle these configurations:

[preparePurchase](/client-sdk/sdk/product/edition-product/preparepurchase)

Performs all necessary checks to ensure the purchase is valid and throws appropriate errors if any validation fails.

**Examples of Thrown Errors:**

* `ErrorCode.NOT_STARTED` — The product start date is in the future.
* `ErrorCode.ENDED` — The product end date has passed.
* `ErrorCode.SOLD_OUT` — The product is sold out (based on total supply).
* `ErrorCode.NOT_ELIGIBLE` — The recipient is not on the allowlist.
* `ErrorCode.INVALID_INPUT` — The desired purchase quantity exceeds the per-wallet limit or total supply.
* `ErrorCode.INSUFFICIENT_FUNDS` — The account does not have enough ETH or ERC-20 tokens for the purchase.

[getStatus](/client-sdk/sdk/product/common/getstatus)

Useful for displaying the current product status (e.g., `active`, `upcoming`, `sold-out`, `ended`).

[getAllocations](/client-sdk/sdk/product/common/getallocations)

Useful for showing the total quantity an [account](/client-sdk/reference/account) is eligible to purchase.


# preparePurchase

**preparePurchase(params)** → [PreparedPurchase](/client-sdk/reference/preparedpurchase)

Simulates a purchase to check eligibility and calculate the total cost.

#### Parameters

<table><thead><tr><th width="182.84375">Parameter</th><th width="181.56640625">Type</th><th width="97.63671875">Required</th><th>Description</th></tr></thead><tbody><tr><td>userAddress</td><td>string</td><td>✅</td><td>The address making the purchase</td></tr><tr><td>recipientAddress</td><td>string</td><td>❌</td><td>If different than <code>address</code></td></tr><tr><td>networkId</td><td>number</td><td>❌</td><td>If specify, forced transaction on the network (handle funds bridging automatically), assume product network otherwise</td></tr><tr><td>payload</td><td>{quantity: number}</td><td>✅</td><td>Specific to Edition Products. Specify quantity of purchase</td></tr><tr><td><strong>gasBuffer</strong></td><td>object</td><td>❌</td><td>How much additional gas to spend on the purchase</td></tr><tr><td>gasBuffer.fixed</td><td>BigInt</td><td>❌</td><td>Fixed gas buffer amount</td></tr><tr><td>gasBuffer.multiplier</td><td>number</td><td>❌</td><td><p>Gas buffer by multiplier.</p><p>The multiplier represents a percentage (as a number out of 100). For example:</p><ul><li>multiplier: 120 means 120% of the original estimate (20% increase)</li><li>multiplier: 150 means 150% of the original estimate (50% increase)</li></ul></td></tr><tr><td>account</td><td><a href="/pages/DkZvF2erej3765bqfPHH">Account</a></td><td>❌</td><td>If provided, it will perform balance checks on the specified account; otherwise, it will skip balance checks.</td></tr></tbody></table>

#### Returns: [PreparedPurchase](/client-sdk/reference/preparedpurchase)

> A purchase can involve more than one transaction.\
> For example, minting and paying with ERC-20 tokens requires an approval transaction followed by a mint transaction.\
> If you’re building your own front end with the SDK, you may want users to trigger these transactions explicitly (e.g., by clicking separate buttons).
>
> [PreparedPurchase](/client-sdk/reference/preparedpurchase) returns a list of steps for this purpose.\
> Each step represents an on-chain transaction that can be executed by calling [step.execute](/client-sdk/sdk/transaction-steps/execute)().\
> Each `execute` call performs the necessary on-chain checks to determine whether the transaction is still required; if it isn’t, the step is skipped. [Click here to learn more](/client-sdk/sdk/transaction-steps)

#### Example

<pre class="language-jsx"><code class="lang-jsx">import { createClient, createPublicProviderViem, type AppType } from '@manifoldxyz/client-sdk'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
})
const publicProvider = createPublicProviderViem({ 1: publicClient })
const client = createClient({ publicProvider });

const product = await client.getProduct('12311232')
if (product.type !== AppType.Edition) {
	throw new Error(`Unsupported app type`)
}
try {
<strong>  const preparedPurchase = await product.preparePurchase&#x3C;EditionPayload>({
</strong>    userAddress: '0x....', // the connected wallet
    payload: {
	  quantity: 1
    },
    gasBuffer: {
     multiplier: 0.25 // 25% gas buffer
    }
<strong>  });
</strong>} catch (error: ClientSDKError) {
  console.log(`Error: ${error.message}`)
  return
}

console.log('Total cost:', preparedPurchase.cost.total.formatted);
</code></pre>

[**Errors**](https://www.notion.so/Manifold-Client-SDK-Complete-Developer-Guide-2676b055ee58800abc38ccd30cdfca70?pvs=21)

<table><thead><tr><th>Code</th><th width="338.7421875">Message</th><th>data</th></tr></thead><tbody><tr><td>INVALID_INPUT</td><td><code>invalid input</code></td><td></td></tr><tr><td>UNSUPPORTED_NETWORK</td><td><code>unsupported networkId ${networkId}</code></td><td></td></tr><tr><td>NOT_ELIGIBLE</td><td><code>wallet not eligible to purchase product</code></td><td>Eligibility</td></tr><tr><td>SOLD_OUT</td><td><code>product sold out</code></td><td></td></tr><tr><td>LIMIT_REACHED</td><td><code>you've reached your purchase limit</code></td><td></td></tr><tr><td>ENDED</td><td><code>ended</code></td><td></td></tr><tr><td>NOT_STARTED</td><td><code>not started, come back later</code></td><td></td></tr><tr><td>ESTIMATION_FAILED</td><td><code>transaction estimation failed</code></td><td><a href="https://docs.ethers.org/v5/api/utils/logger/#errors--call-exception">CallExceptions</a></td></tr></tbody></table>

Eligibiliy

| Field        | Type                                    | Description                                                                 |
| ------------ | --------------------------------------- | --------------------------------------------------------------------------- |
| reason       | string                                  | Why not eligible                                                            |
| missingFunds | [Money](/client-sdk/reference/money)\[] | Missing required funds to purchase (Could be in native ETH or ERC20 tokens) |


# fetchOnchainData

**fetchOnchainData()** → [EditionOnchainData](/client-sdk/reference/editiononchaindata)

Get on-chain data and assign `onchainData` properties for the product object.

#### Example

```tsx
const product = await sdk.getProduct('31231232')
await product.fetchOnchainData()
console.log(`cost: ${product.onchainData.cost.formatted}`)
```


# isEditionProduct

isEditionProduc&#x74;**()** → boolean

Validate whether a [product](/client-sdk/sdk/product) is an [Edition product](/client-sdk/reference/editionproduct)

Provides additional TypeScript typing support by narrowing the product type to [EditionProduct](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/references/editionproduct)

#### Example

```tsx
const product = await sdk.getProduct('31231232')
if (!isEditionProduct(product)) {
  throw new Error('Is not an edition instance')
}
// product is now EditionProduct
```


# Blind Mint

Blind Mints offer randomized, gacha-style minting.

Follow [this guide](https://help.manifold.xyz/en/articles/9449681-serendipity) to create a Blind Mint product.

This data is returned when calling the [getProduct](/client-sdk/sdk/manifold-client/getproduct) method

#### Handling different configurations

A Blind Mint product can be created or updated with various configurations:

* **Price:** Can be set in ETH.
* **Total Supply:** Can be unlimited or limited.
* **Start/End Date:** Defines the timeline for the drop.
* **Audience:**
  * **anyone:** Anyone can purchase.

The SDK provides convenient methods to handle these configurations:

[preparePurchase](/client-sdk/sdk/product/edition-product/preparepurchase)

Performs all necessary checks to ensure the purchase is valid and throws appropriate errors if any validation fails.

**Examples of Thrown Errors:**

* `ErrorCode.NOT_STARTED` — The product start date is in the future.
* `ErrorCode.ENDED` — The product end date has passed.
* `ErrorCode.SOLD_OUT` — The product is sold out (based on total supply).
* `ErrorCode.INSUFFICIENT_FUNDS` — The account does not have enough ETH for the purchase.

[getStatus](/client-sdk/sdk/product/common/getstatus)

Useful for displaying the current product status (e.g., `active`, `upcoming`, `sold-out`, `ended`).

[getAllocations](/client-sdk/sdk/product/common/getallocations)

Useful for showing the total quantity an [account](/client-sdk/reference/account) is eligible to purchase.


# preparePurchase

**preparePurchase(params)** → [PreparedPurchase](/client-sdk/reference/preparedpurchase)

Simulates purchase to check eligibility and get total cost.

#### Parameters

<table><thead><tr><th width="181.0078125">Parameter</th><th width="168.01171875">Type</th><th width="107.44140625">Required</th><th>Description</th></tr></thead><tbody><tr><td>userAddress</td><td>string</td><td>✅</td><td>The address making the purchase</td></tr><tr><td>recipientAddress</td><td>string</td><td>❌</td><td>If different than <code>address</code></td></tr><tr><td>networkId</td><td>number</td><td>❌</td><td>If specify, forced transaction on the network (handle funds bridging automatically), assume product network otherwise</td></tr><tr><td>payload</td><td>{quantity: number}</td><td>✅</td><td>Specific to Blind Mint Products. Specify quantity of purchase</td></tr><tr><td><strong>gasBuffer</strong></td><td>object</td><td>❌</td><td>How much additional gas to spend on the purchase</td></tr><tr><td>gasBuffer.fixed</td><td>BigInt</td><td>❌</td><td>Fixed gas buffer amount</td></tr><tr><td>gasBuffer.multiplier</td><td>number</td><td>❌</td><td><p>Gas buffer by multiplier.</p><p>The multiplier represents a percentage (as a number out of 100). For example:</p><ul><li>multiplier: 120 means 120% of the original estimate (20% increase)</li><li>multiplier: 150 means 150% of the original estimate (50% increase)</li></ul></td></tr><tr><td>account</td><td><a href="/pages/DkZvF2erej3765bqfPHH">Account</a></td><td>❌</td><td>If provided, it will perform balance checks on the specified account; otherwise, it will skip balance checks.</td></tr></tbody></table>

#### Returns: [PreparedPurchase](https://www.notion.so/Manifold-Client-SDK-Complete-Developer-Guide-2676b055ee58800abc38ccd30cdfca70?pvs=21)

#### Example

```jsx
import { createClient, createPublicProviderViem, isBlindMintProduct } from '@manifoldxyz/client-sdk'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
})
const publicProvider = createPublicProviderViem({ 1: publicClient })
const client = createClient({ publicProvider });

const product = await client.getProduct('12311232')
if (!isBlindMintProduct(product)) {
  throw new Error(`Unsupported app type`)
}
try {
  const preparedPurchase = await product.preparePurchase({
    userAddress: '0x....', // the connected wallet
    payload: {
      quantity: 1
    },
    gasBuffer: {
     multiplier: 0.25 // 25% gas buffer
    }
  });
} catch (error: ClientSDKError) {
  console.log(`Error: ${error.message}`)
  return
}

console.log('Total cost:', preparedPurchase.cost.total.formatted);
```

[**Errors**](https://www.notion.so/Manifold-Client-SDK-Complete-Developer-Guide-2676b055ee58800abc38ccd30cdfca70?pvs=21)

| Code                 | Message                              | data                                                                                  |
| -------------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
| INVALID\_INPUT       | `invalid input`                      |                                                                                       |
| UNSUPPORTED\_NETWORK | `unsupported networkId ${networkId}` |                                                                                       |
| SOLD\_OUT            | `product sold out`                   |                                                                                       |
| LIMIT\_REACHED       | `you've reached your purchase limit` |                                                                                       |
| ENDED                | `ended`                              |                                                                                       |
| NOT\_STARTED         | `not started, come back later`       |                                                                                       |
| ESTIMATION\_FAILED   | `transaction estimation failed`      | [CallExceptions](https://docs.ethers.org/v5/api/utils/logger/#errors--call-exception) |


# fetchOnchainData

**fetchOnchainData()** → [BlindMintOnchainData](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/references/blindmintonchaindata)

Retrieves on-chain data and assigns the `onchainData` properties to the [product](/client-sdk/sdk/product) object.

#### Example

```tsx
const product = await sdk.getProduct('31231232')
await product.fetchOnchainData()
console.log(`cost: ${product.onchainData.cost.formatted}`)
```


# isBlindMintProduct

isBlindMintProduc&#x74;**()** → boolean

Validate whether a [product](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/sdk/product) is a [Blind Mint product](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/sdk/product/product-types/blind-mint)

Provides additional TypeScript typing support by narrowing the product type to [BlindMintProduct](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/wX9Yl8DLygpenDBVWGPF/~/changes/1/references/blindmintproduct)

#### Example

```tsx
const product = await sdk.getProduct('31231232')
if (!isBlindMintProduct(product)) {
  throw new Error('Is not a blind mint instance')
}
// product is now BlindMintProduct
```


# Public Provider Adapters

Public providers enable the SDK to perform read-only blockchain operations such as fetching balances, estimating gas, reading smart contract data, and subscribing to contract events. They are required when initializing the Manifold Client.

## Available Adapters

* [Wagmi Public Provider](/client-sdk/sdk/public-provider-adapters/wagmi)
* [Viem Public Provider](/client-sdk/sdk/public-provider-adapters/viem)
* [Ethers v5 Public Provider](/client-sdk/sdk/public-provider-adapters/ethersv5)

## Multi-Network Support

The SDK supports multiple networks simultaneously. Provide a public client/provider for each network you want to support:

```typescript
const publicProvider = createPublicProviderViem({
  1: mainnetClient,       // Ethereum Mainnet
  8453: baseClient,       // Base
  10: optimismClient,     // Optimism
  360: shapeClient,       // Shape
  11155111: sepoliaClient // Sepolia Testnet
});
```

## Network Requirements

The public provider must include a client/provider for the network where the NFT product is deployed. The SDK will automatically use the appropriate provider based on the product's network.

[**ClientSDKError**](/client-sdk/reference/clientsdkerror)

| Code                    | Message                                |
| ----------------------- | -------------------------------------- |
| INVALID\_INPUT          | Public provider is required            |
| NETWORK\_NOT\_SUPPORTED | No provider available for network      |
| WRONG\_NETWORK          | Provider is connected to wrong network |


# Wagmi

**createPublicProviderWagmi(params)** → IPublicProvider

Creates a public provider from a Wagmi `Config`. This adapter lets the SDK reuse the public clients that Wagmi configures for read-only blockchain operations such as fetching balances, estimating gas, and reading contracts.

## Parameters

| Parameter | Type                                             | Required | Description                             |
| --------- | ------------------------------------------------ | -------- | --------------------------------------- |
| config    | [Config](https://wagmi.sh/core/api/createConfig) | ✅        | Wagmi config with chains and transports |

## Examples

### Basic usage

```typescript
import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { createConfig, http } from '@wagmi/core';
import { mainnet, base } from '@wagmi/core/chains';

// Configure Wagmi with the networks you intend to support
const config = createConfig({
  chains: [mainnet, base],
  transports: {
    [mainnet.id]: http('YOUR_MAINNET_RPC_URL'),
    [base.id]: http('YOUR_BASE_RPC_URL'),
  },
});

// Create the Manifold public provider
const publicProvider = createPublicProviderWagmi({ config });

// Pass into the Manifold client
const client = createClient({ publicProvider });
```

### Multi-network with fallback transports

```typescript
import { createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { createConfig } from '@wagmi/core';
import { fallback, http } from 'viem';
import { mainnet, optimism } from '@wagmi/core/chains';

const config = createConfig({
  chains: [mainnet, optimism],
  transports: {
    [mainnet.id]: fallback([
      http('PRIMARY_MAINNET_RPC_URL'),
      http('BACKUP_MAINNET_RPC_URL'),
    ]),
    [optimism.id]: http('YOUR_OPTIMISM_RPC_URL'),
  },
});

const publicProvider = createPublicProviderWagmi({ config });
```

### Browser usage with Wagmi React

```typescript
import { useMemo } from 'react';
import { WagmiProvider, useConfig } from 'wagmi';
import { createConfig, http } from '@wagmi/core';
import { mainnet } from '@wagmi/core/chains';
import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';

const config = createConfig({
  chains: [mainnet],
  transports: {
    [mainnet.id]: http(), // Uses the default public RPC
  },
});

export function App() {
  return (
    <WagmiProvider config={config}>
      <SdkInitializer />
    </WagmiProvider>
  );
}

function SdkInitializer() {
  const config = useConfig();
  const publicProvider = createPublicProviderWagmi({ config });

  return /* your UI */;
}
```

## Event Subscription

Subscribe to contract events in real-time using the `subscribeToContractEvents` method:

```typescript
import { createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { createConfig, http } from '@wagmi/core';
import { mainnet } from '@wagmi/core/chains';

const config = createConfig({
  chains: [mainnet],
  transports: {
    [mainnet.id]: http('YOUR_MAINNET_RPC_URL'),
  },
});

const publicProvider = createPublicProviderWagmi({ config });

// Subscribe to Transfer events
const unsubscribe = await publicProvider.subscribeToContractEvents({
  contractAddress: '0x...',
  abi: erc20Abi,
  networkId: 1,
  topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], // Transfer event signature
  callback: (log) => {
    console.log('Transfer event:', log);
  }
});

// Later: unsubscribe from events
unsubscribe();
```

## Notes

* The Wagmi config **must** include a transport for every chain you expect to access. If a chain is missing, calls will throw `ClientSDKError` with `UNSUPPORTED_NETWORK`.
* The adapter uses Wagmi's `getPublicClient` under the hood, so the config should expose a public client transport (e.g., `http`, `fallback`).
* Wagmi handles provider caching; reuse the same config instance when possible to avoid creating redundant clients.


# Viem

**createPublicProviderViem(publicClients, fallbackProviders?)** → IPublicProvider

Creates a public provider from Viem public clients with optional fallback support.

## Parameters

| Parameter     | Type                                             | Required | Description                               |
| ------------- | ------------------------------------------------ | -------- | ----------------------------------------- |
| publicClients | Record\<number, PublicClient \| PublicClient\[]> | ✅        | Map of network IDs to Viem public clients |

## Examples

### Basic usage

```typescript
import { createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet, base, optimism } from 'viem/chains';

// Create public clients for each network
const publicClients = {
  1: createPublicClient({
    chain: mainnet,
    transport: http('YOUR_MAINNET_RPC_URL')
  }),
  8453: createPublicClient({
    chain: base,
    transport: http('YOUR_BASE_RPC_URL')
  }),
  10: createPublicClient({
    chain: optimism,
    transport: http('YOUR_OPTIMISM_RPC_URL')
  })
};

// Create the public provider
const publicProvider = createPublicProviderViem(publicClients);

// Use with Manifold client
const client = createClient({ publicProvider });
```

### With fallback providers

```typescript
import { createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet, base } from 'viem/chains';

// Providers with fallback (used when primary fails or is on wrong network)
const publicClients = {
  1: [
  createPublicClient({
    chain: mainnet,
    transport: http('PRIMARY_MAINNET_RPC_URL')
  }),
  createPublicClient({
    chain: mainnet,
    transport: http('FALLBACK_MAINNET_RPC_URL')
  })
  ]
};

// Create the public provider with fallback support
const publicProvider = createPublicProviderViem(publicClients);

// Use with Manifold client
const client = createClient({ publicProvider });
```

## Browser usage

For browser applications using MetaMask or other injected wallets:

```typescript
import { createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, custom } from 'viem';
import { mainnet } from 'viem/chains';

// Use the browser's injected provider
const publicClient = createPublicClient({
  chain: mainnet,
  transport: custom(window.ethereum)
});

const publicProvider = createPublicProviderViem({
  1: publicClient
});

const client = createClient({ publicProvider });
```

## Event Subscription

Subscribe to contract events in real-time using the `subscribeToContractEvents` method:

```typescript
import { createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('YOUR_MAINNET_RPC_URL')
});

const publicProvider = createPublicProviderViem({
  1: publicClient
});

// Subscribe to Transfer events
const unsubscribe = await publicProvider.subscribeToContractEvents({
  contractAddress: '0x...',
  abi: erc20Abi,
  networkId: 1,
  topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], // Transfer event signature
  callback: (log) => {
    console.log('Transfer event:', log);
  }
});

// Later: unsubscribe from events
unsubscribe();
```


# Ethers v5

**createPublicProviderEthers5(providers, fallbackProviders?)** → IPublicProvider

Creates a public provider from Ethers v5 providers with optional fallback support.

## Parameters

| Parameter | Type                                                                                                                                                                                                               | Required | Description                            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -------------------------------------- |
| providers | Record\<number, [JsonRPCProvider](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/#JsonRpcProvider) \| [JsonRPCProvider](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/#JsonRpcProvider)\[]> | ✅        | Map of network IDs to Ethers providers |

## Examples

### Basic usage

```typescript
import { createPublicProviderEthers5 } from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

// Create providers for each network
const providers = {
  1: new ethers.providers.JsonRpcProvider('YOUR_MAINNET_RPC_URL'),
  8453: new ethers.providers.JsonRpcProvider('YOUR_BASE_RPC_URL'),
  10: new ethers.providers.JsonRpcProvider('YOUR_OPTIMISM_RPC_URL')
};

// Create the public provider
const publicProvider = createPublicProviderEthers5(providers);

// Use with Manifold client
const client = createClient({ publicProvider });
```

### With fallback providers

```typescript
import { createPublicProviderEthers5 } from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

// Primary providers
const providers = {
  1: [new ethers.providers.JsonRpcProvider('PRIMARY_MAINNET_RPC_URL'), 
  new ethers.providers.JsonRpcProvider('SECONDARY_MAINNET_RPC_URL')
  ],
};

// Create the public provider with fallback support
const publicProvider = createPublicProviderEthers5(providers);

// Use with Manifold client
const client = createClient({ publicProvider });
```

## Event Subscription

Subscribe to contract events in real-time using the `subscribeToContractEvents` method:

```typescript
import { createPublicProviderEthers5 } from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

const provider = new ethers.providers.JsonRpcProvider('YOUR_MAINNET_RPC_URL');

const publicProvider = createPublicProviderEthers5({
  1: provider
});

// Subscribe to Transfer events
const unsubscribe = await publicProvider.subscribeToContractEvents({
  contractAddress: '0x...',
  abi: erc20Abi,
  networkId: 1,
  topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], // Transfer event signature
  callback: (log) => {
    console.log('Transfer event:', log);
  }
});

// Later: unsubscribe from events
unsubscribe();
```


# Account Adapters

The SDK requires two types of blockchain connections:

## Public Providers

[Public providers](/client-sdk/sdk/public-provider-adapters) enable read-only blockchain operations like fetching balances, estimating gas, and reading contracts. They are **required** when initializing the Manifold Client.

* [Viem Public Provider](/client-sdk/sdk/public-provider-adapters/viem) - For Viem users
* [Ethers v5 Public Provider](/client-sdk/sdk/public-provider-adapters/ethersv5) - For Ethers v5 users

## Account Adapters

An [account](/client-sdk/reference/account) is required to execute on-chain transactions.\
Both [**purchase**](/client-sdk/sdk/product/common/purchase) and [**execute**](/client-sdk/sdk/transaction-steps/execute) operations require an account, as they involve triggering transactions on the user's behalf.\
The SDK provides convenient methods for creating an account using popular Web3 libraries:

* [**Viem**](/client-sdk/sdk/account-adapters/viem) - Modern, TypeScript-first library
* [**Ethers v5**](/client-sdk/sdk/account-adapters/ethersv5) - Popular, battle-tested library

## Quick Start

```typescript
import { createClient, createPublicProviderViem, createAccountViem } from '@manifoldxyz/client-sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// 1. Create public provider for blockchain reads
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('YOUR_RPC_URL')
});
const publicProvider = createPublicProviderViem({ 1: publicClient });

// 2. Initialize Manifold client
const client = createClient({ publicProvider });

// 3. Create account for transactions (when needed)
const walletClient = createWalletClient({ /* ... */ });
const account = createAccountViem({ walletClient });
```


# EthersV5

**createAccountEthers5(params)** → [Account](/client-sdk/reference/account)

Creates an account representation from an **Ethers v5** signer or wallet.\
(You only need to provide either a signer **or** a wallet.)

#### Parameters

| Parameter | Type                                                                                      | Required | Description                                                                                           |
| --------- | ----------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| signer    | [JsonRpcSigner](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/#JsonRpcSigner) | ❌        | Instance of [JsonRpcSigner](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/#JsonRpcSigner) |
| wallet    | [Wallet](https://docs.ethers.org/v5/api/signer/#Wallet)                                   | ❌        | Useful for server-side applications using programmatic wallet generation.                             |

#### Returns: [Account](/client-sdk/reference/account)

#### Example

<pre class="language-typescript"><code class="lang-typescript">import { createAccountEthers5, createClient, createPublicProviderEthers5, EditionProduct } from '@manifoldxyz/client-sdk';
import { ethers } from 'ethers';

// Create provider for the network
const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_URL');

// Create public provider for the client
const publicProvider = createPublicProviderEthers5({
  1: provider // mainnet
});

// Initialize the client
const client = createClient({ publicProvider });

<strong>const product = await client.getProduct('4150231280') as EditionProduct;
</strong>
// Create wallet for signing
const wallet = new ethers.Wallet('wallet-private-key', provider);

const prepared = await product.preparePurchase({
  address: wallet.address,
  payload: {
    quantity: 1
  },
});

// Create account from wallet
const account = createAccountEthers5({
  wallet
})

const order = await product.purchase({
  account,
  preparedPurchase: prepared,
});
</code></pre>

[**ClientSDKError**](/client-sdk/reference/clientsdkerror)

| Code           | Message                                                                   |
| -------------- | ------------------------------------------------------------------------- |
| INVALID\_INPUT | Provide either signer or wallet, not both \| Signer or wallet is required |


# Viem

**createAccountViem(params)** → [Account](/client-sdk/reference/account)

Create an account representation from [Viem Wallet Client](https://viem.sh/docs/clients/wallet)

#### Parameters

| Parameter    | Type                                                | Required | Description                    |
| ------------ | --------------------------------------------------- | -------- | ------------------------------ |
| walletClient | [WalletClient](https://viem.sh/docs/clients/wallet) | ✅        | Instance of Viem Wallet client |

#### Returns: [Account](/client-sdk/reference/account)

#### Example

{% tabs %}
{% tab title="index.ts" %}

```typescript
import { createClient, BlindMintProduct, createAccountViem, createPublicProviderViem } from '@manifoldxyz/client-sdk';
import { walletClient, publicClient } from './client.ts';

// Create public provider for blockchain interactions
const publicProvider = createPublicProviderViem({
  1: publicClient // mainnet
});

// Initialize client with public provider
const client = createClient({ publicProvider });

// Grab product
const product = await client.getProduct('4150231280') as BlindMintProduct;

const prepared = await product.preparePurchase({
  address: '0xBuyer',
  payload: { quantity: 1 },
});

const account = createAccountViem({
  walletClient
})
const order = await product.purchase({
  account,
  preparedPurchase: prepared,
});
const txHash = order.receipts[0]?.txHash;
console.log(`Transaction submitted ${txHash}`)
```

{% endtab %}

{% tab title="client.ts" %}

```typescript
import { createWalletClient, createPublicClient, custom, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'

// Create public client for read operations
export const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('YOUR_RPC_URL') // or custom(window.ethereum) for browser
})

// Create wallet client for transactions
const account = privateKeyToAccount('0x...') 
export const walletClient = createWalletClient({
  account, 
  chain: mainnet,
  transport: custom(window.ethereum)
})
```

{% endtab %}
{% endtabs %}

[**ClientSDKError**](/client-sdk/reference/clientsdkerror)

| Code           | Message                                                                                |
| -------------- | -------------------------------------------------------------------------------------- |
| INVALID\_INPUT | Account not found. Please provide an Account explicitly when creating the Viem client. |


# Transaction Steps

## Introduction

Some product purchases may require more than one transaction to complete. For example, Edition products configured with ERC20 payments require:

1. **ERC20 spend approval**: Grant contract permission to spend user tokens
2. **Mint**: Execute the actual purchase transaction

Other scenarios include cross-chain purchases (bridge, then mint) or batch operations.

## Execution Methods

### 1. Built-in Execution (Recommended)

For **user-facing apps**, the SDK separates transactions into explicit steps. This gives you:

* Progress tracking for multi-step flows
* Fine-grained error handling per transaction
* Safer UX - users approve each step individually

The [preparePurchase](/client-sdk/sdk/product/blind-mint/preparepurchase) function returns [TransactionStep](/client-sdk/sdk/transaction-steps) objects. Call [execute](/client-sdk/sdk/transaction-steps/execute) on each step to submit the transaction. The SDK automatically skips unnecessary steps (e.g., if approval is already granted).

For **server-side apps** or **agentic flows**, call [purchase](/client-sdk/sdk/product/common/purchase) directly and the SDK handles all transactions sequentially without explicit step management.

### 2. Custom Execution with transactionData

Each `TransactionStep` includes a [transactionData](https://github.com/manifoldxyz/client-sdk/blob/main/packages/client-sdk/docs/sdk/transaction-steps/transactionData.md) field containing raw blockchain transaction data. This enables custom transaction execution for advanced use cases:

* **EIP-7702 Support**: Account abstraction and smart contract wallets
* **Custom Gas Management**: Implement your own gas strategies
* **Transaction Infrastructure**: Integrate with existing systems
* **Batch Transactions**: Combine multiple transactions
* **Custom Error Handling**: Implement retry logic

Example:

```typescript
// Prepare purchase normally
const preparedPurchase = await product.preparePurchase({
  userAddress: walletAddress,
  payload: { quantity: 1 }
});

// Execute with custom logic
for (const step of preparedPurchase.steps) {
  const { contractAddress, transactionData, value, gasEstimate } = step.transactionData;
  
  // Use your preferred Web3 library
  const txHash = await walletClient.sendTransaction({
    to: contractAddress,
    data: transactionData,
    value: value,
    gas: gasEstimate * 110n / 100n, // Custom gas buffer
  });
}
```

## Available Documentation

* [execute](/client-sdk/sdk/transaction-steps/execute) - Built-in execution method for transaction steps
* [TransactionData](/client-sdk/reference/transactiondata) - Raw transaction data for custom execution


# execute

Handles the execution of a specific step.

**execute(params)** → [Receipt](/client-sdk/reference/receipt)

| Parameter     | Type                                     | Required | Description                               |
| ------------- | ---------------------------------------- | -------- | ----------------------------------------- |
| account       | [Account](/client-sdk/reference/account) | ✅        | Buyer’s account                           |
| confirmations | number                                   | ❌        | Number of confirmation blocks (Default 1) |

#### Returns: [Receipt](/client-sdk/reference/receipt)

#### Example:

```tsx
// account is created via createAccountEthers5 / createAccountViem
const receipts = []

for (const step of preparedPurchase.steps) {
  const receipt = await step.execute(account)
  receipts.push(receipt)

  console.log('Confirmed tx:', receipt.transactionReceipt.txHash)

  if (receipt.order) {
    receipt.order.items.forEach((item) => {
      console.log(`Minted token ${item.token.tokenId}`)
    })
  }
}
```


# InstanceData

| Field          | Type                                                                                                                             | Required | Description                     |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------- |
| **id**         | string                                                                                                                           | ✅        | Unique identifier (instance ID) |
| **creator**    | [Creator](/client-sdk/reference/creator)                                                                                         | ✅        | The creator of the product      |
| **publicData** | [EditionPublicData](/client-sdk/reference/editionpublicdata) \| [BlindMintPublicData](/client-sdk/reference/blindmintpublicdata) | ✅        | The product data                |
| appId          | number                                                                                                                           | ✅        | The appId of the product        |
| appName        | string                                                                                                                           | ✅        | The app name of the product     |


# EditionProduct

| Field                                                                          | Type                                                           | Required | Description                                                 |
| ------------------------------------------------------------------------------ | -------------------------------------------------------------- | -------- | ----------------------------------------------------------- |
| onchainData                                                                    | [EditionOnchainData](/client-sdk/reference/editiononchaindata) | ❌        | Product onchain data                                        |
| [**preparePurchase**](/client-sdk/sdk/product/edition-product/preparepurchase) | function                                                       | ✅        | Simulates purchase to check eligibility and get total cost. |
| [**purchase**](/client-sdk/sdk/product/common/purchase)                        | function                                                       | ✅        | Make a purchase on the product                              |
| [fetchOnchainData](/client-sdk/sdk/product/edition-product/fetchonchaindata)   | function                                                       | ✅        | Fetch on-chain data for this product                        |
| [getAllocations](/client-sdk/sdk/product/common/getallocations)                | function                                                       | ✅        | Check product eligibility quantity for a wallet address     |
| [getInventory](/client-sdk/sdk/product/common/getinventory)                    | [ProductInventory](/client-sdk/reference/productinventory)     | ✅        | Get inventory of the product                                |
| [getRules](/client-sdk/sdk/product/common/getrules)                            | [ProductRule](/client-sdk/reference/productrule)               | ✅        | Product specific rules                                      |
| [getProvenance](/client-sdk/sdk/product/common/getprovenance)                  | [ProductProvenance](/client-sdk/reference/productprovenance)   | ✅        | Product provenance info                                     |


# EditionPublicData

| Field            | Type                                       | Required | Description                      |
| ---------------- | ------------------------------------------ | -------- | -------------------------------- |
| **title**        | string                                     | ✅        | Title of the product             |
| description      | string                                     | ❌        | Description of the product       |
| **asset**        | [Asset](/client-sdk/reference/asset)       | ✅        | Primary media of the product     |
| network          | number                                     | ✅        | The network the product is on    |
| contract         | [Contract](/client-sdk/reference/contract) | ✅        | The contract of the token        |
| extensionAddress | string                                     | ✅        | Extension address of the product |


# EditionOnchainData

| Field           | Type                                 | Required | Description                 |
| --------------- | ------------------------------------ | -------- | --------------------------- |
| totalSupply     | number                               | ✅        | Total supply of the product |
| totalMinted     | number                               | ✅        | Total token minted          |
| walletMax       | number                               | ✅        | Max tokens per wallet       |
| startDate       | Date                                 | ✅        | Start drop date             |
| endDate         | Date                                 | ✅        | End drop date               |
| audienceType    | enum                                 | ✅        | `None`                      |
| cost            | [Money](/client-sdk/reference/money) | ✅        | Cost of the product         |
| paymentReceiver | string                               | ✅        | Receiver of mint payment    |


# BlindMintProduct

<table><thead><tr><th>Field</th><th width="195.76953125">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>onchainData</td><td><a href="/pages/8lCJtTwwQ35WAeoidopp">BlindMintOnchainData</a></td><td>❌</td><td>Product onchain data</td></tr><tr><td><a href="/pages/yqpBMUHsgcTbE6HKbpce"><strong>preparePurchase</strong></a></td><td>function</td><td>✅</td><td>Simulates purchase to check eligibility and get total cost.</td></tr><tr><td><a href="/pages/SdRoeJBqoktXL2qlOtk8"><strong>purchase</strong></a></td><td>function</td><td>✅</td><td>Make a purchase on the product</td></tr><tr><td><a href="/pages/c1aznG4CUWyrMZWtPYaD">fetchOnchainData</a></td><td>function</td><td>✅</td><td>Fetch on-chain data for this product</td></tr><tr><td><a href="/pages/Ch7dnjqMRLLr39FWdBlt">getAllocations</a></td><td>function</td><td>✅</td><td>Check product eligibility quantity for a wallet address</td></tr><tr><td><a href="/pages/jtfgfaKN9SQZEXJT1rJS">getInventory</a></td><td><a href="/pages/vWY7uqazjdaTdmSqbA6n">ProductInventory</a></td><td>✅</td><td>Get inventory of the product</td></tr><tr><td><a href="/pages/d1UjTDPq30FYRyaMiODK">getRules</a></td><td><a href="/pages/hDDyfNGsjeyTwo43NbrP">ProductRule</a></td><td>✅</td><td>Product specific rules</td></tr><tr><td><a href="/pages/663IwQyaAlMVcJCFsNHn">getProvenance</a></td><td><a href="/pages/o70F2Wy6jS7n8jatUbSi">ProductProvenance</a></td><td>✅</td><td>Product provenance info</td></tr></tbody></table>


# BlindMintPublicData

<table><thead><tr><th>Field</th><th width="212.953125">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>title</strong></td><td>string</td><td>✅</td><td>Title of the product</td></tr><tr><td>description</td><td>string</td><td>❌</td><td>Description of the product</td></tr><tr><td>network</td><td>number</td><td>✅</td><td>The network the product is on</td></tr><tr><td>contract</td><td><a href="/pages/uHNBcFNVqj40mCjaA4Qb">Contract</a></td><td>✅</td><td>The contract of the token</td></tr><tr><td>extensionAddress</td><td>string</td><td>✅</td><td>Extension address of the product</td></tr><tr><td>tierProbabilities</td><td><a href="/pages/olq4OKy51vN7rNXws5J3">BlindMintTierProbability</a></td><td>✅</td><td>Tier and probability of each group</td></tr><tr><td>pool</td><td><a href="/pages/HZyRndJTadCAQGAPjo92">BlindMintPool</a></td><td>✅</td><td>Pool of assets</td></tr></tbody></table>


# BlindMintOnchaindata

| Field           | Type                                 | Required | Description                            |
| --------------- | ------------------------------------ | -------- | -------------------------------------- |
| totalSupply     | number                               | ✅        | Total supply of the product            |
| **totalMinted** | number                               | ✅        | Total token minted                     |
| walletMax       | number                               | ✅        | Max tokens per wallet                  |
| startDate       | Date                                 | ✅        | Start drop date                        |
| endDate         | Date                                 | ✅        | End drop date                          |
| audiencetype    | enum                                 | ✅        | `None`                                 |
| cost            | [Money](/client-sdk/reference/money) | ✅        | Cost of the product                    |
| paymentReceiver | string                               | ✅        | Receiver of mint payment               |
| tokenVariations | number                               | ✅        | Number of asset variations             |
| startingTokenId | number                               | ✅        | The starting tokenId of the asset pool |


# BlindMintTierProbability

| Field   | Type      | Required | Description                                       |
| ------- | --------- | -------- | ------------------------------------------------- |
| group   | string    | ✅        | Name of the tier group                            |
| indices | number\[] | ✅        | Asset indices belonging to the group              |
| rate    | number    |          | Probability chance in basis points (10000 = 100%) |


# BlindMintPool

| Field    | Type                                 | Required | Description                    |
| -------- | ------------------------------------ | -------- | ------------------------------ |
| index    | number                               | ✅        | Index of the asset in the pool |
| metadata | [Asset](/client-sdk/reference/asset) | ✅        | Asset metadata                 |


# PreviewData

| Field         | Type                                       | Required | Description                          |
| ------------- | ------------------------------------------ | -------- | ------------------------------------ |
| title         | string                                     | ❌        | Product Title                        |
| description   | string                                     | ❌        | Product description                  |
| contract      | [Contract](/client-sdk/reference/contract) | ❌        | Contract associated with the product |
| thumbnail     | string                                     | ❌        | Thumbnail for the product            |
| payoutAddress | string                                     | ❌        | Payout wallet address of the product |
| network       | number                                     | ❌        | Network the product is on            |
| startDate     | Date                                       | ❌        | Start date for the product           |
| endDate       | Date                                       | ❌        | End date for the product             |
| price         | [Money](/client-sdk/reference/money)       | ❌        | Price of the product                 |


# ProductMetadata

ProductMetadata

| Field       | Type   | Required | Description         |
| ----------- | ------ | -------- | ------------------- |
| name        | string | ✅        | Product name        |
| description | string | ❌        | Product description |


# ProductRule

| Field                   | Type   | Required | Description                                     |
| ----------------------- | ------ | -------- | ----------------------------------------------- |
| startDate               | Date   | ❌        | Start date (if not provided, start immediately) |
| endDate                 | Date   | ❌        | End date (not provided, never ends)             |
| **audienceRestriction** | enum   | ✅        | `allowlist`                                     |
| maxPerWallet            | number | ❌        | Number of allowed purchased per wallet          |


# ProductInventory

| Field          | Type   | Required | Description                                                       |
| -------------- | ------ | -------- | ----------------------------------------------------------------- |
| totalSupply    | number | ✅        | Total product supply (a value of `-1` indicates unlimited supply) |
| totalPurchased | number | ✅        | Total product purchased                                           |


# ProductProvenance

| Field     | Type                                       | Required | Description                                 |
| --------- | ------------------------------------------ | -------- | ------------------------------------------- |
| creator   | [Creator](/client-sdk/reference/creator)   | ✅        | Media url of the product                    |
| contract  | [Contract](/client-sdk/reference/contract) | ❌        | Link to the contract related to the product |
| token     | [Token](/client-sdk/reference/token)       | ❌        | Link to the token related to the product    |
| networkId | number                                     | ❌        | Network ID of the product (if applicable)   |


# TransactionStep

Individual transaction step within a purchase flow. Represents a single blockchain transaction that needs to be executed. Steps enable explicit user control over multi-transaction operations, following Web3 best practices for transparency and user consent.

## Fields

| Field                                                                                                                                    | Type                | Required | Description                                                 |
| ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -------- | ----------------------------------------------------------- |
| id                                                                                                                                       | string              | ✅        | Unique identifier for this step                             |
| name                                                                                                                                     | string              | ✅        | Human-readable step name (e.g., "Approve USDC", "Mint NFT") |
| type                                                                                                                                     | TransactionStepType | ✅        | Type of transaction: `'mint'` \| `'approve'`                |
| [transactionData](https://github.com/manifoldxyz/client-sdk/blob/main/packages/client-sdk/docs/sdk/transaction-steps/transactionData.md) | TransactionData     | ✅        | Raw transaction data for custom execution                   |
| [execute](/client-sdk/sdk/transaction-steps/execute)                                                                                     | function            | ✅        | Executes this transaction step                              |
| description                                                                                                                              | string              | ❌        | Optional detailed description of what this step does        |
| cost                                                                                                                                     | object              | ❌        | Cost breakdown for this specific step                       |
| cost.native                                                                                                                              | Money               | ❌        | Native token cost (ETH, etc.)                               |
| cost.erc20s                                                                                                                              | Money\[]            | ❌        | ERC20 token costs                                           |

## Common Step Types

* **`approve`**: ERC20 token approval for payment
* **`mint`**: Actual NFT minting transaction

## Usage

Steps should be executed in order, as later steps may depend on earlier ones.

### Example: Iterating Through Steps

```typescript
// Execute steps manually for better UX
for (const step of preparedPurchase.steps) {
  console.log(`Executing: ${step.name}`);
  const receipt = await step.execute(adapter);
  console.log(`✓ ${step.name} complete: ${receipt.txHash}`);
}
```

## See Also

* [TransactionData](https://github.com/manifoldxyz/client-sdk/blob/main/packages/client-sdk/docs/sdk/transaction-steps/transactionData.md) - Raw transaction data for custom execution
* [execute](/client-sdk/sdk/transaction-steps/execute) - Built-in execution function
* [PreparedPurchase](/client-sdk/reference/preparedpurchase) - Parent structure containing steps


# PreparedPurchase

| Field               | Type                                                        | Required | Description                                                            |
| ------------------- | ----------------------------------------------------------- | -------- | ---------------------------------------------------------------------- |
| **cost**            | [Cost](/client-sdk/reference/cost)                          | ✅        | Purchase cost                                                          |
| **transactionData** | [TransactionData](/client-sdk/reference/transactiondata)    | ✅        | Transaction to execute                                                 |
| steps               | [TransactionStep](/client-sdk/reference/transactionstep)\[] | ✅        | Manual steps that need to be executed in order (For manual executions) |
| gasEstimate         | [Money](/client-sdk/reference/money)                        | ✅        | Total gas estimated                                                    |


# TransactionCallbacks

| Field                                      | Type     | Required | Description                         |
| ------------------------------------------ | -------- | -------- | ----------------------------------- |
| onProgress (progress: TransactionProgress) | function | ❌        | Called when purchase status changes |


# TransactionProgress

| Field           | Type                                                        | Required | Description                                                                                                                                                                                              |
| --------------- | ----------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| status          | enum                                                        | ✅        | `pending-approval`                                                                                                                                                                                       |
| steps           | [TransactionStep](/client-sdk/reference/transactionstep)\[] | ✅        | This is the full steps object as it’s returned by the api and enhanced with some additional properties on the client. Notably the step status has been updated as well as any errors have been attached. |
| **currentStep** | [TransactionStep](/client-sdk/reference/transactionstep)    | ✅        | We’ve conveniently pinpointed the current step that’s being processed and made it accessible in this callback.                                                                                           |
| receipts        | [Receipt](/client-sdk/reference/receipt)\[]                 | ❌        | A full list of all the transaction hashes that have been processed so far during execution.                                                                                                              |
| data            | object                                                      | ❌        | Additional context data                                                                                                                                                                                  |


# TransactionData

| Field           | Type   | Required | Description                            |
| --------------- | ------ | -------- | -------------------------------------- |
| contractAddress | string | ✅        | Target contract                        |
| transactionData | string | ✅        | Encoded calldata                       |
| gasEstimate     | BigInt | ✅        | Gas estimate of the transaction        |
| networkId       | number | ✅        | Network of transaction                 |
| value           | bigint | ✅        | The required value for the transaction |


# Order

Base order information shared across all purchase flows:

| Field            | Type                               | Required | Description                                    |
| ---------------- | ---------------------------------- | -------- | ---------------------------------------------- |
| recipientAddress | string                             | ✅        | Wallet address that receives the minted tokens |
| total            | [Cost](/client-sdk/reference/cost) | ✅        | Total cost for this purchase flow              |

`TokenOrder` extends `Order` and is returned when mint results are available:

| Field | Type                                            | Required | Description                                                                |
| ----- | ----------------------------------------------- | -------- | -------------------------------------------------------------------------- |
| items | [OrderItem](/client-sdk/reference/orderitem)\[] | ✅        | Detailed list of minted tokens with quantities and per-item cost breakdown |


# OrderItem

Represents a minted token (or group of tokens) within a purchase order.

| Field    | Type                                 | Required | Description                               |
| -------- | ------------------------------------ | -------- | ----------------------------------------- |
| total    | [Cost](/client-sdk/reference/cost)   | ✅        | Cost allocation for this specific item    |
| token    | [Token](/client-sdk/reference/token) | ✅        | Token metadata (contract, media, tokenId) |
| quantity | number                               | ✅        | Number of tokens represented by this item |


# Receipt

| Field              | Type                                                           | Required | Description                                                            |
| ------------------ | -------------------------------------------------------------- | -------- | ---------------------------------------------------------------------- |
| transactionReceipt | [TransactionReceipt](/client-sdk/reference/transactionreceipt) | ✅        | Normalized transaction metadata (hash, block number, gas usage)        |
| order              | [TokenOrder](/client-sdk/reference/order)                      | ❌        | Parsed mint results including tokens, quantities, and cost allocations |


# TransactionReceipt

| Field       | Type   | Required | Description                                 |
| ----------- | ------ | -------- | ------------------------------------------- |
| networkId   | number | ✅        | The network ID                              |
| txHash      | string | ✅        | The transaction hash                        |
| blockNumber | number | ✅        | The block number of the transaction         |
| gasUsed     | number | ✅        | The amount of gas used from the transaction |


# Account

| Field             | Type     | Required | Description                                        |
| ----------------- | -------- | -------- | -------------------------------------------------- |
| address           | string   | ✅        | The Ethereum Address                               |
| sendTransaction() | function | ✅        | Send the given transaction to the blockchain       |
| signMessage()     | function | ✅        | Sign the given message and return the signature    |
| signTypedData()   | function | ✅        | Sign the given typed data and return the signature |
| getNetworkId      | number   | ✅        | Currently connected networkId                      |
| switchNetwork     | function | ✅        | Switch to target network                           |


# Money

| Field        | Type   | Required | Description                                                                  |
| ------------ | ------ | -------- | ---------------------------------------------------------------------------- |
| value        | BigInt | ✅        | The raw amount                                                               |
| decimals     | number | ✅        | Number of decimal places for the currency                                    |
| currency     | string | ✅        | `ETH`                                                                        |
| erc20        | string | ✅        | “0x0000000000000000000000000000000000000000” for native ETH or ERC20 address |
| symbol       | string | ✅        | `ETH`                                                                        |
| name         | string | ✅        | Name of the currency                                                         |
| formatted    | string | ✅        | The formatted amount (Ex: “0.1 ETH” )                                        |
| formattedUSD | string | ✅        | The formatted amount in USD                                                  |


# Cost

| Field    | Type                                 | Required | Description                   |
| -------- | ------------------------------------ | -------- | ----------------------------- |
| total    | [Money](/client-sdk/reference/money) | ✅        | Total cost including all fees |
| subtotal | [Money](/client-sdk/reference/money) | ✅        | Cost excluding fees           |
| fees     | [Money](/client-sdk/reference/money) | ✅        | Platform fees                 |


# AppType

| Field       | Type   | Required | Description          |
| ----------- | ------ | -------- | -------------------- |
| Edition     | string | ✅        | Edition app type     |
| Burn Redeem | string | ✅        | Burn Redeem app type |
| Blind Mint  | string | ✅        | Blind Mint app type  |


# TokenRequirement

| Field         | Type                                                                  | Required | Description                 |
| ------------- | --------------------------------------------------------------------- | -------- | --------------------------- |
| items         | [TokenItemRequirement](/client-sdk/reference/tokenitemrequirement)\[] | ✅        | List of eligible token sets |
| requiredCount | number                                                                | ✅        | Number of tokens required   |


# TokenItemRequirement

| Field           | Type      | Required | Description                     |
| --------------- | --------- | -------- | ------------------------------- |
| quantity        | number    | ✅        | Required quantity of tokens     |
| burnSpec        | enum      | ✅        | `manifold`                      |
| tokenSpec       | enum      | ✅        | `erc721`                        |
| tokenIds        | string\[] | ❌        | list of required tokenIds       |
| maxTokenId      | string    | ❌        | Max tokenId range               |
| minTokenId      | string    | ❌        | Min tokenId range               |
| contractAddress | string    | ✅        | The required contract address   |
| merkleRoot      | string    | ❌        | For allowlist token requirement |
| validationType  | enum      | ✅        | `contract`                      |


# Creator

| Field   | Type   | Required | Description                                  |
| ------- | ------ | -------- | -------------------------------------------- |
| id      | string | ✅        | identifier of the workspace                  |
| slug    | string | ✅        | Slug of workspace                            |
| address | string | ✅        | The Ethereum wallet address of the workspace |
| name    | string | ❌        | Name of workspace                            |


# Identity

| Field             | Type   | Required | Description                 |
| ----------------- | ------ | -------- | --------------------------- |
| walletAddress     | string | ✅        | The Ethereum wallet address |
| twitterUsername   | string | ❌        | The user X username         |
| instagramUsername | string | ❌        | The user instagram username |


# Contract

| Field     | Type                                        |   | Description                       |
| --------- | ------------------------------------------- | - | --------------------------------- |
| networkId | number                                      | ✅ | The Ethereum Network              |
| address   | string                                      | ✅ | The contract address              |
| explorer  | [Explorer](/client-sdk/reference/explorers) | ✅ | The explorer urls of the contract |
| name      | string                                      |   |                                   |
| symbol    | string                                      |   |                                   |
| spec      | enum                                        | ✅ | `erc1155`                         |


# Token

| Field    | Type                                        |   | Description                    |
| -------- | ------------------------------------------- | - | ------------------------------ |
| contract | [Contract](/client-sdk/reference/contract)  | ✅ |                                |
| tokenId  | string                                      | ✅ | The token ID                   |
| explorer | [Explorer](/client-sdk/reference/explorers) | ✅ | The explorer urls of the token |


# Explorers

| Field        | Type   | Required | Description                            |
| ------------ | ------ | -------- | -------------------------------------- |
| etherscanUrl | string | ✅        | Link to Etherscan Explorer hosted site |
| manifoldUrl  | string | ❌        | Link to Manifold hosted site           |
| openseaUrl   | string | ❌        | Link to Opensea hosted site            |


# Asset

| Field             | Type   | Required | Description                                           |
| ----------------- | ------ | -------- | ----------------------------------------------------- |
| name              | string | ✅        |                                                       |
| description       | string | ❌        |                                                       |
| attributes        | object | ❌        | Extra attributes (key/value)                          |
| image             | string | ✅        | Optimized preview image url of the product            |
| animation         | string | ❌        | Optimized preview animation url of the product        |
| originalImage     | string | ❌        | Original full-resolution image url of the product     |
| originalAnimation | string | ❌        | Original full-resolution animation url of the product |


# ClientSDKError

Base error class with typed error codes.

| Field   | Type   | Required |
| ------- | ------ | -------- |
| code    | string | ✅        |
| message | string | ✅        |
| details | object | ❌        |

**Error Codes**

| Code                  | Description                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| **Network Errors**    |                                                                                                 |
| UNSUPPORTED\_NETWORK  | Unsupported network                                                                             |
| **Data Errors**       |                                                                                                 |
| NOT\_FOUND            | Resource not found                                                                              |
| INVALID\_INPUT        | Invalid parameters                                                                              |
| MISSING\_TOKENS       | Missing required tokens to purchase                                                             |
| UNSUPPORTED\_TYPE     | Unsupported product type (Only support the following types: AppType.Edition, AppType.BlindMint) |
| **Blockchain Errors** |                                                                                                 |
| ESTIMATION\_FAILED    | Can’t estimate gas                                                                              |
| TRANSACTION\_FAILED   | Transaction reverted                                                                            |
| LEDGER\_ERROR         | Ledger wallet error                                                                             |
| TRANSACTION\_REVERTED | Transaction revert                                                                              |
| TRANSACTION\_REJECTED | User rejected                                                                                   |
| INSUFFICIENT\_FUNDS   | Wallet does not have the required funds                                                         |
| **Permission Errors** |                                                                                                 |
| NOT\_ELIGIBLE         | Not eligible to purchase                                                                        |
| SOLD\_OUT             | Product sold out                                                                                |
| LIMIT\_REACHED        | Limit reach for wallet                                                                          |
| ENDED                 | Product not available anymore                                                                   |
| NOT\_STARTED          | Not started, come back late                                                                     |


# Introduction

Manifold for Developers are a set of tools provided for NFT community to easily create rich consumer experiences.  We believe that the growth of the NFT ecosystem depends on creating more engaging mechanics, and our tools help make that simple.

### The Shopify Merch Bridge

The Shopify merch bridge is an app that we've developed for gated web3 retail commerce.  To find more details and use it, [skip right to this section of the docs](/manifold-for-developers/shopify-merch-bridge/overview)

### For Creators and Web Developers

Creators and web developers can create engaging NFT experiences by taking advantage of our suite of [Widgets](/manifold-for-developers/resources/apps).  Widgets are UI components that you can place on your own website to enable web3 and NFT functionality.

To get started, follow our [Getting Started Guide](/manifold-for-developers/guides/getting-started)

### For Smart Contract Developers

Manifold has also provided a set of open source smart contracts for the community.  Details about these smart contracts can be found in the [Smart Contracts](/manifold-for-developers/smart-contracts/manifold-creator) section of the documentation.

## [Contact Us](#contact-info)

Got questions or awesome ideas? Feel free to [reach out](/manifold-for-developers/contact-us)!


# Overview

{% hint style="info" %}
We believe secure gating is of the utmost importance for NFT communities, as it provides confidence that gated experiences accessible are truly exclusive.

The Manifold Merch Bridge is a TRUE token gate, not a simple overlay. **It can't be hacked!**
{% endhint %}

{% hint style="warning" %}
Wallet Connect will not work out of the box for Connect Widget. Please upgrade to Connect Widget >= 3.0.0 to ensure Wallet Connect functionality. Refer to [Connect Widget > Wallet Connect section](/manifold-for-developers/resources/widgets/connect-widget/advanced-configuration#wallet-connect) for more details.
{% endhint %}

## Shopify Merch Bridge

Merch Bridge allows you to offer exclusive items and discounts to NFT holders via your Shopify store. We currently support ERC-721 and ERC-1155 tokens on Ethereum Mainnet, Optimism, Polygon, Base, and Sepolia.

## Storefront - The Merch Bridge Shopify App

The [Merch Bridge App](https://apps.shopify.com/manifold-token-gate) is the simplest solution for stores that already have an existing storefront or that are looking to set up a standard storefront experience. The Shopify Merch Bridge App allows configuration of NFT-gates at the product level and leverages pre-built widgets to add "Connect Wallet" and "Add to cart" buttons to specified product pages automatically. Customers experience the same smooth checkout they're used to for non-gated items and can fill their cart with gated and non-gated items to checkout all at once.

For any product where you configure gated access and/or a discount, we'll automatically display a note and button on the product page where customers can connect their wallet and prove NFT ownership. Once complete they can add the item to their cart and continue shopping. When customers are ready to checkout Merch Bridge will automatically double check the cart to ensure items and discounts follow your campaign rules and finalize the Shopify order like normal. Check out the overview video and get started below.

{% embed url="<https://youtu.be/ms_Io2KxqMk>" %}

## Ready to get started?

[Install the App](broken://pages/n8PtnOQgZAIBugzGd8sJ)


# Tutorial

Walkthrough of how to add the Merch Bridge to your Store

In this tutorial, we will cover how to create a gated Shopify Product and configure your store.

When finished you will have crafted a full token gated UX in your store in just a few minutes

<figure><img src="/files/q3MmX3e3Yyfur4HO2DkV" alt=""><figcaption><p>Merch Bridge UX flow for a customer on a token gated product.</p></figcaption></figure>

## Video Walkthrough

if you prefer following a video

{% embed url="<https://www.youtube.com/watch?ab_channel=ManifoldStudio&t=2s&v=9nYzZpv4oKQ>" %}


# Step 1: Product Gate Setup

First we will need to set up the the product gate. This includes the following: modifying the product's settings, creating a Gate, then adding a product and rules to the gate.

## What's a Product Gate?

Manifold Merch Bridge uses the concept of "Product Gates" to configure token gated discounts and access. Product Gates are created and managed in the "Product Gates" section of the Shopify App and specify the following:

* Name
* Start date/time
* End date/time (or specification of "No End Date")
* If the gate uses real time ownership information for token holders or a point in time snapshot of ownership
  * **Snapshots MUST be used for ERC 1155 tokens and can be used for ERC 721**
  * **Non-snapshot gates can ONLY be used for ERC 721 tokens**
* Product being gated
* Redemption limits and inventory
* Audience/allow list rules for what NFT holders get what discounts and benefits on the product


# 1.1: Configure the Product

## What you must do

The following are required for Merch Bridge to function properly:

1. A custom product type "**Manifold**" must be added to the product
2. *Token Exclusive* items <mark style="color:red;">**must**</mark> have a [high price point set](#price-point-settings)

## 1. Set Product Type to "Manifold"

In order for Manifold Merch Bridge widgets to take effect on the product page you are creating a gate on, you must first mark the product as "Type" "**Manifold**"

Find and click the product you plan to gate in the "Product" section of the your Shopify store's admin panel.

![Go to the Products Section of your store and click into the product you want to gate.](/files/cSTjqMYwZUWw7eqB3kuh)

In the "Product organization" section, find the "Product Type" field and type in "*Manifold"* (make sure the M is capitalized)

<figure><img src="/files/qcFmZm1nlTWwQOSglwgw" alt=""><figcaption><p>On the product view. Make sure to set the Product Type to: "Manifold"</p></figcaption></figure>

Click "Save" in the top right corner to save changes.

## **Price Point Settings**

<mark style="color:red;">**For Token Holder Exclusive Products**</mark>

{% hint style="danger" %}
If you intend the product to be an exclusive item for NFT holders, you will need to set an extremely high product price (e.g. $10,000) and apply a discount for NFT holders to bring it back down to the expected price.&#x20;
{% endhint %}

The way Shopify is built, there is no underlying ability to restrict who can sell a product.  Anyone with a bit of technical knowledge can bypass any gate by directly making the Shopify storefront add the product to the cart.

To get around this limitation, we recommend set a prohibitively high price and discount it for token holders. Any individuals bypassing the gate would result in an extremely high cost to that individual, and could be caught and cancelled if needed.


# 1.2: Install Manifold Merch Bridge

Install the Manifold Merch Bridge App to create a Product Gate

If you have not already, download the Manifold Merch Bridge Shopify App to your Shopify store.

[Download the app here!](https://apps.shopify.com/manifold-token-gate)

Log in and choose "Add App". To install the app you'll need to agree to share some information with Manifold. Merch Bridge uses this information to safely and securely create token-gated orders for your customers.

<figure><img src="/files/41EGVs39pBUsdiGCJqBh" alt=""><figcaption></figcaption></figure>


# 1.2: Create a New Product Gate

Now that the product is ready for token gating, create the product gate in the Manifold App

From your admin home page click "Apps" and select the "Manifold - Token Gate" app. From here you can click into the "Product Gates" sub menu to get started.

![](/files/E40XqnRCWuZYM4NoOaqN)

Click the green "New Product Gate" button to get started.

![](/files/3yTbg78HFYfPsav6MmWw)

Start by entering a "Campaign Name" for tracking purposes and the dates you'd like your gate to be active. If this is an evergreen gate with no specific end date simply check the "No End Date" box.

![](/files/wTfRkBXw7FdjARWp2QfM)

## Allow Multiple Redemptions Checkbox

By default, a Product Gate will allow a single redemption for every eligible token specified in the allow list. This means that if a collector uses their token to obtain the token-gated benefit, that token will be "marked" as used and will not be able to be used again for the same benefit.

When configuring your product gate use the "Allow Multiple Redemptions" setting to bypass this. Using this feature is desirable in situations where the Product Gate is not a "one time" benefit, but instead an evergreen experience where the holder of that token can come and go and place multiple orders over time that include the gated product of interest.

![](/files/32TSwvZYQKG928Sqp0xr)

## Snapshot Checkbox

Check the "Snapshot" checkbox if you'd like to allow only wallets holding tokens at a specified time to be eligible for the gate (i.e. a wallet "snapshot").

If you plan to specify ERC-721 tokens for this gate, leaving this unchecked will allow gate eligibility to travel with the NFT (**most common setup**).

![](/files/91cQP0O5dR6Q8uHitodq)

**Snapshots are required for ERC-1155 tokens** as these tokens are fungible in nature and do not have a distinct token ID, or way for us to distinguish which ones have been consumed or not.


# 1.3: Link a Product to the Gate

Now that you have created the Gate, link a product to it

{% hint style="info" %}
Product Gates represent one singular product, and you can only have one active Product Gate per product at any time.
{% endhint %}

## Adding a Product

After you have created the gate. Click into it and scroll down. You will see sections marked **Products** and **Rules**. We will start with Products. Use the green "Add product" button to add a product to your gate.

<figure><img src="/files/5gAzKBphwNXUucsrqfhF" alt=""><figcaption><p>Products setup is this step 1.3. We will cover rule setup in step 1.4</p></figcaption></figure>

Then select the product you want to setup the gate for. If your product has different variations (e.g. sizes) then make sure all the variations you want to be gated are selected

![In this example we are creating a product gate for this hat with different sizes.](/files/uG6WZgW0smZr09ECQvAG)

## Exclusive Product Checkbox

**Check "This is an exclusive product" if the product gate you want to set up is for token holders only.** If you want anyone to be able to purchase the product at the regular price, while offering token holders a discount (configured next in "Rules") then leaves this unchecked.

{% hint style="warning" %}
Remember: Only items with the [required Merch Bridge configuration](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/8n4plerMUJrsrAiaKpc2/~/changes/eEnAMuL5wFbL9t2YpeZ5/shopify-merch-bridge/tutorials/storefront-tutorial/step-3-product-gate-setup/3.1-configure-shopify-products) (see Step 1.1) will be available for selection. If attempting to select a product without proper configuration you will receive an error.
{% endhint %}

![The Exclusive Product Checkbox](/files/0VYmzq9MQHiFFuJJS8e8)

## Edit Product Settings

When you add a product, the Max Quantity Per Order is set at "1" by default and "Total inventory" is not tracked. You can make changes to these settings, and the "Exclusive" product indication, by using the pencil icon in the Products section.

* **Max Quantity Per Order:** This number determines how many of this product can be purchased in one redemption (e.g. "order").
* **Total Inventory:** Merch Bridge will continue selling Exclusive products with 0 inventory as this is the way we've designed the system to be most secure in keeping Exclusive products gated. If you want to track inventory and make an Exclusive product unavailable after a certain number of sales you can do that by setting a total inventory level here which will decrement with each redemption. For this example my Cool Hat! product is exclusive only to certain NFT holders and I know my max inventory is 253.

![](/files/JelHVw55U5IOl09y65IM)

Make any desired changes and "Submit".


# 1.4: Add Rules

Now that we have created the gate and linked a product to it, we will create rules on the product gate. This is setting who will be on the allowlist to get the discount for your product

Here we'll indicate *what* groups of NFT holders will get *what* discount or access for this gate.

Click on the green "Add Rule" button to add a rule.

![](/files/yuDc0kTtyQhDgOdKbYfA)

## Network, Contract Address and Audience Selection

<figure><img src="/files/AEH9b1lGg5xNRhQTUvJi" alt=""><figcaption><p>First select the Network and then enter the contract address</p></figcaption></figure>

First select the Network. Currently **we support the following networks:**

* **Ethereum Mainnet**
* **Optimism**
* **Polygon**
* **Base**
* **Sepolia**

Then enter in the contract address of the NFT collection in the "Contract address" field. For example, I know I want to use Cool Cats for these Cool Hats! so I'm selecting   inputting `0x1A92f7381B9F03921564a437210bB9396471050C`into the form with `ethereum` (mainnet) as the network. Contract addresses can be obtained by finding the official collection pages for projects on marketplaces like [OpenSea](https://opensea.io/collection/cool-cats-nft) and finding the [Etherscan link](https://etherscan.io/address/0x1A92f7381B9F03921564a437210bB9396471050C) for the project's contract.

![You can create allowlist on a full contract, specific token IDs, or on NFT Attributes](/files/bqs7D2jcLLMTJY1yC3eW)

Now that we've got the contract address loaded in we can select from the following:

* **All tokens:** Selecting this option will make holders of any token of the specified contract eligible for redemption. This is great for giving access to "all Cool Cats" or "all holders of my artwork".
* **Select individual token:** Selecting this option will make holders of a single specific token on the specified contract eligible for redemption. For example, I can specify that only token #11 from contract 0x123... is eligible.
* **Filter by token id range:** Selecting this option will allow you to specify a "Minimum" and "Maximum" token ID to indicate a range of eligible tokens. For example, if I were to put "1" and "100" as inputs for this contract, Cool Cats #1 - #100 would only be eligible (Token ID).
* **Filter by attribute:** Selecting this option will allow gating based on specific NFT attributes, even required sets of specific attributes. For example, with my Cool Hat! product I want to ensure only those Cool Cats NFTs that also wear the same hat have access to it (those with the attribute `hats: hat white`).

## Attribute Filtering Explained

To select an attribute (or a combination of attributes that must be necessary) click in the "Search attributes" box and the selection of available attributes will populate. For some projects with many attributes (like PFPs) the set of attributes might take a moment to load. If you are not seeing the correct set of attributes double check the submitted contract address. If that's correct try hitting the "Refresh metadata" button.

Check all the attributes you want to add to the rule. Note that if you select multiple attributes, then the NFT will need to have all of those attributes to receive the discount. (Ex: If I selected both `hats: hat white` AND `body: blue cat skin`) then only holders of Cool Cats with BOTH those attributes would get my discount.

If you want to provide the discount to Either holders of Cool Cats with attribute `hats: hat white` OR `body: blue cat skin` you will create 2 seperate rules.

![Select the attributes you want on the rule then hit "Add". Remember if multiple are selected only tokens that have all of the attributes selected will have access to your discount on this rule.](/files/4GZLAJpR0XNPInFUqYUF)

## Discount Value

Lastly, select the Discount percentage or dollar amount you want to apply to this rule.

![Make sure to set the discount (either as a percentage of the product's price or a fixed $ amount)](/files/7IkWgBsBbtae2mBTUPNq)

Click "Add" when complete to configure the rule!

### Completed Rule

You'll see the rule gets added as a line item to the "Rules" table with details of contract, restriction (all contract tokens, range, or attributes), restriction details, and the desired discount percent for this group of holders.

![What a configured rule will look like](/files/avbYn7tQd13U7Pskkdmf)

Congrats! You have setup all the behind the scenes data for your Product Gate. In the next step we will add App Blocks to your Store's Theme to provide the UX components that your customers will interact with.


# Step 2: Store Theme Setup

Now that we have everything behind the scenes setup. We will configure your Store's theme.

### What Themes Can I Use?

Our app blocks will work on most Shopify Themes. The requirements are:

1. The Theme meets Shopify's recommended [Online Store 2.0](https://shopify.dev/docs/themes/os20) standards

   (Almost all of the Themes you download directly from Shopify meet this standard)
2. The Theme has a specified **Cart** page. (Not just a slide out window for displaying the cart and going to checkout)

For reference, our app blocks will work with all of these themes in the image below (on their most up to date version)

<figure><img src="/files/RgXeT5yAdbqgPAOpsp98" alt=""><figcaption><p>You can use Dawn, Craft, Sense, Ride, and more</p></figcaption></figure>

Make sure you are using `version 10.0.0` or greater on the Theme

<figure><img src="/files/qcaAHMpS9ZAC5uycuBRk" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Previously we had a Dawn template theme where we modified the liquid code directly for you. We no longer suggest this, and instead recommend using our App Blocks for an easier experience. However the previous way still works!
{% endhint %}


# 2.1 The Theme Editor

To setup your store we will use app blocks in the Theme Editor.

### Getting to the Theme Editor

In your Shopify Store page find the "Online Store" section in the navigation on the left. Then select "Themes".

1. Make sure you have downloaded an eligible theme (See Step 2). We will use the free **Dawn** theme we downloaded from Shopify (version 10.0.0 or greater).
2. Click "Customize"

<figure><img src="/files/TNRfC3jJ89EONGdFBdej" alt=""><figcaption><p>Once you have a theme that will work with App Blocks, click Customize</p></figcaption></figure>

3. This will take you to the Theme Editor that will look like this. Here you can use our drag and drop App Blocks to easily configure your store for the Product Gates!

<figure><img src="/files/jz2Ga853CnSMMKMTsAQU" alt=""><figcaption><p>The Theme Editor</p></figcaption></figure>


# 2.2 Product Page Setup

First we will need to add the "Manifold Buy Button" to the product page to handle product gates.

We will configure the product page first. In the drop down selector at the top, Navigate from Home page -> Products -> Default Product

<figure><img src="/files/9KtykmbHuMB5YKe41J52" alt=""><figcaption><p>Navigate to the Default Product Page in the Theme Editor</p></figcaption></figure>

Then In the *Product Information* section:

* click Add Block
* select the "Manifold Buy Button" App Block
* drag it just below the default "Buy buttons"

<figure><img src="/files/SBAwFBd2FYJUTE9PKKw9" alt=""><figcaption><p>Add the Manifold Buy Button on the Product Page</p></figcaption></figure>

{% hint style="info" %}
Note: You do not have to hide the default "Buy buttons" on token gated pages, they will automatically be hidden and replaced with the "Manifold Buy Button"
{% endhint %}

**Make sure to press the Save button in the top right!**

<figure><img src="/files/sf08L0d1QMdeKRLgFZln" alt=""><figcaption><p>Press Save on the Product Page.</p></figcaption></figure>

### Optional Customizations

You can also modify the Manifold buy button further for

1. The confetti emojis after a holder makes a successful redemption on the product
2. The Opensea link to get some tokens (displayed when a signed in wallet has not tokens on your product rules)&#x20;

<figure><img src="/files/Qm0tFzY8s6pNV7LCjCT3" alt="" width="357"><figcaption><p>Just click into the block and you will see the following fields that you can customize.</p></figcaption></figure>


# 2.3 Cart Page Setup

On the Cart Page we need to add the "Manifold Items (Cart)" block as a new section and the "Manifold Checkout Buttons" block.

### Navigate to the Cart Page

Use the drop down to navigate to the Cart page of the Theme

<figure><img src="/files/jOmh5WzaWPlqYpFACfen" alt="" width="563"><figcaption><p>Navigate to the Cart Page</p></figcaption></figure>

### Add the Manifold Checkout Buttons block

In the *Subtotal* section

* click Add block
* Select the Manifold Checkout Buttons

<figure><img src="/files/uX5jRXYmBvSrPxh0oTqd" alt="" width="563"><figcaption><p>Add the Manifold Checkout Buttons</p></figcaption></figure>

{% hint style="info" %}
Just like with the Manifold Buy Button, you do not have to hide the default Checkout button. The Checkout button will load if an item in your cart is token gated, and replace the normal checkout button.
{% endhint %}

### Add the Manifold Items (Cart)

* Click Add section
* navigate to Apps and select **Manifold Items (Cart)**
* Drag the new section up to where the Items section is
* **Hide the existing Items section!**

<figure><img src="/files/VJZGinQmX1BEKonDSIYd" alt="" width="563"><figcaption><p>Add the Manifold Items block as a new section. Drag it up. Then hide the default Items section.</p></figcaption></figure>

{% hint style="info" %}
Note: Make sure to hide the default Items section in the Cart page. As the Manifold Items (Cart) component can handle token gated and non-token gated products.&#x20;
{% endhint %}

**Once again, make sure to hit Save** in the top right.

<figure><img src="/files/sf08L0d1QMdeKRLgFZln" alt=""><figcaption></figcaption></figure>

**Congrats! If you followed Steps 1-2, You now have a fully configured product gate!**


# Advanced Configuration

### Specifying Manifold-Widget Versions in App Blocks

**We recommend using the default values** and not touching the advanced settings. The defaults are the most up to date & stable version of Manifold's`connect-widget` and `campaign-widget.`

However, if you are a merchant that is transitioning from modifying liquid code directly to app blocks, and have been using older versions of the widgets, we allow you to specify version in the corresponding app blocks in the Theme Editor:

1. Manifold Buy Button (Product Page)
2. Manifold Checkout Buttons (Cart Page)

![](/files/9ThEvYLTXbmI4JEnhW2h)

![](/files/V07MRb0O6tjzVOOHdpsq)


# FAQ / Error Help

<details>

<summary>Wallet Connect is stuck loading on my product page</summary>

1. Make sure you have no other token gating apps installed that inject Wallet Connect code onto your theme. If you have any of these apps installed you may have to uninstall them
2. If you are using a custom domain (i.e. your shop URL does not contain `.myshopify.com`) -> Go to the Welcome tab of the Manifold Merch Bridge App and make sure you have entered in a Wallet Connect Project ID. If you do not have one you can create one [here](https://walletconnect.com/)

</details>

<details>

<summary>Checkout Error: Please clear cart items as currencies have changed</summary>

If you are seeing this error you changed your store currency in the shopify settings, but have a Market (region that you accept sales in Shopify) that does NOT have a consistent currency as your store currency.

To fix:

1. Go to your store's settings in shopify and select the currency you want to support.

<img src="/files/cKSZpxFoQcWJEDcaX84s" alt="" data-size="original">

2. Then still within your stores settings in Shopify, search for markets. For each market here you must set the currency to your store's currency

<img src="/files/tnOUJ5G6V7rpwC7OP7fb" alt="" data-size="original">

<img src="/files/GSSsSXA7H871oQU90lxv" alt="" data-size="original">

</details>


# Reference

{% hint style="warning" %}
**IMPORTANT**: The Merch Bridge relies on Shopify's Draft Order system to gate products. Due to this limitation, support for non-native checkout flows may vary.  Please contact us if you use a non-native checkout flow.
{% endhint %}

{% hint style="warning" %}
**PREREQUISITE**: Make sure you have installed the [Merch Bridge App](broken://pages/n8PtnOQgZAIBugzGd8sJ)
{% endhint %}

These guides will help you add the Merch Bridge to your Shopify Storefront Themes, and set up gated products.

Before you can gate any products, you will need to follow the [Liquid Theme Setup](broken://pages/z5zykf60tbytnqH5kkjZ) guide.


# Product and Gate Configuration

There are two parts to configuring a gated product

* [Configuring the Shopify Product](/manifold-for-developers/shopify-merch-bridge/reference/product-and-gate-configuration/shopify-products) itself
* [Configuring the Gate](/manifold-for-developers/shopify-merch-bridge/reference/product-and-gate-configuration/product-gates) for which products it grants access to, which token holders get access and what perks they receive


# Shopify Products

This page describes how to configure Shopify Products to be used within the Manifold Merch Bridge

## Product Type Configuration

In order for Manifold Merch Bridge widgets to take effect on products included in Merch Bridge campaigns, you must first mark products as "Type" "Manifold" in their product settings.

Find and click the product you plan to gate in the "Product" section of the your Shopify store's admin panel.

![](/files/bNifzD4WFuH3zNm69Oef)

In the "Product organization" section click in the Type box and choose "+ Add custom type".

![](/files/P3jP4RFReDlrFJnCaNeP)

Add custom type "Manifold".

![](/files/BaXD6mBmpPzt5R0xOGVG)

Click "Save" in the top right corner to save changes.

### *<mark style="color:red;">**Token Holder Exclusive Items**</mark>*

If you intend the product to be an exclusive item for NFT holders, you would need to set an extremely high product price (e.g. $10,000) and apply a discount for NFT holders to bring it back down to the expected price.

Why do we do this? The way Shopify is built opens shops up to vulnerabilities and exploits if a product ID is known. Essentially it allows hackers to modify the front-end code of the website and build a cart that includes that product ID and check out.

This isn't such a bad thing for products you intend to sell at a set price in your store (the more sales the merrier, right?), but for upholding community integrity with holder exclusive items, whether paid or free, we recommend setting inventory to zero (with "Track quantity" enabled and "Continue selling when out of stock" enabled) and managing the volume of the products inside Merch Bridge campaigns.


# Product Gates

Click the green "New Product Gate" button to get started.

<figure><img src="/files/JlP6Lo4Jp1DPAjXN8j4u" alt=""><figcaption></figcaption></figure>

Start by entering a "Gate Name" for tracking purposes and the dates you'd like your gate to be active. If this is an evergreen gate with no specific end date simply check the "No End Date" box.

<figure><img src="/files/H7dCvkOIpJV7UaGrpYd0" alt=""><figcaption></figcaption></figure>

## Snapshot Checkbox

Check the "Snapshot" checkbox if you'd like to allow only wallets holding tokens at a specified time  to be eligible for the campaign (i.e. a wallet "snapshot").

If you plan to specify ERC-721 tokens for this gate, leaving this unchecked will allow gate eligibility to travel with the NFT until consumed (**the most common setup**).

![](/files/91cQP0O5dR6Q8uHitodq)

A snapshot date/time is required for campaigns using ERC-1155 tokens or if wallet-based redemption is desired for ERC-721 token types. Snapshots are required for ERC-1155 tokens as these tokens are fungible in nature and do not have a distinct token ID, or way for us to distinguish which one has been consumed or not for a campaign. Thus, taking a picture of the wallets that hold these tokens at a particular time and "crediting" the wallet addresses based on the number of tokens held is how we keep proper track of redemptions.


# Gate Products

## Adding a Product to the Gate

Inside your gate in the Manifold Merch Bridge App, use the green "Add product" button to add products to your gate's Products list.

![](/files/oBEtXNsHzOboup0JOz0i)

Select the desired product to add from your list.&#x20;

![](/files/nCWm1kIthjFdJISznWsd)

When you add a product, the Redemption Limit is set at "1" by default and "Total inventory" is not tracked.

* **Redemption limit:** The maximum number of redemptions for a product on a gate. If I changed this to "2" then anyone with the NFT I specify later in the Rules section would be able to claim 2 of these hats, each with the discount configured in that rule (e.g. 2 here would be 2 hats with 100% off on each). For this example I'm going to keep it at 1 (**most common setting**).
* **Total Inventory:** Merch Bridge will continue selling products with 0 inventory as this is the way we've designed the system to be most secure when selling NFT-exclusive items (review this concept [here](https://app.gitbook.com/o/FkM3zqPi1O0VypWXgiUZ/s/8n4plerMUJrsrAiaKpc2/~/changes/l1EukdQvTfsiCj1ITaAK/shopify-merch-bridge/product-gate-setup)). If you want to track inventory and make an NFT-exclusive product unavailable after a certain number of sales you can do that by setting a total inventory level here which will decrement with each redemption. For this example my Cool Hat! product is exclusive only to certain NFT holders and I know my max inventory is only 253.

![](/files/1oEu36fE1eV24Qfp2gVg)

Make any desired changes and "Submit".


# Rules

Here we'll indicate *what* groups of NFT holders will get *what* discount or access.

Click on the green "Manage Rules" button to open the Campaign Rule Manager.

![](/files/Pma98Tdk21MQi6CDsC8g)

Select the "Product" from the drop down for which you want to make a token gated rule. You'll notice that only items you've added to your product list (previous step) are available. This is on purpose and you'll need to add desired products there first before selecting.

![](/files/6uxa1P4C5pT4ciOFWNpL)

Next, select the "Discount %" you want to apply to this product with this rule.

{% hint style="danger" %}
If you are giving *only* access to this product to certain NFT holders (NFT-Exclusive, yet still regular price), please ensure that your product has an artifically high price (e.g. $10,000) and apply a discount for NFT holders to bring the product back down to the expect NFT-holder price.\
This is necessary due to limitations of Shopify's native cart system. If you did not do this, anyone could programatically add the item to the cart and checkout.
{% endhint %}

![](/files/LKNIntHKDYivP83PU5bR)

Next we'll specify the NFT holding requirements for this rule with the "Restricted rule" box!

## Restricted Rule

This is where the NFT magic happens. Check the "Restricted rule" box to open up the UI for specifying which NFTs you want to make sure customers have to be eligible.

![](/files/4A9NZnmwASSM7rQaDzcn)

Start by entering in the contract address of the NFT collection in the "Contract address" box. For example, I know I want to use Cool Cats for these Cool Hats! so I'm inputting `0x1A92f7381B9F03921564a437210bB9396471050C` into the form. Contract addresses can be obtained by finding the official collection pages for projects on marketplaces like [LooksRare](https://looksrare.org/collections/0x1A92f7381B9F03921564a437210bB9396471050C?queryID=aa575abd946bb9d262f29f3732f249ef\&queryIndex=prod_tokens) or [OpenSea](https://opensea.io/collection/cool-cats-nft) and clicking through to the projects [Etherscan link for the contract](https://etherscan.io/address/0x1A92f7381B9F03921564a437210bB9396471050C).

![](/files/aItzAZv2aIgn2sdY0WIs)

Now that we've got the contract address loaded in we can select from the following:

* **All tokens:** Selecting this option will make holders of any token of the specified contract eligible for redemption (one token = one redemption). This is great for giving access to "all Cool Cats" or "all holders of my artwork".
* **Filter by token id range:** Selecting this option will allow you to specify a "Minimum" and "Maximum" token ID to indicate a range of ho is eligible. For example, if I were to put "1" and "100" as inputs for this contract, Cool Cats #1 - #100 would only be eligible (Token ID).
* **Filter by attribute:** Selecting this option will allow gating based on specific NFT attributes, even required sets of specific attributes. For example, with my Cool Hat! product I want to ensure only those Cool Cats NFTs that also wear the same hat have access to it (attribute "hat: hat white".

## Attribute Filtering

To select an attribute simply click in the "Search attributes" box and the selection of available attributes will populate. For some projects with many attributes (like PFPs) the set of attributes might take a moment to load. If you are not seeing the correct set of attributes double check the submitted contract address. If that's correct it may be that our system needs a nudge to re-scan the collection for the updated set. You can do this by clicking the "Refresh metadata" button if needed.

When selecting an attribute check the box next to the attribute and you will see the attribute populate below the search bar in a grey box. Click "Add" to add this rule

![](/files/4GZLAJpR0XNPInFUqYUF)

You'll see the rule gets added as a line item to the "Rules" table with details of contract, restriction (all contract tokens, range, or attributes), restriction details, and the desired discount percent for this group of holders.

![](/files/avbYn7tQd13U7Pskkdmf)

## Attribute Combinations

If more than one attribute is selected before "adding" the rule, this will function as an "and", requiring holders' NFTs to have attribute 1 AND attribute 2 AND attribute 3 (and so on) to be eligible for the claim.

In the below example we've created a rule that only Cool Cats with "face - glasses" AND "tier - exotic\_1" traits are eligible for the 100% discount of the Mani Hat. Be sure to double check your intended audience when combining attributes. For example, this combination of two traits represents [only 12 NFTs](https://opensea.io/collection/cool-cats-nft?search\[sortAscending]=true\&search\[sortBy]=PRICE\&search\[stringTraits]\[0]\[name]=tier\&search\[stringTraits]\[0]\[values]\[0]=exotic_1\&search\[stringTraits]\[1]\[name]=face\&search\[stringTraits]\[1]\[values]\[0]=glasses) from the 10k collection.

![](/files/KmamaKv7FKD2cuJRTmqX)


# Custom Themes

If you are a Shopify developer and wish to add the Manifold Merch Bridge to your own custom theme, please see the README and merch\_bridge.diff files in our [themes repository](https://github.com/manifoldxyz/manifold-templates/tree/main/shopify/themes) to get a sense of the modifications that need to be made.

At the core, two **snippets** are added:

1. snippets/manifold-campaign-widget.liquid
2. snippets/manifold-checkout-widget.liquid

These two **snippets** add the basic product page and checkout functionality to the theme.

With these snippets added, they still need to be rendered into the appropriate product and checkout pages, as well as replace certain default elements on the theme (e.g. the 'Add To Cart' and 'Checkout' buttons). Those are changes to the various **sections** in the theme.


# Updating to the Latest Version

To update to the latest version of the Manifold Merch Bridge, you will need to modify two files.

## snippets/manifold-campaign-widget.liquid

Update the `<script>` and `<link>` tags to reference the latest widget versions of the [Connect Widget](/manifold-for-developers/resources/widgets/connect-widget) and [Campaign Widget](/manifold-for-developers/resources/widgets/campaign-widget).

{% hint style="warning" %}
If this file contains:

```html
<div data-widget="m-oauth-connect" ...></div>
```

please replace that with:

```html
<div data-widget="m-connect" data-multi="true" data-detect-app="true"></div>
```

and modify the #m-connection CSS to read

```css
#m-connection {
  display: block !important;
  width: 100% !important;
}
```

{% endhint %}

## snippets/manifold-checkout-widget.liquid

Update the `<script>` and `<link>` tags to reference the latest widget versions of the [Connect Widget](/manifold-for-developers/resources/widgets/connect-widget) and [Campaign Widget](/manifold-for-developers/resources/widgets/campaign-widget).

{% hint style="warning" %}
If this file contains:

```html
<div data-widget="m-oauth-connect" ...></div>
```

please replace that with:

```html
<div data-widget="m-connect" data-multi="true" data-detect-app="true"></div>
```

and modify the #m-connection CSS to read

```css
#m-connection {
  display: block !important;
  width: 100% !important;
}
```

{% endhint %}


# UI Configuration Options

Each of the following configuration options can be made by adding props to the `m-add-to-cart` div, within the theme's `snippets/manifold-campaign-widget.liquid` file.

## OpenSea Link

The Shopify Gate can show an OpenSea link to get NFTs if you are on a product page and you don't have any qualifying NFTs.  To display and configure the link location, simply add the prop:

```
data-opensea-collection-slug="<YOUR_OPENSEA_SLUG>"
```

The OpenSea slug is the string which leads to your collection.  For example, the slug "**replicator-by-mad-dog-jones**" will result in a link to <https://opensea.io/collection/replicator-by-mad-dog-jones>

## Campaign Confetti

Whenever a product is added, we show a confetti animation.  By default, they are the 👕 and 🧢 emojis.  You can change this to your own emoji by adding the prop:

```
data-add-to-cart-emojis="<CONFETTI_EMOJI>"
```

You can disable the confetti by adding the prop

```
data-no-emojis="true"
```

## Refreshing non-Dawn Theme Cart Counters

If you've implemented your own cart icon and want the counter to refresh whenever a product is added, it must be a liquid Section.  To get this Section counter to refresh, add the prop:

```
data-cart-icon-section="<YOUR_SECTION(S)_COMMA_SEPARATED>"
```

This will cause the section to update whenever the cart is updated.  For reference, the default Dawn theme section is called "cart-icon-bubble" and can be found under "Sections: cart-icon-bubble.liquid".  Please check this out to see how they show cart counters.

{% hint style="danger" %}
Make sure your div id is the same as your section name!
{% endhint %}

## Redirect To Cart Page When Items Are Added

You can force a redirect to the cart page when an item is added. To do this, add the prop:

```
data-redirect-to-cart="true"
```

## Force Reload When Items Are Added

You can also force a page reload when items are added.  To do this, add the prop:

```
data-reload-page-on-add-to-cart="true"
```

## Using Custom Product Properties

You may want some [custom properties](https://ui-elements-generator.myshopify.com/pages/line-item-property) for your products (e.g. an Engraving message).  If you have added a custom input property field, you MUST configure the Merch Bridge to know about these properties.  To do this, add the prop:

```
data-product-properties="your-property-field-id"
```

{% hint style="danger" %}
Make sure the property field id you are using matches the id of the input field of the property.
{% endhint %}

You can add multiple properties by putting a comma separated list of property field id's.

## Changing Text of Buttons

The text of the various buttons can be changed as well.  The props to configure are:

**data-add-to-cart-text**: Default "Add to Cart"

**data-apply-discount-text**: Default "Apply Discount"

**data-nft-available-title**: Default "Discount Available"

**data-nft-available-message**: Default "LFG! Apply Discount for more info"

**data-nft-eligible-title**: Default "NFT Discount Eligible"

**data-nft-eligible-message**: Default "Use "Connect Wallet" button to apply discounts"

**data-no-eligible-tokens-message**: Default "You don't have eligible tokens."

**data-no-eligible-tokens-title**: Default "Token Exclusive"

**data-checkout-text** (applied to `m-checkout`): Default "Checkout with NFTs"

**data-clear-cart-text** (applied to `m-checkout`): Default "Clear Cart"

**data-already-existing-checkout-message** (applied to `m-checkout`): Default "Already existing checkout, click here to complete."


# Advanced Usage

## Observable Window Events

There are a few window events you may want to listen to in order to dynamically update your storefront.  Below is a list of available events.

### m-campaign\_cart\_updated

This event is emitted whenever token gated items are added to or removed from the cart.

## Token Checker Widget

<figure><img src="/files/3cQcnfL83XZ8BPEq2gLl" alt=""><figcaption><p>Allow collectors to check if a token has been redeemed on a campaign</p></figcaption></figure>

When a token is redeemed on a campaign, there is no change to the token's on chain metadata. So, we have included a widget that allows collectors to check if that token has already been used. \
\
It can be included by doing the following:

```
<div
   data-widget="m-token-checker"
   data-campaign-id="<your-campaign-id>"
></div>
```

## Token Set Gating

Token set gating is gating a product based on whether or not someone has a complete set of tokens.  This will only work via a snapshot and will require multiple steps, including minting tokens to Sepolia.  The following are instructions on how to do this in conjunction with [Manifold Studio](https://studio.manifold.xyz).

{% hint style="info" %}
We will be improving this feature in the future to make it more straightforward.
{% endhint %}

### Step 1: Determine all users that have a complete set

Using the [Snapshot Tool](/manifold-for-developers/tools-and-apis/snapshot-tool), take a snapshot of all holders of the relevant tokens.  Once you have this list, determine all the wallets that have the complete set, and the number of gated items you wish them to receive.

### Step 2: Create a Sepolia testnet token

Using [Manifold Studio](https://studio.manifold.xyz), create a Sepolia ERC1155 testnet token representing the complete set.  Choose an name, image and description that suits this.  Mint a token to yourself, and mint the token to all the addresses above in the quantities desired.  For further information on using Manifold Studio, [please see these docs](https://docs.manifold.xyz/v/manifold-studio/).

### Step 3: Create a Token gate using the Sepolia token

Configure a Product Gate using this token (snapshot based gate).  This will gated access to the product to all the wallets you sent the Sepolia token to in step 2.


# Common Issues

And their solutions!

<details>

<summary>Config Error on the Connect Widget</summary>

<img src="/files/irMrazHY8HVYwlp438j6" alt="Config Error" data-size="original">

#### Custom Domain

This error will happen if your store has a custom domain and does not use the default `helloworld.myshopify.com` domain.&#x20;

To fix this, [make an app](https://docs.manifold.xyz/v/manifold-for-developers/client-widgets/connect-widget/tutorial#get-a-developer-account-and-create-an-app) in the [Manifold Developer Portal](https://developer.manifoldxyz.dev/apps) with the following settings:

* **App Name**: \<YOUR STORE NAME> (e.g. "My Cool Store")
* **Description**: \<YOUR STORE DESCRIPTION>
* **Grant Type**: Token
* **Redirect URI**: \<YOUR CUSTOM DOMAIN **WITHOUT** TRAILING SLASH> (i.e. `https://helloworld.com`)

Once you do this, the Shopify Merch Bridge will recognise your custom domain.

#### Changed Store Name

This will also happen if you changed your store name.  To fix it, simply uninstall and reinstall the Manifold Token Gate by visiting **`<your shopify store>`.myshopify.com/admin**

</details>

<details>

<summary>Quantity is always 1 when adding a gated item</summary>

The Manifold Merch Bridge relies on `<div>` class names to retrieve the selected quantity when adding a gated item to the cart. This issue arises when you are using a custom theme and the `<div>` tag enclosing quantity input field does not have the proper id.

To fix this issue, ensure that the parent `<div>` of the quantity input box has the class **quantity**.&#x20;

#### Example:

The Debut theme needed the following modification within the `section/product-template.liquid` file:

```html
<div class="quantity">
  <label for="Quantity-{{ section.id }}">{{ 'products.product.quantity' | t }}</label>
  <input type="number" id="Quantity-{{ section.id }}"
    name="quantity" value="1" min="1" pattern="[0-9]*"
    class="product-form__input product-form__input--quantity" data-quantity-input
  >
</div>
```

</details>

<details>

<summary>Cart icon not updating when a gated item is added</summary>

This can occur if you are using a custom theme where the section's `<div>` does not match the section name. To correct this, add ensure the id prop of the section  `<div>` matches the section name, and ensure that the `data-cart-icon-section` property is set with the corresponding sections that contain the cart icon (see [UI Configuration Options](/manifold-for-developers/shopify-merch-bridge/reference/ui-configuration-options#refreshing-non-dawn-theme-cart-counters)).

#### Example:

The Debut theme needed the following customizations because the cart icon is located within `sections/header.liquid`:

1. snippets/manifold-campaign-widget.liquid

   <pre class="language-html"><code class="lang-html"><strong>&#x3C;div data-widget="m-add-to-cart" data-cart-icon-section="header">
   </strong>&#x3C;/div>
   </code></pre>

2. snippets/header.liquid (modify so that the header div has the id value of 'header')

   ```html
   <div id="{{ section.id }}" data-section-id="{{ section.id }}" data-section-type="header-section" data-header-section>
   ```

</details>

<details>

<summary>Add to cart button hidden</summary>

When setting up a gate there is a checkbox to indicate if a token exclusive or not. If “exclusive” is checked off the regular add to cart will not show.

</details>


# Getting Started

## Step 1: Creating a Developer Account

Visit <https://developer.manifoldxyz.dev/> to create a developer account.

## Step 2: Create an App&#x20;

Login to your account on <https://developer.manifoldxyz.dev/> and go to the **Dashboard**. The Dashboard is where you will see a list of your [**Apps**](/manifold-for-developers/resources/apps).

<img src="/files/wGmCts5qyicDWENRRRiQ" alt="" data-size="original">

Every widget must be configured with an [**App**](/manifold-for-developers/resources/apps).  An [**App**](/manifold-for-developers/resources/apps) allows an end user to see who they are interacting with.

Click <mark style="color:blue;">`CREATE APP`</mark> to make a new [**App**](/manifold-for-developers/resources/apps). You will be greeted with the following screen:

![](/files/bb8OLqQQTLO5Oh3Zn1uQ)

* **App Name**: This name is customer facing and will be used for authentication messaging
* **App Description**: This is a description only visible to you on the developer dashboard
* **Redirect URI**: Put your domain url
* **Grant Type**: This is the authentication method. Choose `Signature Grant`

Click <mark style="color:blue;">`CREATE`</mark>.

## Step 3: Add Manifold Widgets to your Website

Add widgets to your website with just a few lines of code. Pick one of the following guides that suit what you want to do.

<details>

<summary>HTML</summary>

[Basics](/manifold-for-developers/guides/html#basics)

[Examples](/manifold-for-developers/guides/html#examples)

</details>

<details>

<summary>React</summary>

[Basics](/manifold-for-developers/guides/react#basics)

[Examples](/manifold-for-developers/guides/react#examples)

</details>

<details>

<summary>Squarespace</summary>

Basics

Examples

</details>

<details>

<summary>Vercel</summary>

Basics

Examples

</details>

<details>

<summary>Vue</summary>

[Basics](/manifold-for-developers/guides/vue#basics)

[Examples](/manifold-for-developers/guides/vue#examples)

</details>

<details>

<summary>Wix</summary>

Basics

Examples

</details>

## Example Repository

{% embed url="<https://github.com/manifoldxyz/manifold-templates>" %}


# HTML

## Basics

### Installing Widgets

Each widget comes with a Javascript and CSS file.  To add a widgets to your website, you will need to add its corresponding Javascript and CSS to the `<head>` of your website.

**Example:**

```html
<head>
  <script src="https://connect.manifoldxyz.dev/latest/connect.umd.min.js"></script>
  <link rel="stylesheet" href="https://connect.manifoldxyz.dev/latest/connect.css">
</head>
```

The uri for the Javascript and CSS for each widget is as follows:

```
https://<WIDGET_NAME>.manifoldxyz.dev/<WIDGET_VERSION>/<WIDGET_NAME>.umd.min.js
https://<WIDGET_NAME>.manifoldxyz.dev/<WIDGET_VERSION>/<WIDGET_NAME>.css
```

{% hint style="warning" %}
If you always want the latest version, use **latest** for the \<WIDGET\_VERSION>. However, due to the changing nature of the codebase, it is possible that the "newer" **latest** is no longer compatible with the "previous" **latest** used to develop the app. It is thus recommended that you use a specific version of a widget for consistent results.
{% endhint %}

You can find a list of widgets, versions and links [here](/manifold-for-developers/resources/widgets/directory).

### Using Widgets

To use a widget, simply add a `<div>` in the `<body>` of your website corresponding to the widget you want to use.

**Example:**

<pre class="language-html"><code class="lang-html">&#x3C;body>
  &#x3C;div  
    data-widget="m-connect"
    data-grant-type="&#x3C;your app grant type, `signature` or `token`>"
    data-client-id="&#x3C;your app client id>"  
    data-app-name="&#x3C;your app name>"  
    data-network="&#x3C;your desired network's chain id>"
  >
<strong>  &#x3C;/div>
</strong><strong>&#x3C;/body>
</strong></code></pre>

You can find a list of widgets, versions and links [here](/manifold-for-developers/resources/widgets/directory).

### Styling

Widgets are styled using standard CSS.

Some widgets may include links pointing to additional stylesheets. These additional stylesheets (usually named `<WIDGET_NAME>.manifold-light.css` or `<WIDGET_NAME>.manifold-dark.css`) build **on top of** the base stylesheet (usually named `<WIDGET_NAME>.css`), thus the additional stylesheets should be included **after** the base one.

## Examples

Examples of commonly used features can be found [here](https://github.com/manifoldxyz/manifold-templates/tree/main/html). Please feel free to use this as a starting point to building your own dApp.




---

[Next Page](/llms-full.txt/1)

