The Kitties API provides a unified interface for interacting with the CryptoKitties smart contract from both browser and Node.js environments.
yarn add @repo/kitties-apiimport { KittiesAPI, configureBrowserProviders } from '@repo/kitties-api';
// Configure providers
const providers = await configureBrowserProviders(wallet, config, zkConfigProvider);
// Deploy new contract
const kittiesApi = await KittiesAPI.deploy(providers, {});
// Or connect to existing contract
const kittiesApi = await KittiesAPI.connect(providers, contractAddress);import { KittiesAPI, configureProviders } from '@repo/kitties-api/node-api';
// Configure providers
const providers = await configureProviders(wallet, config, zkConfigProvider);
// Deploy or connect
const kittiesApi = await KittiesAPI.deploy(providers, {});Deploy a new kitties contract.
Parameters:
providers: KittiesProviders- Configured Midnight providers
Returns: Promise<KittiesAPI>
const kittiesApi = await KittiesAPI.deploy(providers, {});Connect to an existing contract.
Parameters:
providers: KittiesProviders- Configured Midnight providerscontractAddress: ContractAddress | string- Contract address
Returns: Promise<KittiesAPI>
const kittiesApi = await KittiesAPI.connect(providers, "123...");Create a new kitty with random DNA.
await kittiesApi.createKitty();Get kitty details by ID.
Parameters:
kittyId: bigint- Kitty ID
Returns: Promise<KittyData>
const kitty = await kittiesApi.getKitty(1n);
console.log(kitty.dna, kitty.gender, kitty.owner);Transfer kitty to another address.
Parameters:
params.to: { bytes: Uint8Array }- Recipient addressparams.kittyId: bigint- Kitty ID
await kittiesApi.transferKitty({
to: { bytes: recipientBytes },
kittyId: 1n
});Get total number of kitties created.
const count = await kittiesApi.getAllKittiesCount();Get all kitties owned by an address.
Parameters:
owner: { bytes: Uint8Array }- Owner address
const kitties = await kittiesApi.getUserKitties(ownerAddress);Convenience method to get kitties for current wallet.
Parameters:
from: { bytes: Uint8Array }- Wallet address
const myKitties = await kittiesApi.getMyKitties(walletAddress);Set sale price for a kitty.
Parameters:
params.kittyId: bigint- Kitty IDparams.price: bigint- Sale price
await kittiesApi.setPrice({
kittyId: 1n,
price: 100n
});Make a purchase offer on a kitty.
Parameters:
params.kittyId: bigint- Kitty IDparams.bidPrice: bigint- Offered price
await kittiesApi.createBuyOffer({
kittyId: 1n,
bidPrice: 120n
});Accept a purchase offer (transfers ownership).
Parameters:
params.kittyId: bigint- Kitty IDparams.buyer: { bytes: Uint8Array }- Buyer address
await kittiesApi.approveOffer({
kittyId: 1n,
buyer: buyerAddress
});Get all kitties currently for sale.
const forSale = await kittiesApi.getKittiesForSale();Get offer details for a specific kitty and buyer.
Parameters:
params.kittyId: bigint- Kitty IDparams.buyer: { bytes: Uint8Array }- Buyer address
const offer = await kittiesApi.getOffer({
kittyId: 1n,
buyer: buyerAddress
});Breed two kitties to create offspring.
Parameters:
params.kittyId1: bigint- First parent IDparams.kittyId2: bigint- Second parent ID
await kittiesApi.breedKitty({
kittyId1: 1n,
kittyId2: 2n
});The API exposes standard ERC-721 operations through the external NFT module:
Get number of tokens owned by an address.
const balance = await kittiesApi.balanceOf(ownerAddress);Get owner of a specific token.
const owner = await kittiesApi.ownerOf(1n);Approve another address to transfer a token.
Parameters:
params.to: { bytes: Uint8Array }- Approved addressparams.tokenId: bigint- Token ID
await kittiesApi.approve({
to: approvedAddress,
tokenId: 1n
});Get approved address for a token.
const approved = await kittiesApi.getApproved(1n);Set approval for all tokens.
Parameters:
params.operator: { bytes: Uint8Array }- Operator addressparams.approved: boolean- Approval status
await kittiesApi.setApprovalForAll({
operator: operatorAddress,
approved: true
});Check if operator is approved for all tokens.
Parameters:
params.owner: { bytes: Uint8Array }- Owner addressparams.operator: { bytes: Uint8Array }- Operator address
const isApproved = await kittiesApi.isApprovedForAll({
owner: ownerAddress,
operator: operatorAddress
});Static wrapper for creating kitties with transaction info.
Get kitty count without full API instance.
Get contract address from API instance.
interface KittyData {
id: bigint;
dna: bigint;
gender: Gender;
owner: { bytes: Uint8Array };
price: bigint;
forSale: boolean;
generation: bigint;
}interface KittyListingData {
id: bigint;
kitty: KittyData;
}interface Offer {
kittyId: bigint;
buyer: { bytes: Uint8Array };
price: bigint;
}interface TransactionResponse {
txId?: string;
txHash?: string;
blockHeight?: bigint | number;
}import { configureBrowserProviders } from '@repo/kitties-api';
const providers = await configureBrowserProviders(
wallet,
config,
zkConfigProvider
);import { configureProviders } from '@repo/kitties-api/node-api';
const providers = await configureProviders(
wallet,
config,
zkConfigProvider
);The API throws descriptive errors for common issues:
try {
await kittiesApi.createBuyOffer({ kittyId: 1n, bidPrice: 50n });
} catch (error) {
if (error.message.includes('Kitty is not for sale')) {
console.log('Cannot make offer on kitty not for sale');
}
}The API provides an observable for real-time state updates:
kittiesApi.state$.subscribe(state => {
console.log('Kitties count:', state.allKittiesCount);
console.log('Kitties data:', state.kitties);
});cd packages/api/kitties
yarn testyarn test-against-testnet- Browser: Uses wallet connector for transactions
- Node.js: Direct wallet integration
- Both: Same API surface, different provider setup
- apps/web/vite.config.ts - Vite configuration with aliases and comments
Problem: The kitties-api needs to work in both Node.js and browser environments with different capabilities.
Solutions Documented:
- Environment abstraction layer (
env.ts,env-node.ts,env-browser.ts) - Universal path resolution (
path-resolver.ts) - Vite alias configuration for browser builds
Configuration Points:
- Vite Aliases: Route browser imports to browser-compatible implementations
- TypeScript: Maintain type safety across environments
- Module Systems: Handle both CommonJS and ESM contexts
The Kitties API uses a sophisticated export structure to handle different environments and prevent dependency leaks:
{
"exports": {
".": {
"browser": {
"types": "./dist/browser-index.d.ts",
"import": "./dist/browser-index.js"
},
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./node-api": {
"types": "./dist/node/api.d.ts",
"import": "./dist/node/api.js"
},
"./browser-api": {
"types": "./dist/browser/api.d.ts",
"import": "./dist/browser/api.js"
},
"./src/browser/env-browser.ts": "./src/browser/env-browser.ts"
}
}Key Benefits:
- Environment Isolation: Browser and Node.js environments get different entry points
- Dependency Control: Prevents Node.js-specific dependencies from leaking into browser builds
- Type Safety: Separate type definitions for each environment
- Direct Access: Allows importing specific modules when needed
The project's web application demonstrates how to configure Vite for proper browser builds. See apps/web/vite.config.ts for the complete configuration:
Key Configuration Highlights:
// From apps/web/vite.config.ts
export default defineConfig({
plugins: [
react(),
viteCommonjs({
include: ['@repo/kitties-api/**'], // Handle CommonJS in kitties-api
}),
wasm(),
],
resolve: {
alias: {
...stdLibBrowser, // Node.js standard library polyfills
// Environment Abstraction Layer Aliases
// These ensure browser builds use browser-compatible implementations
'@repo/kitties-api/src/env': '@repo/kitties-api/dist/browser/env-browser',
'@repo/kitties-api/dist/env': '@repo/kitties-api/dist/browser/env-browser',
'@repo/kitties-api/dist/env-node': '@repo/kitties-api/dist/browser/env-browser',
'./env-node.js': '@repo/kitties-api/dist/browser/env-browser',
'./env-node': '@repo/kitties-api/dist/browser/env-browser',
'../node/env-node.js': '@repo/kitties-api/dist/browser/env-browser',
},
},
optimizeDeps: {
include: ['@repo/kitties-api'],
exclude: ['@midnight-ntwrk/kitties-contract', 'node-fetch', 'fetch-blob'],
},
build: {
rollupOptions: {
external: [
'@midnight-ntwrk/midnight-js-node-zk-config-provider',
'fetch-blob',
'node-domexception',
'formdata-polyfill',
],
},
},
});Critical Configuration Elements:
-
Environment Abstraction Aliases: Multiple alias patterns ensure that any import path referring to Node.js environment code gets redirected to browser-compatible implementations.
-
CommonJS Handling: The
viteCommonjsplugin specifically includes the kitties-api package to handle mixed module formats. -
Standard Library Polyfills: Uses
node-stdlib-browserto provide browser-compatible versions of Node.js built-ins. -
Dependency Optimization: Includes the API package for pre-bundling while excluding Node.js-specific dependencies.
-
External Dependencies: Explicitly marks Node.js-only packages as external to prevent them from being bundled.
The API uses an environment abstraction layer to handle differences between browser and Node.js:
Structure:
src/
├── common/ # Shared code
├── browser/ # Browser-specific implementations
│ ├── env-browser.ts # Browser environment stubs
│ └── api.ts # Browser API layer
├── node/ # Node.js-specific implementations
│ ├── env-node.ts # Node.js environment functions
│ └── api.ts # Node.js API layer
└── env.ts # Runtime environment detection
Key Features:
- Runtime Detection: Automatically detects browser vs Node.js environment
- Graceful Fallbacks: Browser stubs for Node.js-only functions
- Type Safety: Consistent interfaces across environments
- Dependency Isolation: Prevents platform-specific dependencies from cross-contaminating
1. Environment-Specific Exports
// Browser builds import from browser-specific entry point
import { KittiesAPI } from '@repo/kitties-api/browser-api';
// Node.js builds import from node-specific entry point
import { KittiesAPI } from '@repo/kitties-api/node-api';2. Conditional Imports
// env.ts - Runtime environment detection
const isNode = typeof process !== 'undefined' && process.versions?.node;
if (isNode) {
// Import Node.js-specific modules only in Node.js environment
const { readFileSync } = await import('fs');
} else {
// Use browser-compatible alternatives
throw new Error('File operations not supported in browser');
}3. Build-Time Exclusions
// package.json - Exclude Node.js dependencies from browser builds
{
"browser": {
"fs": false,
"path": false,
"crypto": false
}
}For Applications Using the API:
-
Use Environment-Specific Imports
// ✅ Good - Browser applications import { KittiesAPI } from '@repo/kitties-api/browser-api'; // ✅ Good - Node.js applications import { KittiesAPI } from '@repo/kitties-api/node-api'; // ❌ Avoid - May pull in wrong dependencies import { KittiesAPI } from '@repo/kitties-api';
-
Configure Bundle Resolvers
// webpack.config.js module.exports = { resolve: { alias: { '@repo/kitties-api$': '@repo/kitties-api/browser-api' }, fallback: { "fs": false, "path": false } } };
-
Handle Dynamic Imports
// Use dynamic imports for optional Node.js functionality let nodeApi; try { nodeApi = await import('@repo/kitties-api/node-api'); } catch (error) { // Fallback for browser environment console.log('Node.js API not available in browser'); }
Common Issues and Solutions:
-
"Module not found" errors in browser
- Check Vite alias configuration
- Ensure using browser-specific import paths
- Verify nodePolyfills plugin configuration
-
Node.js dependencies in browser bundle
- Review export structure in package.json
- Check for incorrect import paths
- Add explicit exclusions in bundler config
-
Type resolution errors
- Ensure TypeScript can resolve environment-specific types
- Check tsconfig.json path mappings
- Verify dist/ folder structure after build
Debug Commands:
# Check resolved imports
yarn why @repo/kitties-api
# Analyze bundle composition
npx vite-bundle-analyzer
# Verify type resolution
tsc --noEmit --listFilesFor detailed implementation information, see:
- ENVIRONMENT_ABSTRACTION.md - Environment abstraction layer
- PATH_RESOLUTION.md - Module resolution documentation
This documentation ensures the kitties-api remains maintainable and understandable as the project evolves.