| name | starknet-js |
| description | Reference for building Starknet applications using starknet.js v9.x SDK, including contract interaction, account management, transaction handling, fee estimation, wallet integration, and paymaster flows. |
| license | Apache-2.0 |
| metadata | {"author":"0xlny","version":"1.0.0","org":"keep-starknet-strange"} |
| compatibility | Node.js 18+, TypeScript 5+, npm package: starknet@^9.0.0 |
| keywords | ["starknet","starknet-js","sdk","typescript","smart-contracts","account-abstraction","paymaster","multicall","snip-9","snip-12","erc-20","erc-721","wallet","rpc","fee-estimation"] |
| allowed-tools | ["Bash","Read","Write","Glob","Grep","Task"] |
| user-invocable | true |
starknet.js v9.x SDK
Related modules: skills catalog.
When to Use
- Building Starknet apps with provider, account, contract, wallet, or paymaster flows.
When NOT to Use
- Cairo contract authoring, deployment-only runbooks, or security audits.
Quick Start
npm install starknet
Minimal setup to read from Starknet:
import { RpcProvider, Contract } from 'starknet';
const provider = await RpcProvider.create({ nodeUrl: 'https://rpc.starknet.lava.build' });
const contract = new Contract(abi, contractAddress, provider);
const result = await contract.get_balance();
Core Architecture
Provider -> Account -> Contract
| | |
Network Identity Interaction
- Provider: Read-only network connection (RpcProvider)
- Account: Extends Provider with signing and transaction capabilities
- Contract: Type-safe interface to deployed contracts
Use Provider for read operations, Account for write operations.
Provider Setup
import { RpcProvider } from 'starknet';
const provider = await RpcProvider.create({
nodeUrl: 'https://rpc.starknet.lava.build'
});
Networks:
- Mainnet:
https://rpc.starknet.lava.build
- Sepolia:
https://rpc.starknet-testnet.lava.build
Key Methods:
const chainId = await provider.getChainId();
const block = await provider.getBlock('latest');
const nonce = await provider.getNonceForAddress(accountAddress);
await provider.waitForTransaction(txHash);
const value = await provider.getStorageAt(contractAddress, storageKey);
Account Management
Account Creation (4 Steps)
Step 1: Compute address
import { hash, ec, encode, CallData } from 'starknet';
const privateKey = '0x' + encode.buf2hex(ec.starkCurve.utils.randomPrivateKey());
const publicKey = ec.starkCurve.getStarkKey(privateKey);
const classHash = '0x540d7f5ec7ecf317e68d48564934cb99259781b1ee3cedbbc37ec5337f8e688';
const constructorCalldata = CallData.compile({ publicKey });
const address = hash.calculateContractAddressFromHash(publicKey, classHash, constructorCalldata, 0);
Step 2: Fund the address with STRK before deployment.
Step 3: Deploy
import { Account } from 'starknet';
const account = new Account({ provider, address, signer: privateKey, cairoVersion: '1' });
const { transaction_hash } = await account.deployAccount({
classHash,
constructorCalldata,
addressSalt: publicKey
});
await provider.waitForTransaction(transaction_hash);
Step 4: Use the account for transactions.
Connect to Existing Account
const account = new Account({
provider,
address: '0x123...',
signer: privateKey,
cairoVersion: '1'
});
Contract Interaction
Connect to Contract
import { Contract } from 'starknet';
const contract = new Contract(abi, contractAddress, provider);
const writeContract = new Contract(abi, contractAddress, account);
Typed Contract (Type-Safe)
const typedContract = contract.typedv2(abi);
const balance = await typedContract.balanceOf(userAddress);
Read State
const balance = await contract.get_balance();
const userBalance = await contract.balanceOf(userAddress);
Write (Execute)
const tx = await contract.increase_balance(100);
await provider.waitForTransaction(tx.transaction_hash);
Multicall (Batch Transactions)
import { CallData, cairo } from 'starknet';
const calls = [
{
contractAddress: tokenAddress,
entrypoint: 'approve',
calldata: CallData.compile({ spender: bridgeAddress, amount: cairo.uint256(1000n) })
},
{
contractAddress: bridgeAddress,
entrypoint: 'deposit',
calldata: CallData.compile({ amount: cairo.uint256(1000n) })
}
];
const tx = await account.execute(calls);
Using populate() for type-safety:
const approveCall = tokenContract.populate('approve', {
spender: bridgeAddress,
amount: cairo.uint256(1000n)
});
const depositCall = bridgeContract.populate('deposit', { amount: cairo.uint256(1000n) });
const tx = await account.execute([approveCall, depositCall]);
Parse Events
const receipt = await provider.getTransactionReceipt(txHash);
const events = contract.parseEvents(receipt);
const transferEvents = contract.parseEvents(receipt, 'Transfer');
Transaction Simulation
Simulate before executing to catch reverts and inspect state changes:
const simResult = await account.simulateTransaction(
[{ type: 'INVOKE', payload: calls }],
{ skipValidate: false }
);
console.log('Fee estimate:', simResult[0].fee_estimation);
console.log('Trace:', simResult[0].transaction_trace);
const trace = simResult[0].transaction_trace;
if (trace?.state_diff) {
console.log('Storage changes:', trace.state_diff.storage_diffs);
}
Fee Estimation
const fee = await account.estimateInvokeFee(calls);
console.log({
overallFee: fee.overall_fee,
resourceBounds: fee.resourceBounds
});
Execute with custom bounds:
const tx = await account.execute(calls, {
resourceBounds: {
l1_gas: { amount: '0x2000', price: '0x1000000000' },
l2_gas: { amount: '0x0', price: '0x0' },
l1_data_gas: { amount: '0x1000', price: '0x1000000000' }
}
});
With priority tip:
const tipStats = await provider.getEstimateTip();
const tx = await account.execute(calls, { tip: tipStats.percentile_75 });
Transaction Receipt Handling
const receipt = await provider.waitForTransaction(txHash);
if (receipt.isSuccess()) {
console.log('Transaction succeeded');
} else if (receipt.isReverted()) {
console.log('Reverted:', receipt.revert_reason);
} else if (receipt.isRejected()) {
console.log('Rejected');
} else if (receipt.isError()) {
console.log('Error');
}
Wallet Integration
Connect to browser wallets (ArgentX, Braavos):
import { connect } from '@starknet-io/get-starknet';
import { WalletAccount } from 'starknet';
const selectedWallet = await connect({ modalMode: 'alwaysAsk' });
const walletAccount = await WalletAccount.connect(
{ nodeUrl: 'https://rpc.starknet.lava.build' },
selectedWallet
);
const tx = await walletAccount.execute(calls);
walletAccount.onAccountChange((accounts) => console.log('New account:', accounts[0]));
walletAccount.onNetworkChanged((chainId) => console.log('Network changed:', chainId));
Paymaster (Gas Sponsorship)
Setup paymaster for sponsored or alternative gas token transactions:
import { PaymasterRpc, Account } from 'starknet';
const paymaster = new PaymasterRpc({ nodeUrl: 'https://sepolia.paymaster.avnu.fi' });
const account = new Account({ provider, address, signer: privateKey, paymaster });
Sponsored (dApp pays gas):
const tx = await account.executePaymasterTransaction(calls, { feeMode: { mode: 'sponsored' } });
Alternative token (e.g., USDC):
const tokens = await account.paymaster.getSupportedTokens();
const feeDetails = { feeMode: { mode: 'default', gasToken: USDC_ADDRESS } };
const estimate = await account.estimatePaymasterTransactionFee(calls, feeDetails);
const tx = await account.executePaymasterTransaction(calls, feeDetails, estimate.suggested_max_fee_in_gas_token);
Message Signing (SNIP-12)
const typedData = {
types: {
StarknetDomain: [
{ name: 'name', type: 'shortstring' },
{ name: 'version', type: 'shortstring' },
{ name: 'chainId', type: 'shortstring' },
{ name: 'revision', type: 'shortstring' }
],
Message: [{ name: 'content', type: 'shortstring' }]
},
primaryType: 'Message',
domain: { name: 'MyDapp', version: '1', chainId: 'SN_SEPOLIA', revision: '1' },
message: { content: 'Hello Starknet' }
};
const signature = await account.signMessage(typedData);
const msgHash = await account.hashMessage(typedData);
const isValid = ec.starkCurve.verify(signature, msgHash, publicKey);
CallData & Cairo Types
import { CallData, cairo, CairoCustomEnum, CairoOption, CairoOptionVariant } from 'starknet';
const calldata = new CallData(abi);
const compiled = calldata.compile('transfer', { recipient: '0x...', amount: cairo.uint256(1000n) });
cairo.uint256(1000n)
cairo.felt252(1000)
cairo.felt('0x123')
cairo.bool(true)
cairo.byteArray('Hello')
import { shortString } from 'starknet';
shortString.encodeShortString('hello')
shortString.decodeShortString('0x...')
const myEnum = new CairoCustomEnum({ Variant1: { value: 123 } });
const some = new CairoOption(CairoOptionVariant.Some, value);
Important: Always use BigInt (e.g., 1000n) for token amounts and balances. Never use Number() or parseFloat() on wei values -- JavaScript numbers lose precision above 2^53.
ERC-20 Token Operations
const erc20 = new Contract(erc20Abi, tokenAddress, account);
const balance = await erc20.balanceOf(account.address);
console.log('Balance (wei):', balance.toString());
const amount = cairo.uint256(1000000000000000000n);
const tx = await erc20.transfer(recipientAddress, amount);
await provider.waitForTransaction(tx.transaction_hash);
await erc20.approve(spenderAddress, cairo.uint256(amount));
Utility Functions
import { stark, ec, encode, num, hash } from 'starknet';
const privateKey = '0x' + encode.buf2hex(ec.starkCurve.utils.randomPrivateKey());
const publicKey = ec.starkCurve.getStarkKey(privateKey);
num.toHex(123);
num.toBigInt('0x7b');
hash.getSelectorFromName('transfer');
hash.calculateContractAddressFromHash(salt, classHash, calldata, deployer);
Contract Deployment
const { transaction_hash, contract_address } = await account.deploy({
classHash: '0x...',
constructorCalldata: CallData.compile({ owner: account.address }),
salt: stark.randomAddress(),
unique: true
});
const declareResponse = await account.declare({
contract: compiledSierra,
casm: compiledCasm
});
await provider.waitForTransaction(declareResponse.transaction_hash);
const deployResponse = await account.deploy({
classHash: declareResponse.class_hash,
constructorCalldata: CallData.compile({ owner: account.address })
});
const result = await account.declareAndDeploy({
contract: compiledContract,
casm: compiledCasm,
constructorCalldata: CallData.compile({ owner: account.address })
});
Outside Execution (SNIP-9)
Execute transactions on behalf of another account (gasless/delegated):
const version = await account.getSnip9Version();
const outsideTransaction = await account.getOutsideTransaction(
{ caller: executorAddress, execute_after: now, execute_before: now + 3600 },
calls,
'V2'
);
const result = await executorAccount.executeFromOutside(outsideTransaction);
Error Handling
import { LibraryError, RpcError } from 'starknet';
try {
const tx = await account.execute(calls);
} catch (error) {
if (error instanceof RpcError) {
console.error('RPC error:', error.code, error.message);
} else if (error instanceof LibraryError) {
console.error('Library error:', error.message);
}
}
Logging & Configuration
import { config, setLogLevel } from 'starknet';
config.set('transactionVersion', '0x3');
config.get('transactionVersion');
setLogLevel('DEBUG');