starknet-jvm

Starknet-jvm is a library allowing for easy interaction with the Starknet JSON-RPC nodes, including querying starknet state, executing transactions and deploying contracts.

Although written in Kotlin, Starknet-jvm has been created with compatibility with Java in mind.

Table of contents

  • #using-provider

  • #reusing-providers

  • #creating-account

  • #transferring-strk-tokens

  • #making-synchronous-requests

  • #making-asynchronous-requests

  • #deploying-account-v3

  • #estimating-fee-for-deploy-account-v3-transaction

  • #deploying-account-v1

  • #estimating-fee-for-deploy-account-v1-transaction

  • #invoking-contract-transferring-eth

  • #estimating-fee-for-invoke-v3-transaction

  • #calling-contract-fetching-eth-balance

  • #making-multiple-calls-get-multiple-transactions-data

  • #making-multiple-calls-of-different-types-in-one-request

  • #declaring-cairo-12-contract-v3

  • #estimating-fee-for-declare-v3-transaction

  • #declaring-cairo-12-contract-v2

  • #estimating-fee-for-declare-v2-transaction

Using provider

Provider is a facade for interacting with Starknet. JsonRpcProvider is a client which interacts with a Starknet full nodes like Pathfinder, Papyrus or Juno. It supports read and write operations, like querying the blockchain state or sending new transactions for execution.

import com.swmansion.starknet.provider.rpc.JsonRpcProvider

fun main() {
val provider = JsonRpcProvider("https://your.node.url/rpc")

val request = provider.getBlockWithTxs(1)
val response = request.send()
}

Reusing providers

Make sure you don't create a new provider every time you want to use one. Instead, you should reuse existing instance. This way you reuse connections and thread pools.

Do:

val provider = JsonRpcProvider("https://your.node.url/rpc")
val account1 = StandardAccount(provider, accountAddress1, privateKey1)
val account2 = StandardAccount(provider, accountAddress2, privateKey2)

Don't:

val provider1 = JsonRpcProvider("https://your.node.url/rpc")
val account1 = StandardAccount(provider1, accountAddress1, privateKey1)
val provider2 = JsonRpcProvider("https://your.node.url/rpc")
val account2 = StandardAccount(provider2, accountAddress2, privateKey2)

Creating account

StandardAccount is the default implementation of Account interface. It supports an account contract which proxies the calls to other contracts on Starknet.

Account can be created in two ways:

  • By constructor (It is required to provide an address and either private key or signer).

  • By methods Account.signDeployAccountV3() or Account.signDeployAccountV3()

There are some examples how to do it:

import com.swmansion.starknet.account.StandardAccount
import com.swmansion.starknet.data.types.Felt
import com.swmansion.starknet.data.types.StarknetChainId

fun main() {
// If you don't have a private key, you can generate a random one
val randomPrivateKey = StandardAccount.generatePrivateKey()

// Create an instance of account which is already deployed
// providing an address and a private key
val account = StandardAccount(
address = Felt.fromHex("0x123"),
privateKey = Felt.fromHex("0x456"),
provider = ...,
chainId = StarknetChainId.SEPOLIA,
)

// It's possible to specify a signer
val accountWithSigner = StandardAccount(
address = Felt.fromHex("0x123"),
signer = ...,
provider = ...,
chainId = StarknetChainId.SEPOLIA,
)
}

Transferring STRK tokens

import com.swmansion.starknet.account.StandardAccount
import com.swmansion.starknet.data.types.Call
import com.swmansion.starknet.data.types.Felt
import com.swmansion.starknet.data.types.StarknetChainId
import com.swmansion.starknet.data.types.Uint256
import com.swmansion.starknet.provider.rpc.JsonRpcProvider

fun main() {
val provider = JsonRpcProvider("https://your.node.url")
val account = StandardAccount(
address = Felt.fromHex("0x123"),
privateKey = Felt.fromHex("0x456"),
provider = provider,
chainId = StarknetChainId.SEPOLIA,
)


val amount = Uint256(Felt(100))
val recipientAccountAddress = Felt.fromHex("0x789")
val strkContractAddress = Felt.fromHex("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d")
val call = Call(
contractAddress = strkContractAddress,
entrypoint = "transfer",
calldata = listOf(recipientAccountAddress) + amount.toCalldata(),
)

val request = account.executeV3(call)
val response = request.send()
}

Making synchronous requests

import com.swmansion.starknet.data.types.BlockTag
import com.swmansion.starknet.data.types.Felt
import com.swmansion.starknet.provider.rpc.JsonRpcProvider

fun main() {
// Create a provider for interacting with Starknet
val provider = JsonRpcProvider("https://your.node.url/rpc")

// Make a request
val contractAddress = Felt.fromHex("0x423623626")
val storageKey = Felt.fromHex("0x132412414")
val request = provider.getStorageAt(contractAddress, storageKey, BlockTag.LATEST)
val response = request.send()

println(response)
}

