> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sei.io/llms.txt
> Use this file to discover all available pages before exploring further.

# ERC-1155 Interaction

> Reading and writing ERC-1155 multi-token contracts on Sei with viem and ethers

export const AddSeiButton = ({network = 'mainnet', label = 'Add Sei to MetaMask'}) => {
  const SEI_MAINNET_CHAIN_PARAMS = {
    chainId: '0x531',
    chainName: 'Sei Network',
    rpcUrls: ['https://evm-rpc.sei-apis.com'],
    nativeCurrency: {
      name: 'Sei',
      symbol: 'SEI',
      decimals: 18
    },
    blockExplorerUrls: ['https://seiscan.io']
  };
  const SEI_TESTNET_CHAIN_PARAMS = {
    chainId: '0x530',
    chainName: 'Sei Testnet',
    rpcUrls: ['https://evm-rpc-testnet.sei-apis.com'],
    nativeCurrency: {
      name: 'Sei',
      symbol: 'SEI',
      decimals: 18
    },
    blockExplorerUrls: ['https://testnet.seiscan.io']
  };
  const chainParams = network === 'testnet' ? SEI_TESTNET_CHAIN_PARAMS : SEI_MAINNET_CHAIN_PARAMS;
  const [status, setStatus] = useState(null);
  const [isHovered, setIsHovered] = useState(false);
  const [isBusy, setIsBusy] = useState(false);
  const addOrSwitchSeiNetwork = async params => {
    if (typeof window === 'undefined' || !window.ethereum) {
      throw new Error('MetaMask is not installed');
    }
    const ethereum = window.ethereum;
    try {
      await ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{
          chainId: params.chainId
        }]
      });
    } catch (switchError) {
      if (switchError && switchError.code === 4902) {
        await ethereum.request({
          method: 'wallet_addEthereumChain',
          params: [params]
        });
      } else {
        throw switchError;
      }
    }
  };
  const onClick = async e => {
    e.preventDefault();
    setIsBusy(true);
    setStatus(null);
    try {
      await addOrSwitchSeiNetwork(chainParams);
      setStatus({
        type: 'success',
        message: `${chainParams.chainName} added or switched.`
      });
    } catch (err) {
      const message = err && err.message ? err.message : 'Failed to add or switch network.';
      setStatus({
        type: 'error',
        message
      });
    } finally {
      setIsBusy(false);
    }
  };
  return <span className="inline-flex flex-col items-start gap-1">
      <button type="button" onClick={onClick} disabled={isBusy} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} className="inline-flex items-center gap-1 px-3 py-1.5 text-white transition-colors min-w-[160px]" style={{
    backgroundColor: isHovered ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
    color: '#ffffff',
    fontFamily: 'var(--sei-font-mono)',
    textTransform: 'uppercase',
    letterSpacing: '0.04em',
    fontSize: '10px',
    opacity: isBusy ? 0.7 : 1,
    cursor: isBusy ? 'default' : 'pointer'
  }}>
        {isBusy ? 'Adding…' : label}
      </button>
      {status && <span className={status.type === 'error' ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'} style={{
    fontSize: '11px'
  }}>
          {status.message}
        </span>}
    </span>;
};

