Contract ABI

Contract ABI is a representation of a Starknet contract’s interface. It is formatted as JSON and describes the functions, structs and events which are defined in the contract.

You can get the contract’s ABI by compiling:

cargo run --bin starknet-compile -- /path/to/input.cairo /path/to/output.json

The Cairo 0 compiler is no longer maintained, see here for more details on Cairo 1 contracts compilation.

The following is an example contract ABI:

  • Cairo v1

  • Cairo v2

[
  {
    "type": "function",
    "name": "constructor",
    "inputs": [
      {
        "name": "name_",
        "type": "core::felt252"
      },
      {
        "name": "symbol_",
        "type": "core::felt252"
      },
      {
        "name": "decimals_",
        "type": "core::integer::u8"
      },
      {
        "name": "initial_supply",
        "type": "core::integer::u256"
      },
      {
        "name": "recipient",
        "type": "core::starknet::contract_address::ContractAddress"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "function",
    "name": "get_name",
    "inputs": [],
    "outputs": [
      {
        "type": "core::felt252"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "get_symbol",
    "inputs": [],
    "outputs": [
      {
        "type": "core::felt252"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "get_decimals",
    "inputs": [],
    "outputs": [
      {
        "type": "core::integer::u8"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "get_total_supply",
    "inputs": [],
    "outputs": [
      {
        "type": "core::integer::u256"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "balance_of",
    "inputs": [
      {
        "name": "account",
        "type": "core::starknet::contract_address::ContractAddress"
      }
    ],
    "outputs": [
      {
        "type": "core::integer::u256"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "allowance",
    "inputs": [
      {
        "name": "owner",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "spender",
        "type": "core::starknet::contract_address::ContractAddress"
      }
    ],
    "outputs": [
      {
        "type": "core::integer::u256"
      }
    ],
    "state_mutability": "view"
  },
  {
    "type": "function",
    "name": "transfer",
    "inputs": [
      {
        "name": "recipient",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "amount",
        "type": "core::integer::u256"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "function",
    "name": "transfer_from",
    "inputs": [
      {
        "name": "sender",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "recipient",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "amount",
        "type": "core::integer::u256"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "function",
    "name": "approve",
    "inputs": [
      {
        "name": "spender",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "amount",
        "type": "core::integer::u256"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "function",
    "name": "increase_allowance",
    "inputs": [
      {
        "name": "spender",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "added_value",
        "type": "core::integer::u256"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "function",
    "name": "decrease_allowance",
    "inputs": [
      {
        "name": "spender",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "subtracted_value",
        "type": "core::integer::u256"
      }
    ],
    "outputs": [],
    "state_mutability": "external"
  },
  {
    "type": "event",
    "name": "Transfer",
    "inputs": [
      {
        "name": "from",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "to",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "value",
        "type": "core::integer::u256"
      }
    ]
  },
  {
    "type": "event",
    "name": "Approval",
    "inputs": [
      {
        "name": "owner",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "spender",
        "type": "core::starknet::contract_address::ContractAddress"
      },
      {
        "name": "value",
        "type": "core::integer::u256"
      }
    ]
  }
]
[
  {
    "type": "impl",
    "name": "CounterContract",
    "interface_name": "new_syntax_test_contract::new_syntax_test_contract::ICounterContract"
  },
  {
    "type": "interface",
    "name": "new_syntax_test_contract::new_syntax_test_contract::ICounterContract",
    "items": [
      {
        "type": "function",
        "name": "increase_counter",
        "inputs": [
          {
            "name": "amount",
            "type": "core::integer::u128"
          }
        ],
        "outputs": [],
        "state_mutability": "external"
      },
      {
        "type": "function",
        "name": "decrease_counter",
        "inputs": [
          {
            "name": "amount",
            "type": "core::integer::u128"
          }
        ],
        "outputs": [],
        "state_mutability": "external"
      },
      {
        "type": "function",
        "name": "get_counter",
        "inputs": [],
        "outputs": [
          {
            "type": "core::integer::u128"
          }
        ],
        "state_mutability": "view"
      }
    ]
  },
  {
    "type": "constructor",
    "name": "constructor",
    "inputs": [
      {
        "name": "initial_counter",
        "type": "core::integer::u128"
      },
      {
        "name": "other_contract_addr",
        "type": "core::starknet::contract_address::ContractAddress"
      }
    ]
  },
  {
    "type": "event",
    "name": "new_syntax_test_contract::new_syntax_test_contract::counter_contract::CounterIncreased",
    "kind": "struct",
    "members": [
      {
        "name": "amount",
        "type": "core::integer::u128",
        "kind": "data"
      }
    ]
  },
  {
    "type": "event",
    "name": "new_syntax_test_contract::new_syntax_test_contract::counter_contract::CounterDecreased",
    "kind": "struct",
    "members": [
      {
        "name": "amount",
        "type": "core::integer::u128",
        "kind": "data"
      }
    ]
  },
  {
    "type": "event",
    "name": "new_syntax_test_contract::new_syntax_test_contract::counter_contract::Event",
    "kind": "enum",
    "variants": [
      {
        "name": "CounterIncreased",
        "type": "new_syntax_test_contract::new_syntax_test_contract::counter_contract::CounterIncreased",
        "kind": "nested"
      },
      {
        "name": "CounterDecreased",
        "type": "new_syntax_test_contract::new_syntax_test_contract::counter_contract::CounterDecreased",
        "kind": "nested"
      }
    ]
  }
]

Cairo v2 ABI changes

With the transition to v2, the contract ABI underwent some changes. Consider the following high level code that generates the ABI in the above example:

#[starknet::interface]
trait IOtherContract<TContractState> {
    fn decrease_allowed(self: @TContractState) -> bool;
}

#[starknet::interface]
trait ICounterContract<TContractState> {
    fn increase_counter(ref self: TContractState, amount: u128);
    fn decrease_counter(ref self: TContractState, amount: u128);
    fn get_counter(self: @TContractState) -> u128;
}

#[starknet::contract]
mod counter_contract {
    use starknet::ContractAddress;
    use super::{
        IOtherContractDispatcher, IOtherContractDispatcherTrait, IOtherContractLibraryDispatcher
    };

    #[storage]
    struct Storage {
        counter: u128,
        other_contract: IOtherContractDispatcher
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        CounterIncreased: CounterIncreased,
        CounterDecreased: CounterDecreased
    }

    #[derive(Drop, starknet::Event)]
    struct CounterIncreased {
        amount: u128
    }

    #[derive(Drop, starknet::Event)]
    struct CounterDecreased {
        amount: u128
    }

    #[constructor]
    fn constructor(
        ref self: ContractState, initial_counter: u128, other_contract_addr: ContractAddress
    ) {
        self.counter.write(initial_counter);
        self
            .other_contract
            .write(IOtherContractDispatcher { contract_address: other_contract_addr });
    }

    #[external(v0)]
    impl CounterContract of super::ICounterContract<ContractState> {
        fn get_counter(self: @ContractState) -> u128 {
            self.counter.read()
        }

        fn increase_counter(ref self: ContractState, amount: u128) {
            let current = self.counter.read();
            self.counter.write(current + amount);
            self.emit(CounterIncreased { amount });
        }

        fn decrease_counter(ref self: ContractState, amount: u128) {
            let allowed = self.other_contract.read().decrease_allowed();
            if allowed {
                let current = self.counter.read();
                self.counter.write(current - amount);
                self.emit(CounterDecreased { amount });
            }
        }
    }
}

Interface and Impl ABI entries

Since the CounterContract impl is annotated with the #[external(v0)] attribute, you’ll find the following impl entry in the ABI:

{
  "type": "impl",
  "name": "CounterContract",
  "interface_name": "new_syntax_test_contract::new_syntax_test_contract::ICounterContract"
},

This means that every function appearing in the ICounterContract interface is a possible entry point of the contract.

Standalone functions in the contract (outside an external impl) can also be anotated with #[external(v0)] (currently, this is the only way to add L1 handlers). In such cases, a corresponding function (or l1_handler) entry will be found in the ABI in the same hierarchy as impls and interfaces.

Events

In Cairo v2, a dedicated type for the contract’s events was introduced. Currently, the contract event type must be an enum named Event, whose variants are structs of the same name as the variant. Types that can be emitted via self.emit(_) must implement the Event trait, which defines how this type should be serialized into two felt252 arrays, keys and data.

The Event enum variants appear in the ABI under "type" = "event" rather than regular structs. For such entries, each member has an additional kind field which specifies how the serialization into keys and data takes place:

  • if the kind is key, then this member or variant are serialized into the event’s keys

  • if the kind is data, then this member or variant are serialized into the event’s data

  • if the kind is nested, then the member or variant are serialized acording to the Event attribute, potentially adding to both keys and data. At the moment, this feature is not yet supported, so no high level code written in Cairo v2.0.0 can generate such ABI.

Specification

You can find a JSON schema specification of the ABI in the starknet-specs repository. For a UI-friendly version, you can use the OPEN-RPC playground.