Making asynchronous requests

It is also possible to make asynchronous requests. Request.sendAsync() returns a CompletableFuture that can be than handled in preferred way.

import com.swmansion.starknet.data.types.BlockTag
import com.swmansion.starknet.data.types.Felt
import com.swmansion.starknet.provider.rpc.JsonRpcProvider

fun main() {
// Create a provider for interacting with Starknet
val provider = JsonRpcProvider("https://your.node.url/rpc")

// Make an asynchronous request
val contractAddress = Felt.fromHex("0x423623626")
val storageKey = Felt.fromHex("0x132412414")
val request = provider.getStorageAt(contractAddress, storageKey, BlockTag.LATEST)
val future = request.sendAsync()

future.thenAccept { println(it) }
}

Deploying account V3

val privateKey = Felt(22222)
val publicKey = StarknetCurve.getPublicKey(privateKey)

val salt = Felt(2)
val calldata = listOf(publicKey)
val address = ContractAddressCalculator.calculateAddressFromHash(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
)

val newAccount = StandardAccount(
address,
privateKey,
provider,
chainId,
)
val l1ResourceBounds = ResourceBounds(
maxAmount = Uint64(20000),
maxPricePerUnit = Uint128(120000000000),
)
val params = DeployAccountParamsV3(
nonce = Felt.ZERO,
l1ResourceBounds = l1ResourceBounds,
)

// Prefund the new account address with STRK
val payload = newAccount.signDeployAccountV3(
classHash = accountContractClassHash,
salt = salt,
calldata = calldata,
params = params,
forFeeEstimate = false,
)

val response = provider.deployAccount(payload).send()
// Make sure tx matches what we sent
val tx = provider.getTransaction(response.transactionHash).send() as DeployAccountTransactionV3
// Invoke function to make sure the account was deployed properly
val call = Call(balanceContractAddress, "increase_balance", listOf(Felt(10)))
val result = newAccount.executeV3(call).send()

val receipt = provider.getTransactionReceipt(result.transactionHash).send()

Estimating fee for deploy account V3 transaction

val privateKey = Felt(22223)
val publicKey = StarknetCurve.getPublicKey(privateKey)

val salt = Felt(2)
val calldata = listOf(publicKey)
val address = ContractAddressCalculator.calculateAddressFromHash(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
)
val account = StandardAccount(
address,
privateKey,
provider,
chainId,
)
val params = DeployAccountParamsV3(
nonce = Felt.ZERO,
l1ResourceBounds = ResourceBounds.ZERO,
)
val payloadForFeeEstimation = account.signDeployAccountV3(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
params = params,
forFeeEstimate = true,
)
val feePayload = provider.getEstimateFee(listOf(payloadForFeeEstimation)).send()

Deploying account V1

val privateKey = Felt(11111)
val publicKey = StarknetCurve.getPublicKey(privateKey)

val salt = Felt.ONE
val calldata = listOf(publicKey)
val address = ContractAddressCalculator.calculateAddressFromHash(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
)

// Make sure to prefund the new account address with ETH
val account = StandardAccount(
address,
privateKey,
provider,
chainId,
)
val payload = account.signDeployAccountV1(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
// 10*fee from estimate deploy account fee
maxFee = Felt.fromHex("0x11fcc58c7f7000"),
)

val response = provider.deployAccount(payload).send()
val tx = provider.getTransaction(response.transactionHash).send() as DeployAccountTransactionV1
// Invoke function to make sure the account was deployed properly
val call = Call(balanceContractAddress, "increase_balance", listOf(Felt(10)))
val result = account.executeV1(call).send()

val receipt = provider.getTransactionReceipt(result.transactionHash).send()

Estimating fee for deploy account V1 transaction

val privateKey = Felt(11112)
val publicKey = StarknetCurve.getPublicKey(privateKey)

val salt = Felt.ONE
val calldata = listOf(publicKey)
val address = ContractAddressCalculator.calculateAddressFromHash(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
)

val account = StandardAccount(
address,
privateKey,
provider,
chainId,
)
val payloadForFeeEstimation = account.signDeployAccountV1(
classHash = accountContractClassHash,
calldata = calldata,
salt = salt,
maxFee = Felt.ZERO,
nonce = Felt.ZERO,
forFeeEstimate = true,
)
val feePayload = provider.getEstimateFee(listOf(payloadForFeeEstimation)).send()

Invoking contract: Transferring ETH

Estimating fee for invoke V3 transaction

val call = Call(balanceContractAddress, "increase_balance", listOf(Felt(10)))

val request = account.estimateFeeV3(
listOf(call),
skipValidate = false,
)
val feeEstimate = request.send().values.first()

Calling contract: Fetching ETH balance

Making multiple calls: get multiple transactions data in one request

val blockNumber = provider.getBlockNumber().send().value
val request = provider.batchRequests(
provider.getTransactionByBlockIdAndIndex(blockNumber, 0),
provider.getTransaction(invokeTransactionHash),
provider.getTransaction(declareTransactionHash),
provider.getTransaction(deployAccountTransactionHash),

)