export const SandboxEmbed = props => {
  const {src, kind = 'codesandbox', title, description, height, label} = props || ({});
  const KINDS = {
    codesandbox: {
      name: 'CodeSandbox',
      host: 'codesandbox.io',
      defaultHeight: 500
    },
    remix: {
      name: 'Remix IDE',
      host: 'remix.ethereum.org',
      defaultHeight: 620
    },
    stackblitz: {
      name: 'StackBlitz',
      host: 'stackblitz.com',
      defaultHeight: 500
    }
  };
  const meta = KINDS[kind] || KINDS.codesandbox;
  const parsedHeight = Number(height);
  const frameHeight = Number.isFinite(parsedHeight) && parsedHeight > 0 ? parsedHeight : meta.defaultHeight;
  const allowAttr = 'clipboard-read; clipboard-write';
  const [loaded, setLoaded] = useState(false);
  const [btnHover, setBtnHover] = useState(false);
  const HAIRLINE = 'rgba(128, 128, 128, 0.25)';
  const surfaceStyle = {
    backgroundColor: 'rgba(128, 128, 128, 0.08)'
  };
  const monoStyle = {
    fontFamily: 'var(--sei-font-mono)'
  };
  const cardClass = 'not-prose w-full rounded-lg border overflow-hidden my-4';
  const headerClass = 'flex items-center justify-between gap-3 px-4 py-2.5 border-b';
  const buttonStyle = {
    backgroundColor: btnHover ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
    color: '#ffffff',
    fontFamily: 'var(--sei-font-mono)',
    textTransform: 'uppercase',
    letterSpacing: '0.04em',
    fontSize: '10px',
    cursor: 'pointer'
  };
  const PlayIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
			<path d="M8 5v14l11-7z" />
		</svg>;
  const ExternalIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
			<path d="M14 5h5v5" />
			<path d="M19 5l-9 9" />
			<path d="M19 14v5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h5" />
		</svg>;
  return <div className={cardClass} style={{
    borderColor: HAIRLINE
  }}>
			<div className={headerClass} style={{
    ...surfaceStyle,
    borderBottomColor: HAIRLINE
  }}>
				<div className="flex flex-col min-w-0">
					<span className="text-sm font-medium text-neutral-900 dark:text-white truncate" style={monoStyle}>
						{title || meta.name}
					</span>
					<span className="text-xs text-neutral-500 dark:text-neutral-500">{meta.name}</span>
				</div>
				<div className="flex items-center gap-3 shrink-0">
					{src ? <a href={src} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors">
							Open <ExternalIcon />
						</a> : null}
					{!loaded && src ? <button type="button" onClick={() => setLoaded(true)} onMouseEnter={() => setBtnHover(true)} onMouseLeave={() => setBtnHover(false)} className="inline-flex items-center gap-1.5 px-3 py-1.5 transition-colors" style={buttonStyle}>
							<PlayIcon />
							{label || 'Load editor'}
						</button> : null}
				</div>
			</div>

			{description ? <div className="px-4 pt-3 pb-1 text-sm text-neutral-600 dark:text-neutral-400">{description}</div> : null}

			{!src ? <div className="px-4 py-6 text-sm text-red-600 dark:text-red-400" style={monoStyle}>
					SandboxEmbed: missing required `src`.
				</div> : loaded ? <iframe src={src} title={title || meta.name} className="w-full block border-0" style={{
    height: frameHeight + 'px',
    backgroundColor: 'rgba(128, 128, 128, 0.05)'
  }} allow={allowAttr} loading="lazy" allowFullScreen /> : <button type="button" onClick={() => setLoaded(true)} className="w-full flex flex-col items-center justify-center gap-2 text-neutral-500 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200 transition-colors" style={{
    height: frameHeight + 'px',
    cursor: 'pointer',
    ...surfaceStyle
  }}>
					<PlayIcon />
					<span className="text-sm" style={monoStyle}>Click to load {meta.name}</span>
					<span className="text-xs">Loads {meta.host} in an embedded editor</span>
				</button>}
		</div>;
};

# ERC-1155 Interaction

ERC-1155 is the multi-token standard — a single contract can hold both fungible tokens (like gold coins in a game) and non-fungible tokens (like unique items), with efficient batch operations built in. Standard ERC-1155 contracts work on Sei without modification.

## Try it: deploy an ERC-1155

Compile and deploy a real ERC-1155 to Sei testnet from the browser — the Remix sandbox preloads an OpenZeppelin-based `DemoMultiToken` with a public `mint(id, amount)`. Then point the read/write patterns below at your deployed address.

First, add Sei testnet to your wallet:

<AddSeiButton network="testnet" label="Add Sei testnet" />

