Start a new dapp
Getting Started with Typink
Typink is a React hooks library for building dApps that interact with ink! and Solidity smart contracts on Polkadot blockchains. With Typink, you get a unified developer experience - the same hooks and APIs work seamlessly across ink! v5 (WASM), ink! v6 (PolkaVM), and Solidity contracts.
Create a New Project
Start by creating a new Typink project using the interactive CLI:
pnpm create typink@latest
The CLI will guide you through an interactive setup:
1. Enter Project Name
? Project name: my-typink-dapp
2. Select Contract Type
Choose the type of contracts you'll be working with:
? Select contract type:
❯ Ink! v6 (PolkaVM, pallet-revive)
Ink! v6 using Sol ABI (PolkaVM, pallet-revive)
Solidity (PolkaVM, pallet-revive)
Ink! v5 (WASM, pallet-contracts)
Contract Types:
Ink! v6 - Latest ink! version on PolkaVM (pallet-revive)
Ink! v6 Sol ABI - ink! v6 with Solidity-style ABI
Solidity - Solidity smart contracts on PolkaVM
Ink! v5 - Legacy ink! on WASM (pallet-contracts)
3. Select Networks
Choose one or more networks for your dApp:
? Select supported networks: (Press <space> to select)
❯◉ Pop Testnet
◯ Passet Hub
Available networks depend on your contract type:
pallet-contracts (Ink! v5): Pop Testnet, Aleph Zero Testnet, Aleph Zero, Astar
pallet-revive (Ink! v6/Solidity): Pop Testnet, Passet Hub
Start Development
Navigate to your project and start the development server:
cd my-typink-dapp
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun dev
Open http://localhost:3000 to see your dApp running!
Project Structure
Your project follows Next.js 15 App Router structure:
my-typink-dapp/
├── src/
│ ├── app/ # Next.js app directory
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ └── globals.css # Global styles
│ ├── components/ # React components
│ │ ├── shared/ # Shared UI components
│ │ └── [contract]-board.tsx # Contract interaction components
│ ├── contracts/ # Contract-related files
│ │ ├── deployments.ts # Contract deployment addresses
│ │ └── types/ # Generated TypeScript bindings
│ ├── lib/ # Utility functions
│ │ └── utils.ts
│ └── providers/ # React providers
│ └── app-provider.tsx # TypinkProvider configuration
├── public/ # Static assets
├── package.json
├── tsconfig.json
└── next.config.ts
Key Files
src/providers/app-provider.tsx
- Configures TypinkProvider with networks, wallets, and contracts:
import { TypinkProvider, setupTxToaster, SonnerAdapter } from 'typink';
import { deployments } from '@/contracts/deployments';
setupTxToaster({
adapter: new SonnerAdapter(toast),
});
export function AppProvider({ children }) {
return (
<TypinkProvider
appName="My Typink dApp"
deployments={deployments}
supportedNetworks={[popTestnet]}
defaultNetworkId={popTestnet.id}
wallets={[subwallet, talisman, polkadotjs]}>
{children}
</TypinkProvider>
);
}
src/contracts/deployments.ts
- Registers contract deployments:
export enum ContractId {
FLIPPER = 'flipper',
}
export const deployments: ContractDeployment[] = [
{
id: ContractId.FLIPPER,
metadata: flipperMetadata,
address: '0x3ddc397c0350cbfb89d4f28d476073d6051067c4',
network: popTestnet.id,
},
];
Explore the Example
Your project includes a pre-deployed example contract with working interactions:
Connect Wallet - Click "Connect Wallet" and select SubWallet, Talisman, or PolkadotJS
View State - See the current contract state (e.g., Flipper value, Storage value)
Send Transactions - Interact with the contract (e.g., flip the boolean, set storage)
Watch Progress - Transaction toasts show real-time progress
Example Contracts
Ink! v5: Greeter contract (set and get messages)
Ink! v6: Flipper contract (flip boolean value)
Ink! v6 Sol ABI: Flipper contract with Solidity-style ABI
Solidity: Storage contract (set and get uint256 value)
Add Your Own Contracts
1. Deploy Your Contract
Deploy your contract to a supported network using:
POP CLI - Command-line tool for deploying to Pop Network
Contracts UI - User-friendly web interface
cargo-contract - CLI tool
Remix IDE - For Solidity contracts on PolkaVM
You'll receive a contract address after successful deployment.
2. Generate TypeScript Bindings
The project includes a pre-configured typegen script (./scripts/typegen.ts
) that generates type-safe bindings for all contracts in src/contracts/artifacts/
.
First, place your contract metadata/ABI files in src/contracts/artifacts/
, then run:
# npm
npm run typegen
# pnpm
pnpm typegen
# yarn
yarn typegen
# bun
bun typegen
This script automatically processes all metadata/ABI files in src/contracts/artifacts/
and generates TypeScript bindings to src/contracts/types/
. The generated TypeScript API (e.g., MyContractApi
) will be available in src/contracts/types/my_contract/
.
3. Register Contract Deployment
Add your contract to src/contracts/deployments.ts
:
import myContractMetadata from './artifacts/my_contract/metadata.json';
export enum ContractId {
FLIPPER = 'flipper',
MY_CONTRACT = 'my-contract', // Add your contract
}
export const deployments: ContractDeployment[] = [
// ... existing deployments
{
id: ContractId.MY_CONTRACT,
metadata: myContractMetadata,
address: 'YOUR_CONTRACT_ADDRESS',
network: popTestnet.id,
},
];
Unified Hooks - Works with All Contract Types
Typink's hooks provide a unified API that works identically across ink! v5, ink! v6, and Solidity contracts.
useContract - Initialize Contract Instance
Get a typed contract instance:
import { useContract } from 'typink';
import { MyContractApi } from '@/contracts/types/my_contract';
function MyComponent() {
const { contract } = useContract<MyContractApi>(ContractId.MY_CONTRACT);
// contract is now fully typed based on your contract ABI
}
useContractQuery - Query Contract State
Read contract state with automatic type inference:
Ink! v6 Flipper Example:
import { useContract, useContractQuery } from 'typink';
import { FlipperContractApi } from '@/contracts/types/flipper';
function FlipperQuery() {
const { contract } = useContract<FlipperContractApi>(ContractId.FLIPPER);
const { data: value, isLoading, refresh } = useContractQuery({
contract,
fn: 'get', // ✅ Fully typed - autocomplete works!
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<p>Current value: {value?.toString()}</p>
<button onClick={refresh}>Refresh</button>
</div>
);
}
Solidity Storage Example:
import { useContract, useContractQuery } from 'typink';
import { StorageContractApi } from '@/contracts/types/storage';
function StorageQuery() {
const { contract } = useContract<StorageContractApi>(ContractId.STORAGE);
const { data: value, isLoading } = useContractQuery({
contract,
fn: 'retrieve', // ✅ Same API, different contract!
});
return <div>Stored value: {value?.toString()}</div>;
}
💡 The hooks are identical! Only the contract type and method names change.
useContractTx - Send Transactions
Execute contract transactions with the same API:
Ink! v6 Flipper Example:
import { useContract, useContractTx, txToaster } from 'typink';
function FlipperTx() {
const { contract } = useContract<FlipperContractApi>(ContractId.FLIPPER);
const flipTx = useContractTx(contract, 'flip');
const handleFlip = async () => {
const toaster = txToaster('Flipping value...');
try {
await flipTx.signAndSend({
callback: (result) => {
toaster.onTxProgress(result);
},
});
} catch (error) {
toaster.onTxError(error);
}
};
return (
<button
onClick={handleFlip}
disabled={flipTx.inBestBlockProgress}>
{flipTx.inBestBlockProgress ? 'Flipping...' : 'Flip'}
</button>
);
}
Solidity Storage Example:
import { useContract, useContractTx, txToaster } from 'typink';
function StorageTx() {
const { contract } = useContract<StorageContractApi>(ContractId.STORAGE);
const storeTx = useContractTx(contract, 'store');
const [value, setValue] = useState('');
const handleStore = async () => {
const toaster = txToaster('Storing value...');
try {
await storeTx.signAndSend({
args: [BigInt(value)], // ✅ Type-safe args!
callback: (result) => {
toaster.onTxProgress(result);
},
});
} catch (error) {
toaster.onTxError(error);
}
};
return (
<>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="number"
/>
<button onClick={handleStore}>Store</button>
</>
);
}
💡 Identical hook usage! The only difference is the contract type and arguments.
useWatchContractEvent - Listen to Events
Watch for contract events with type-safe event data:
Ink! v6 Example:
import { useContract, useWatchContractEvent } from 'typink';
function FlipperEvents() {
const { contract } = useContract<FlipperContractApi>(ContractId.FLIPPER);
const [events, setEvents] = useState([]);
useWatchContractEvent(
contract,
'Flipped', // ✅ Event name is typed!
useCallback((newEvents) => {
newEvents.forEach((event) => {
const { name, data } = event;
console.log(`${name} event:`, data); // ✅ data is typed!
setEvents((prev) => [...prev, event]);
});
}, [])
);
return (
<div>
<h3>Recent Events:</h3>
{events.map((event, i) => (
<div key={i}>{event.name}</div>
))}
</div>
);
}
💡 Works the same for Solidity contracts! Just use your Solidity contract's event names.
useDeployerTx - Deploy New Contracts
Deploy new contract instances:
import { useDeployer, useDeployerTx, txToaster } from 'typink';
import { generateRandomHex } from 'dedot/utils';
import metadata from '@/contracts/types/flipper/metadata.json';
function DeployFlipper() {
const wasm = metadata.source.wasm; // or metadata.source.hash
const { deployer } = useDeployer<FlipperContractApi>(metadata, wasm);
const newFlipperTx = useDeployerTx(deployer, 'new');
const handleDeploy = async () => {
const toaster = txToaster('Deploying contract...');
try {
await newFlipperTx.signAndSend({
args: [false], // Initial value
txOptions: { salt: generateRandomHex() },
callback: (result, deployedAddress) => {
toaster.onTxProgress(result);
if (deployedAddress) {
console.log('Deployed at:', deployedAddress);
}
},
});
} catch (error) {
toaster.onTxError(error);
}
};
return <button onClick={handleDeploy}>Deploy New Flipper</button>;
}
Setup Transaction Toaster
Before using txToaster()
, configure the global adapter in your app provider:
import { setupTxToaster, SonnerAdapter } from 'typink';
import { toast } from 'sonner';
// Setup once at app initialization
setupTxToaster({
adapter: new SonnerAdapter(toast),
initialMessage: 'Signing transaction...',
autoCloseDelay: 5000,
});
Supported toast libraries:
Sonner (recommended) -
SonnerAdapter
React-Toastify -
ReactToastifyAdapter
React-Hot-Toast -
ReactHotToastAdapter
See the txToaster documentation for more details.
Complete Example
Here's a complete component showing query, transaction, and events:
'use client';
import { useCallback, useState } from 'react';
import { useContract, useContractQuery, useContractTx, useWatchContractEvent, txToaster } from 'typink';
import { FlipperContractApi } from '@/contracts/types/flipper';
export function FlipperBoard() {
const { contract } = useContract<FlipperContractApi>(ContractId.FLIPPER);
const [events, setEvents] = useState<string[]>([]);
// Query current value
const { data: value, isLoading, refresh } = useContractQuery({
contract,
fn: 'get',
});
// Transaction to flip value
const flipTx = useContractTx(contract, 'flip');
// Watch for Flipped events
useWatchContractEvent(
contract,
'Flipped',
useCallback((newEvents) => {
newEvents.forEach((event) => {
setEvents((prev) => [...prev, `Flipped to: ${event.data.newValue}`]);
});
}, [])
);
const handleFlip = async () => {
const toaster = txToaster('Flipping value...');
try {
await flipTx.signAndSend({
callback: (result) => {
toaster.onTxProgress(result);
if (result.status.type === 'BestChainBlockIncluded' && !result.dispatchError) {
refresh(); // Refresh query after success
}
},
});
} catch (error) {
toaster.onTxError(error);
}
};
return (
<div>
<h2>Flipper Contract</h2>
{/* Display current value */}
<div>
<p>Current Value:</p>
{isLoading ? (
<span>Loading...</span>
) : (
<span>{value?.toString()}</span>
)}
</div>
{/* Flip button */}
<button
onClick={handleFlip}
disabled={flipTx.inBestBlockProgress}>
{flipTx.inBestBlockProgress ? 'Flipping...' : 'Flip Value'}
</button>
{/* Recent events */}
<div>
<h3>Recent Events:</h3>
{events.map((event, i) => (
<div key={i}>{event}</div>
))}
</div>
</div>
);
}
💡 This exact same pattern works for Solidity contracts! Just change the contract type and method names.
Happy building with Typink! 🎉
Last updated
Was this helpful?