val response = request.send()

Making multiple calls of different types in one request

val request = provider.batchRequestsAny(
provider.getTransaction(invokeTransactionHash),
provider.getBlockNumber(),
provider.getTransactionStatus(invokeTransactionHash),
)

val response = request.send()

val transaction = response[0].getOrThrow() as Transaction
val blockNumber = (response[1].getOrThrow() as IntResponse).value
val txStatus = response[2].getOrThrow() as GetTransactionStatusResponse

Declaring Cairo 1/2 contract V3

ScarbClient.buildSaltedContract(
placeholderContractPath = Path.of("src/test/resources/contracts_v2/src/placeholder_counter_contract.cairo"),
saltedContractPath = Path.of("src/test/resources/contracts_v2/src/salted_counter_contract.cairo"),
)
val contractCode = Path.of("src/test/resources/contracts_v2/target/release/ContractsV2_SaltedCounterContract.sierra.json").readText()
val casmCode = Path.of("src/test/resources/contracts_v2/target/release/ContractsV2_SaltedCounterContract.casm.json").readText()

val contractDefinition = Cairo2ContractDefinition(contractCode)
val contractCasmDefinition = CasmContractDefinition(casmCode)
val nonce = account.getNonce().send()

val params = DeclareParamsV3(
nonce = nonce,
l1ResourceBounds = ResourceBounds(
maxAmount = Uint64(100000),
maxPricePerUnit = Uint128(1000000000000),
),
)
val declareTransactionPayload = account.signDeclareV3(
contractDefinition,
contractCasmDefinition,
params,
)
val request = provider.declareContract(declareTransactionPayload)
val result = request.send()

val receipt = provider.getTransactionReceipt(result.transactionHash).send()

Estimating fee for declare V3 transaction

val contractCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.sierra.json").readText()
val casmCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.casm.json").readText()

val contractDefinition = Cairo1ContractDefinition(contractCode)
val contractCasmDefinition = CasmContractDefinition(casmCode)
val nonce = account.getNonce().send()

val params = DeclareParamsV3(nonce = nonce, l1ResourceBounds = ResourceBounds.ZERO)
val declareTransactionPayload = account.signDeclareV3(
contractDefinition,
contractCasmDefinition,
params,
true,
)
val request = provider.getEstimateFee(payload = listOf(declareTransactionPayload), simulationFlags = emptySet())
val feeEstimate = request.send().values.first()

Declaring Cairo 1/2 contract V2

val contractCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.sierra.json").readText()
val casmCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.casm.json").readText()

val contractDefinition = Cairo1ContractDefinition(contractCode)
val contractCasmDefinition = CasmContractDefinition(casmCode)
val nonce = account.getNonce().send()

val declareTransactionPayload = account.signDeclareV2(
contractDefinition,
contractCasmDefinition,
ExecutionParams(nonce, Felt(5000000000000000L)),
)
val request = provider.declareContract(declareTransactionPayload)
val result = request.send()

val receipt = provider.getTransactionReceipt(result.transactionHash).send()

Estimating fee for declare V2 transaction

val contractCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.sierra.json").readText()
val casmCode = Path.of("src/test/resources/contracts_v1/target/release/ContractsV1_HelloStarknet.casm.json").readText()

val contractDefinition = Cairo1ContractDefinition(contractCode)
val contractCasmDefinition = CasmContractDefinition(casmCode)
val nonce = account.getNonce().send()

val declareTransactionPayload = account.signDeclareV2(
sierraContractDefinition = contractDefinition,
casmContractDefinition = contractCasmDefinition,
params = ExecutionParams(nonce, Felt.ZERO),
forFeeEstimate = true,
)
val request = provider.getEstimateFee(payload = listOf(declareTransactionPayload), simulationFlags = emptySet())
val feeEstimate = request.send().values.first()

Packages

Link copied to clipboard

src/main/kotlin/com/swmansion/starknet/account/Account.kt interface is used to send Starknet transactions for execution. Its base implementation is src/main/kotlin/com/swmansion/starknet/account/StandardAccount.kt.

Link copied to clipboard

Cryptography and signature related classes. This is a low level module. Recommended way of using is through Signer and Account implementations.

Link copied to clipboard

Data classes representing Starknet objects and utilities for handling them.

Link copied to clipboard

Data classes representing Starknet objects and transactions.

Link copied to clipboard

Classes for interacting with Universal Deployer Contract (UDC).

Link copied to clipboard
Link copied to clipboard

Provider interface and its implementations.

Exceptions thrown by the Starknet providers.

Link copied to clipboard

Provider implementing the JSON-RPC interface to communicate with the network.

Link copied to clipboard

Http service used to communicate with Starknet.

Link copied to clipboard

Signer interface and its implementations for manually signing transactions to be sent to Starknet. Recommended way of using Signer is through an Account.