In Remix, compile under **Solidity Compiler**, then **Deploy & Run** with **Environment** set to **Injected Provider — MetaMask**.

<SandboxEmbed kind="remix" src="https://remix.ethereum.org/?#activate=solidity,fileManager&code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yNDsKCmltcG9ydCAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHMvdG9rZW4vRVJDMTE1NS9FUkMxMTU1LnNvbCI7Cgpjb250cmFjdCBEZW1vTXVsdGlUb2tlbiBpcyBFUkMxMTU1IHsKICAgIHVpbnQyNTYgcHVibGljIGNvbnN0YW50IEdPTEQgPSAwOwogICAgdWludDI1NiBwdWJsaWMgY29uc3RhbnQgU0lMVkVSID0gMTsKICAgIGNvbnN0cnVjdG9yKCkgRVJDMTE1NSgiaHR0cHM6Ly9leGFtcGxlLmNvbS9hcGkvaXRlbS97aWR9Lmpzb24iKSB7fQogICAgZnVuY3Rpb24gbWludCh1aW50MjU2IGlkLCB1aW50MjU2IGFtb3VudCkgZXh0ZXJuYWwgeyBfbWludChtc2cuc2VuZGVyLCBpZCwgYW1vdW50LCAiIik7IH0KfQo" title="DemoMultiToken (ERC-1155) · deploy to Sei testnet" description="Remix IDE with an OpenZeppelin ERC-1155 preloaded. Compile in-browser and deploy to Sei testnet." />

## Setup

<CodeGroup>
  ```ts viem theme={"dark"}
  import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
  import { privateKeyToAccount } from 'viem/accounts';
  import { sei } from 'viem/chains';

  const client = createPublicClient({ chain: sei, transport: http() });
  const account = privateKeyToAccount('0xYourPrivateKey');
  const walletClient = createWalletClient({ account, chain: sei, transport: http() });

  const ERC1155_ABI = parseAbi([
    'function balanceOf(address account, uint256 id) view returns (uint256)',
    'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])',
    'function isApprovedForAll(address account, address operator) view returns (bool)',
    'function setApprovalForAll(address operator, bool approved)',
    'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)',
    'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)',
    'function uri(uint256 id) view returns (string)',
    'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)',
    'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)',
  ]);

  const CONTRACT = '0xContractAddress';
  ```

  ```ts ethers theme={"dark"}
  import { ethers } from 'ethers';

  const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
  const wallet = new ethers.Wallet('0xYourPrivateKey', provider);

  const ERC1155_ABI = [
    'function balanceOf(address account, uint256 id) view returns (uint256)',
    'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])',
    'function isApprovedForAll(address account, address operator) view returns (bool)',
    'function setApprovalForAll(address operator, bool approved)',
    'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)',
    'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)',
    'function uri(uint256 id) view returns (string)',
    'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)',
    'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)',
  ];

  const CONTRACT = '0xContractAddress';
  const readContract = new ethers.Contract(CONTRACT, ERC1155_ABI, provider);
  const writeContract = new ethers.Contract(CONTRACT, ERC1155_ABI, wallet);
  ```
</CodeGroup>

## Reading a Single Balance

<CodeGroup>
  ```ts viem theme={"dark"}
  const balance = await client.readContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'balanceOf',
    args: ['0xOwnerAddress', 1n], // token ID 1
  });
  ```

  ```ts ethers theme={"dark"}
  const balance = await readContract.balanceOf('0xOwnerAddress', 1n);
  ```
</CodeGroup>

## Batch Balance Query

`balanceOfBatch` retrieves multiple owner/ID pairs in one call — the most efficient way to load a user's inventory:

<CodeGroup>
  ```ts viem theme={"dark"}
  const owners = ['0xOwner', '0xOwner', '0xOwner'] as const;
  const ids = [1n, 2n, 3n];

  const balances = await client.readContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'balanceOfBatch',
    args: [owners, ids],
  });
  // balances[0] = balance of token 1, balances[1] = token 2, etc.
  ```

  ```ts ethers theme={"dark"}
  const owners = ['0xOwner', '0xOwner', '0xOwner'];
  const ids = [1n, 2n, 3n];

  const balances = await readContract.balanceOfBatch(owners, ids);
  ```
