AddDeclareTransaction
Submits a declare transaction (V3) to the Starknet network to register a new contract class. Declare transactions make contract classes available for deployment by publishing the Sierra contract class and its compiled CASM hash. Once declared, the class can be deployed multiple times using deploy account or deploy system call transactions.
Method Signature
func (provider *Provider) AddDeclareTransaction(
ctx context.Context,
declareTransaction *BroadcastDeclareTxnV3,
) (AddDeclareTransactionResponse, error)Source: write.go
Parameters
ctx(context.Context): Context for request cancellation and timeoutdeclareTransaction(*BroadcastDeclareTxnV3): The V3 declare transaction containing the contract class and metadata
Returns
AddDeclareTransactionResponse: Response containing both the transaction hash and the declared class hasherror: Error if the submission fails
Prerequisites
Before declaring a contract, you need compiled Cairo contract files:
Compile Your Cairo Contract
# Create and build your contract
mkdir my_contract && cd my_contract
scarb init
# Edit src/lib.cairo with your contract code
scarb buildThis generates two files in target/dev/:
{package_name}_{contract_name}.contract_class.json- Sierra contract class{package_name}_{contract_name}.compiled_contract_class.json- CASM compiled bytecode
Type Definitions
- Input Type (
BroadcastDeclareTxnV3) contains the contract class definition, compiled class hash, sender information, and resource bounds. - Result Type (
AddDeclareTransactionResponse) returns both the transaction hash for tracking execution and the class hash for future deployments. - Contract Class (
contracts.ContractClass) represents the Sierra contract class with entry points, ABI, and program data.
The method validates the compiled class hash matches the provided contract class and registers it on-chain.
BroadcastDeclareTxnV3
type BroadcastDeclareTxnV3 struct {
Type TransactionType `json:"type"`
SenderAddress *felt.Felt `json:"sender_address"`
CompiledClassHash *felt.Felt `json:"compiled_class_hash"`
Version TransactionVersion `json:"version"`
Signature []*felt.Felt `json:"signature"`
Nonce *felt.Felt `json:"nonce"`
ContractClass *contracts.ContractClass `json:"contract_class"`
ResourceBounds *ResourceBoundsMapping `json:"resource_bounds"`
Tip U64 `json:"tip"`
// The data needed to allow the paymaster to pay for the transaction in native tokens
PayMasterData []*felt.Felt `json:"paymaster_data"`
// The data needed to deploy the account contract from which this tx will be initiated
AccountDeploymentData []*felt.Felt `json:"account_deployment_data"`
// The storage domain of the account's nonce (an account has a nonce per DA mode)
NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"`
// The storage domain of the account's balance from which fee will be charged
FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"`
}Source: types_broadcast_transaction.go
AddDeclareTransactionResponse
type AddDeclareTransactionResponse struct {
Hash *felt.Felt `json:"transaction_hash"`
ClassHash *felt.Felt `json:"class_hash"`
}Source: types_transaction_response.go
ResourceBoundsMapping
type ResourceBoundsMapping struct {
// The max amount and max price per unit of L1 gas used in this tx
L1Gas ResourceBounds `json:"l1_gas"`
// The max amount and max price per unit of L1 blob gas used in this tx
L1DataGas ResourceBounds `json:"l1_data_gas"`
// The max amount and max price per unit of L2 gas used in this tx
L2Gas ResourceBounds `json:"l2_gas"`
}Source: types_transaction.go
Usage Example
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"math/big"
"os"
"time"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/account"
"github.com/NethermindEth/starknet.go/contracts"
"github.com/NethermindEth/starknet.go/hash"
"github.com/NethermindEth/starknet.go/rpc"
"github.com/joho/godotenv"
)
func main() {
// Load environment variables
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
// Get configuration from environment
rpcURL := os.Getenv("STARKNET_RPC_URL")
privateKeyStr := os.Getenv("ACCOUNT_PRIVATE_KEY")
publicKeyStr := os.Getenv("ACCOUNT_PUBLIC_KEY")
accountAddressStr := os.Getenv("ACCOUNT_ADDRESS")
if rpcURL == "" || privateKeyStr == "" || publicKeyStr == "" || accountAddressStr == "" {
log.Fatal("Missing required environment variables")
}
// Parse credentials
privateKey, err := new(felt.Felt).SetString(privateKeyStr)
if err != nil {
log.Fatal(err)
}
publicKey, err := new(felt.Felt).SetString(publicKeyStr)
if err != nil {
log.Fatal(err)
}
accountAddress, err := new(felt.Felt).SetString(accountAddressStr)
if err != nil {
log.Fatal(err)
}
// Initialize provider
ctx := context.Background()
client, err := rpc.NewProvider(ctx, rpcURL)
if err != nil {
log.Fatal(err)
}
// Create keystore
ks := account.NewMemKeystore()
ks.Put(publicKey.String(), privateKey.BigInt(new(big.Int)))
// Create account controller
acct, err := account.NewAccount(client, accountAddress, publicKey.String(), ks, 2)
if err != nil {
log.Fatal(err)
}
// Get current nonce
nonce, err := acct.Nonce(ctx)
if err != nil {
log.Fatal(err)
}
// Load the compiled contract files
// Replace with your actual contract paths
sierraPath := "counter_contract/target/dev/counter_Counter.contract_class.json"
casmPath := "counter_contract/target/dev/counter_Counter.compiled_contract_class.json"
// Check if files exist
if _, err := os.Stat(sierraPath); os.IsNotExist(err) {
log.Fatal("Contract files not found! Run: cd counter_contract && scarb build")
}
// Load Sierra contract
sierraContent, err := os.ReadFile(sierraPath)
if err != nil {
log.Fatal(err)
}
var contractClass contracts.ContractClass
if err := json.Unmarshal(sierraContent, &contractClass); err != nil {
log.Fatal(err)
}
// Load CASM contract
casmContent, err := os.ReadFile(casmPath)
if err != nil {
log.Fatal(err)
}
var casmClass contracts.CasmClass
if err := json.Unmarshal(casmContent, &casmClass); err != nil {
log.Fatal(err)
}
// Calculate the compiled class hash first
// Note: There's sometimes a mismatch between local calculation and what the sequencer expects
// If you get a mismatch error, use the "Expected" hash from the error message
compiledClassHash, err := hash.CompiledClassHash(&casmClass)
if err != nil {
log.Fatal(err)
}
// Override with the correct hash if there's a known mismatch
// IMPORTANT: Replace this with the "Expected" hash from your error message if you get a mismatch
// Example: compiledClassHash, _ = new(felt.Felt).SetString("0x4fe67cc3cb8e0e3c06161a5b8ccaed841e5e3116138bef832b19fa298c74f6c")
// Calculate class hash from the contract
classHash := hash.ClassHash(&contractClass)
fmt.Printf("Class Hash: %s\n", classHash.String())
fmt.Printf("Compiled Class Hash: %s\n", compiledClassHash.String())
// Check if contract is already declared
fmt.Println("Checking if contract is already declared...")
_, err = client.ClassAt(ctx, rpc.WithBlockTag(rpc.BlockTagLatest), classHash)
if err == nil {
fmt.Println("Contract is already declared!")
fmt.Printf("Class Hash: %s\n", classHash.String())
return
}
// Try alternative check with Class method
_, err = client.Class(ctx, rpc.WithBlockTag(rpc.BlockTagLatest), classHash)
if err == nil {
fmt.Println("Contract is already declared!")
fmt.Printf("Class Hash: %s\n", classHash.String())
return
}
fmt.Println("Contract not found on-chain, proceeding with declaration...")
// Create the declare transaction
declareTx := &rpc.BroadcastDeclareTxnV3{
Type: rpc.TransactionTypeDeclare,
Version: rpc.TransactionV3,
SenderAddress: accountAddress,
Nonce: nonce,
ContractClass: &contractClass,
CompiledClassHash: compiledClassHash,
Signature: []*felt.Felt{}, // Will be filled by signing
ResourceBounds: &rpc.ResourceBoundsMapping{
L1Gas: rpc.ResourceBounds{
MaxAmount: rpc.U64("0x186a0"), // 100000
MaxPricePerUnit: rpc.U128("0x33a937098d80"), // High price for estimation
},
L1DataGas: rpc.ResourceBounds{
MaxAmount: rpc.U64("0x186a0"), // 100000
MaxPricePerUnit: rpc.U128("0x33a937098d80"), // High price for estimation
},
L2Gas: rpc.ResourceBounds{
MaxAmount: rpc.U64("0x6b5c0"), // 440000 (minimum required)
MaxPricePerUnit: rpc.U128("0x10c388d00"), // High price for estimation
},
},
Tip: rpc.U64("0x0"),
PayMasterData: []*felt.Felt{},
AccountDeploymentData: []*felt.Felt{},
NonceDataMode: rpc.DAModeL1,
FeeMode: rpc.DAModeL1,
}
// Estimate fee
fmt.Println("Estimating transaction fee...")
simFlags := []rpc.SimulationFlag{rpc.SkipValidate}
feeEstimate, err := client.EstimateFee(
ctx,
[]rpc.BroadcastTxn{declareTx},
simFlags,
rpc.WithBlockTag(rpc.BlockTagLatest),
)
if err != nil {
fmt.Printf("Fee estimation failed: %v\n", err)
fmt.Println("Continuing with default resource bounds...")
} else if len(feeEstimate) > 0 {
// Update resource bounds based on estimate with 20% buffer
estimatedL1DataGas := feeEstimate[0].L1DataGasConsumed.Uint64()
estimatedL2Gas := feeEstimate[0].L2GasConsumed.Uint64()
declareTx.ResourceBounds = &rpc.ResourceBoundsMapping{
L1Gas: rpc.ResourceBounds{
MaxAmount: rpc.U64(fmt.Sprintf("0x%x", estimatedL1DataGas*12/10)),
MaxPricePerUnit: rpc.U128("0x33a937098d80"),
},
L1DataGas: rpc.ResourceBounds{
MaxAmount: rpc.U64(fmt.Sprintf("0x%x", estimatedL1DataGas*12/10)),
MaxPricePerUnit: rpc.U128("0x33a937098d80"),
},
L2Gas: rpc.ResourceBounds{
MaxAmount: rpc.U64(fmt.Sprintf("0x%x", estimatedL2Gas*12/10)),
MaxPricePerUnit: rpc.U128("0x10c388d00"),
},
}
fmt.Printf("Estimated L1 Data Gas: %d\n", estimatedL1DataGas)
fmt.Printf("Estimated L2 Gas: %d\n", estimatedL2Gas)
fmt.Printf("Overall Fee: %s STRK\n", feeEstimate[0].OverallFee.String())
}
// Sign the transaction
// Create a DeclareTxnV3 for signing (required for proper hash calculation)
declareTxnV3 := rpc.DeclareTxnV3{
Type: declareTx.Type,
SenderAddress: declareTx.SenderAddress,
CompiledClassHash: declareTx.CompiledClassHash,
Version: declareTx.Version,
Signature: declareTx.Signature,
Nonce: declareTx.Nonce,
ClassHash: classHash, // Important: ClassHash is required for signing
ResourceBounds: declareTx.ResourceBounds,
Tip: declareTx.Tip,
PayMasterData: declareTx.PayMasterData,
AccountDeploymentData: declareTx.AccountDeploymentData,
NonceDataMode: declareTx.NonceDataMode,
FeeMode: declareTx.FeeMode,
}
err = acct.SignDeclareTransaction(ctx, &declareTxnV3)
if err != nil {
log.Fatal(err)
}
// Copy signature back to broadcast transaction
declareTx.Signature = declareTxnV3.Signature
// Submit the transaction
resp, err := client.AddDeclareTransaction(ctx, declareTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Contract declared successfully!\n")
fmt.Printf("Transaction Hash: %s\n", resp.Hash.String())
fmt.Printf("Class Hash: %s\n", resp.ClassHash.String())
// Wait for transaction confirmation
receipt, err := waitForTransaction(ctx, client, resp.Hash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transaction confirmed!\n")
fmt.Printf("Block Number: %d\n", receipt.BlockNumber)
fmt.Printf("Status: %s\n", receipt.FinalityStatus)
fmt.Printf("Actual Fee: %s\n", receipt.ActualFee.Amount.String())
}
// waitForTransaction waits for a transaction to be confirmed on the network
func waitForTransaction(ctx context.Context, client *rpc.Provider, txHash *felt.Felt) (*rpc.TransactionReceiptWithBlockInfo, error) {
for i := 0; i < 60; i++ { // Wait up to 5 minutes
receipt, err := client.TransactionReceipt(ctx, txHash)
if err == nil {
if receipt.FinalityStatus == rpc.TxnFinalityStatusAcceptedOnL2 ||
receipt.FinalityStatus == rpc.TxnFinalityStatusAcceptedOnL1 {
return receipt, nil
}
}
time.Sleep(5 * time.Second)
fmt.Print(".")
}
return nil, fmt.Errorf("transaction confirmation timeout")
}Error Handling
result, err := client.AddDeclareTransaction(ctx, &declareTx)
if err != nil {
switch {
case errors.Is(err, rpc.ErrClassAlreadyDeclared):
log.Printf("Class already declared - use existing class hash")
return
case errors.Is(err, rpc.ErrCompilationFailed):
log.Printf("Contract class compilation failed")
return
case errors.Is(err, rpc.ErrCompiledClassHashMismatch):
log.Printf("Compiled class hash mismatch - check the expected hash in error")
return
case errors.Is(err, rpc.ErrContractClassSizeTooLarge):
log.Printf("Contract class exceeds maximum size limit")
return
case errors.Is(err, rpc.ErrInsufficientAccountBalance):
log.Printf("Insufficient balance to pay for declaration fees")
return
case errors.Is(err, rpc.ErrInvalidTransactionNonce):
log.Printf("Invalid nonce - transaction nonce must match account nonce")
return
default:
log.Printf("Failed to declare class: %v", err)
return
}
}
fmt.Printf("Class Hash: %s\n", result.ClassHash)Common Use Cases
- Deploying new smart contracts by first declaring the contract class on-chain
- Upgrading existing contracts by declaring a new version of the contract class
- Sharing reusable contract logic by declaring libraries that multiple contracts can use
- Publishing open-source contract implementations for community use and verification
Related Methods
- Class - Retrieve a declared contract class by its hash
- ClassHashAt - Get the class hash of a deployed contract
- EstimateFee - Estimate fees for the declare transaction before submission
- SimulateTransactions - Test declaration before submitting to the network

