Skip to main content

Frequently Asked Questions

Common questions and solutions for build issues, authentication, transactions, and debugging with the TypeScript SDK.

Build & Environment

Modern bundlers don’t include Node.js polyfills by default.Vite:
npm install vite-plugin-node-polyfills
// vite.config.ts
import { nodePolyfills } from 'vite-plugin-node-polyfills';

export default defineConfig({
  plugins: [
    nodePolyfills({
      include: ['buffer', 'crypto', 'stream', 'util'],
    }),
  ],
});
Next.js / Create React App:
// Add to top of your entry file
import { Buffer } from 'buffer';
if (typeof window !== 'undefined') {
  window.Buffer = Buffer;
}
Add global polyfill to your bundler config:
// vite.config.ts
export default defineConfig({
  define: {
    global: 'globalThis',
  },
});
Ensure tsconfig.json uses modern module resolution:
{
  "compilerOptions": {
    "moduleResolution": "bundler", // or "node16"
    "esModuleInterop": true
  }
}
Import from modular packages instead of the umbrella SDK:
// ❌ May cause issues with App Router
import { config, auth } from '@imtbl/sdk';

// ✅ Works correctly - use modular packages
import { Auth } from '@imtbl/auth';
import { connectWallet } from '@imtbl/wallet';
import { Environment } from '@imtbl/config';
If you see elliptic package errors, add to next.config.js:
const nextConfig = {
  experimental: {
    esmExternals: false,
  },
};
Import modular packages instead of the umbrella package:
// ❌ Imports everything
import { auth, orderbook } from '@imtbl/sdk';

// ✅ Imports only what you need
import { Auth } from '@imtbl/auth';
import { Orderbook } from '@imtbl/orderbook';
Using modular packages can significantly reduce your bundle size by importing only what you need.
Clear your cache and reinstall dependencies:
# Remove node_modules and package lock
rm -rf node_modules package-lock.json

# Clear npm cache
npm cache clean --force

# Reinstall dependencies
npm install
For Vite projects:
rm -rf node_modules .vite package-lock.json
npm cache clean --force
npm install
For Next.js projects:
rm -rf node_modules .next package-lock.json
npm cache clean --force
npm install
For framework-specific bundler configuration, see the Framework Setup tabs in the overview.

Regional Support

The Immutable Game SDK supports only the regions that Immutable Passport supports.Passport is a globally available product. However, our wallet infrastructure is subject to the regulation of the US Department of the Treasury’s Office of Foreign Assets Control (“OFAC”). OFAC administers and enforces comprehensive and targeted economic and trade sanctions programs on multiple countries and regions.Users attempting to access Passport in any of the regions under OFAC sanction will have their access blocked and will be unable to use our product. Additionally, components of Passport’s infrastructure also rely on technology provided by Magic, which maintains further details regarding unsupported regions on their website here.

Authentication

The redirectUri in your code must exactly match what’s configured in Hub.Common mismatches:
  • http vs https
  • Trailing slash (/callback vs /callback/)
  • Port number differences
  • Different paths
// ❌ Wrong - trailing slash mismatch
redirectUri: 'http://localhost:3000/callback/'

// ✅ Correct - matches Hub config exactly
redirectUri: 'http://localhost:3000/callback'
The redirect URI must match exactly including trailing slashes and port numbers.
Verify the following:
  1. clientId matches your Hub configuration
  2. You’re using the correct environment (Sandbox vs Production)
  3. publishableKey is set correctly
Ensure loginCallback() is called on your redirect URI page:
// app/callback/page.tsx
useEffect(() => {
  auth.loginCallback()
    .then(() => router.push('/'))
    .catch(console.error);
}, []);

Transactions

User cancelled the transaction in the Passport popup. Handle this gracefully:
try {
  await walletClient.sendTransaction({ to, value });
} catch (error) {
  if (error.name === 'UserRejectedRequestError') {
    // User cancelled - show friendly message
    return;
  }
  throw error;
}
The user doesn’t have enough IMX for the transaction + gas fees.
import { formatEther } from 'viem';

const balance = await publicClient.getBalance({ address });
console.log('Balance:', formatEther(balance), 'IMX');
Direct users to fund their wallet via the Checkout widgets.
Usually caused by pending transactions. Wait for pending transactions to confirm:
const pendingNonce = await publicClient.getTransactionCount({
  address,
  blockTag: 'pending',
});

Performance & Debugging

Initialize once and reuse the instance:
// ❌ Wrong - creates new instance every call
function getAuth() {
  return new Auth({ ... });
}

// ✅ Correct - singleton pattern
let auth: Auth | null = null;
function getAuth() {
  if (!auth) {
    auth = new Auth({ ... });
  }
  return auth;
}
Use the singleton pattern to initialize SDK instances once and reuse them throughout your application.
Enable debug logs in the browser console:
// In browser console
localStorage.setItem('debug', 'imtbl:*');
Then refresh the page to see SDK debug output.
SDK calls go to:
  • Sandbox: api.sandbox.immutable.com
  • Production: api.immutable.com
Check the browser Network tab for failed requests and error responses.
CodeMeaningSolution
401UnauthorizedCheck API key or access token
403ForbiddenCheck permissions in Hub
429Rate limitedImplement exponential backoff
500Server errorRetry with backoff, contact support if persistent

Integration & Usage

viem has built-in Immutable Chain support:
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { immutableZkEvm, immutableZkEvmTestnet } from 'viem/chains';
import { connectWallet } from '@imtbl/wallet';

const provider = await connectWallet({ auth });

const walletClient = createWalletClient({
  chain: immutableZkEvmTestnet,
  transport: custom(provider),
});

const publicClient = createPublicClient({
  chain: immutableZkEvmTestnet,
  transport: http(),
});

const [address] = await walletClient.getAddresses();

const hash = await walletClient.sendTransaction({
  to: '0x...',
  value: parseEther('0.1'),
});
Use @imtbl/webhook to verify incoming webhooks from Immutable:
import { validateWebhook } from '@imtbl/webhook';

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-immutable-signature'] as string;
  const rawBody = req.body; // Must be raw body, not parsed JSON

  const isValid = validateWebhook(
    rawBody,
    signature,
    process.env.WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(403).send('Invalid signature');
  }

  res.status(200).send('OK');
});
For browser-only applications without a build step:
<script src="https://cdn.jsdelivr.net/npm/@imtbl/sdk"></script>

<script>
  const passport = new window.immutable.passport.Passport({
    clientId: 'YOUR_CLIENT_ID',
    redirectUri: 'https://your-app.com/callback',
    logoutRedirectUri: 'https://your-app.com',
    audience: 'platform_api',
    scope: 'openid offline_access email transact'
  });

  passport.login();
</script>
CDN deployment is useful for quick prototypes and static sites. For production applications, use npm packages with proper bundling.
Requires TypeScript 5.0+ with moduleResolution: "bundler" in tsconfig.json.
// ✅ Correct - imports only the module you need
import { Passport } from '@imtbl/sdk/passport';
import { Orderbook } from '@imtbl/sdk/orderbook';
import { BlockchainData } from '@imtbl/sdk/blockchain-data';

// ❌ Avoid - imports entire SDK
import { Passport, Orderbook } from '@imtbl/sdk';
Breaking Change: V2 SDK uses Ethers.js v6 (upgraded from v5). If your application uses Ethers.js, you must migrate. Key changes:
  • WrappedBrowserProvider for Checkout SDK
  • passport.connectEvm() is now async — must await before passing to Checkout
  • See Ethers.js v6 migration guide for details

Still Need Help?