</CodeGroup>

## Reading Token Metadata URI

```ts viem theme={"dark"}
const uri = await client.readContract({
  address: CONTRACT,
  abi: ERC1155_ABI,
  functionName: 'uri',
  args: [1n],
});
// URI often contains {id} — replace it with the hex token ID per ERC-1155 spec
const resolvedUri = uri.replace('{id}', (1n).toString(16).padStart(64, '0'));
```

## Transferring a Single Token

<CodeGroup>
  ```ts viem theme={"dark"}
  const hash = await walletClient.writeContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'safeTransferFrom',
    args: [account.address, '0xRecipient', 1n, 5n, '0x'], // transfer 5 of token ID 1
  });

  const receipt = await client.waitForTransactionReceipt({ hash });
  ```

  ```ts ethers theme={"dark"}
  const tx = await writeContract.safeTransferFrom(wallet.address, '0xRecipient', 1n, 5n, '0x');
  const receipt = await tx.wait();
  ```
</CodeGroup>

## Batch Transfer

Send multiple token IDs in a single transaction:

<CodeGroup>
  ```ts viem theme={"dark"}
  const hash = await walletClient.writeContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'safeBatchTransferFrom',
    args: [
      account.address,
      '0xRecipient',
      [1n, 2n, 3n],     // token IDs
      [5n, 10n, 1n],    // amounts
      '0x',
    ],
  });
  ```

  ```ts ethers theme={"dark"}
  const tx = await writeContract.safeBatchTransferFrom(
    wallet.address,
    '0xRecipient',
    [1n, 2n, 3n],
    [5n, 10n, 1n],
    '0x',
  );
  ```
</CodeGroup>

## Operator Approval

<CodeGroup>
  ```ts viem theme={"dark"}
  // Grant an operator permission to transfer all tokens
  const hash = await walletClient.writeContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'setApprovalForAll',
    args: ['0xOperatorAddress', true],
  });

  // Check approval status
  const isApproved = await client.readContract({
    address: CONTRACT,
    abi: ERC1155_ABI,
    functionName: 'isApprovedForAll',
    args: ['0xOwnerAddress', '0xOperatorAddress'],
  });
  ```

  ```ts ethers theme={"dark"}
  const tx = await writeContract.setApprovalForAll('0xOperatorAddress', true);

  const isApproved = await readContract.isApprovedForAll('0xOwnerAddress', '0xOperatorAddress');
  ```
</CodeGroup>

## Watching Transfer Events

<CodeGroup>
  ```ts viem theme={"dark"}
  // Watch single transfers
  const unwatch = client.watchContractEvent({
    address: CONTRACT,
    abi: ERC1155_ABI,
    eventName: 'TransferSingle',
    onLogs: (logs) => {
      logs.forEach(({ args }) => {
        console.log(`Token ${args.id}: ${args.from} → ${args.to}, amount: ${args.value}`);
      });
    },
  });

  // Watch batch transfers
  const unwatchBatch = client.watchContractEvent({
    address: CONTRACT,
    abi: ERC1155_ABI,
    eventName: 'TransferBatch',
    onLogs: (logs) => {
      logs.forEach(({ args }) => {
        console.log(`Batch from ${args.from} → ${args.to}:`, args.ids, args.values);
      });
    },
  });
  ```

  ```ts ethers theme={"dark"}
  readContract.on('TransferSingle', (operator, from, to, id, value) => {
    console.log(`Token ${id}: ${from} → ${to}, amount: ${value}`);
  });

  readContract.on('TransferBatch', (operator, from, to, ids, values) => {
    console.log(`Batch from ${from} → ${to}:`, ids, values);
  });
  ```
</CodeGroup>
