Skip to main content
starknet-js 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.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/keep-starknet-strange/starknet-agentic --skill starknet-jsThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Routes Starknet agent, wallet, DeFi, identity, SDK, and Cairo contract work to the smallest focused skill module.
SNIP-36 virtual block proving on Starknet. Trigger on "virtual block", "SNIP-36", "off-chain proof", "anonymous vote", "heavy computation off-chain", "prove a transaction". Covers Cairo virtual contract, proof server, starknet.js integration, and on-chain verification.
Security audit of Cairo/Starknet code. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo), deep (+ adversarial reasoning), or specific filenames.
Related occupations SOC
Based on SOC occupation classification
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'
});
Mainnet: https://rpc.starknet.lava.build
Sepolia: https://rpc.starknet-testnet.lava.build
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) 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.
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' }
}
});
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' );