# core::RangeCheck Source: https://docs.starknet.io/build/corelib/core-RangeCheck General purpose implicits. ## Signature ```rust theme={null} pub extern type RangeCheck; ``` # core::SegmentArena Source: https://docs.starknet.io/build/corelib/core-SegmentArena ## Signature ```rust theme={null} pub extern type SegmentArena; ``` # core::assert Source: https://docs.starknet.io/build/corelib/core-assert Panics if `cond` is false with the given `felt252` as error message. ## Signature ```rust theme={null} pub fn assert(cond: bool, err_code: felt252) ``` ## Examples ```rust theme={null} assert(false, 'error message'); ``` # core::blake::blake2s_compress Source: https://docs.starknet.io/build/corelib/core-blake-blake2s_compress The blake2s compress function, which takes a state, a byte count, and a message, and returns a new state. `byte_count` should be the total number of bytes hashed after hashing the current `msg`. ## Signature ```rust theme={null} pub extern fn blake2s_compress(state: Box, byte_count: u32, msg: Box) -> Box nopanic; ``` # core::blake::blake2s_finalize Source: https://docs.starknet.io/build/corelib/core-blake-blake2s_finalize A variant of `blake2s_compress` for the final block of the message. The input `msg` must always be exactly 16 `u32` elements, padded with zeros if necessary, regardless of the value of `byte_count`. Using any padding scheme other than zero-padding will produce a different hash output. ## Signature ```rust theme={null} pub extern fn blake2s_finalize(state: Box, byte_count: u32, msg: Box) -> Box nopanic; ``` # core::bool Source: https://docs.starknet.io/build/corelib/core-bool `bool` enum representing either `false` or `true`. ## Signature ```rust theme={null} pub enum bool { False, True, } ``` ## Variants ### False #### Signature ```rust theme={null} False ``` ### True #### Signature ```rust theme={null} True ``` # core::boolean::BoolTrait Source: https://docs.starknet.io/build/corelib/core-boolean-BoolTrait ## Signature ```rust theme={null} pub trait BoolTrait> ``` ## Trait functions ### then\_some Returns `Some(t)` if the `bool` is `true`, `None` otherwise. #### Signature ```rust theme={null} fn then_some, T, +Drop>(self: bool, t: T) -> Option ``` #### Examples ```rust theme={null} assert!(false.then_some(0) == None); assert!(true.then_some(0) == Some(0)); ``` # core::box::Box Source: https://docs.starknet.io/build/corelib/core-box-Box A `Box` is a type that points to a wrapped value. It allows for cheap moving around of the value, as its size is small, and may wrap a large size. ## Signature ```rust theme={null} pub extern type Box; ``` # core::box::BoxTrait Source: https://docs.starknet.io/build/corelib/core-box-BoxTrait ## Signature ```rust theme={null} pub trait BoxTrait ``` ## Trait functions ### new Creates a new `Box` with the given value. Allocates space in the boxed segment for the provided value and returns a `Box` that points to it. #### Signature ```rust theme={null} fn new(value: T) -> Box ``` #### Examples ```rust theme={null} let x = 42; let boxed_x = BoxTrait::new(x); ``` ### unbox Unboxes the given `Box` and returns the wrapped value. #### Signature ```rust theme={null} fn unbox(self: Box) -> T ``` #### Examples ```rust theme={null} let boxed = BoxTrait::new(42); assert!(boxed.unbox() == 42); ``` ### as\_snapshot Converts the given snapshot of a `Box` into a `Box` of a snapshot. Useful for structures that aren't copyable. #### Signature ```rust theme={null} fn as_snapshot(self: @Box) -> Box ``` #### Examples ```rust theme={null} let snap_boxed_arr = @BoxTraits::new(array![1, 2, 3]); let boxed_snap_arr = snap_boxed_arr.as_snapshot(); let snap_arr = boxed_snap_arr.unbox(); ``` # core::byte_array::BYTE_ARRAY_MAGIC Source: https://docs.starknet.io/build/corelib/core-byte_array-BYTE_ARRAY_MAGIC A magic constant for identifying serialization of `ByteArray` variables. An array of `felt252` with this magic value as one of the `felt252` indicates that you should expect right after it a serialized `ByteArray`. This is currently used mainly for prints and panics. ## Signature ```rust theme={null} pub const BYTE_ARRAY_MAGIC: felt252 = 1997209042069643135709344952807065910992472029923670688473712229447419591075; ``` # core::byte_array::ByteArray Source: https://docs.starknet.io/build/corelib/core-byte_array-ByteArray Byte array type. ## Signature ```rust theme={null} #[derive(Drop, Clone, PartialEq, Serde, Default)] pub struct ByteArray { pub(crate) data: Array, pub(crate) pending_word: felt252, pub(crate) pending_word_len: u32, } ``` # core::byte_array::ByteArrayImpl Source: https://docs.starknet.io/build/corelib/core-byte_array-ByteArrayImpl Functions associated with the `ByteArray` type. ## Signature ```rust theme={null} pub impl ByteArrayImpl of ByteArrayTrait; ``` ## Impl functions ### append\_word Appends a single word of `len` bytes to the end of the `ByteArray`. This function assumes that: 1. `word` could be validly converted to a `bytes31` which has no more than `len` bytes of data. 2. len ByteArray ```` ### append_byte Appends a single byte to the end of the `ByteArray`. #### Signature ```rust fn append_byte(ref self: ByteArray, byte: u8) ```` #### Examples ```rust theme={null} let mut ba = ""; ba.append_byte(0); assert!(ba == "0"); ``` ### len Returns the length of the `ByteArray`. #### Signature ```rust theme={null} fn len(self: ByteArray) -> u32 ``` #### Examples ```rust theme={null} let ba: ByteArray = "byte array"; let len = ba.len(); assert!(len == 10); ``` ### at Returns an option of the byte at the given index of `self` or `None` if the index is out of bounds. #### Signature ```rust theme={null} fn at(self: ByteArray, index: u32) -> Option ``` #### Examples ```rust theme={null} let ba: ByteArray = "byte array"; let byte = ba.at(0).unwrap(); assert!(byte == 98); ``` ### rev Returns a `ByteArray` with the reverse order of `self`. #### Signature ```rust theme={null} fn rev(self: ByteArray) -> ByteArray ``` #### Examples ```rust theme={null} let ba: ByteArray = "123"; let rev_ba = ba.rev(); assert!(rev_ba == "321"); ``` ### append\_word\_rev Appends the reverse of the given word to the end of `self`. This function assumes that: 1. len \< 31 2. word is validly convertible to bytes31 of length `len`. #### Signature ```rust theme={null} fn append_word_rev(ref self: ByteArray, word: felt252, len: u32) ``` #### Examples ```rust theme={null} let mut ba: ByteArray = ""; ba.append_word_rev('123', 3); assert!(ba == "321"); ``` # core::byte_array::ByteArrayIter Source: https://docs.starknet.io/build/corelib/core-byte_array-ByteArrayIter An iterator struct over a ByteArray. ## Signature ```rust theme={null} #[derive(Drop, Clone)] pub struct ByteArrayIter { ba: ByteArray, current_index: IntRange, } ``` # core::byte_array::ByteArrayTrait Source: https://docs.starknet.io/build/corelib/core-byte_array-ByteArrayTrait ## Signature ```rust theme={null} pub trait ByteArrayTrait ``` ## Trait functions ### append\_word Appends a single word of `len` bytes to the end of the `ByteArray`. This function assumes that: 1. `word` could be validly converted to a `bytes31` which has no more than `len` bytes of data. 2. len ByteArray ```` ### append_byte Appends a single byte to the end of the `ByteArray`. #### Signature ```rust fn append_byte(ref self: ByteArray, byte: u8) ```` #### Examples ```rust theme={null} let mut ba = ""; ba.append_byte(0); assert!(ba == "0"); ``` ### len Returns the length of the `ByteArray`. #### Signature ```rust theme={null} fn len(self: ByteArray) -> u32 ``` #### Examples ```rust theme={null} let ba: ByteArray = "byte array"; let len = ba.len(); assert!(len == 10); ``` ### at Returns an option of the byte at the given index of `self` or `None` if the index is out of bounds. #### Signature ```rust theme={null} fn at(self: ByteArray, index: u32) -> Option ``` #### Examples ```rust theme={null} let ba: ByteArray = "byte array"; let byte = ba.at(0).unwrap(); assert!(byte == 98); ``` ### rev Returns a `ByteArray` with the reverse order of `self`. #### Signature ```rust theme={null} fn rev(self: ByteArray) -> ByteArray ``` #### Examples ```rust theme={null} let ba: ByteArray = "123"; let rev_ba = ba.rev(); assert!(rev_ba == "321"); ``` ### append\_word\_rev Appends the reverse of the given word to the end of `self`. This function assumes that: 1. len \< 31 2. word is validly convertible to bytes31 of length `len`. #### Signature ```rust theme={null} fn append_word_rev(ref self: ByteArray, word: felt252, len: u32) ``` #### Examples ```rust theme={null} let mut ba: ByteArray = ""; ba.append_word_rev('123', 3); assert!(ba == "321"); ``` # core::bytes_31::Bytes31Impl Source: https://docs.starknet.io/build/corelib/core-bytes_31-Bytes31Impl A trait for accessing a specific byte of a `bytes31` type. ## Signature ```rust theme={null} pub impl Bytes31Impl of Bytes31Trait; ``` ## Impl functions ### at Returns the byte at the given index (LSB's index is 0). Assumes that \`index u8 ``` ``` # core::bytes_31::Bytes31Trait Source: https://docs.starknet.io/build/corelib/core-bytes_31-Bytes31Trait ## Signature ```rust theme={null} pub trait Bytes31Trait ``` ## Trait functions ### at Returns the byte at the given index (LSB's index is 0). Assumes that \`index u8 ``` ``` # core::bytes_31::bytes31 Source: https://docs.starknet.io/build/corelib/core-bytes_31-bytes31 Represents a 31-byte fixed-size byte type. ## Signature ```rust theme={null} pub extern type bytes31; ``` # core::circuit::AddInputResult Source: https://docs.starknet.io/build/corelib/core-circuit-AddInputResult The result of filling an input in the circuit instance's data. This enum represents the state of input filling process, indicating whether all inputs have been provided or more are needed. ## Signature ```rust theme={null} pub enum AddInputResult { Done: CircuitData, More: CircuitInputAccumulator, } ``` ## Variants ### Done All inputs have been filled and the circuit data is complete. #### Signature ```rust theme={null} Done: CircuitData ``` ### More More inputs are needed to complete the circuit instance's data. #### Signature ```rust theme={null} More: CircuitInputAccumulator ``` # core::circuit::AddInputResultTrait Source: https://docs.starknet.io/build/corelib/core-circuit-AddInputResultTrait ## Signature ```rust theme={null} pub trait AddInputResultTrait ``` ## Trait functions ### next Adds an input value to the circuit instance. #### Arguments * `value` - The value to add as input, must be convertible to circuit input value #### Returns A new `AddInputResult` that can be used to add more inputs or finalize #### Panics Panics if all inputs have already been filled #### Signature ```rust theme={null} fn next, +Drop>( self: AddInputResult, value: Value, ) -> AddInputResult ``` ### done Finalizes the input process and returns the circuit data. #### Returns The complete circuit data ready for evaluation #### Panics Panics if not all required inputs have been filled #### Signature ```rust theme={null} fn done(self: AddInputResult) -> CircuitData ``` # core::circuit::AddMod Source: https://docs.starknet.io/build/corelib/core-circuit-AddMod Builtin for modular addition operations. ## Signature ```rust theme={null} pub extern type AddMod; ``` # core::circuit::Circuit Source: https://docs.starknet.io/build/corelib/core-circuit-Circuit A type that creates a circuit from a tuple of outputs. This type represents a complete circuit instance, constructed from its output gates. The type parameter `Outputs` defines the structure of the circuit's outputs. ## Signature ```rust theme={null} pub extern type Circuit; ``` # core::circuit::CircuitDefinition Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitDefinition A trait for defining a circuit's structure and behavior. This trait is used to define the structure of a circuit, including its inputs, gates, and outputs. It provides the foundation for circuit evaluation. The `CES` type parameter represents a tuple of `CircuitElement`s that together define the circuit's structure. ## Signature ```rust theme={null} pub trait CircuitDefinition ``` ## Trait types ### CircuitType The internal circuit type representing a tuple of `CircuitElement`s. #### Signature ```rust theme={null} type CircuitType; ``` # core::circuit::CircuitElement Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitElement A wrapper for circuit elements, used to construct circuits. This type provides a generic wrapper around different circuit components (inputs, gates) and enables composition of circuit elements through arithmetic operations. The type parameter `T` defines the specific role of the element in the circuit. ## Signature ```rust theme={null} pub struct CircuitElement {} ``` # core::circuit::CircuitElementCopy Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitElementCopy ## Signature ```rust theme={null} pub impl CircuitElementCopy of Copy>; ``` # core::circuit::CircuitElementDrop Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitElementDrop ## Signature ```rust theme={null} pub impl CircuitElementDrop of Drop>; ``` # core::circuit::CircuitElementTrait Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitElementTrait A marker trait for keeping track of which types are valid circuit elements. This trait is implemented for all valid circuit components including inputs and gates. It provides type safety when composing circuit elements. ## Signature ```rust theme={null} pub trait CircuitElementTrait ``` # core::circuit::CircuitInput Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitInput Defines an input for a circuit. Represents an input signal in the circuit, indexed by `N`. Each input must be assigned a value before circuit evaluation. ## Signature ```rust theme={null} pub extern type CircuitInput; ``` # core::circuit::CircuitInputs Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitInputs ## Signature ```rust theme={null} pub trait CircuitInputs ``` ## Trait functions ### new\_inputs Initializes a new circuit instance with inputs. This function creates a new input accumulator for the circuit, which can then be used to add input values sequentially. #### Signature ```rust theme={null} fn new_inputs, +Drop>( self: CES, ) -> AddInputResult ``` #### Returns An `AddInputResult` that can be used to add input values to the circuit # core::circuit::CircuitModulus Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitModulus A type that can be used as a circuit modulus (a u384 that is not zero or one). The modulus defines the finite field over which the circuit operates. It must be: * A 384-bit number (represented as four 96-bit limbs) * Not zero or one * Typically a prime number for cryptographic applications ## Signature ```rust theme={null} pub extern type CircuitModulus; ``` # core::circuit::CircuitOutputsTrait Source: https://docs.starknet.io/build/corelib/core-circuit-CircuitOutputsTrait A trait for retrieving output values from a circuit evaluation. This trait provides methods to access the output values of a circuit after successful evaluation. ## Signature ```rust theme={null} pub trait CircuitOutputsTrait ``` ## Examples ```rust theme={null} let a = CircuitElement::> {}; let b = CircuitElement::> {}; let modulus = TryInto::::try_into([2, 0, 0, 0]).unwrap(); let circuit = (a,b).new_inputs() .next([10, 0, 0, 0]) .next([11, 0, 0, 0]) .done() .eval(modulus) .unwrap(); let a_mod_2 = circuit.get_output(a); // Returns the output value of `a mod 2` let b_mod_2 = circuit.get_output(b); // Returns the output value of `b mod 2` assert!(a_mod_2 == 0.into()); assert!(b_mod_2 == 1.into()); ``` ## Trait functions ### get\_output Gets the output value for a specific circuit element. #### Signature ```rust theme={null} fn get_output( self: Outputs, output: OutputElement, ) -> u384 ``` #### Arguments * `output` - The circuit element to get the output for #### Returns The output value as a u384 # core::circuit::ConstOne Source: https://docs.starknet.io/build/corelib/core-circuit-ConstOne ## Signature ```rust theme={null} pub type ConstOne = BoundedInt; ``` # core::circuit::ConstZero Source: https://docs.starknet.io/build/corelib/core-circuit-ConstZero Expose the const required by the libfunc to allow the compiler const reusage. ## Signature ```rust theme={null} pub type ConstZero = BoundedInt; ``` # core::circuit::DestructFailureGuarantee Source: https://docs.starknet.io/build/corelib/core-circuit-DestructFailureGuarantee ## Signature ```rust theme={null} pub impl DestructFailureGuarantee of Destruct; ``` ## Impl functions ### destruct #### Signature ```rust theme={null} fn destruct(self: CircuitFailureGuarantee) ``` # core::circuit::EvalCircuitTrait Source: https://docs.starknet.io/build/corelib/core-circuit-EvalCircuitTrait ## Signature ```rust theme={null} pub trait EvalCircuitTrait ``` ## Trait functions ### eval Evaluates the circuit with the given modulus. #### Arguments * `modulus` - The modulus to use for arithmetic operations #### Returns Result containing either the circuit outputs or a failure indication #### Signature ```rust theme={null} fn eval( self: CircuitData, modulus: CircuitModulus, ) -> Result, (CircuitPartialOutputs, CircuitFailureGuarantee)> ``` ### eval\_ex Evaluates the circuit with an explicit descriptor and modulus. #### Arguments * `descriptor` - The circuit descriptor * `modulus` - The modulus to use for arithmetic operations #### Returns Result containing either the circuit outputs or a failure indication #### Signature ```rust theme={null} fn eval_ex( self: CircuitData, descriptor: CircuitDescriptor, modulus: CircuitModulus, ) -> Result, (CircuitPartialOutputs, CircuitFailureGuarantee)> ``` # core::circuit::MulMod Source: https://docs.starknet.io/build/corelib/core-circuit-MulMod Builtin for modular multiplication operations. ## Signature ```rust theme={null} pub extern type MulMod; ``` # core::circuit::RangeCheck96 Source: https://docs.starknet.io/build/corelib/core-circuit-RangeCheck96 Range check builtin for 96-bit operations. ## Signature ```rust theme={null} pub extern type RangeCheck96; ``` # core::circuit::circuit_add Source: https://docs.starknet.io/build/corelib/core-circuit-circuit_add Creates a new circuit element representing addition modulo p of two input circuits. This function combines two circuit elements using modular addition, creating a new circuit element that represents their sum modulo the circuit's modulus. ## Signature ```rust theme={null} pub fn circuit_add, +CircuitElementTrait>( lhs: CircuitElement, rhs: CircuitElement, ) -> CircuitElement> ``` #### Arguments * `lhs` - Left-hand side circuit element * `rhs` - Right-hand side circuit element #### Returns A new circuit element representing `(lhs + rhs) mod p` ## Examples ```rust theme={null} let a = CircuitElement::> {}; let b = CircuitElement::> {}; let sum = circuit_add(a, b); ``` # core::circuit::circuit_inverse Source: https://docs.starknet.io/build/corelib/core-circuit-circuit_inverse Creates a new circuit element representing the multiplicative inverse modulo p of an input circuit. This function creates a new circuit element representing the multiplicative inverse of the input element modulo the circuit's modulus. The operation will fail during evaluation if the input is not invertible (not coprime with the modulus). ## Signature ```rust theme={null} pub fn circuit_inverse>( input: CircuitElement, ) -> CircuitElement> ``` #### Arguments * `input` - Circuit element to compute the inverse of #### Returns A new circuit element representing `input^(-1) mod p` ## Examples ```rust theme={null} let a = CircuitElement::> {}; let inv_a = circuit_inverse(a); ``` # core::circuit::circuit_mul Source: https://docs.starknet.io/build/corelib/core-circuit-circuit_mul Creates a new circuit element representing multiplication modulo p of two input circuits. This function combines two circuit elements using modular multiplication, creating a new circuit element that represents their product modulo the circuit's modulus. ## Signature ```rust theme={null} pub fn circuit_mul, +CircuitElementTrait>( lhs: CircuitElement, rhs: CircuitElement, ) -> CircuitElement> ``` #### Arguments * `lhs` - Left-hand side circuit element * `rhs` - Right-hand side circuit element #### Returns A new circuit element representing `(lhs * rhs) mod p` ## Examples ```rust theme={null} let a = CircuitElement::> {}; let b = CircuitElement::> {}; let product = circuit_mul(a, b); ``` # core::circuit::circuit_sub Source: https://docs.starknet.io/build/corelib/core-circuit-circuit_sub Creates a new circuit element representing subtraction modulo p of two input circuits. This function combines two circuit elements using modular subtraction, creating a new circuit element that represents their difference modulo the circuit's modulus. ## Signature ```rust theme={null} pub fn circuit_sub, +CircuitElementTrait>( lhs: CircuitElement, rhs: CircuitElement, ) -> CircuitElement> ``` #### Arguments * `lhs` - Left-hand side circuit element (minuend) * `rhs` - Right-hand side circuit element (subtrahend) #### Returns A new circuit element representing `(lhs - rhs) mod p` ## Examples ```rust theme={null} let a = CircuitElement::> {}; let b = CircuitElement::> {}; let diff = circuit_sub(a, b); ``` # core::circuit::u384 Source: https://docs.starknet.io/build/corelib/core-circuit-u384 A 384-bit unsigned integer, used for circuit values. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug, PartialEq)] pub struct u384 { pub limb0: BoundedInt, pub limb1: BoundedInt, pub limb2: BoundedInt, pub limb3: BoundedInt, } ``` ## Members ### limb0 The least significant 96 bits #### Signature ```rust theme={null} pub limb0: BoundedInt ``` ### limb1 Bits 96-191 #### Signature ```rust theme={null} pub limb1: BoundedInt ``` ### limb2 Bits 192-287 #### Signature ```rust theme={null} pub limb2: BoundedInt ``` ### limb3 The most significant 96 bits #### Signature ```rust theme={null} pub limb3: BoundedInt ``` # core::circuit::u96 Source: https://docs.starknet.io/build/corelib/core-circuit-u96 A 96-bit unsigned integer type used as the basic building block for multi-limb arithmetic. ## Signature ```rust theme={null} pub type u96 = BoundedInt; ``` # core::clone::Clone Source: https://docs.starknet.io/build/corelib/core-clone-Clone A common trait for the ability to explicitly duplicate an object. Differs from `Copy` in that `Copy` is implicit and inexpensive, while `Clone` is always explicit and may or may not be expensive. Since `Clone` is more general than `Copy`, you can automatically make anything `Copy` be `Clone` as well. ## Signature ```rust theme={null} pub trait Clone ``` ## Derivable This trait can be used with `#[derive]` if all fields are `Clone`. The `derive`d implementation of `Clone` calls `clone` on each field. ## Trait functions ### clone Returns a copy of the value. #### Signature ```rust theme={null} fn clone(self: @T) -> T ``` #### Examples ```rust theme={null} let arr = array![1, 2, 3]; assert!(arr == arr.clone()); ``` # core::cmp::max Source: https://docs.starknet.io/build/corelib/core-cmp-max Takes two comparable values `a` and `b` and returns the greater of the two values. ## Signature ```rust theme={null} pub fn max, +Drop, +Copy>(a: T, b: T) -> T ``` ## Examples ```rust theme={null} use core::cmp::max; assert!(max(0, 1) == 1); ``` # core::cmp::min Source: https://docs.starknet.io/build/corelib/core-cmp-min Takes two comparable values `a` and `b` and returns the smaller of the two values. ## Signature ```rust theme={null} pub fn min, +Drop, +Copy>(a: T, b: T) -> T ``` ## Examples ```rust theme={null} use core::cmp::min; assert!(min(0, 1) == 0); ``` # core::cmp::minmax Source: https://docs.starknet.io/build/corelib/core-cmp-minmax Takes two comparable values `a` and `b` and returns a tuple with the smaller value and the greater value. ## Signature ```rust theme={null} pub fn minmax, +Drop, +Copy>(a: T, b: T) -> (T, T) ``` ## Examples ```rust theme={null} use core::cmp::minmax; assert!(minmax(0, 1) == (0, 1)); assert!(minmax(1, 0) == (0, 1)); ``` # core::debug::print_byte_array_as_string Source: https://docs.starknet.io/build/corelib/core-debug-print_byte_array_as_string Prints a `ByteArray` as a string. ## Signature ```rust theme={null} pub fn print_byte_array_as_string(self: ByteArray) ``` ## Examples ```rust theme={null} let ba: ByteArray = "123"; print_byte_array_as_string(@ba); ``` # core::ec::EcOp Source: https://docs.starknet.io/build/corelib/core-ec-EcOp ## Signature ```rust theme={null} pub extern type EcOp; ``` # core::ec::EcPoint Source: https://docs.starknet.io/build/corelib/core-ec-EcPoint A point on the STARK curve. Points can be created using `EcPointTrait::new` or `EcPointTrait::new_from_x`. The zero point represents the point at infinity. ## Signature ```rust theme={null} pub extern type EcPoint; ``` # core::ec::EcPointImpl Source: https://docs.starknet.io/build/corelib/core-ec-EcPointImpl ## Signature ```rust theme={null} pub impl EcPointImpl of EcPointTrait; ``` ## Impl functions ### new Creates a new EC point from its (x, y) coordinates. #### Arguments * `x` - The x-coordinate of the point * `y` - The y-coordinate of the point #### Returns Returns `None` if the point (x, y) is not on the curve. #### Examples ```rust theme={null} let point = EcPointTrait::new( x: 336742005567258698661916498343089167447076063081786685068305785816009957563, y: 1706004133033694959518200210163451614294041810778629639790706933324248611779, ).unwrap(); ``` #### Signature ```rust theme={null} fn new(x: felt252, y: felt252) -> Option ``` ### new\_nz Creates a new NonZero EC point from its (x, y) coordinates. #### Signature ```rust theme={null} fn new_nz(x: felt252, y: felt252) -> Option> ``` ### new\_from\_x Creates a new EC point from its x coordinate. #### Arguments * `x` - The x-coordinate of the point #### Returns Returns `None` if no point with the given x-coordinate exists on the curve. #### Panics Panics if `x` is 0, as this would be the point at infinity. #### Examples ```rust theme={null} let valid = EcPointTrait::new_from_x(1); assert!(valid.is_some()); let invalid = EcPointTrait::new_from_x(0); assert!(invalid.is_none()); ``` #### Signature ```rust theme={null} fn new_from_x(x: felt252) -> Option ``` ### new\_nz\_from\_x Creates a new NonZero EC point from its x coordinate. #### Signature ```rust theme={null} fn new_nz_from_x(x: felt252) -> Option> ``` ### coordinates Returns the coordinates of the EC point. #### Returns A tuple containing the (x, y) coordinates of the point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let point_nz = EcPointTrait::new_nz_from_x(1).unwrap(); let (x, _y) = point_nz.coordinates(); assert!(x == 1); ``` #### Signature ```rust theme={null} fn coordinates(self: NonZero) -> (felt252, felt252) ``` ### x Returns the x coordinate of the EC point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let point_nz = EcPointTrait::new_nz_from_x(1).unwrap(); let x = point_nz.x(); assert!(x == 1); ``` #### Signature ```rust theme={null} fn x(self: NonZero) -> felt252 ``` ### y Returns the y coordinate of the EC point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let gen_point = EcPointTrait::new_nz_from_x(0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca).unwrap(); let y = gen_point.y(); assert!(y == 0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f); ``` #### Signature ```rust theme={null} fn y(self: NonZero) -> felt252 ``` ### mul Computes the product of an EC point by the given scalar. #### Arguments * `scalar` - The scalar to multiply the point by #### Returns The resulting point after scalar multiplication. #### Signature ```rust theme={null} fn mul(self: EcPoint, scalar: felt252) -> EcPoint ``` # core::ec::EcPointTrait Source: https://docs.starknet.io/build/corelib/core-ec-EcPointTrait ## Signature ```rust theme={null} pub trait EcPointTrait ``` ## Trait functions ### new Creates a new EC point from its (x, y) coordinates. #### Arguments * `x` - The x-coordinate of the point * `y` - The y-coordinate of the point #### Returns Returns `None` if the point (x, y) is not on the curve. #### Examples ```rust theme={null} let point = EcPointTrait::new( x: 336742005567258698661916498343089167447076063081786685068305785816009957563, y: 1706004133033694959518200210163451614294041810778629639790706933324248611779, ).unwrap(); ``` #### Signature ```rust theme={null} fn new(x: felt252, y: felt252) -> Option ``` ### new\_nz Creates a new NonZero EC point from its (x, y) coordinates. #### Signature ```rust theme={null} fn new_nz(x: felt252, y: felt252) -> Option> ``` ### new\_from\_x Creates a new EC point from its x coordinate. #### Arguments * `x` - The x-coordinate of the point #### Returns Returns `None` if no point with the given x-coordinate exists on the curve. #### Panics Panics if `x` is 0, as this would be the point at infinity. #### Examples ```rust theme={null} let valid = EcPointTrait::new_from_x(1); assert!(valid.is_some()); let invalid = EcPointTrait::new_from_x(0); assert!(invalid.is_none()); ``` #### Signature ```rust theme={null} fn new_from_x(x: felt252) -> Option ``` ### new\_nz\_from\_x Creates a new NonZero EC point from its x coordinate. #### Signature ```rust theme={null} fn new_nz_from_x(x: felt252) -> Option> ``` ### coordinates Returns the coordinates of the EC point. #### Returns A tuple containing the (x, y) coordinates of the point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let point_nz = EcPointTrait::new_nz_from_x(1).unwrap(); let (x, _y) = point_nz.coordinates(); assert!(x == 1); ``` #### Signature ```rust theme={null} fn coordinates(self: NonZero) -> (felt252, felt252) ``` ### x Returns the x coordinate of the EC point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let point_nz = EcPointTrait::new_nz_from_x(1).unwrap(); let x = point_nz.x(); assert!(x == 1); ``` #### Signature ```rust theme={null} fn x(self: NonZero) -> felt252 ``` ### y Returns the y coordinate of the EC point. #### Panics Panics if the point is the point at infinity. #### Examples ```rust theme={null} let gen_point = EcPointTrait::new_nz_from_x(0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca).unwrap(); let y = gen_point.y(); assert!(y == 0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f); ``` #### Signature ```rust theme={null} fn y(self: NonZero) -> felt252 ``` ### mul Computes the product of an EC point by the given scalar. #### Arguments * `scalar` - The scalar to multiply the point by #### Returns The resulting point after scalar multiplication. #### Signature ```rust theme={null} fn mul(self: EcPoint, scalar: felt252) -> EcPoint ``` # core::ec::EcState Source: https://docs.starknet.io/build/corelib/core-ec-EcState Elliptic curve state. Use this to perform multiple point operations efficiently. Initialize with `EcStateTrait::init`, add points with `EcStateTrait::add` or `EcStateTrait::add_mul`, and finalize with `EcStateTrait::finalize`. ## Signature ```rust theme={null} pub extern type EcState; ``` # core::ec::EcStateImpl Source: https://docs.starknet.io/build/corelib/core-ec-EcStateImpl ## Signature ```rust theme={null} pub impl EcStateImpl of EcStateTrait; ``` ## Impl functions ### init Initializes an EC computation with the zero point. #### Signature ```rust theme={null} fn init() -> EcState ``` #### Examples ```rust theme={null} let mut state = EcStateTrait::init(); ``` ### add Adds a point to the computation. #### Arguments * `p` - The non-zero point to add #### Signature ```rust theme={null} fn add(ref self: EcState, p: NonZero) ``` ### sub Subtracts a point to the computation. #### Arguments * `p` - The non-zero point to subtract #### Signature ```rust theme={null} fn sub(ref self: EcState, p: NonZero) ``` ### add\_mul Adds the product `p * scalar` to the state. #### Arguments * `scalar` - The scalar to multiply the point by * `p` - The non-zero point to multiply and add #### Signature ```rust theme={null} fn add_mul(ref self: EcState, scalar: felt252, p: NonZero) ``` ### finalize\_nz Finalizes the EC computation and returns the result as a non-zero point. #### Returns * `Option` - The resulting point, or None if the result is the zero point #### Panics Panics if the result is the point at infinity. #### Signature ```rust theme={null} fn finalize_nz(self: EcState) -> Option> ``` ### finalize Finalizes the EC computation and returns the result. Returns the zero point if the computation results in the point at infinity. #### Signature ```rust theme={null} fn finalize(self: EcState) -> EcPoint ``` # core::ec::EcStateTrait Source: https://docs.starknet.io/build/corelib/core-ec-EcStateTrait ## Signature ```rust theme={null} pub trait EcStateTrait ``` ## Trait functions ### init Initializes an EC computation with the zero point. #### Signature ```rust theme={null} fn init() -> EcState ``` #### Examples ```rust theme={null} let mut state = EcStateTrait::init(); ``` ### add Adds a point to the computation. #### Arguments * `p` - The non-zero point to add #### Signature ```rust theme={null} fn add(ref self: EcState, p: NonZero) ``` ### sub Subtracts a point to the computation. #### Arguments * `p` - The non-zero point to subtract #### Signature ```rust theme={null} fn sub(ref self: EcState, p: NonZero) ``` ### add\_mul Adds the product `p * scalar` to the state. #### Arguments * `scalar` - The scalar to multiply the point by * `p` - The non-zero point to multiply and add #### Signature ```rust theme={null} fn add_mul(ref self: EcState, scalar: felt252, p: NonZero) ``` ### finalize\_nz Finalizes the EC computation and returns the result as a non-zero point. #### Returns * `Option` - The resulting point, or None if the result is the zero point #### Panics Panics if the result is the point at infinity. #### Signature ```rust theme={null} fn finalize_nz(self: EcState) -> Option> ``` ### finalize Finalizes the EC computation and returns the result. Returns the zero point if the computation results in the point at infinity. #### Signature ```rust theme={null} fn finalize(self: EcState) -> EcPoint ``` # core::ec::NonZeroEcPoint Source: https://docs.starknet.io/build/corelib/core-ec-NonZeroEcPoint A non-zero point on the STARK curve (cannot be the point at infinity). ## Signature ```rust theme={null} pub type NonZeroEcPoint = NonZero; ``` # core::ec::ec_point_unwrap Source: https://docs.starknet.io/build/corelib/core-ec-ec_point_unwrap Unwraps a non-zero point into its (x, y) coordinates. ## Signature ```rust theme={null} pub extern fn ec_point_unwrap(p: NonZero) -> (felt252, felt252) nopanic; ``` # core::ec::stark_curve::ALPHA Source: https://docs.starknet.io/build/corelib/core-ec-stark_curve-ALPHA The STARK Curve is defined by the equation y² ≡ x³ + α·x + β (mod p). ## Signature ```rust theme={null} pub const ALPHA: felt252 = 1; ``` # core::ec::stark_curve::BETA Source: https://docs.starknet.io/build/corelib/core-ec-stark_curve-BETA The STARK Curve is defined by the equation y² ≡ x³ + α·x + β (mod p). ## Signature ```rust theme={null} pub const BETA: felt252 = 3141592653589793238462643383279502884197169399375105820974944592307816406665; ``` # core::ec::stark_curve::GEN_X Source: https://docs.starknet.io/build/corelib/core-ec-stark_curve-GEN_X The x coordinate of the generator point used in the ECDSA signature. ## Signature ```rust theme={null} pub const GEN_X: felt252 = 874739451078007766457464989774322083649278607533249481151382481072868806602; ``` # core::ec::stark_curve::GEN_Y Source: https://docs.starknet.io/build/corelib/core-ec-stark_curve-GEN_Y The y coordinate of the generator point used in the ECDSA signature. ## Signature ```rust theme={null} pub const GEN_Y: felt252 = 152666792071518830868575557812948353041420400780739481342941381225525861407; ``` # core::ec::stark_curve::ORDER Source: https://docs.starknet.io/build/corelib/core-ec-stark_curve-ORDER The order (number of points) of the STARK Curve. ## Signature ```rust theme={null} pub const ORDER: felt252 = 3618502788666131213697322783095070105526743751716087489154079457884512865583; ``` # core::ecdsa::check_ecdsa_signature Source: https://docs.starknet.io/build/corelib/core-ecdsa-check_ecdsa_signature Verifies an ECDSA signature against a message hash and public key. Note: the verification algorithm implemented by this function slightly deviates from the standard ECDSA. While this does not allow to create valid signatures if one does not possess the private key, it means that the signature algorithm used should be modified accordingly. This function validates that `s` and `r` are not 0 or equal to the curve order, but does not check that \`r, s bool ``` ``` # core::ecdsa::recover_public_key Source: https://docs.starknet.io/build/corelib/core-ecdsa-recover_public_key Recovers the public key from an ECDSA signature and message hash. Given a valid ECDSA signature, the original message hash, and the y-coordinate parity of point R, this function recovers the signer's public key. This is useful in scenarios where you need to verify a message has been signed by a specific public key. ## Signature ```rust theme={null} pub fn recover_public_key( message_hash: felt252, signature_r: felt252, signature_s: felt252, y_parity: bool, ) -> Option ``` #### Arguments * `message_hash` - The hash of the signed message * `signature_r` - The r component of the ECDSA signature (x-coordinate of point R) * `signature_s` - The s component of the ECDSA signature * `y_parity` - The parity of the y-coordinate of point R (`true` for odd, `false` for even) #### Returns Returns `Some(public_key)` containing the x-coordinate of the recovered public key point if the signature is valid, `None` otherwise. ## Examples ```rust theme={null} use core::ecdsa::recover_public_key; let message_hash = 0x503f4bea29baee10b22a7f10bdc82dda071c977c1f25b8f3973d34e6b03b2c; let signature_r = 0xbe96d72eb4f94078192c2e84d5230cde2a70f4b45c8797e2c907acff5060bb; let signature_s = 0x677ae6bba6daf00d2631fab14c8acf24be6579f9d9e98f67aa7f2770e57a1f5; assert!( recover_public_key(:message_hash, :signature_r, :signature_s, y_parity: false) .unwrap() == 0x7b7454acbe7845da996377f85eb0892044d75ae95d04d3325a391951f35d2ec, ) ``` # core::felt252 Source: https://docs.starknet.io/build/corelib/core-felt252 `felt252` is the basic field element used in Cairo. It corresponds to an integer in the range 0 ≤ x \< P where P is a very large prime number currently equal to 2^251 + 17⋅2^192 + 1. Any operation that uses `felt252` will be computed modulo P. ## Signature ```rust theme={null} pub extern type felt252; ``` # core::felt252_div Source: https://docs.starknet.io/build/corelib/core-felt252_div Performs division on `felt252` values in Cairo's finite field. Unlike regular integer division, `felt252` division returns a field element n that satisfies the equation: n \* rhs ≡ lhs (mod P), where P is the `felt252` prime. ## Signature ```rust theme={null} pub extern fn felt252_div(lhs: felt252, rhs: NonZero) -> felt252 nopanic; ``` ## Examples ```rust theme={null} use core::felt252_div; // Division with 0 remainder works the same way as integer division. assert!(felt252_div(4, 2) == 2); // Division with non 0 remainder returns a field element n where n * 3 ≡ 4 (mod P) assert!(felt252_div(4, 3) == 1206167596222043737899107594365023368541035738443865566657697352045290673495); ``` # core::fmt::Debug Source: https://docs.starknet.io/build/corelib/core-fmt-Debug A trait for debug formatting, using the empty format (`{:?}`). ## Signature ```rust theme={null} pub trait Debug ``` ## Examples ```rust theme={null} let word: ByteArray = "123"; println!("{:?}", word); ``` ## Trait functions ### fmt #### Signature ```rust theme={null} fn fmt(self: @T, ref f: Formatter) -> Result ``` # core::fmt::Display Source: https://docs.starknet.io/build/corelib/core-fmt-Display A trait for standard formatting, using the empty format (""). ## Signature ```rust theme={null} pub trait Display ``` ## Examples ```rust theme={null} let word: ByteArray = "123"; println!("{}", word); ``` ## Trait functions ### fmt #### Signature ```rust theme={null} fn fmt(self: @T, ref f: Formatter) -> Result ``` # core::fmt::Error Source: https://docs.starknet.io/build/corelib/core-fmt-Error Dedicated type for representing formatting errors. ## Signature ```rust theme={null} #[derive(Drop)] pub struct Error {} ``` # core::fmt::Formatter Source: https://docs.starknet.io/build/corelib/core-fmt-Formatter Configuration for formatting. ## Signature ```rust theme={null} #[derive(Default, Drop)] pub struct Formatter { pub buffer: ByteArray, } ``` ## Members ### buffer The pending result of formatting. #### Signature ```rust theme={null} pub buffer: ByteArray ``` # core::fmt::LowerHex Source: https://docs.starknet.io/build/corelib/core-fmt-LowerHex A trait for hex formatting in lower case, using the empty format (`{:x}`). ## Signature ```rust theme={null} pub trait LowerHex ``` ## Trait functions ### fmt #### Signature ```rust theme={null} fn fmt(self: @T, ref f: Formatter) -> Result ``` # core::fmt::into_felt252_based Source: https://docs.starknet.io/build/corelib/core-fmt-into_felt252_based Implementations for `Debug` and `LowerHex` for types that can be converted into `felt252` using the `Into` trait. ## Examples ```rust theme={null} impl MyTypeDebug = crate::fmt::into_felt252_based::DebugImpl; impl MyTypeLowerHex = crate::fmt::into_felt252_based::LowerHexImpl; ``` # core::gas::BuiltinCosts Source: https://docs.starknet.io/build/corelib/core-gas-BuiltinCosts Type representing the table of the costs of the different builtin usages. ## Signature ```rust theme={null} pub extern type BuiltinCosts; ``` # core::gas::GasBuiltin Source: https://docs.starknet.io/build/corelib/core-gas-GasBuiltin The gas builtin. This type is used to handle gas in the Cairo code. Contains the amount of gas available for the current run. ## Signature ```rust theme={null} pub extern type GasBuiltin; ``` # core::gas::GasReserve Source: https://docs.starknet.io/build/corelib/core-gas-GasReserve Represents a gas reserve. Gas reserves can be created at any point using gas from the gas counter, and can be utilized at a later point in time. ## Signature ```rust theme={null} pub extern type GasReserve; ``` # core::gas::gas_reserve_create Source: https://docs.starknet.io/build/corelib/core-gas-gas_reserve_create Creates a new gas reserve by withdrawing the specified amount from the gas counter. Returns `Some(GasReserve)` if there is sufficient gas, otherwise returns `None`. ## Signature ```rust theme={null} pub extern fn gas_reserve_create(amount: u128) -> Option implicits(RangeCheck, GasBuiltin) nopanic; ``` # core::gas::gas_reserve_utilize Source: https://docs.starknet.io/build/corelib/core-gas-gas_reserve_utilize Adds the gas stored in the reserve back to the gas counter. The reserve is consumed in the process. ## Signature ```rust theme={null} pub extern fn gas_reserve_utilize(reserve: GasReserve) implicits(GasBuiltin) nopanic; ``` # core::gas::get_builtin_costs Source: https://docs.starknet.io/build/corelib/core-gas-get_builtin_costs Returns the `BuiltinCosts` table to be used in `withdraw_gas_all`. ## Signature ```rust theme={null} pub extern fn get_builtin_costs() -> BuiltinCosts nopanic; ``` # core::gas::redeposit_gas Source: https://docs.starknet.io/build/corelib/core-gas-redeposit_gas Returns unused gas into the gas builtin. Useful for cases where different branches take different amounts of gas, but gas withdrawal is the same for both. ## Signature ```rust theme={null} pub extern fn redeposit_gas() implicits(GasBuiltin) nopanic; ``` # core::gas::withdraw_gas Source: https://docs.starknet.io/build/corelib/core-gas-withdraw_gas Withdraws gas from the `GasBuiltin` to handle the success case flow. Returns `Some(())` if there is sufficient gas to handle the success case, otherwise returns `None`. ## Signature ```rust theme={null} pub extern fn withdraw_gas() -> Option implicits(RangeCheck, GasBuiltin) nopanic; ``` ## Examples ```rust theme={null} // The success branch is the following lines, the failure branch is the `panic` caused by the // `unwrap` call. withdraw_gas().unwrap(); ``` ```rust theme={null} // Direct handling of `withdraw_gas`. match withdraw_gas() { Some(()) => success_case(), None => cheap_not_enough_gas_case(), } ``` # core::gas::withdraw_gas_all Source: https://docs.starknet.io/build/corelib/core-gas-withdraw_gas_all Same as `withdraw_gas`, but directly receives `BuiltinCosts`, which enables optimizations by removing the need for repeated internal calls for fetching the table of constants that may internally happen in calls to `withdraw_gas`. Should be used with caution. ## Signature ```rust theme={null} pub extern fn withdraw_gas_all(costs: BuiltinCosts) -> Option implicits(RangeCheck, GasBuiltin) nopanic; ``` # core::hash::Hash Source: https://docs.starknet.io/build/corelib/core-hash-Hash A trait for values that can be hashed. This trait should be implemented for any type that can be included in a hash calculation. The most common way to implement this trait is by using `#[derive(Hash)]`. ## Signature ```rust theme={null} pub trait Hash> ``` ## Trait functions ### update\_state Updates the hash state with the given value and returns a new hash state. #### Signature ```rust theme={null} fn update_state, T, S, +HashStateTrait>(state: S, value: T) -> S ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; use core::hash::Hash; let mut state = PedersenTrait::new(0); let new_state = Hash::update_state(state, 1); ``` # core::hash::HashStateExTrait Source: https://docs.starknet.io/build/corelib/core-hash-HashStateExTrait Extension trait for hash state accumulators. This trait adds the `update_with` method to hash states, allowing you to directly hash values of any type T that implements `Hash`, rather than having to manually convert values to felt252 first. This provides a more ergonomic API when working with complex types. ## Signature ```rust theme={null} pub trait HashStateExTrait ``` ## Trait functions ### update\_with Updates the hash state with the given value and returns the updated state. #### Signature ```rust theme={null} fn update_with(self: S, value: T) -> S ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; use core::hash::HashStateExTrait; #[derive(Copy, Drop, Hash)] struct Point { x: u32, y: u32 } let point = Point { x: 1, y: 2 }; let hash = PedersenTrait::new(0) .update_with(point) .update_with(42) .finalize(); ``` # core::hash::HashStateTrait Source: https://docs.starknet.io/build/corelib/core-hash-HashStateTrait A trait for hash state accumulators. Provides methods to update a hash state with new values and finalize it into a hash result. ## Signature ```rust theme={null} pub trait HashStateTrait ``` ## Trait functions ### update Updates the current hash state `self` with the given `felt252` value and returns a new hash state. #### Signature ```rust theme={null} fn update(self: S, value: felt252) -> S ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; use core::hash::HashStateTrait; let mut state = PedersenTrait::new(0); state = state.update(1); ``` ### finalize Takes the current state `self` and returns the hash result. #### Signature ```rust theme={null} fn finalize(self: S) -> felt252 ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; use core::hash::HashStateTrait; let mut state = PedersenTrait::new(0); let hash = state.finalize(); ``` # core::hash::LegacyHash Source: https://docs.starknet.io/build/corelib/core-hash-LegacyHash A trait for hashing values using a `felt252` as hash state, used for backwards compatibility. NOTE: Implement `Hash` instead of this trait if possible. ## Signature ```rust theme={null} pub trait LegacyHash ``` ## Trait functions ### hash Takes a `felt252` state and a value of type `T` and returns the hash result. #### Signature ```rust theme={null} fn hash(state: felt252, value: T) -> felt252 ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; use core::hash::LegacyHash; let hash = LegacyHash::hash(0, 1); ``` # core::hash::into_felt252_based Source: https://docs.starknet.io/build/corelib/core-hash-into_felt252_based Implementation for `Hash` for types that can be converted into `felt252` using the `Into` trait. ## Examples ```rust theme={null} impl MyTypeHash, +Drop> = core::hash::into_felt252_based::HashImpl;` ``` # core::integer::Bitwise Source: https://docs.starknet.io/build/corelib/core-integer-Bitwise ## Signature ```rust theme={null} pub extern type Bitwise; ``` # core::integer::BoundedInt Source: https://docs.starknet.io/build/corelib/core-integer-BoundedInt Trait for getting the maximal and minimal values of an integer type. ## Signature ```rust theme={null} pub trait BoundedInt ``` ## Trait functions ### min Returns the minimal value of the type. #### Signature ```rust theme={null} fn min() -> T ``` ### max Returns the maximal value of the type. #### Signature ```rust theme={null} fn max() -> T ``` # core::integer::NumericLiteral Source: https://docs.starknet.io/build/corelib/core-integer-NumericLiteral ## Signature ```rust theme={null} pub trait NumericLiteral ``` # core::integer::U128MulGuarantee Source: https://docs.starknet.io/build/corelib/core-integer-U128MulGuarantee A type that contains 4 u128s (a, b, c, d) and guarantees that `a * b = 2**128 * c + d`. The guarantee is verified by `u128_mul_guarantee_verify`, which is the only way to destruct this type. This way, one can trust that the guarantee holds although it has not yet been verified. ## Signature ```rust theme={null} pub extern type U128MulGuarantee; ``` # core::integer::i128 Source: https://docs.starknet.io/build/corelib/core-integer-i128 The 128-bit signed integer type. ## Signature ```rust theme={null} pub extern type i128; ``` # core::integer::i128_diff Source: https://docs.starknet.io/build/corelib/core-integer-i128_diff If `lhs` >= `rhs` returns `Ok(lhs - rhs)` else returns `Err(2**128 + lhs - rhs)`. ## Signature ```rust theme={null} pub extern fn i128_diff(lhs: i128, rhs: i128) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::i16 Source: https://docs.starknet.io/build/corelib/core-integer-i16 The 16-bit signed integer type. ## Signature ```rust theme={null} pub extern type i16; ``` # core::integer::i16_diff Source: https://docs.starknet.io/build/corelib/core-integer-i16_diff If `lhs` >= `rhs` returns `Ok(lhs - rhs)` else returns `Err(2**16 + lhs - rhs)`. ## Signature ```rust theme={null} pub extern fn i16_diff(lhs: i16, rhs: i16) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::i16_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-i16_wide_mul ## Signature ```rust theme={null} pub extern fn i16_wide_mul(lhs: i16, rhs: i16) -> i32 nopanic; ``` # core::integer::i32 Source: https://docs.starknet.io/build/corelib/core-integer-i32 The 32-bit signed integer type. ## Signature ```rust theme={null} pub extern type i32; ``` # core::integer::i32_diff Source: https://docs.starknet.io/build/corelib/core-integer-i32_diff If `lhs` >= `rhs` returns `Ok(lhs - rhs)` else returns `Err(2**32 + lhs - rhs)`. ## Signature ```rust theme={null} pub extern fn i32_diff(lhs: i32, rhs: i32) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::i32_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-i32_wide_mul ## Signature ```rust theme={null} pub extern fn i32_wide_mul(lhs: i32, rhs: i32) -> i64 nopanic; ``` # core::integer::i64 Source: https://docs.starknet.io/build/corelib/core-integer-i64 The 64-bit signed integer type. ## Signature ```rust theme={null} pub extern type i64; ``` # core::integer::i64_diff Source: https://docs.starknet.io/build/corelib/core-integer-i64_diff If `lhs` >= `rhs` returns `Ok(lhs - rhs)` else returns `Err(2**64 + lhs - rhs)`. ## Signature ```rust theme={null} pub extern fn i64_diff(lhs: i64, rhs: i64) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::i64_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-i64_wide_mul ## Signature ```rust theme={null} pub extern fn i64_wide_mul(lhs: i64, rhs: i64) -> i128 nopanic; ``` # core::integer::i8 Source: https://docs.starknet.io/build/corelib/core-integer-i8 The 8-bit signed integer type. ## Signature ```rust theme={null} pub extern type i8; ``` # core::integer::i8_diff Source: https://docs.starknet.io/build/corelib/core-integer-i8_diff If `lhs` >= `rhs` returns `Ok(lhs - rhs)` else returns `Err(2**8 + lhs - rhs)`. ## Signature ```rust theme={null} pub extern fn i8_diff(lhs: i8, rhs: i8) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::i8_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-i8_wide_mul ## Signature ```rust theme={null} pub extern fn i8_wide_mul(lhs: i8, rhs: i8) -> i16 nopanic; ``` # core::integer::u128 Source: https://docs.starknet.io/build/corelib/core-integer-u128 The 128-bit unsigned integer type. ## Signature ```rust theme={null} pub extern type u128; ``` # core::integer::u128_byte_reverse Source: https://docs.starknet.io/build/corelib/core-integer-u128_byte_reverse ## Signature ```rust theme={null} pub extern fn u128_byte_reverse(input: u128) -> u128 implicits(Bitwise) nopanic; ``` # core::integer::u128_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u128_overflowing_add ## Signature ```rust theme={null} pub extern fn u128_overflowing_add(lhs: u128, rhs: u128) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u128_overflowing_mul Source: https://docs.starknet.io/build/corelib/core-integer-u128_overflowing_mul ## Signature ```rust theme={null} pub fn u128_overflowing_mul(lhs: u128, rhs: u128) -> (u128, bool) ``` # core::integer::u128_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u128_overflowing_sub ## Signature ```rust theme={null} pub extern fn u128_overflowing_sub(lhs: u128, rhs: u128) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u128_safe_divmod Source: https://docs.starknet.io/build/corelib/core-integer-u128_safe_divmod ## Signature ```rust theme={null} pub extern fn u128_safe_divmod(lhs: u128, rhs: NonZero) -> (u128, u128) implicits(RangeCheck) nopanic; ``` # core::integer::u128_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u128_sqrt ## Signature ```rust theme={null} pub extern fn u128_sqrt(value: u128) -> u64 implicits(RangeCheck) nopanic; ``` # core::integer::u128_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u128_wide_mul Multiplies two u128s and returns `(high, low)` - the 128-bit parts of the result. ## Signature ```rust theme={null} pub fn u128_wide_mul(a: u128, b: u128) -> (u128, u128) ``` # core::integer::u128_wrapping_add Source: https://docs.starknet.io/build/corelib/core-integer-u128_wrapping_add ## Signature ```rust theme={null} pub fn u128_wrapping_add(lhs: u128, rhs: u128) -> u128 ``` # core::integer::u128_wrapping_sub Source: https://docs.starknet.io/build/corelib/core-integer-u128_wrapping_sub ## Signature ```rust theme={null} pub fn u128_wrapping_sub(a: u128, b: u128) -> u128 ``` # core::integer::u16 Source: https://docs.starknet.io/build/corelib/core-integer-u16 The 16-bit unsigned integer type. ## Signature ```rust theme={null} pub extern type u16; ``` # core::integer::u16_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u16_overflowing_add ## Signature ```rust theme={null} pub extern fn u16_overflowing_add(lhs: u16, rhs: u16) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u16_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u16_overflowing_sub ## Signature ```rust theme={null} pub extern fn u16_overflowing_sub(lhs: u16, rhs: u16) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u16_safe_divmod Source: https://docs.starknet.io/build/corelib/core-integer-u16_safe_divmod ## Signature ```rust theme={null} pub extern fn u16_safe_divmod(lhs: u16, rhs: NonZero) -> (u16, u16) implicits(RangeCheck) nopanic; ``` # core::integer::u16_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u16_sqrt ## Signature ```rust theme={null} pub extern fn u16_sqrt(value: u16) -> u8 implicits(RangeCheck) nopanic; ``` # core::integer::u16_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u16_wide_mul ## Signature ```rust theme={null} pub extern fn u16_wide_mul(lhs: u16, rhs: u16) -> u32 nopanic; ``` # core::integer::u16_wrapping_add Source: https://docs.starknet.io/build/corelib/core-integer-u16_wrapping_add ## Signature ```rust theme={null} pub fn u16_wrapping_add(lhs: u16, rhs: u16) -> u16 ``` # core::integer::u16_wrapping_sub Source: https://docs.starknet.io/build/corelib/core-integer-u16_wrapping_sub ## Signature ```rust theme={null} pub fn u16_wrapping_sub(lhs: u16, rhs: u16) -> u16 ``` # core::integer::u256 Source: https://docs.starknet.io/build/corelib/core-integer-u256 The 256-bit unsigned integer type. The `u256` type is composed of two 128-bit parts: the low part \[0, 128) and the high part \[128, 256\). ## Signature ```rust theme={null} #[derive(Copy, Drop, Hash, PartialEq, Serde)] pub struct u256 { pub low: u128, pub high: u128, } ``` ## Members ### low #### Signature ```rust theme={null} pub low: u128 ``` ### high #### Signature ```rust theme={null} pub high: u128 ``` # core::integer::u256_overflow_mul Source: https://docs.starknet.io/build/corelib/core-integer-u256_overflow_mul ## Signature ```rust theme={null} pub fn u256_overflow_mul(lhs: u256, rhs: u256) -> (u256, bool) ``` # core::integer::u256_overflow_sub Source: https://docs.starknet.io/build/corelib/core-integer-u256_overflow_sub ## Signature ```rust theme={null} pub fn u256_overflow_sub(lhs: u256, rhs: u256) -> (u256, bool) ``` # core::integer::u256_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u256_overflowing_add ## Signature ```rust theme={null} pub fn u256_overflowing_add(lhs: u256, rhs: u256) -> (u256, bool) ``` # core::integer::u256_overflowing_mul Source: https://docs.starknet.io/build/corelib/core-integer-u256_overflowing_mul ## Signature ```rust theme={null} pub fn u256_overflowing_mul(lhs: u256, rhs: u256) -> (u256, bool) ``` # core::integer::u256_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u256_overflowing_sub ## Signature ```rust theme={null} pub fn u256_overflowing_sub(lhs: u256, rhs: u256) -> (u256, bool) ``` # core::integer::u256_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u256_sqrt ## Signature ```rust theme={null} pub extern fn u256_sqrt(a: u256) -> u128 implicits(RangeCheck) nopanic; ``` # core::integer::u256_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u256_wide_mul ## Signature ```rust theme={null} pub fn u256_wide_mul(a: u256, b: u256) -> u512 ``` # core::integer::u32 Source: https://docs.starknet.io/build/corelib/core-integer-u32 The 32-bit unsigned integer type. ## Signature ```rust theme={null} pub extern type u32; ``` # core::integer::u32_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u32_overflowing_add ## Signature ```rust theme={null} pub extern fn u32_overflowing_add(lhs: u32, rhs: u32) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u32_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u32_overflowing_sub ## Signature ```rust theme={null} pub extern fn u32_overflowing_sub(lhs: u32, rhs: u32) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u32_safe_divmod Source: https://docs.starknet.io/build/corelib/core-integer-u32_safe_divmod ## Signature ```rust theme={null} pub extern fn u32_safe_divmod(lhs: u32, rhs: NonZero) -> (u32, u32) implicits(RangeCheck) nopanic; ``` # core::integer::u32_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u32_sqrt ## Signature ```rust theme={null} pub extern fn u32_sqrt(value: u32) -> u16 implicits(RangeCheck) nopanic; ``` # core::integer::u32_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u32_wide_mul ## Signature ```rust theme={null} pub extern fn u32_wide_mul(lhs: u32, rhs: u32) -> u64 nopanic; ``` # core::integer::u32_wrapping_add Source: https://docs.starknet.io/build/corelib/core-integer-u32_wrapping_add ## Signature ```rust theme={null} pub fn u32_wrapping_add(lhs: u32, rhs: u32) -> u32 ``` # core::integer::u32_wrapping_sub Source: https://docs.starknet.io/build/corelib/core-integer-u32_wrapping_sub ## Signature ```rust theme={null} pub fn u32_wrapping_sub(lhs: u32, rhs: u32) -> u32 ``` # core::integer::u512 Source: https://docs.starknet.io/build/corelib/core-integer-u512 ## Signature ```rust theme={null} #[derive(Copy, Drop, Hash, PartialEq, Serde)] pub struct u512 { pub limb0: u128, pub limb1: u128, pub limb2: u128, pub limb3: u128, } ``` ## Members ### limb0 #### Signature ```rust theme={null} pub limb0: u128 ``` ### limb1 #### Signature ```rust theme={null} pub limb1: u128 ``` ### limb2 #### Signature ```rust theme={null} pub limb2: u128 ``` ### limb3 #### Signature ```rust theme={null} pub limb3: u128 ``` # core::integer::u512_safe_div_rem_by_u256 Source: https://docs.starknet.io/build/corelib/core-integer-u512_safe_div_rem_by_u256 Calculates division with remainder of a u512 by a non-zero u256. ## Signature ```rust theme={null} pub fn u512_safe_div_rem_by_u256(lhs: u512, rhs: NonZero) -> (u512, u256) ``` # core::integer::u64 Source: https://docs.starknet.io/build/corelib/core-integer-u64 The 64-bit unsigned integer type. ## Signature ```rust theme={null} pub extern type u64; ``` # core::integer::u64_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u64_overflowing_add ## Signature ```rust theme={null} pub extern fn u64_overflowing_add(lhs: u64, rhs: u64) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u64_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u64_overflowing_sub ## Signature ```rust theme={null} pub extern fn u64_overflowing_sub(lhs: u64, rhs: u64) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u64_safe_divmod Source: https://docs.starknet.io/build/corelib/core-integer-u64_safe_divmod ## Signature ```rust theme={null} pub extern fn u64_safe_divmod(lhs: u64, rhs: NonZero) -> (u64, u64) implicits(RangeCheck) nopanic; ``` # core::integer::u64_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u64_sqrt ## Signature ```rust theme={null} pub extern fn u64_sqrt(value: u64) -> u32 implicits(RangeCheck) nopanic; ``` # core::integer::u64_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u64_wide_mul ## Signature ```rust theme={null} pub extern fn u64_wide_mul(lhs: u64, rhs: u64) -> u128 nopanic; ``` # core::integer::u64_wrapping_add Source: https://docs.starknet.io/build/corelib/core-integer-u64_wrapping_add ## Signature ```rust theme={null} pub fn u64_wrapping_add(lhs: u64, rhs: u64) -> u64 ``` # core::integer::u64_wrapping_sub Source: https://docs.starknet.io/build/corelib/core-integer-u64_wrapping_sub ## Signature ```rust theme={null} pub fn u64_wrapping_sub(lhs: u64, rhs: u64) -> u64 ``` # core::integer::u8 Source: https://docs.starknet.io/build/corelib/core-integer-u8 The 8-bit unsigned integer type. ## Signature ```rust theme={null} pub extern type u8; ``` # core::integer::u8_overflowing_add Source: https://docs.starknet.io/build/corelib/core-integer-u8_overflowing_add ## Signature ```rust theme={null} pub extern fn u8_overflowing_add(lhs: u8, rhs: u8) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u8_overflowing_sub Source: https://docs.starknet.io/build/corelib/core-integer-u8_overflowing_sub ## Signature ```rust theme={null} pub extern fn u8_overflowing_sub(lhs: u8, rhs: u8) -> Result implicits(RangeCheck) nopanic; ``` # core::integer::u8_safe_divmod Source: https://docs.starknet.io/build/corelib/core-integer-u8_safe_divmod ## Signature ```rust theme={null} pub extern fn u8_safe_divmod(lhs: u8, rhs: NonZero) -> (u8, u8) implicits(RangeCheck) nopanic; ``` # core::integer::u8_sqrt Source: https://docs.starknet.io/build/corelib/core-integer-u8_sqrt ## Signature ```rust theme={null} pub extern fn u8_sqrt(value: u8) -> u8 implicits(RangeCheck) nopanic; ``` # core::integer::u8_wide_mul Source: https://docs.starknet.io/build/corelib/core-integer-u8_wide_mul ## Signature ```rust theme={null} pub extern fn u8_wide_mul(lhs: u8, rhs: u8) -> u16 nopanic; ``` # core::integer::u8_wrapping_add Source: https://docs.starknet.io/build/corelib/core-integer-u8_wrapping_add ## Signature ```rust theme={null} pub fn u8_wrapping_add(lhs: u8, rhs: u8) -> u8 ``` # core::integer::u8_wrapping_sub Source: https://docs.starknet.io/build/corelib/core-integer-u8_wrapping_sub ## Signature ```rust theme={null} pub fn u8_wrapping_sub(lhs: u8, rhs: u8) -> u8 ``` # core::internal::DestructWith Source: https://docs.starknet.io/build/corelib/core-internal-DestructWith Wrapper type to ensure that a type `T` is destructed using a specific `Destruct` impl. ## Signature ```rust theme={null} pub struct DestructWith> { pub value: T, } ``` ## Members ### value #### Signature ```rust theme={null} pub value: T ``` # core::internal::DropWith Source: https://docs.starknet.io/build/corelib/core-internal-DropWith Wrapper type to ensure that a type `T` is dropped using a specific `Drop` impl. ## Signature ```rust theme={null} pub struct DropWith> { pub value: T, } ``` ## Members ### value #### Signature ```rust theme={null} pub value: T ``` # core::internal::InferDestruct Source: https://docs.starknet.io/build/corelib/core-internal-InferDestruct Helper to have the same interface as `DestructWith` while inferring the `Destruct` implementation. ## Signature ```rust theme={null} #[derive(Destruct)] pub struct InferDestruct { pub value: T, } ``` ## Members ### value #### Signature ```rust theme={null} pub value: T ``` # core::internal::InferDrop Source: https://docs.starknet.io/build/corelib/core-internal-InferDrop Helper to have the same interface as `DropWith` while inferring the `Drop` implementation. ## Signature ```rust theme={null} #[derive(Drop)] pub struct InferDrop { pub value: T, } ``` ## Members ### value #### Signature ```rust theme={null} pub value: T ``` # core::internal::LoopResult Source: https://docs.starknet.io/build/corelib/core-internal-LoopResult The return type for loops with an early return. ## Signature ```rust theme={null} pub enum LoopResult { Normal: N, EarlyReturn: E, } ``` ## Variants ### Normal #### Signature ```rust theme={null} Normal: N ``` ### EarlyReturn #### Signature ```rust theme={null} EarlyReturn: E ``` # core::internal::OptionRev Source: https://docs.starknet.io/build/corelib/core-internal-OptionRev Same as `Option`, except that the order of the variants is reversed. This is used as the return type of some libfuncs for efficiency reasons. ## Signature ```rust theme={null} pub enum OptionRev { None, Some: T, } ``` ## Variants ### None #### Signature ```rust theme={null} None ``` ### Some #### Signature ```rust theme={null} Some: T ``` # core::internal::bounded_int::AddHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-AddHelper A helper trait for adding two `BoundedInt` instances. ## Signature ```rust theme={null} pub trait AddHelper ``` ## Trait types ### Result #### Signature ```rust theme={null} type Result; ``` # core::internal::bounded_int::ConstrainHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-ConstrainHelper A helper trait for constraining a `BoundedInt` instance. ## Signature ```rust theme={null} pub trait ConstrainHelper ``` ## Trait types ### LowT #### Signature ```rust theme={null} type LowT; ``` ### HighT #### Signature ```rust theme={null} type HighT; ``` # core::internal::bounded_int::DivRemHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-DivRemHelper A helper trait for dividing two `BoundedInt` instances. ## Signature ```rust theme={null} pub trait DivRemHelper ``` ## Trait types ### DivT #### Signature ```rust theme={null} type DivT; ``` ### RemT #### Signature ```rust theme={null} type RemT; ``` # core::internal::bounded_int::MulHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-MulHelper A helper trait for multiplying two `BoundedInt` instances. ## Signature ```rust theme={null} pub trait MulHelper ``` ## Trait types ### Result #### Signature ```rust theme={null} type Result; ``` # core::internal::bounded_int::NegateHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-NegateHelper A helper trait for negating a `BoundedInt` instance. ## Signature ```rust theme={null} pub trait NegateHelper ``` ## Trait functions ### negate Negates the given value. #### Signature ```rust theme={null} fn negate(self: T) -> NegateHelperResult ``` ## Trait types ### Result The result of negating the given value. #### Signature ```rust theme={null} type Result; ``` # core::internal::bounded_int::SubHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-SubHelper A helper trait for subtracting two `BoundedInt` instances. ## Signature ```rust theme={null} pub trait SubHelper ``` ## Trait types ### Result #### Signature ```rust theme={null} type Result; ``` # core::internal::bounded_int::TrimMaxHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-TrimMaxHelper A helper trait for trimming a `BoundedInt` instance max value. ## Signature ```rust theme={null} pub trait TrimMaxHelper ``` ## Trait types ### Target #### Signature ```rust theme={null} type Target; ``` # core::internal::bounded_int::TrimMinHelper Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-TrimMinHelper A helper trait for trimming a `BoundedInt` instance min value. ## Signature ```rust theme={null} pub trait TrimMinHelper ``` ## Trait types ### Target #### Signature ```rust theme={null} type Target; ``` # core::internal::bounded_int::UnitInt Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-UnitInt ## Signature ```rust theme={null} pub type UnitInt = BoundedInt; ``` # core::internal::bounded_int::bounded_int_add Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_add ## Signature ```rust theme={null} extern fn bounded_int_add(lhs: Lhs, rhs: Rhs) -> Result nopanic; ``` # core::internal::bounded_int::bounded_int_constrain Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_constrain ## Signature ```rust theme={null} extern fn bounded_int_constrain(value: T) -> Result implicits(RangeCheck) nopanic; ``` # core::internal::bounded_int::bounded_int_div_rem Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_div_rem ## Signature ```rust theme={null} extern fn bounded_int_div_rem(lhs: Lhs, rhs: NonZero) -> (DivT, RemT) implicits(RangeCheck) nopanic; ``` # core::internal::bounded_int::bounded_int_is_zero Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_is_zero ## Signature ```rust theme={null} extern fn bounded_int_is_zero(value: T) -> IsZeroResult nopanic; ``` # core::internal::bounded_int::bounded_int_mul Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_mul ## Signature ```rust theme={null} extern fn bounded_int_mul(lhs: Lhs, rhs: Rhs) -> Result nopanic; ``` # core::internal::bounded_int::bounded_int_sub Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_sub ## Signature ```rust theme={null} extern fn bounded_int_sub(lhs: Lhs, rhs: Rhs) -> Result nopanic; ``` # core::internal::bounded_int::bounded_int_trim_max Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_trim_max ## Signature ```rust theme={null} extern fn bounded_int_trim_max(value: T) -> OptionRev nopanic; ``` # core::internal::bounded_int::bounded_int_trim_min Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-bounded_int_trim_min ## Signature ```rust theme={null} extern fn bounded_int_trim_min(value: T) -> OptionRev nopanic; ``` # core::internal::bounded_int::downcast Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-downcast Downcasts `FromType` to `ToType` - for types where conversion may fail. If done for wrong types would cause a compiler panic at the Sierra stage. ## Signature ```rust theme={null} pub extern fn downcast(x: FromType) -> Option implicits(RangeCheck) nopanic; ``` # core::internal::bounded_int::upcast Source: https://docs.starknet.io/build/corelib/core-internal-bounded_int-upcast Upcasts `FromType` to `ToType` - for types where conversion is always legal. If done for wrong types would cause a compiler panic at the Sierra stage. ## Signature ```rust theme={null} pub extern fn upcast(x: FromType) -> ToType nopanic; ``` # core::internal::require_implicit Source: https://docs.starknet.io/build/corelib/core-internal-require_implicit Function to enforce that `Implicit` is used by a function calling it. Note: This extern function is not mapped to a Sierra function, and all usages of it are removed during compilation. ## Signature ```rust theme={null} pub extern fn require_implicit() implicits(Implicit) nopanic; ``` # core::internal::revoke_ap_tracking Source: https://docs.starknet.io/build/corelib/core-internal-revoke_ap_tracking ## Signature ```rust theme={null} pub extern fn revoke_ap_tracking() nopanic; ``` # core::iter::adapters::peekable::PeekableTrait Source: https://docs.starknet.io/build/corelib/core-iter-adapters-peekable-PeekableTrait ## Signature ```rust theme={null} pub trait PeekableTrait+Copy+Drop> ``` ## Trait functions ### peek Returns a copy of the next() value without advancing the iterator. Like `next`, if there is a value, it is wrapped in a `Some(T)`. But if the iteration is over, `None` is returned. #### Signature ```rust theme={null} fn peek, +Copy, +Drop, I, impl IterI: Iterator, +Copy, +Drop, >( ref self: Peekable, ) -> Option ``` #### Examples Basic usage: ```rust theme={null} let mut iter = (1..4_u8).into_iter().peekable(); // peek() lets us see one step into the future assert_eq!(iter.peek(), Some(1)); assert_eq!(iter.next(), Some(1)); assert_eq!(iter.next(), Some(2)); // The iterator does not advance even if we `peek` multiple times assert_eq!(iter.peek(), Some(3)); assert_eq!(iter.peek(), Some(3)); assert_eq!(iter.next(), Some(3)); // After the iterator is finished, so is `peek()` assert_eq!(iter.peek(), None); assert_eq!(iter.next(), None); ``` # core::iter::adapters::zip::zip Source: https://docs.starknet.io/build/corelib/core-iter-adapters-zip-zip Converts the arguments to iterators and zips them. See the documentation of `Iterator::zip` for more. ## Signature ```rust theme={null} pub fn zip, impl BIntoIter: IntoIterator, +Destruct, +Destruct, >( a: A, b: B, ) -> Zip ``` ## Examples ```rust theme={null} use std::iter::zip; let xs = array![1, 2, 3]; let ys = array![4, 5, 6]; let mut iter = zip(xs, ys); assert_eq!(iter.next().unwrap(), (1, 4)); assert_eq!(iter.next().unwrap(), (2, 5)); assert_eq!(iter.next().unwrap(), (3, 6)); assert!(iter.next().is_none()); ``` # core::iter::traits::collect::Extend Source: https://docs.starknet.io/build/corelib/core-iter-traits-collect-Extend Extend a collection with the contents of an iterator. Iterators produce a series of values, and collections can also be thought of as a series of values. The `Extend` trait bridges this gap, allowing you to extend a collection by including the contents of that iterator. When extending a collection with an already existing key, that entry is updated or, in the case of collections that permit multiple entries with equal keys, that entry is inserted. ## Signature ```rust theme={null} pub trait Extend ``` ## Examples Basic usage: ```rust theme={null} let mut arr = array![1, 2]; arr.extend(array![3, 4, 5]); assert_eq!(arr, array![1, 2, 3, 4, 5]); ``` ## Trait functions ### extend Extends a collection with the contents of an iterator. #### Signature ```rust theme={null} fn extend, +TypeEqual, +Destruct, +Destruct, >( ref self: T, iter: I, ) ``` # core::iter::traits::collect::FromIterator Source: https://docs.starknet.io/build/corelib/core-iter-traits-collect-FromIterator Conversion from an [`Iterator`](./core-iter-traits-iterator-Iterator). By implementing `FromIterator` for a type, you define how it will be created from an iterator. This is common for types which describe a collection of some kind. If you want to create a collection from the contents of an iterator, the `Iterator::collect()` method is preferred. However, when you need to specify the container type, `FromIterator::from_iter()` can be more readable than using a turbofish (e.g. `::>()`). See the `Iterator::collect()` documentation for more examples of its use. See also: [`IntoIterator`](./core-iter-traits-collect-IntoIterator). ## Signature ```rust theme={null} pub trait FromIterator ``` ## Examples Basic usage: ```rust theme={null} let v = FromIterator::from_iter(0..5_u32); assert_eq!(v, array![0, 1, 2, 3, 4]); ``` Implementing `FromIterator` for your type: ```rust theme={null} use core::metaprogramming::TypeEqual; // A sample collection, that's just a wrapper over Array #[derive(Drop, Debug)] struct MyCollection { arr: Array, } // Let's give it some methods so we can create one and add things // to it. #[generate_trait] impl MyCollectionImpl of MyCollectionTrait { fn new() -> MyCollection { MyCollection { arr: ArrayTrait::new() } } fn add(ref self: MyCollection, elem: u32) { self.arr.append(elem); } } // and we'll implement FromIterator impl MyCollectionFromIterator of FromIterator { fn from_iter, +TypeEqual, +Destruct, +Destruct, >( iter: I ) -> MyCollection { let mut c = MyCollectionTrait::new(); for i in iter { c.add(i); }; c } } // Now we can make a new iterator... let iter = (0..5_u32).into_iter(); // ... and make a MyCollection out of it let c = FromIterator::::from_iter(iter); assert_eq!(c.arr, array![0, 1, 2, 3, 4]); ``` ## Trait functions ### from\_iter Creates a value from an iterator. See the [module-level documentation](./core-iter) for more. #### Signature ```rust theme={null} fn from_iter, +TypeEqual, +Destruct, +Destruct, >( iter: I, ) -> T ``` #### Examples ```rust theme={null} let iter = (0..5_u32).into_iter(); let v = FromIterator::from_iter(iter); assert_eq!(v, array![0, 1, 2, 3, 4]); ``` # core::iter::traits::collect::IntoIterator Source: https://docs.starknet.io/build/corelib/core-iter-traits-collect-IntoIterator Conversion into an [`Iterator`](./core-iter-traits-iterator-Iterator). By implementing `IntoIterator` for a type, you define how it will be converted to an iterator. This is common for types which describe a collection of some kind. One benefit of implementing `IntoIterator` is that your type will work with Cairo's `for` loop syntax. See also: [`FromIterator`](./core-iter-traits-collect-FromIterator). ## Signature ```rust theme={null} pub trait IntoIterator ``` ## Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert!(Some(1) == iter.next()); assert!(Some(2) == iter.next()); assert!(Some(3) == iter.next()); assert!(None == iter.next()); ``` Implementing `IntoIterator` for your type: ```rust theme={null} // A sample collection, that's just a wrapper over `Array` #[derive(Drop, Debug)] struct MyCollection { arr: Array } // Let's give it some methods so we can create one and add things // to it. #[generate_trait] impl MyCollectionImpl of MyCollectionTrait { fn new() -> MyCollection { MyCollection { arr: ArrayTrait::new() } } fn add(ref self: MyCollection, elem: u32) { self.arr.append(elem); } } // and we'll implement `IntoIterator` impl MyCollectionIntoIterator of IntoIterator { type IntoIter = core::array::ArrayIter; fn into_iter(self: MyCollection) -> Self::IntoIter { self.arr.into_iter() } } // Now we can make a new collection... let mut c = MyCollectionTrait::new(); // ... add some stuff to it ... c.add(0); c.add(1); c.add(2); // ... and then turn it into an `Iterator`: let mut n = 0; for i in c { assert!(i == n); n += 1; }; ``` ## Trait functions ### into\_iter Creates an iterator from a value. See the [module-level documentation](./core-iter) for more. #### Signature ```rust theme={null} fn into_iter(self: T) -> IntoIteratorIntoIter ``` #### Examples ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(Some(1), iter.next()); assert_eq!(Some(2), iter.next()); assert_eq!(Some(3), iter.next()); assert_eq!(None, iter.next()); ``` ## Trait types ### IntoIter The iterator type that will be created. #### Signature ```rust theme={null} type IntoIter; ``` # core::iter::traits::iterator::Iterator Source: https://docs.starknet.io/build/corelib/core-iter-traits-iterator-Iterator A trait for dealing with iterators. This is the main iterator trait. For more about the concept of iterators generally, please see the [module-level documentation](./core-iter). In particular, you may want to know how to [implement `Iterator`](./core-iter). ## Signature ```rust theme={null} pub trait Iterator ``` ## Trait functions ### next Advances the iterator and returns the next value. Returns [`None`](./core-option#none) when iteration is finished. Individual iterator implementations may choose to resume iteration, and so calling `next()` again may or may not eventually start returning [`Some(Item)`](./core-option#some) again at some point. #### Signature ```rust theme={null} fn next(ref self: T) -> OptionItem> ``` #### Examples ```rust theme={null} let mut iter = [1, 2, 3].span().into_iter(); // A call to next() returns the next value... assert_eq!(Some(@1), iter.next()); assert_eq!(Some(@2), iter.next()); assert_eq!(Some(@3), iter.next()); // ... and then None once it's over. assert_eq!(None, iter.next()); // More calls may or may not return `None`. Here, they always will. assert_eq!(None, iter.next()); assert_eq!(None, iter.next()); ``` ### count Consumes the iterator, counting the number of iterations and returning it. This method will call `next` repeatedly until [`None`](./core-option#none) is encountered, returning the number of times it saw [`Some`](./core-option#some). Note that `next` has to be called at least once even if the iterator does not have any elements. #### Overflow Behavior The method does no guarding against overflows, so counting elements of an iterator with more than [`Bounded::::MAX`](./core-num-traits-bounded-Bounded) elements either produces the wrong result or panics. #### Panics This function might panic if the iterator has more than [`Bounded::::MAX`](./core-num-traits-bounded-Bounded) elements. #### Examples ```rust theme={null} let mut a = array![1, 2, 3].into_iter(); assert_eq!(a.count(), 3); let mut a = array![1, 2, 3, 4, 5].into_iter(); assert_eq!(a.count(), 5); ``` #### Signature ```rust theme={null} fn count, +Destruct>(self: T) -> u32 ``` ### last Consumes the iterator, returning the last element. This method will evaluate the iterator until it returns [`None`](./core-option#none). While doing so, it keeps track of the current element. After [`None`](./core-option#none) is returned, `last()` will then return the last element it saw. #### Signature ```rust theme={null} fn last, +Destruct>(self: T) -> OptionItem> ``` #### Examples ```rust theme={null} let mut a = array![1, 2, 3].into_iter(); assert_eq!(a.last(), Option::Some(3)); let mut a = array![].into_iter(); assert_eq!(a.last(), Option::None); ``` ### advance\_by Advances the iterator by `n` elements. This method will eagerly skip `n` elements by calling `next` up to `n` times until [`None`](./core-option#none) is encountered. `advance_by(n)` will return `Ok(())` if the iterator successfully advances by `n` elements, or a `Err(NonZero)` with value `k` if [`None`](./core-option#none) is encountered, where `k` is remaining number of steps that could not be advanced because the iterator ran out. If `self` is empty and `n` is non-zero, then this returns `Err(n)`. Otherwise, `k` is always less than `n`. #### Signature ```rust theme={null} fn advance_by, +Destruct>( ref self: T, n: u32, ) -> Result> ``` #### Examples ```rust theme={null} let mut iter = array![1_u8, 2, 3, 4].into_iter(); assert_eq!(iter.advance_by(2), Ok(())); assert_eq!(iter.next(), Some(3)); assert_eq!(iter.advance_by(0), Ok(())); assert_eq!(iter.advance_by(100), Err(99)); ``` ### nth Returns the `n`th element of the iterator. Like most indexing operations, the count starts from zero, so `nth(0)` returns the first value, `nth(1)` the second, and so on. Note that all preceding elements, as well as the returned element, will be consumed from the iterator. That means that the preceding elements will be discarded, and also that calling `nth(0)` multiple times on the same iterator will return different elements. `nth()` will return [`None`](./core-option#none) if `n` is greater than or equal to the length of the iterator. #### Signature ```rust theme={null} fn nth, +Destruct>(ref self: T, n: u32) -> OptionItem> ``` #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(iter.nth(1), Some(2)); ``` Calling `nth()` multiple times doesn't rewind the iterator: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(iter.nth(1), Some(2)); assert_eq!(iter.nth(1), None); ``` Returning `None` if there are less than `n + 1` elements: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(iter.nth(10), None); ``` ### map Takes a closure and creates an iterator which calls that closure on each element. `map()` transforms one iterator into another, by means of its argument: something that implements `FnOnce`. It produces a new iterator which calls this closure on each element of the original iterator. If you are good at thinking in types, you can think of `map()` like this: If you have an iterator that gives you elements of some type `A`, and you want an iterator of some other type `B`, you can use `map()`, passing a closure that takes an `A` and returns a `B`. `map()` is conceptually similar to a `for` loop. However, as `map()` is lazy, it is best used when you're already working with other iterators. If you're doing some sort of looping for a side effect, it's considered more idiomatic to use `for` than `map()`. #### Signature ```rust theme={null} fn map[Output: B], +Drop, +Drop>( self: T, f: F, ) -> Map ``` #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter().map(|x| 2 * x); assert!(iter.next() == Some(2)); assert!(iter.next() == Some(4)); assert!(iter.next() == Some(6)); assert!(iter.next() == None); ``` If you're doing some sort of side effect, prefer `for` to `map()`: ```rust theme={null} // don't do this: let _ = (0..5_usize).into_iter().map(|x| println!("{x}")); // it won't even execute, as it is lazy. Cairo will warn you about this if not specifically ignored, as is done here. // Instead, use for: for x in 0..5_usize { println!("{x}"); } ``` ### enumerate Creates an iterator which gives the current iteration count as well as the next value. The iterator returned yields pairs `(i, val)`, where `i` is the current index of iteration and `val` is the value returned by the iterator. `enumerate()` keeps its count as a [`usize`](./core-usize). #### Overflow Behavior The method does no guarding against overflows, so enumerating more than `Bounded::::MAX` elements will always panic. #### Panics Will panic if the to-be-returned index overflows a `usize`. #### Examples ```rust theme={null} let mut iter = array!['a', 'b', 'c'].into_iter().enumerate(); assert_eq!(iter.next(), Some((0, 'a'))); assert_eq!(iter.next(), Some((1, 'b'))); assert_eq!(iter.next(), Some((2, 'c'))); assert_eq!(iter.next(), None); ``` #### Signature ```rust theme={null} fn enumerate(self: T) -> Enumerate ``` ### fold Folds every element into an accumulator by applying an operation, returning the final result. `fold()` takes two arguments: an initial value, and a closure with two arguments: an 'accumulator', and an element. The closure returns the value that the accumulator should have for the next iteration. The initial value is the value the accumulator will have on the first call. After applying this closure to every element of the iterator, `fold()` returns the accumulator. Folding is useful whenever you have a collection of something, and want to produce a single value from it. Note: `fold()`, and similar methods that traverse the entire iterator, might not terminate for infinite iterators, even on traits for which a result is determinable in finite time. Note: `fold()` combines elements in a left-associative fashion. For associative operators like `+`, the order the elements are combined in is not important, but for non-associative operators like `-` the order will affect the final result. #### Note to Implementers Several of the other (forward) methods have default implementations in terms of this one, so try to implement this explicitly if it can do something better than the default `for` loop implementation. In particular, try to have this call `fold()` on the internal parts from which this iterator is composed. #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); // the sum of all of the elements of the array let sum = iter.fold(0, |acc, x| acc + x); assert_eq!(sum, 6); ``` Let's walk through each step of the iteration here: | element | acc | x | result | | - | - | - | - | | | 0 | | | | 1 | 0 | 1 | 1 | | 2 | 1 | 2 | 3 | | 3 | 3 | 3 | 6 | And so, our final result, `6`. It's common for people who haven't used iterators a lot to use a `for` loop with a list of things to build up a result. Those can be turned into `fold()`s: ```rust theme={null} let mut numbers = array![1, 2, 3, 4, 5].span(); let mut result = 0; // for loop: for i in numbers{ result = result + (*i); }; // fold: let mut numbers_iter = numbers.into_iter(); let result2 = numbers_iter.fold(0, |acc, x| acc + (*x)); // they're the same assert_eq!(result, result2); ``` #### Signature ```rust theme={null} fn fold[Output: B], +Destruct, +Destruct, +Destruct, >( ref self: T, init: B, f: F, ) -> B ``` ### any Tests if any element of the iterator matches a predicate. `any()` takes a closure that returns `true` or `false`. It applies this closure to each element of the iterator, and if any of them return `true`, then so does `any()`. If they all return `false`, it returns `false`. `any()` is short-circuiting; in other words, it will stop processing as soon as it finds a `true`, given that no matter what else happens, the result will also be `true`. An empty iterator returns `false`. #### Signature ```rust theme={null} fn any[Output: bool], +Destruct, +Destruct, +Destruct, >( ref self: T, predicate: P, ) -> bool ``` #### Examples Basic usage: ```rust theme={null} assert!(array![1, 2, 3].into_iter().any(|x| x == 2)); assert!(!array![1, 2, 3].into_iter().any(|x| x > 5)); ``` ### all Tests if every element of the iterator matches a predicate. `all()` takes a closure that returns `true` or `false`. It applies this closure to each element of the iterator, and if all of them return `true`, then so does `all()`. If any of them return `false`, it returns `false`. `all()` is short-circuiting; in other words, it will stop processing as soon as it finds a `false`, given that no matter what else happens, the result will also be `false`. An empty iterator returns `true`. #### Signature ```rust theme={null} fn all[Output: bool], +Destruct, +Destruct, +Destruct, >( ref self: T, predicate: P, ) -> bool ``` #### Examples Basic usage: ```rust theme={null} assert!(array![1, 2, 3].into_iter().all(|x| x > 0)); assert!(!array![1, 2, 3].into_iter().all(|x| x > 2)); ``` ### find Searches for an element of an iterator that satisfies a predicate. `find()` takes a closure that returns `true` or `false`. It applies this closure to each element of the iterator as a snapshot, and if any of them return `true`, then `find()` returns `Some(element)`. If they all return `false`, it returns [`None`](./core-option#none). `find()` is short-circuiting; in other words, it will stop processing as soon as the closure returns `true`. #### Signature ```rust theme={null} fn find[Output: bool], +Destruct, +Destruct, +Destruct>(ref self: T, predicate: P) -> OptionItem> ``` #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(iter.find(|x| *x == 2), Option::Some(2)); assert_eq!(iter.find(|x| *x == 5), Option::None); ``` Stopping at the first `true`: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); assert_eq!(iter.find(|x| *x == 2), Option::Some(2)); // we can still use `iter`, as there are more elements. assert_eq!(iter.next(), Option::Some(3)); ``` Note that `iter.find(f)` is equivalent to `iter.filter(f).next()`. ### filter Creates an iterator which uses a closure to determine if an element should be yielded. The closure takes each element as a snapshot. Given an element the closure must return `true` or `false`. The returned iterator will yield only the elements for which the closure returns `true`. #### Signature ```rust theme={null} fn filter[Output: bool], +Destruct, +Destruct, +Destruct, >( self: T, predicate: P, ) -> Filter ``` #### Examples Basic usage: ```rust theme={null} let a = array![0_u32, 1, 2]; let mut iter = a.into_iter().filter(|x| *x > 0); assert_eq!(iter.next(), Option::Some(1)); assert_eq!(iter.next(), Option::Some(2)); assert_eq!(iter.next(), Option::None); ``` Note that `iter.filter(f).next()` is equivalent to `iter.find(f)`. ### zip 'Zips up' two iterators into a single iterator of pairs. `zip()` returns a new iterator that will iterate over two other iterators, returning a tuple where the first element comes from the first iterator, and the second element comes from the second iterator. In other words, it zips two iterators together, into a single one. If either iterator returns [`None`](./core-option#none), `next` from the zipped iterator will return [`None`](./core-option#none). If the zipped iterator has no more elements to return then each further attempt to advance it will first try to advance the first iterator at most one time and if it still yielded an item try to advance the second iterator at most one time. #### Signature ```rust theme={null} fn zip, +Destruct>( self: T, other: U, ) -> Zip ``` #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter().zip(array![4, 5, 6].into_iter()); assert_eq!(iter.next(), Some((1, 4))); assert_eq!(iter.next(), Some((2, 5))); assert_eq!(iter.next(), Some((3, 6))); assert_eq!(iter.next(), None); ``` Since the argument to `zip()` uses [`IntoIterator`](./core-iter-traits-collect-IntoIterator), we can pass anything that can be converted into an [`Iterator`](./core-iter-traits-iterator-Iterator), not just an [`Iterator`](./core-iter-traits-iterator-Iterator) itself. For example: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter().zip(array![4, 5, 6]); assert_eq!(iter.next(), Some((1, 4))); assert_eq!(iter.next(), Some((2, 5))); assert_eq!(iter.next(), Some((3, 6))); assert_eq!(iter.next(), None); ``` ### collect Transforms an iterator into a collection. `collect()` can take anything iterable, and turn it into a relevant collection. This is one of the more powerful methods in the core library, used in a variety of contexts. The most basic pattern in which `collect()` is used is to turn one collection into another. You take a collection, call `iter` on it, do a bunch of transformations, and then `collect()` at the end. `collect()` can also create instances of types that are not typical collections. Because `collect()` is so general, it can cause problems with type inference. As such, `collect()` is one of the few times you'll see the syntax affectionately known as the 'turbofish': `::<>`. This helps the inference algorithm understand specifically which collection you're trying to collect into. #### Signature ```rust theme={null} fn collect, impl ItemEqual: TypeEqual::Item, >, +Destruct, +FromIterator, +Destruct, >( self: T, ) -> B ``` #### Examples Basic usage: ```rust theme={null} let doubled: Array = array![1, 2, 3].into_iter().map(|x| x * 2).collect(); assert_eq!(array![2, 4, 6], doubled); ``` Note that we needed the `: Array` on the left-hand side. Using the 'turbofish' instead of annotating `doubled`: ```rust theme={null} let doubled = array![1, 2, 3].into_iter().map(|x| x * 2).collect::>(); assert_eq!(array![2, 4, 6], doubled); ``` Because `collect()` only cares about what you're collecting into, you can still use a partial type hint, `_`, with the turbofish: ```rust theme={null} let doubled = array![1, 2, 3].into_iter().map(|x| x * 2).collect::>(); assert_eq!(array![2, 4, 6], doubled); ``` ### peekable Creates an iterator which can use the `peek` method to look at the next element of the iterator. See its documentation for more information. Note that the underlying iterator is still advanced when `peek` is called for the first time: In order to retrieve the next element, `next` is called on the underlying iterator, hence any side effects (i.e. anything other than fetching the next value) of the `next` method will occur. #### Signature ```rust theme={null} fn peekable(self: T) -> PeekableItem> ``` #### Examples Basic usage: ```rust theme={null} let mut iter = (1..4_u8).into_iter().peekable(); // peek() lets us see one step into the future assert_eq!(iter.peek(), Option::Some(1)); assert_eq!(iter.next(), Option::Some(1)); assert_eq!(iter.next(), Option::Some(2)); // we can peek() multiple times, the iterator won't advance assert_eq!(iter.peek(), Option::Some(3)); assert_eq!(iter.peek(), Option::Some(3)); assert_eq!(iter.next(), Option::Some(3)); // after the iterator is finished, so is peek() assert_eq!(iter.peek(), Option::None); assert_eq!(iter.next(), Option::None); ``` ### take Creates an iterator that yields the first `n` elements, or fewer if the underlying iterator ends sooner. `take(n)` yields elements until `n` elements are yielded or the end of the iterator is reached (whichever happens first). The returned iterator is a prefix of length `n` if the original iterator contains at least `n` elements, otherwise it contains all of the (fewer than `n`) elements of the original iterator. #### Signature ```rust theme={null} fn take(self: T, n: u32) -> Take ``` #### Examples Basic usage: ```rust theme={null} let mut iter = array![1, 2, 3].into_iter().take(2); assert_eq!(iter.next(), Some(1)); assert_eq!(iter.next(), Some(2)); assert_eq!(iter.next(), None); ``` If less than `n` elements are available, `take` will limit itself to the size of the underlying iterator: ```rust theme={null} let mut iter = array![1, 2].into_iter().take(5); assert_eq!(iter.next(), Some(1)); assert_eq!(iter.next(), Some(2)); assert_eq!(iter.next(), None); ``` ### sum Sums the elements of an iterator. Takes each element, adds them together, and returns the result. An empty iterator returns the zero value of the type. `sum()` can be used to sum any type implementing \[`Sum`]\[`core::iter::Sum`], including \[`Option`]\[`Option::sum`] and \[`Result`]\[`Result::sum`]. #### Panics When calling `sum()` and a primitive integer type is being returned, this method will panic if the computation overflows. #### Examples ```rust theme={null} let mut iter = array![1, 2, 3].into_iter(); let sum: usize = iter.sum(); assert_eq!(sum, 6); ``` #### Signature ```rust theme={null} fn sum, +Destruct, +Sum>(self: T) -> IteratorItem ``` ### product Iterates over the entire iterator, multiplying all the elements An empty iterator returns the one value of the type. #### Panics When calling `product()` and a primitive integer type is being returned, this method will panic if the computation overflows. #### Examples ```rust theme={null} fn factorial(n: u32) -> u32 { (1..=n).into_iter().product() } assert_eq!(factorial(0), 1); assert_eq!(factorial(1), 1); assert_eq!(factorial(5), 120); ``` #### Signature ```rust theme={null} fn product, +Destruct, +Product>(self: T) -> IteratorItem ``` ### chain Takes two iterators and creates a new iterator over both in sequence. `chain()` will return a new iterator which will first iterate over values from the first iterator and then over values from the second iterator. In other words, it links two iterators together, in a chain. 🔗 Arguments do not have to be of the same type as long as the underlying iterated over items are. #### Signature ```rust theme={null} fn chain, +TypeEqual, +Destruct, >( self: T, other: U, ) -> Chain ``` #### Examples Basic usage: ```rust theme={null} use core::ops::Range; let a: Array = array![7, 8, 9]; let b: Range = 0..5; let mut iter = a.into_iter().chain(b.into_iter()); assert_eq!(iter.next(), Option::Some(7)); assert_eq!(iter.next(), Option::Some(8)); assert_eq!(iter.next(), Option::Some(9)); assert_eq!(iter.next(), Option::Some(0)); assert_eq!(iter.next(), Option::Some(1)); assert_eq!(iter.next(), Option::Some(2)); assert_eq!(iter.next(), Option::Some(3)); assert_eq!(iter.next(), Option::Some(4)); assert_eq!(iter.next(), Option::None); ``` Since the argument to `chain()` uses [`IntoIterator`](./core-iter-traits-collect-IntoIterator), we can pass anything that can be converted into an [`Iterator`](./core-iter-traits-iterator-Iterator), not just an [`Iterator`](./core-iter-traits-iterator-Iterator) itself. For example, arrays implement [`IntoIterator`](./core-iter-traits-collect-IntoIterator), and so can be passed to `chain()` directly: ```rust theme={null} let a = array![1, 2, 3]; let b = array![4, 5, 6]; let mut iter = a.into_iter().chain(b); assert_eq!(iter.next(), Option::Some(1)); assert_eq!(iter.next(), Option::Some(2)); assert_eq!(iter.next(), Option::Some(3)); assert_eq!(iter.next(), Option::Some(4)); assert_eq!(iter.next(), Option::Some(5)); assert_eq!(iter.next(), Option::Some(6)); assert_eq!(iter.next(), Option::None); ``` ## Trait types ### Item The type of the elements being iterated over. #### Signature ```rust theme={null} type Item; ``` # core::keccak::cairo_keccak Source: https://docs.starknet.io/build/corelib/core-keccak-cairo_keccak Computes the Keccak-256 hash of a byte sequence with custom padding. This function allows hashing arbitrary byte sequences by providing the input as 64-bit words in little-endian format and a final partial word. ## Signature ```rust theme={null} pub fn cairo_keccak(ref input: Array, last_input_word: u64, last_input_num_bytes: u32) -> u256 ``` #### Arguments * `input` - Array of complete 64-bit words in little-endian format * `last_input_word` - Final partial word (if any) * `last_input_num_bytes` - Number of valid bytes in the final word (0-7) #### Returns The 32-byte Keccak-256 hash as a little-endian `u256` #### Panics Panics if `last_input_num_bytes` is greater than 7. ## Examples ```rust theme={null} use core::keccak::cairo_keccak; // Hash "Hello world!" by splitting into 64-bit words in little-endian let mut input = array![0x6f77206f6c6c6548]; // a full 8-byte word let hash = cairo_keccak(ref input, 0x21646c72, 4); // 4 bytes of the last word assert!(hash == 0xabea1f2503529a21734e2077c8b584d7bee3f45550c2d2f12a198ea908e1d0ec); ``` # core::keccak::compute_keccak_byte_array Source: https://docs.starknet.io/build/corelib/core-keccak-compute_keccak_byte_array Computes the Keccak-256 hash of a `ByteArray`. ## Signature ```rust theme={null} pub fn compute_keccak_byte_array(arr: ByteArray) -> u256 ``` #### Arguments * `arr` - The input bytes to hash #### Returns The 32-byte Keccak-256 hash as a little-endian `u256` ## Examples ```rust theme={null} use core::keccak::compute_keccak_byte_array; let text: ByteArray = "Hello world!"; let hash = compute_keccak_byte_array(@text); assert!(hash == 0xabea1f2503529a21734e2077c8b584d7bee3f45550c2d2f12a198ea908e1d0ec); ``` # core::keccak::keccak_u256s_be_inputs Source: https://docs.starknet.io/build/corelib/core-keccak-keccak_u256s_be_inputs Computes the Keccak-256 hash of multiple `u256` values in big-endian format. ## Signature ```rust theme={null} pub fn keccak_u256s_be_inputs(mut input: Span) -> u256 ``` #### Arguments * `input` - A span of big-endian `u256` values to be hashed #### Returns The 32-byte Keccak-256 hash as a little-endian `u256` ## Examples ```rust theme={null} use core::keccak::keccak_u256s_be_inputs; let input = array![0x1234_u256, 0x5678_u256].span(); let hash = assert!(keccak_u256s_be_inputs(input) == 0xfa31cb2326ed629f79d2da5beb78e2bd8ac7a1b8b86cae09eeb6a89a908b12a); ``` # core::keccak::keccak_u256s_le_inputs Source: https://docs.starknet.io/build/corelib/core-keccak-keccak_u256s_le_inputs Computes the Keccak-256 hash of multiple `u256` values in little-endian format. ## Signature ```rust theme={null} pub fn keccak_u256s_le_inputs(mut input: Span) -> u256 ``` #### Arguments * `input` - A span of little-endian `u256` values to be hashed #### Returns The 32-byte Keccak-256 hash as a little-endian `u256` ## Examples ```rust theme={null} use core::keccak::keccak_u256s_le_inputs; let input: Span = array![0, 1, 2].span(); assert!(keccak_u256s_le_inputs(input) == 0xf005473605efc7d8ff67d9f23fe2e4a4f23454c12b49b38822ed362e0a92a0a6); ``` # core::math::egcd Source: https://docs.starknet.io/build/corelib/core-math-egcd Computes the extended GCD and Bezout coefficients for two numbers. Uses the Extended Euclidean algorithm to find (g, s, t, sub\_direction) where `g = gcd(a, b)`. The relationship between inputs and outputs is: * If `sub_direction` is true: `g = s * a - t * b` * If `sub_direction` is false: `g = t * b - s * a` Returns a tuple (g, s, t, sub\_direction) where g is the GCD and `(s, -t)` or `(-s, t)` are the Bezout coefficients (according to `sub_direction`). ## Signature ```rust theme={null} pub fn egcd, +Drop, +Add, +Mul, +DivRem, +core::num::traits::Zero, +core::num::traits::One, +TryInto>, >( a: NonZero, b: NonZero, ) -> (T, T, T, bool) ``` ## Examples ```rust theme={null} use core::math::egcd; let (g, s, t, dir) = egcd::(12, 8); assert!(g == 4); ``` # core::math::inv_mod Source: https://docs.starknet.io/build/corelib/core-math-inv_mod Computes the modular multiplicative inverse of `a` modulo `n`. Returns `s` such that `a*s ≡ 1 (mod n)` where `s` is between `1` and `n-1` inclusive, or `None` if `gcd(a,n) > 1` (inverse doesn't exist). ## Signature ```rust theme={null} pub fn inv_mod, +Drop, +Add, +Sub, +Mul, +DivRem, +core::num::traits::Zero, +core::num::traits::One, +TryInto>, >( a: NonZero, n: NonZero, ) -> Option ``` ## Examples ```rust theme={null} use core::math::inv_mod; let inv = inv_mod::(3, 7); assert!(inv == Some(5)); ``` # core::math::u256_div_mod_n Source: https://docs.starknet.io/build/corelib/core-math-u256_div_mod_n Returns `a / b (mod n)`, or `None` if `b` is not invertible modulo `n`. ## Signature ```rust theme={null} pub fn u256_div_mod_n(a: u256, b: u256, n: NonZero) -> Option ``` ## Examples ```rust theme={null} use core::math::u256_inv_mod; let result = u256_div_mod_n(17, 7, 29); assert!(result == Some(19)); ``` # core::math::u256_inv_mod Source: https://docs.starknet.io/build/corelib/core-math-u256_inv_mod Returns the inverse of `a` modulo `n`, or `None` if `a` is not invertible modulo `n`. All `a`s will be considered not invertible for `n == 1`. ## Signature ```rust theme={null} pub fn u256_inv_mod(a: u256, n: NonZero) -> Option> ``` ## Examples ```rust theme={null} use core::math::u256_inv_mod; let inv = u256_inv_mod(3, 17); assert!(inv == Some(6)); ``` # core::math::u256_mul_mod_n Source: https://docs.starknet.io/build/corelib/core-math-u256_mul_mod_n Returns `a * b (mod n)`. ## Signature ```rust theme={null} pub fn u256_mul_mod_n(a: u256, b: u256, n: NonZero) -> u256 ``` ## Examples ```rust theme={null} use core::math::u256_mul_mod_n; let result = u256_mul_mod_n(17, 23, 29); assert!(result == 14); ``` # core::metaprogramming::TypeEqual Source: https://docs.starknet.io/build/corelib/core-metaprogramming-TypeEqual A trait that can be used to disable implementations based on the types of the generic args. Assumes that `TypeEqualImpl` is the only implementation of this trait. Primarily used for optimizations by enabling type-specific implementations. Since `TypeEqualImpl` is the only implementation, adding `-TypeEqual` as a trait bound ensures the implementation is only available when T and U are different types. ## Signature ```rust theme={null} pub trait TypeEqual ``` # core::never Source: https://docs.starknet.io/build/corelib/core-never ## Signature ```rust theme={null} pub enum never {} ``` # core::nullable::FromNullableResult Source: https://docs.starknet.io/build/corelib/core-nullable-FromNullableResult Represents the result of matching a `Nullable` value. Used to safely handle both null and non-null cases when using `match_nullable` on a `Nullable`. ## Signature ```rust theme={null} pub enum FromNullableResult { Null, NotNull: Box, } ``` ## Variants ### Null Represents a null value #### Signature ```rust theme={null} Null ``` ### NotNull The boxed value when not null #### Signature ```rust theme={null} NotNull: Box ``` # core::nullable::Nullable Source: https://docs.starknet.io/build/corelib/core-nullable-Nullable A type that can either be null or contain a boxed value. ## Signature ```rust theme={null} pub extern type Nullable; ``` # core::nullable::NullableTrait Source: https://docs.starknet.io/build/corelib/core-nullable-NullableTrait ## Signature ```rust theme={null} pub trait NullableTrait ``` ## Trait functions ### deref Wrapper for `Deref::deref`. Prefer using `Deref::deref` directly. This function exists for backwards compatibility. #### Signature ```rust theme={null} fn deref(nullable: Nullable) -> T ``` #### Examples Preferred way: ```rust theme={null} let value: Nullable = NullableTrait::new(42); let unwrapped = value.deref(); ``` This function method does the same thing: ```rust theme={null} use core::nullable::NullableTrait; let also_unwrapped = NullableTrait::deref(value); ``` ### deref\_or Returns the contained value if not null, or returns the provided default value. #### Signature ```rust theme={null} fn deref_or>(self: Nullable, default: T) -> T ``` #### Examples ```rust theme={null} let value: Nullable = NullableTrait::new(42); assert!(value.deref_or(0) == 42); let null_value: Nullable = Default::default(); assert!(null_value.deref_or(0) == 0); ``` ### deref\_or\_else Returns the contained value if not null, or computes it from a closure. #### Signature ```rust theme={null} fn deref_or_else, impl func: FnOnce, +Drop>( self: Nullable, f: F, ) -> T ``` #### Examples ```rust theme={null} let value: Nullable = NullableTrait::new(42); assert!(value.deref_or_else(|| 0) == 42); let null_value: Nullable = Default::default(); assert!(null_value.deref_or_else(|| 0) == 0); ``` ### new Creates a new non-null `Nullable` with the given value. #### Signature ```rust theme={null} fn new(value: T) -> Nullable ``` #### Examples ```rust theme={null} let value: Nullable = NullableTrait::new(42); assert!(!value.is_null()); ``` ### is\_null Returns `true` if the value is null. #### Signature ```rust theme={null} fn is_null(self: @Nullable) -> bool ``` #### Examples ```rust theme={null} let value: Nullable = NullableTrait::new(42); assert!(!value.is_null()); let null_value: Nullable = Default::default(); assert!(null_value.is_null()); ``` ### as\_snapshot Creates a new `Nullable` containing a snapshot of the value. This is useful when working with non-copyable types inside a `Nullable`. This allows you to keep using the original value while also having access to a snapshot of it, preventing the original value from being moved. #### Signature ```rust theme={null} fn as_snapshot(self: @Nullable) -> Nullable ``` #### Examples ```rust theme={null} let value: Nullable> = NullableTrait::new(array![1, 2, 3]); let res = (@value).as_snapshot(); assert!(res.deref() == @array![1, 2, 3]); assert!(value.deref() == array![1, 2, 3]); ``` # core::nullable::match_nullable Source: https://docs.starknet.io/build/corelib/core-nullable-match_nullable ## Signature ```rust theme={null} pub extern fn match_nullable(value: Nullable) -> FromNullableResult nopanic; ``` # core::nullable::null Source: https://docs.starknet.io/build/corelib/core-nullable-null ## Signature ```rust theme={null} pub extern fn null() -> Nullable nopanic; ``` # core::num::traits::bit_size::BitSize Source: https://docs.starknet.io/build/corelib/core-num-traits-bit_size-BitSize A trait used to retrieve the size of a type in bits. ## Signature ```rust theme={null} pub trait BitSize ``` ## Trait functions ### bits Returns the bit size of `T` as a `usize`. #### Signature ```rust theme={null} fn bits() -> u32 ``` #### Examples ```rust theme={null} use core::num::traits::BitSize; let bits = BitSize::::bits(); assert!(bits == 8); ``` # core::num::traits::bounded::Bounded Source: https://docs.starknet.io/build/corelib/core-num-traits-bounded-Bounded A trait defining minimum and maximum bounds for numeric types. This trait only supports types that can have constant values. ## Signature ```rust theme={null} pub trait Bounded ``` ## Trait constants ### MIN Returns the minimum value for type `T`. #### Signature ```rust theme={null} const MIN: T; ``` #### Examples ```rust theme={null} use core::num::traits::Bounded; let min = Bounded::::MIN; assert!(min == 0); ``` ### MAX Returns the maximum value for type `T`. #### Signature ```rust theme={null} const MAX: T; ``` #### Examples ```rust theme={null} use core::num::traits::Bounded; let max = Bounded::::MAX; assert!(max == 255); ``` # core::num::traits::one::One Source: https://docs.starknet.io/build/corelib/core-num-traits-one-One Defines a multiplicative identity element for `T`. ## Signature ```rust theme={null} pub trait One ``` # Laws ```text theme={null} a * 1 = a ∀ a ∈ T 1 * a = a ∀ a ∈ T ``` ## Trait functions ### one Returns the multiplicative identity element of `T`, `1`. #### Signature ```rust theme={null} fn one() -> T ``` #### Examples ```rust theme={null} use core::num::traits::One; assert!(One::::one() == 1); ``` ### is\_one Returns true if `self` is equal to the multiplicative identity. #### Signature ```rust theme={null} fn is_one(self: @T) -> bool ``` #### Examples ```rust theme={null} use core::num::traits::One; assert!(1.is_one()); assert!(!0.is_one()); ``` ### is\_non\_one Returns false if `self` is equal to the multiplicative identity. #### Signature ```rust theme={null} fn is_non_one(self: @T) -> bool ``` #### Examples ```rust theme={null} use core::num::traits::One; assert!(0.is_non_one()); assert!(!1.is_non_one()); ``` # core::num::traits::ops::checked::CheckedAdd Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-checked-CheckedAdd Performs addition that returns `None` instead of wrapping around on overflow. ## Signature ```rust theme={null} pub trait CheckedAdd ``` ## Examples ```rust theme={null} use core::num::traits::CheckedAdd; let result = 1_u8.checked_add(2); assert!(result == Some(3)); let result = 255_u8.checked_add(1); assert!(result == None); // Overflow ``` ## Trait functions ### checked\_add Adds two numbers, checking for overflow. If overflow happens, `None` is returned. #### Signature ```rust theme={null} fn checked_add(self: T, v: T) -> Option ``` # core::num::traits::ops::divrem::DivRem Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-divrem-DivRem Performs truncated division and remainder. `T` – dividend type (left-hand operand) `U` – divisor type (right-hand operand, must be wrapped in `NonZero` at call-site) The division truncates toward zero, like Cairo’s `/` and `%`. ## Signature ```rust theme={null} pub trait DivRem ``` # Associated types * `Quotient` – the type produced by the division * `Remainder` – the type produced by the modulo ## Examples Identical operand types: ```rust theme={null} use core::traits::{DivRem, NonZero}; let lhs: u32 = 7; let rhs: NonZero = 3.try_into().unwrap(); assert!(DivRem::::div_rem(lhs, rhs) == (2, 1)); ``` Heterogeneous division (`u256` by `u128`): ```rust theme={null} use core::traits::DivRem; use integer::u256_as_non_zero; let big: u256 = 1_000_000; // dividend let nz10: NonZero = u256_as_non_zero(10_u128.into()); // divisor let (q, r) = DivRem::::div_rem(big, nz10); // q : u256, r : u128 ``` ## Trait functions ### div\_rem Computes both `/` and `%` in a single pass. #### Signature ```rust theme={null} fn div_rem(self: T, other: NonZero) -> (DivRemQuotient, DivRemRemainder) ``` ## Trait types ### Quotient Quotient returned by the division. #### Signature ```rust theme={null} type Quotient; ``` ### Remainder Remainder returned by the modulo operation. #### Signature ```rust theme={null} type Remainder; ``` # core::num::traits::ops::overflowing::OverflowingAdd Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-overflowing-OverflowingAdd Performs addition with a flag for overflow. ## Signature ```rust theme={null} pub trait OverflowingAdd ``` ## Examples ```rust theme={null} use core::num::traits::OverflowingAdd; let (result, is_overflow) = 1_u8.overflowing_add(255_u8); assert!(result == 0); assert!(is_overflow); ``` ## Trait functions ### overflowing\_add Returns a tuple of the sum along with a boolean indicating whether an arithmetic overflow would occur. If an overflow would have occurred then the wrapped value is returned. #### Signature ```rust theme={null} fn overflowing_add(self: T, v: T) -> (T, bool) ``` # core::num::traits::ops::overflowing::OverflowingMul Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-overflowing-OverflowingMul Performs multiplication with a flag for overflow. ## Signature ```rust theme={null} pub trait OverflowingMul ``` ## Examples ```rust theme={null} use core::num::traits::OverflowingMul; let (result, is_overflow) = 1_u8.overflowing_mul(2_u8); assert!(result == 2); assert!(!is_overflow); ``` ## Trait functions ### overflowing\_mul Returns a tuple of the product along with a boolean indicating whether an arithmetic overflow would occur. If an overflow would have occurred then the wrapped value is returned. #### Signature ```rust theme={null} fn overflowing_mul(self: T, v: T) -> (T, bool) ``` # core::num::traits::ops::pow::Pow Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-pow-Pow Raises a value to the power of `exp`. Note that `0⁰` (`pow(0, 0)`) returns `1`. Mathematically this is undefined. ## Signature ```rust theme={null} pub trait Pow ``` #### Panics Panics if the result of the exponentiation operation overflows the output type. ## Examples ```rust theme={null} use core::num::traits::Pow; assert!(2_i8.pow(4_usize) == 16_i8); assert!(6_u8.pow(3_usize) == 216_u8); assert!(0_u8.pow(0_usize) == 1_u8); ``` ## Trait functions ### pow Returns `self` to the power `exp`. #### Signature ```rust theme={null} fn pow(self: Base, exp: Exp) -> PowOutput ``` ## Trait types ### Output The type of the result of the power calculation. #### Signature ```rust theme={null} type Output; ``` # core::num::traits::ops::saturating::SaturatingAdd Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-saturating-SaturatingAdd Performs addition that saturates at the numeric bounds instead of overflowing. ## Signature ```rust theme={null} pub trait SaturatingAdd ``` ## Examples ```rust theme={null} use core::num::traits::SaturatingAdd; assert!(255_u8.saturating_add(1_u8) == 255); ``` ## Trait functions ### saturating\_add Saturating addition. Computes `self + other`, saturating at the relevant high or low boundary of the type. #### Signature ```rust theme={null} fn saturating_add(self: T, other: T) -> T ``` # core::num::traits::ops::saturating::SaturatingMul Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-saturating-SaturatingMul Performs multiplication that saturates at the numeric bounds instead of overflowing. ## Signature ```rust theme={null} pub trait SaturatingMul ``` ## Examples ```rust theme={null} use core::num::traits::SaturatingMul; assert!(100_u8.saturating_mul(3_u8) == 255); ``` ## Trait functions ### saturating\_mul Saturating multiplication. Computes `self * other`, saturating at the relevant high or low boundary of the type. #### Signature ```rust theme={null} fn saturating_mul(self: T, other: T) -> T ``` # core::num::traits::ops::saturating::SaturatingSub Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-saturating-SaturatingSub Performs subtraction that saturates at the numeric bounds instead of overflowing. ## Signature ```rust theme={null} pub trait SaturatingSub ``` ## Examples ```rust theme={null} use core::num::traits::SaturatingSub; assert!(1_u8.saturating_sub(2_u8) == 0); ``` ## Trait functions ### saturating\_sub Saturating subtraction. Computes `self - other`, saturating at the relevant high or low boundary of the type. #### Signature ```rust theme={null} fn saturating_sub(self: T, other: T) -> T ``` # core::num::traits::ops::sqrt::Sqrt Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-sqrt-Sqrt A trait for computing the square root of a number. ## Signature ```rust theme={null} pub trait Sqrt ``` ## Examples ```rust theme={null} use core::num::traits::Sqrt; assert!(9_u8.sqrt() == 3); ``` ## Trait functions ### sqrt Computes the square root of a number. #### Signature ```rust theme={null} fn sqrt(self: T) -> SqrtTarget ``` ## Trait types ### Target The type of the result of the square root operation. #### Signature ```rust theme={null} type Target; ``` # core::num::traits::ops::widesquare::WideSquare Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-widesquare-WideSquare A trait for a type that can be squared to produce a wider type. This trait enables squaring operations where the result type has double the bit width of the input type, preventing overflow in cases where the result would exceed the input type's maximum value. ## Signature ```rust theme={null} pub trait WideSquare ``` ## Examples ```rust theme={null} use core::num::traits::WideSquare; let a: u8 = 16; let result: u16 = a.wide_square(); assert!(result == 256); ``` ## Trait functions ### wide\_square Calculates the square, producing a wider type. #### Signature ```rust theme={null} fn wide_square(self: T) -> WideSquareTarget ``` ## Trait types ### Target The type of the result of the square. #### Signature ```rust theme={null} type Target; ``` # core::num::traits::ops::wrapping::WrappingAdd Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-wrapping-WrappingAdd Performs addition that wraps around on overflow. ## Signature ```rust theme={null} pub trait WrappingAdd ``` ## Examples ```rust theme={null} use core::num::traits::WrappingAdd; let result = 255_u8.wrapping_add(1); assert!(result == 0); let result = 100_u8.wrapping_add(200); assert!(result == 44); // (100 + 200) % 256 = 44 ``` ## Trait functions ### wrapping\_add Wrapping (modular) addition. Computes `self + other`, wrapping around at the boundary of the type. #### Signature ```rust theme={null} fn wrapping_add(self: T, v: T) -> T ``` # core::num::traits::ops::wrapping::WrappingMul Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-wrapping-WrappingMul Performs multiplication that wraps around on overflow. ## Signature ```rust theme={null} pub trait WrappingMul ``` ## Examples ```rust theme={null} use core::num::traits::WrappingMul; let result = 10_u8.wrapping_mul(30); assert!(result == 44); // (10 * 30) % 256 = 44 let result = 200_u8.wrapping_mul(2); assert!(result == 144); // (200 * 2) % 256 = 144 ``` ## Trait functions ### wrapping\_mul Wrapping (modular) multiplication. Computes `self * other`, wrapping around at the boundary of the type. #### Signature ```rust theme={null} fn wrapping_mul(self: T, v: T) -> T ``` # core::num::traits::ops::wrapping::WrappingSub Source: https://docs.starknet.io/build/corelib/core-num-traits-ops-wrapping-WrappingSub Performs subtraction that wraps around on overflow. ## Signature ```rust theme={null} pub trait WrappingSub ``` ## Examples ```rust theme={null} use core::num::traits::WrappingSub; let result = 0_u8.wrapping_sub(1); assert!(result == 255); let result = 100_u8.wrapping_sub(150); assert!(result == 206); ``` ## Trait functions ### wrapping\_sub Wrapping (modular) subtraction. Computes `self - other`, wrapping around at the boundary of the type. #### Signature ```rust theme={null} fn wrapping_sub(self: T, v: T) -> T ``` # core::num::traits::zero::Zero Source: https://docs.starknet.io/build/corelib/core-num-traits-zero-Zero Defines an additive identity element for `T`. ## Signature ```rust theme={null} pub trait Zero ``` # Laws ```text theme={null} a + 0 = a ∀ a ∈ T 0 + a = a ∀ a ∈ T ``` ## Trait functions ### zero Returns the additive identity element of `T`, `0`. #### Signature ```rust theme={null} fn zero() -> T ``` #### Examples ```rust theme={null} use core::num::traits::Zero; assert!(Zero::::zero() == 0); ``` ### is\_zero Returns true if `self` is equal to the additive identity. #### Signature ```rust theme={null} fn is_zero(self: @T) -> bool ``` #### Examples ```rust theme={null} use core::num::traits::Zero; assert!(0.is_zero()); assert!(!5.is_zero()); ``` ### is\_non\_zero Returns false if `self` is equal to the additive identity. #### Signature ```rust theme={null} fn is_non_zero(self: @T) -> bool ``` #### Examples ```rust theme={null} use core::num::traits::Zero; assert!(5.is_non_zero()); assert!(!0.is_non_zero()); ``` # core::ops::arith::AddAssign Source: https://docs.starknet.io/build/corelib/core-ops-arith-AddAssign The addition assignment operator `+=`. ## Signature ```rust theme={null} pub trait AddAssign ``` ## Trait functions ### add\_assign Performs the `+=` operation. #### Signature ```rust theme={null} fn add_assign(ref self: Lhs, rhs: Rhs) ``` #### Examples ```rust theme={null} let mut x: u8 = 3; x += x; assert!(x == 6); ``` # core::ops::arith::DivAssign Source: https://docs.starknet.io/build/corelib/core-ops-arith-DivAssign The division assignment operator `/=`. ## Signature ```rust theme={null} pub trait DivAssign ``` ## Trait functions ### div\_assign Performs the `/=` operation. #### Signature ```rust theme={null} fn div_assign(ref self: Lhs, rhs: Rhs) ``` #### Examples ```rust theme={null} let mut x: u8 = 3; x /= x; assert!(x == 1); ``` # core::ops::arith::MulAssign Source: https://docs.starknet.io/build/corelib/core-ops-arith-MulAssign The multiplication assignment operator `*=`. ## Signature ```rust theme={null} pub trait MulAssign ``` ## Trait functions ### mul\_assign Performs the `*=` operation. #### Signature ```rust theme={null} fn mul_assign(ref self: Lhs, rhs: Rhs) ``` #### Examples ```rust theme={null} let mut x: u8 = 3; x *= x; assert!(x == 9); ``` # core::ops::arith::RemAssign Source: https://docs.starknet.io/build/corelib/core-ops-arith-RemAssign The remainder assignment operator `%=`. ## Signature ```rust theme={null} pub trait RemAssign ``` ## Trait functions ### rem\_assign Performs the `%=` operation. #### Signature ```rust theme={null} fn rem_assign(ref self: Lhs, rhs: Rhs) ``` #### Examples ```rust theme={null} let mut x: u8 = 3; x %= x; assert!(x == 0); ``` # core::ops::arith::SubAssign Source: https://docs.starknet.io/build/corelib/core-ops-arith-SubAssign The subtraction assignment operator `-=`. ## Signature ```rust theme={null} pub trait SubAssign ``` ## Trait functions ### sub\_assign Performs the `-=` operation. #### Signature ```rust theme={null} fn sub_assign(ref self: Lhs, rhs: Rhs) ``` #### Examples ```rust theme={null} let mut x: u8 = 3; x -= x; assert!(x == 0); ``` # core::ops::deref::Deref Source: https://docs.starknet.io/build/corelib/core-ops-deref-Deref A trait for dereferencing a value to provide transparent access to its contents. Implementing this trait allows a type to behave like its inner type, enabling direct access to the inner type's fields. Note: The `Deref` mechanism is limited and cannot be used to implicitly convert a type to its target type when passing arguments to functions. For example, if you have a function that takes an `Inner`, you cannot pass an `Outer` to it even if `Outer` implements `Deref`. ## Signature ```rust theme={null} pub trait Deref ``` ## Examples ```rust theme={null} struct Wrapper { inner: T } impl WrapperDeref of Deref> { type Target = T; fn deref(self: Wrapper) -> T { self.inner } } let wrapped = Wrapper { inner: 42 }; assert!(wrapped.deref() == 42); ``` ## Trait functions ### deref Returns the dereferenced value. #### Signature ```rust theme={null} fn deref(self: T) -> DerefTarget ``` ## Trait types ### Target The type of the dereferenced value. #### Signature ```rust theme={null} type Target; ``` # core::ops::deref::DerefMut Source: https://docs.starknet.io/build/corelib/core-ops-deref-DerefMut A trait for dereferencing in mutable contexts. This trait is similar to `Deref` but specifically handles cases where the value accessed is mutable. Despite its name, `DerefMut` does NOT allow modifying the inner value - it only indicates that the container itself is mutable. ## Signature ```rust theme={null} pub trait DerefMut ``` ## Examples ```rust theme={null} #[derive(Copy, Drop)] struct MutWrapper { value: T } impl MutWrapperDerefMut> of DerefMut> { type Target = T; fn deref_mut(ref self: MutWrapper) -> T { self.value } } // This will work since x is mutable let mut x = MutWrapper { value: 42 }; let val = x.deref_mut(); assert!(val == 42); // This would fail to compile since y is not mutable let y = MutWrapper { value: 42 }; let val = y.deref_mut(); // Compile error ``` ## Trait functions ### deref\_mut Returns the dereferenced value. #### Signature ```rust theme={null} fn deref_mut(ref self: T) -> DerefMutTarget ``` ## Trait types ### Target The type of the dereferenced value. #### Signature ```rust theme={null} type Target; ``` # core::ops::function::Fn Source: https://docs.starknet.io/build/corelib/core-ops-function-Fn The version of the call operator that takes a by-snapshot receiver. Instances of `Fn` can be called repeatedly. `Fn` is implemented automatically by closures whose captured variables are all `Copy`. Additionally, for any type `F` that implements `Fn`, `@F` implements `Fn`, too. Since [`FnOnce`](./core-ops-function-FnOnce) is implemented for all implementers of `Fn`, any instance of `Fn` can be used as a parameter where a [`FnOnce`](./core-ops-function-FnOnce) is expected. Use `Fn` as a bound when you want to accept a parameter of function-like type and need to call it repeatedly. If you do not need such strict requirements, use [`FnOnce`](./core-ops-function-FnOnce) as bounds. ## Signature ```rust theme={null} pub trait Fn ``` ## Examples ## Calling a closure ```rust theme={null} let square = |x| x * x; assert_eq!(square(5), 25); ``` ## Using a `Fn` parameter ```rust theme={null} fn call_with_one, +core::ops::Fn[Output: usize]>(func: F) -> usize { func(1) } let double = |x| x * 2; assert_eq!(call_with_one(double), 2); ``` ## Trait functions ### call Performs the call operation. #### Signature ```rust theme={null} fn call(self: @T, args: Args) -> FnOutput ``` ## Trait types ### Output The returned type after the call operator is used. #### Signature ```rust theme={null} type Output; ``` # core::ops::function::FnOnce Source: https://docs.starknet.io/build/corelib/core-ops-function-FnOnce The version of the call operator that takes a by-value receiver. Instances of `FnOnce` can be called, but might not be callable multiple times. Because of this, if the only thing known about a type is that it implements `FnOnce`, it can only be called once. `FnOnce` is implemented automatically by closures that might consume captured variables. ## Signature ```rust theme={null} pub trait FnOnce ``` ## Examples ```rust theme={null} fn consume_with_relish, +core::ops::FnOnce[Output: O], +core::fmt::Display, +Drop, >( func: F, ) { // `func` consumes its captured variables, so it cannot be run more // than once. println!("Consumed: {}", func()); println!("Delicious!"); // Attempting to invoke `func()` again will throw a `Variable was previously moved.` // error for `func`. } let x: ByteArray = "x"; let consume_and_return_x = || x; consume_with_relish(consume_and_return_x); // `consume_and_return_x` can no longer be invoked at this point ``` ## Trait functions ### call Performs the call operation. #### Signature ```rust theme={null} fn call(self: T, args: Args) -> FnOnceOutput ``` ## Trait types ### Output The returned type after the call operator is used. #### Signature ```rust theme={null} type Output; ``` # core::ops::index::Index Source: https://docs.starknet.io/build/corelib/core-ops-index-Index A trait for indexing operations (`container[index]`) where the input type is mutated. This trait should be implemented when you want to implement indexing operations on a type that's mutated by a read access. This is useful for any type depending on a [`Felt252Dict`](./core-dict-Felt252Dict), where dictionary accesses are modifying the data structure itself. `container[index]` is syntactic sugar for `container.index(index)`. ## Signature ```rust theme={null} pub trait Index ``` ## Examples The following example implements `Index` on a `Stack` type. This `Stack` is implemented based on a [`Felt252Dict`](./core-dict-Felt252Dict), where dictionary accesses are modifying the dictionary itself. As such, we must implement the `Index` trait instead of the `IndexView` trait. ```rust theme={null} use core::ops::Index; #[derive(Destruct, Default)] struct Stack { items: Felt252Dict, len: usize } #[generate_trait] impl StackImpl of StackTrait { fn push(ref self: Stack, item: u128) { self.items.insert(self.len.into(), item); self.len += 1; } } impl StackIndex of Index { type Target = u128; fn index(ref self: Stack, index: usize) -> Self::Target { if index >= self.len { panic!("Index out of bounds"); } self.items.get(index.into()) } } let mut stack: Stack = Default::default(); stack.push(1); assert!(stack[0] == 1); ``` ## Trait functions ### index Performs the indexing (`container[index]`) operation. #### Panics May panic if the index is out of bounds. #### Signature ```rust theme={null} fn index(ref self: C, index: I) -> IndexTarget ``` ## Trait types ### Target The returned type after indexing. #### Signature ```rust theme={null} type Target; ``` # core::ops::index::IndexView Source: https://docs.starknet.io/build/corelib/core-ops-index-IndexView A trait for indexing operations (`container[index]`) where the input type is not modified. `container[index]` is syntactic sugar for `container.index(index)`. ## Signature ```rust theme={null} pub trait IndexView ``` ## Examples The following example implements `IndexView` on a `NucleotideCount` container, which can be indexed without modifying the input, enabling individual counts to be retrieved with index syntax. ```rust theme={null} use core::ops::IndexView; #[derive(Copy, Drop)] enum Nucleotide { A, C, G, T, } #[derive(Copy, Drop)] struct NucleotideCount { a: usize, c: usize, g: usize, t: usize, } impl NucleotideIndex of IndexView { type Target = usize; fn index(self: @NucleotideCount, index: Nucleotide) -> Self::Target { match index { Nucleotide::A => *self.a, Nucleotide::C => *self.c, Nucleotide::G => *self.g, Nucleotide::T => *self.t, } } } let nucleotide_count = NucleotideCount {a: 14, c: 9, g: 10, t: 12}; assert!(nucleotide_count[Nucleotide::A] == 14); assert!(nucleotide_count[Nucleotide::C] == 9); assert!(nucleotide_count[Nucleotide::G] == 10); assert!(nucleotide_count[Nucleotide::T] == 12); ``` ## Trait functions ### index Performs the indexing (`container[index]`) operation. #### Panics May panic if the index is out of bounds. #### Signature ```rust theme={null} fn index(self: @C, index: I) -> IndexViewTarget ``` ## Trait types ### Target The returned type after indexing. #### Signature ```rust theme={null} type Target; ``` # core::ops::range::Range Source: https://docs.starknet.io/build/corelib/core-ops-range-Range A (half-open) range bounded inclusively below and exclusively above (`start..end`). The range `start..end` contains all values with `start = end`. ## Signature ```rust theme={null} #[derive(Clone, Drop, PartialEq)] pub struct Range { pub start: T, pub end: T, } ``` ## Examples The `start..end` syntax is a `Range`: ```rust theme={null} assert!((3..5) == core::ops::Range { start: 3, end: 5 }); let mut sum = 0; for i in 3..6 { sum += i; } assert!(sum == 3 + 4 + 5); ``` ## Members ### start The lower bound of the range (inclusive). #### Signature ```rust theme={null} pub start: T ``` ### end The upper bound of the range (exclusive). #### Signature ```rust theme={null} pub end: T ``` # core::ops::range::RangeInclusive Source: https://docs.starknet.io/build/corelib/core-ops-range-RangeInclusive Represents the range start, end. ## Signature ```rust theme={null} #[derive(Clone, Drop, PartialEq)] pub struct RangeInclusive { pub start: T, pub end: T, } ``` ## Members ### start The lower bound of the range (inclusive). #### Signature ```rust theme={null} pub start: T ``` ### end The upper bound of the range (inclusive). #### Signature ```rust theme={null} pub end: T ``` # core::ops::range::RangeInclusiveIterator Source: https://docs.starknet.io/build/corelib/core-ops-range-RangeInclusiveIterator ## Signature ```rust theme={null} #[derive(Clone, Drop)] pub struct RangeInclusiveIterator { pub(crate) cur: T, pub(crate) end: T, pub(crate) exhausted: bool, } ``` # core::ops::range::RangeInclusiveTrait Source: https://docs.starknet.io/build/corelib/core-ops-range-RangeInclusiveTrait ## Signature ```rust theme={null} pub trait RangeInclusiveTrait+PartialOrd> ``` ## Trait functions ### contains Returns `true` if `item` is contained in the range. #### Signature ```rust theme={null} fn contains, +PartialOrd, T, +Destruct, +PartialOrd>( self: @RangeInclusive, item: @T, ) -> bool ``` #### Examples ```rust theme={null} assert!(!(3..=5).contains(@2)); assert!( (3..=5).contains(@3)); assert!( (3..=5).contains(@4)); assert!( (3..=5).contains(@5)); assert!(!(3..=5).contains(@6)); assert!( (3..=3).contains(@3)); assert!(!(3..=2).contains(@3)); ``` ### is\_empty Returns `true` if the range contains no items. #### Signature ```rust theme={null} fn is_empty, +PartialOrd, T, +Destruct, +PartialOrd>( self: @RangeInclusive, ) -> bool ``` #### Examples ```rust theme={null} assert!(!(3_u8..=5_u8).is_empty()); assert!(!(3_u8..=3_u8).is_empty()); assert!( (3_u8..=2_u8).is_empty()); ``` # core::ops::range::RangeIterator Source: https://docs.starknet.io/build/corelib/core-ops-range-RangeIterator Represents an iterator located at `cur`, whose end is `end` (`cur < end`). ```rust theme={null} struct RangeIterator { cur: T, end: T, } ``` # core::ops::range::RangeTrait Source: https://docs.starknet.io/build/corelib/core-ops-range-RangeTrait ## Signature ```rust theme={null} pub trait RangeTrait+PartialOrd> ``` ## Trait functions ### contains Returns `true` if `item` is contained in the range. #### Signature ```rust theme={null} fn contains, +PartialOrd, T, +Destruct, +PartialOrd>( self: @Range, item: @T, ) -> bool ``` #### Examples ```rust theme={null} assert!(!(3..5).contains(@2)); assert!( (3..5).contains(@3)); assert!( (3..5).contains(@4)); assert!(!(3..5).contains(@5)); assert!(!(3..3).contains(@3)); assert!(!(3..2).contains(@3)); ``` ### is\_empty Returns `true` if the range contains no items. #### Signature ```rust theme={null} fn is_empty, +PartialOrd, T, +Destruct, +PartialOrd>( self: @Range, ) -> bool ``` #### Examples ```rust theme={null} assert!(!(3_u8..5_u8).is_empty()); assert!( (3_u8..3_u8).is_empty()); assert!( (3_u8..2_u8).is_empty()); ``` # core::option::Option Source: https://docs.starknet.io/build/corelib/core-option-Option The `Option` enum representing either `Some(value)` or `None`. ## Signature ```rust theme={null} pub enum Option { Some: T, None, } ``` ## Variants ### Some #### Signature ```rust theme={null} Some: T ``` ### None #### Signature ```rust theme={null} None ``` # core::option::OptionTrait Source: https://docs.starknet.io/build/corelib/core-option-OptionTrait A trait for handling `Option` related operations. ## Signature ```rust theme={null} pub trait OptionTrait ``` ## Trait functions ### expect Returns the contained `Some` value, consuming the `self` value. #### Signature ```rust theme={null} fn expect(self: Option, err: felt252) -> T ``` #### Panics Panics if the option value is `None` with a custom `felt252` panic message `err`. #### Examples ```rust theme={null} let option = Some(123); let value = option.expect('no value'); assert!(value == 123); ``` ### unwrap Returns the contained `Some` value, consuming the `self` value. #### Signature ```rust theme={null} fn unwrap(self: Option) -> T ``` #### Panics Panics if the `self` value equals `None`. #### Examples ```rust theme={null} let option = Some(123); let value = option.unwrap(); assert!(value == 123); ``` ### ok\_or Transforms the `Option` into a `Result`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err)`. #### Signature ```rust theme={null} fn ok_or>(self: Option, err: E) -> Result ``` #### Examples ```rust theme={null} assert_eq!(Some('foo').ok_or(0), Ok('foo')); let option: Option = None; assert_eq!(option.ok_or(0), Err(0)); ``` ### ok\_or\_else Transforms the `Option` into a `Result`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err())`. #### Signature ```rust theme={null} fn ok_or_else, +core::ops::FnOnce[Output: E], +Drop>( self: Option, err: F, ) -> Result ``` #### Examples ```rust theme={null} assert_eq!(Some('foo').ok_or_else(|| 0), Ok('foo')); let option: Option = None; assert_eq!(option.ok_or_else(|| 0), Err(0)); ``` ### and Returns [`None`](./core-option#none) if the option is [`None`](./core-option#none), otherwise returns `optb`. Arguments passed to `and` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `and_then`, which is lazily evaluated. #### Signature ```rust theme={null} fn and, +Drop>(self: Option, optb: Option) -> Option ``` #### Examples ```rust theme={null} let x = Some(2); let y: Option = None; assert_eq!(x.and(y), None); let x: Option = None; let y: Option = Some("foo"); assert_eq!(x.and(y), None); let x = Some(2); let y: Option = Some("foo"); assert_eq!(x.and(y), Some("foo")); let x: Option = None; let y: Option = None; assert_eq!(x.and(y), None); ``` ### and\_then Returns [`None`](./core-option#none) if the option is [`None`](./core-option#none), otherwise calls `f` with the wrapped value and returns the result. Some languages call this operation flatmap. #### Signature ```rust theme={null} fn and_then, +core::ops::FnOnce[Output: Option]>( self: Option, f: F, ) -> Option ``` #### Examples ```rust theme={null} use core::num::traits::CheckedMul; let option: Option = checked_mul(2_u32, 2_u32) .and_then(|v| Some(format!("{}", v))); assert_eq!(option, Some("4")); let option: Option = checked_mul(65536_u32, 65536_u32) .and_then(|v| Some(format!("{}", v))); assert_eq!(option, None); // overflowed! let option: Option = Option::::None .and_then(|v| Some(format!("{}", v))); assert_eq!(option, None); ``` ### or Returns the option if it contains a value, otherwise returns `optb`. Arguments passed to `or` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `or_else`, which is lazily evaluated. #### Signature ```rust theme={null} fn or>(self: Option, optb: Option) -> Option ``` #### Examples ```rust theme={null} let x = Some(2); let y = None; assert_eq!(x.or(y), Some(2)); let x = None; let y = Some(100); assert_eq!(x.or(y), Some(100)); let x = Some(2); let y = Some(100); assert_eq!(x.or(y), Some(2)); let x: Option = None; let y = None; assert_eq!(x.or(y), None); ``` ### or\_else Returns the option if it contains a value, otherwise calls `f` and returns the result. #### Signature ```rust theme={null} fn or_else, +core::ops::FnOnce[Output: Option]>( self: Option, f: F, ) -> Option ``` #### Examples ```rust theme={null} let nobody = || Option::::None; let vikings = || Option::::Some("vikings"); assert_eq!(Some("barbarians").or_else(vikings), Some("barbarians")); assert_eq!(None.or_else(vikings), Some("vikings")); assert_eq!(None.or_else(nobody), None); ``` ### xor Returns [`Some`](./core-option#some) if exactly one of `self`, `optb` is [`Some`](./core-option#some), otherwise returns [`None`](./core-option#none). #### Signature ```rust theme={null} fn xor>(self: Option, optb: Option) -> Option ``` #### Examples ```rust theme={null} let x = Some(2); let y: Option = None; assert_eq!(x.xor(y), Some(2)); let x: Option = None; let y = Some(2); assert_eq!(x.xor(y), Some(2)); let x = Some(2); let y = Some(2); assert_eq!(x.xor(y), None); let x: Option = None; let y: Option = None; assert_eq!(x.xor(y), None); ``` ### is\_some Returns `true` if the `Option` is `Some`, `false` otherwise. #### Signature ```rust theme={null} fn is_some(self: @Option) -> bool ``` #### Examples ```rust theme={null} let option = Some(123); assert!(option.is_some()); ``` ### is\_some\_and Returns `true` if the `Option` is `Some` and the value inside of it matches a predicate. #### Signature ```rust theme={null} fn is_some_and, +core::ops::FnOnce[Output: bool]>( self: Option, f: F, ) -> bool ``` #### Examples ```rust theme={null} assert_eq!(Some(2_u8).is_some_and(|x| x > 1), true); assert_eq!(Some(0_u8).is_some_and(|x| x > 1), false); let option: Option = None; assert_eq!(option.is_some_and(|x| x > 1), false); ``` ### is\_none Returns `true` if the `Option` is `None`, `false` otherwise. #### Signature ```rust theme={null} fn is_none(self: @Option) -> bool ``` #### Examples ```rust theme={null} let option = Some(123); assert!(!option.is_none()); ``` ### is\_none\_or Returns `true` if the `Option` is `None` or the value inside of it matches a predicate. #### Signature ```rust theme={null} fn is_none_or, +core::ops::FnOnce[Output: bool]>( self: Option, f: F, ) -> bool ``` #### Examples ```rust theme={null} assert_eq!(Some(2_u8).is_none_or(|x| x > 1), true); assert_eq!(Some(0_u8).is_none_or(|x| x > 1), false); let option: Option = None; assert_eq!(option.is_none_or(|x| x > 1), true); ``` ### unwrap\_or Returns the contained `Some` value if `self` is `Some(x)`. Otherwise, returns the provided default. #### Signature ```rust theme={null} fn unwrap_or>(self: Option, default: T) -> T ``` #### Examples ```rust theme={null} let option = Some(123); assert!(option.unwrap_or(456) == 123); let option = None; assert!(option.unwrap_or(456) == 456); ``` ### unwrap\_or\_default Returns the contained `Some` value if `self` is `Some(x)`. Otherwise, returns `Default::::default()`. #### Signature ```rust theme={null} fn unwrap_or_default>(self: Option) -> T ``` #### Examples ```rust theme={null} let option = Some(123); assert!(option.unwrap_or_default() == 123); let option: Option = None; assert!(option.unwrap_or_default() == Default::default()); ``` ### unwrap\_or\_else Returns the contained [`Some`](./core-option#some) value or computes it from a closure. #### Signature ```rust theme={null} fn unwrap_or_else, impl func: FnOnce, +Drop>( self: Option, f: F, ) -> T ``` #### Examples ```rust theme={null} let k = 10; assert!(Some(4).unwrap_or_else(|| 2 * k) == 4); assert!(None.unwrap_or_else(|| 2 * k) == 20); ``` ### map Maps an `Option` to `Option` by applying a function to a contained value (if `Some`) or returns `None` (if `None`). #### Signature ```rust theme={null} fn map, +core::ops::FnOnce[Output: U]>( self: Option, f: F, ) -> Option ``` #### Examples ```rust theme={null} let maybe_some_string: Option = Some("Hello, World!"); // `Option::map` takes self *by value*, consuming `maybe_some_string` let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len()); assert!(maybe_some_len == Some(13)); let x: Option = None; assert!(x.map(|s: ByteArray| s.len()) == None); ``` ### map\_or Returns the provided default result (if none), or applies a function to the contained value (if any). Arguments passed to `map_or` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `map_or_else`, which is lazily evaluated. #### Signature ```rust theme={null} fn map_or, +Drop, +core::ops::FnOnce[Output: U]>( self: Option, default: U, f: F, ) -> U ``` #### Examples ```rust theme={null} assert_eq!(Some("foo").map_or(42, |v: ByteArray| v.len()), 3); let x: Option = None; assert_eq!(x.map_or(42, |v: ByteArray| v.len()), 42); ``` ### map\_or\_else Computes a default function result (if none), or applies a different function to the contained value (if any). #### Signature ```rust theme={null} fn map_or_else, +Drop, +Drop, +core::ops::FnOnce[Output: U], +core::ops::FnOnce[Output: U], >( self: Option, default: D, f: F, ) -> U ``` #### Examples ```rust theme={null} let k = 21; let x = Some("foo"); assert_eq!(x.map_or_else( || 2 * k, |v: ByteArray| v.len()), 3); let x: Option = None; assert_eq!(x.map_or_else( || 2 * k, |v: ByteArray| v.len()), 42); ``` ### take Takes the value out of the option, leaving a [`None`](./core-option#none) in its place. #### Signature ```rust theme={null} fn take(ref self: Option) -> Option ``` #### Examples ```rust theme={null} let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2)); let mut x: Option = None; let y = x.take(); assert_eq!(x, None); assert_eq!(y, None); ``` ### filter Returns [`None`](./core-option#none) if the option is [`None`](./core-option#none), otherwise calls `predicate` with the wrapped value and returns: * `Some(t)` if `predicate` returns `true` (where `t` is the wrapped value), and * [`None`](./core-option#none) if `predicate` returns `false`. #### Signature ```rust theme={null} fn filter[Output: bool], +Destruct, +Destruct>( self: Option, predicate: P, ) -> Option ``` #### Examples ```rust theme={null} let is_even = |n: @u32| -> bool { *n % 2 == 0 }; assert_eq!(None.filter(is_even), None); assert_eq!(Some(3).filter(is_even), None); assert_eq!(Some(4).filter(is_even), Some(4)); ``` ### flatten Converts from `Option>` to `Option`. #### Signature ```rust theme={null} fn flatten(self: Option>) -> Option ``` #### Examples Basic usage: ```rust theme={null} let x: Option> = Some(Some(6)); assert_eq!(Some(6), x.flatten()); let x: Option> = Some(None); assert_eq!(None, x.flatten()); let x: Option> = None; assert_eq!(None, x.flatten()); ``` Flattening only removes one level of nesting at a time: ```rust theme={null} let x: Option>> = Some(Some(Some(6))); assert_eq!(Some(Some(6)), x.flatten()); assert_eq!(Some(6), x.flatten().flatten()); ``` # core::panic_with_const_felt252 Source: https://docs.starknet.io/build/corelib/core-panic_with_const_felt252 Panics with the given const argument `felt252` as error message. ## Signature ```rust theme={null} pub fn panic_with_const_felt252() -> never ``` ## Examples ```rust theme={null} use core::panic_with_const_felt252; panic_with_const_felt252::(); ``` # core::panic_with_felt252 Source: https://docs.starknet.io/build/corelib/core-panic_with_felt252 Panics with the given `felt252` as error message. ## Signature ```rust theme={null} pub fn panic_with_felt252(err_code: felt252) -> never ``` ## Examples ```rust theme={null} use core::panic_with_felt252; panic_with_felt252('error message'); ``` # panic Source: https://docs.starknet.io/build/corelib/core-panics-Panic ## panic ```rust theme={null} extern fn panic(data: Array) -> never; ``` Triggers an immediate panic with the provided data and terminates execution. This function is the core panic mechanism in Cairo. When called, it: 1. Takes an array of `felt252` values as panic data 2. Terminates the current execution 3. Propagates the panic data up the call stack ### Parameters * `data: Array` - The panic data to be included with the panic ### Returns * `never` - This function never returns as it terminates execution ### Example ```rust theme={null} use core::panics::panic; // Panic with custom data panic(array!['Error code', 42, 'Additional info']); ``` ### Related Functions * [`panic_with_byte_array`](./core-panics-panic_with_byte_array) - Panic with a `ByteArray` message * [`panic!` macro](./core-panic_with_felt252) - Convenient macro for panicking with messages # core::panics::PanicResult Source: https://docs.starknet.io/build/corelib/core-panics-PanicResult Result type for operations that can trigger a panic. ## Signature ```rust theme={null} pub enum PanicResult { Ok: T, Err: (Panic, Array), } ``` ## Variants ### Ok #### Signature ```rust theme={null} Ok: T ``` ### Err #### Signature ```rust theme={null} Err: (Panic, Array) ``` # core::panics::panic_with_byte_array Source: https://docs.starknet.io/build/corelib/core-panics-panic_with_byte_array Panics with a `ByteArray` message. Constructs a panic message by prepending the `BYTE_ARRAY_MAGIC` value and serializing the provided `ByteArray` into the panic data. ## Signature ```rust theme={null} pub fn panic_with_byte_array(err: ByteArray) -> never ``` ## Examples ```rust theme={null} use core::panics::panic_with_byte_array; let error_msg = "An error occurred"; panic_with_byte_array(@error_msg); ``` # core::pedersen::HashState Source: https://docs.starknet.io/build/corelib/core-pedersen-HashState Represents the current state of a Pedersen hash computation. The state is maintained as a single `felt252` value, which is updated through the `HashStateTrait::finalize` method. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug)] pub struct HashState { pub state: felt252, } ``` ## Members ### state The current hash state #### Signature ```rust theme={null} pub state: felt252 ``` # pedersen Source: https://docs.starknet.io/build/corelib/core-pedersen-Pedersen ## pedersen ```rust theme={null} extern fn pedersen(a: felt252, b: felt252) -> felt252; ``` Computes the Pedersen hash of two `felt252` values. The Pedersen hash function is a collision-resistant cryptographic hash function that takes two field elements as input and produces a single field element as output. ### Parameters * `a: felt252` - The first input value * `b: felt252` - The second input value ### Returns * `felt252` - The Pedersen hash of the two input values ### Example ```rust theme={null} use core::pedersen::pedersen; let hash = pedersen(1, 2); // Result: hash value as felt252 ``` ### Related * [`HashState`](./core-pedersen-HashState) - For computing Pedersen hashes of multiple values * [`PedersenTrait`](./core-pedersen-PedersenTrait) - Trait for creating new hash states # core::pedersen::PedersenImpl Source: https://docs.starknet.io/build/corelib/core-pedersen-PedersenImpl A trait for creating a new Pedersen hash state. ## Signature ```rust theme={null} pub impl PedersenImpl of PedersenTrait; ``` ## Impl functions ### new Creates a new Pedersen hash state with the given base value. #### Signature ```rust theme={null} fn new(base: felt252) -> HashState ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; let mut state = PedersenTrait::new(0); assert!(state.state == 0); ``` # core::pedersen::PedersenTrait Source: https://docs.starknet.io/build/corelib/core-pedersen-PedersenTrait ## Signature ```rust theme={null} pub trait PedersenTrait ``` ## Trait functions ### new Creates a new Pedersen hash state with the given base value. #### Signature ```rust theme={null} fn new(base: felt252) -> HashState ``` #### Examples ```rust theme={null} use core::pedersen::PedersenTrait; let mut state = PedersenTrait::new(0); assert!(state.state == 0); ``` # core::poseidon::HashState Source: https://docs.starknet.io/build/corelib/core-poseidon-HashState State for Poseidon hash. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug)] pub struct HashState { pub s0: felt252, pub s1: felt252, pub s2: felt252, pub odd: bool, } ``` ## Members ### s0 #### Signature ```rust theme={null} pub s0: felt252 ``` ### s1 #### Signature ```rust theme={null} pub s1: felt252 ``` ### s2 #### Signature ```rust theme={null} pub s2: felt252 ``` ### odd #### Signature ```rust theme={null} pub odd: bool ``` # core::poseidon::Poseidon Source: https://docs.starknet.io/build/corelib/core-poseidon-Poseidon ## Signature ```rust theme={null} pub extern type Poseidon; ``` # core::poseidon::PoseidonImpl Source: https://docs.starknet.io/build/corelib/core-poseidon-PoseidonImpl A trait for creating a new Poseidon hash state. ## Signature ```rust theme={null} pub impl PoseidonImpl of PoseidonTrait; ``` ## Impl functions ### new Creates an initial state with all fields set to 0. #### Signature ```rust theme={null} fn new() -> HashState ``` #### Examples ```rust theme={null} use core::poseidon::PoseidonTrait; let mut state = PoseidonTrait::new(); ``` # core::poseidon::PoseidonTrait Source: https://docs.starknet.io/build/corelib/core-poseidon-PoseidonTrait ## Signature ```rust theme={null} pub trait PoseidonTrait ``` ## Trait functions ### new Creates an initial state with all fields set to 0. #### Signature ```rust theme={null} fn new() -> HashState ``` #### Examples ```rust theme={null} use core::poseidon::PoseidonTrait; let mut state = PoseidonTrait::new(); ``` # core::poseidon::hades_permutation Source: https://docs.starknet.io/build/corelib/core-poseidon-hades_permutation ## Signature ```rust theme={null} pub extern fn hades_permutation(s0: felt252, s1: felt252, s2: felt252) -> (felt252, felt252, felt252) implicits(Poseidon) nopanic; ``` # core::poseidon::poseidon_hash_span Source: https://docs.starknet.io/build/corelib/core-poseidon-poseidon_hash_span Computes the Poseidon hash on the given span input. Applies the sponge construction to digest many elements. To distinguish between use cases, the capacity element is initialized to 0. To distinguish between different input sizes always pads with 1, and possibly with another 0 to complete to an even-sized input. ## Signature ```rust theme={null} pub fn poseidon_hash_span(mut span: Span) -> felt252 ``` ## Examples ```rust theme={null} let span = [1, 2].span(); let hash = poseidon_hash_span(span); assert!(hash == 0x0371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7); ``` # core::qm31::QM31Trait Source: https://docs.starknet.io/build/corelib/core-qm31-QM31Trait ## Signature ```rust theme={null} pub trait QM31Trait ``` ## Trait functions ### new Returns a new `qm31` composed of the given parts. #### Signature ```rust theme={null} fn new( w0: BoundedInt, w1: BoundedInt, w2: BoundedInt, w3: BoundedInt, ) -> qm31 ``` ### unpack Returns the parts of the given `qm31` as `m31`s. #### Signature ```rust theme={null} fn unpack(self: qm31) -> [BoundedInt; 4] ``` # core::qm31::m31_ops Source: https://docs.starknet.io/build/corelib/core-qm31-m31_ops Additional `m31` actions for specific implementations based on `qm31` opcode. ## [Extern functions](./core-qm31-m31_ops-extern_functions) | | | | :- | :- | | [m31\_add](./core-qm31-m31_ops-m31_add) | Addition of `m31` s in field. | | [m31\_div](./core-qm31-m31_ops-m31_div) | Division of `m31` s in field. | | [m31\_mul](./core-qm31-m31_ops-m31_mul) | Multiplication of `m31` s in field. | | [m31\_sub](./core-qm31-m31_ops-m31_sub) | Subtraction of `m31` s in field. | *** # Re-exports: * ### Extern functions | | | | :- | :- | | [m31\_add](./core-qm31-m31_ops-m31_add) | Addition of `m31` s in field. | | [m31\_div](./core-qm31-m31_ops-m31_div) | Division of `m31` s in field. | | [m31\_mul](./core-qm31-m31_ops-m31_mul) | Multiplication of `m31` s in field. | | [m31\_sub](./core-qm31-m31_ops-m31_sub) | Subtraction of `m31` s in field. | # core::qm31::m31_ops::m31_add Source: https://docs.starknet.io/build/corelib/core-qm31-m31_ops-m31_add Addition of `m31`s in field. ## Signature ```rust theme={null} extern fn m31_add(a: BoundedInt, b: BoundedInt) -> BoundedInt nopanic; ``` # core::qm31::m31_ops::m31_div Source: https://docs.starknet.io/build/corelib/core-qm31-m31_ops-m31_div Division of `m31`s in field. ## Signature ```rust theme={null} extern fn m31_div(a: BoundedInt, b: NonZero>) -> BoundedInt nopanic; ``` # core::qm31::m31_ops::m31_mul Source: https://docs.starknet.io/build/corelib/core-qm31-m31_ops-m31_mul Multiplication of `m31`s in field. ## Signature ```rust theme={null} extern fn m31_mul(a: BoundedInt, b: BoundedInt) -> BoundedInt nopanic; ``` # core::qm31::m31_ops::m31_sub Source: https://docs.starknet.io/build/corelib/core-qm31-m31_ops-m31_sub Subtraction of `m31`s in field. ## Signature ```rust theme={null} extern fn m31_sub(a: BoundedInt, b: BoundedInt) -> BoundedInt nopanic; ``` # core::qm31::qm31 Source: https://docs.starknet.io/build/corelib/core-qm31-qm31 The `qm31` type, defining an extension field over 4 `m31`s. ## Signature ```rust theme={null} pub extern type qm31; ``` # core::qm31::qm31_const Source: https://docs.starknet.io/build/corelib/core-qm31-qm31_const Returns a `qm31` given its values as constants. ## Signature ```rust theme={null} pub extern fn qm31_const() -> qm31 nopanic; ``` # core::serde::Serde Source: https://docs.starknet.io/build/corelib/core-serde-Serde A trait that allows for serializing and deserializing values of any type. The `Serde` trait defines two core operations: * `serialize`: Converts a value into a sequence of `felt252`s * `deserialize`: Reconstructs a value from a sequence of `felt252`s ## Signature ```rust theme={null} pub trait Serde ``` ## Examples ## Simple Types (u8, u16, u32, u64, u128) Simple types are serialized into a single `felt252`: ```rust theme={null} let value: u8 = 42; let mut output: Array = array![]; value.serialize(ref output); assert!(output == array![42]); // Single value ``` ## Compound Types (u256) Compound types may be serialized into multiple `felt252` values: ```rust theme={null} let value: u256 = u256 { low: 1, high: 2 }; let mut output: Array = array![]; value.serialize(ref output); assert!(output == array![1, 2]); // Two `felt252`s: low and high ``` # Implementing `Serde` ## Using the `Derive` Macro In most cases, you can use the `#[derive(Serde)]` attribute to automatically generate the implementation for your type: ```rust theme={null} #[derive(Serde)] struct Point { x: u32, y: u32 } ``` ## Manual Implementation Should you need to customize the serialization behavior for a type in a way that derive does not support, you can implement the `Serde` yourself: ```rust theme={null} impl PointSerde of Serde { fn serialize(self: @Point, ref output: Array) { output.append((*self.x).into()); output.append((*self.y).into()); } fn deserialize(ref serialized: Span) -> Option { let x = (*serialized.pop_front()?).try_into()?; let y = (*serialized.pop_front()?).try_into()?; Some(Point { x, y }) } } ``` ## Trait functions ### serialize Serializes a value into a sequence of `felt252`s. #### Signature ```rust theme={null} fn serialize(self: @T, ref output: Array) ``` #### Examples ```rust theme={null} let value: u256 = 1; let mut serialized: Array = array![]; value.serialize(ref serialized); assert!(serialized == array![1, 0]); // `serialized` contains the [low, high] parts of the `u256` value ``` ### deserialize Deserializes a value from a sequence of `felt252`s. If the value cannot be deserialized, returns `None`. #### Signature ```rust theme={null} fn deserialize(ref serialized: Span) -> Option ``` #### Examples ```rust theme={null} let mut serialized: Span = array![1, 0].span(); let value: u256 = Serde::deserialize(ref serialized).unwrap(); assert!(value == 1); ``` # core::serde::into_felt252_based Source: https://docs.starknet.io/build/corelib/core-serde-into_felt252_based # core::sha256::compute_sha256_byte_array Source: https://docs.starknet.io/build/corelib/core-sha256-compute_sha256_byte_array Computes the SHA-256 hash of the input `ByteArray`. ## Signature ```rust theme={null} pub fn compute_sha256_byte_array(arr: ByteArray) -> [u32; 8] ``` ## Examples ```rust theme={null} use core::sha256::compute_sha256_byte_array; ``` # core::sha256::compute_sha256_u32_array Source: https://docs.starknet.io/build/corelib/core-sha256-compute_sha256_u32_array Computes the SHA-256 hash of an array of 32-bit words. ## Signature ```rust theme={null} pub fn compute_sha256_u32_array( mut input: Array, last_input_word: u32, last_input_num_bytes: u32, ) -> [u32; 8] ``` #### Arguments * `input` - An array of `u32` values to hash * `last_input_word` - The final word when input is not word-aligned * `last_input_num_bytes` - Number of bytes in the last input word (must be less than 4) #### Returns * The SHA-256 hash of the `input array` + `last_input_word` as big endian ## Examples ```rust theme={null} use core::sha256::compute_sha256_u32_array; let hash = compute_sha256_u32_array(array![0x68656c6c], 0x6f, 1); assert!(hash == [0x2cf24dba, 0x5fb0a30e, 0x26e83b2a, 0xc5b9e29e, 0x1b161e5c, 0x1fa7425e, 0x73043362, 0x938b9824]); ``` # core::starknet::SyscallResult Source: https://docs.starknet.io/build/corelib/core-starknet-SyscallResult The `Result` type for a syscall. ## Signature ```rust theme={null} pub type SyscallResult = Result>; ``` # core::starknet::SyscallResultTrait Source: https://docs.starknet.io/build/corelib/core-starknet-SyscallResultTrait Trait for handling syscall results. ## Signature ```rust theme={null} pub trait SyscallResultTrait ``` ## Trait functions ### unwrap\_syscall Unwraps a syscall result, yielding the content of an `Ok`. #### Panics Panics with the syscall error message if the value is an `Err`. #### Examples ```rust theme={null} let result = starknet::syscalls::get_execution_info_v2_syscall(); let info = result.unwrap_syscall(); ``` #### Signature ```rust theme={null} fn unwrap_syscall(self: SyscallResult) -> T ``` # core::starknet::System Source: https://docs.starknet.io/build/corelib/core-starknet-System ## Signature ```rust theme={null} pub extern type System; ``` # core::starknet::VALIDATED Source: https://docs.starknet.io/build/corelib/core-starknet-VALIDATED The expected return value of the `__validate__` function in account contracts. This constant is used to indicate that a transaction validation was successful. Account contracts must return this value from their `__validate__` function to signal that the transaction should proceed. ## Signature ```rust theme={null} pub const VALIDATED: felt252 = 370462705988; ``` # core::starknet::account::AccountContract Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContract A trait for account contracts that support class declarations (only `__validate__` and `__execute__` are mandatory for an account). This trait assumes that the calldata for invoke transactions is `Array`. This is the network standard following SNIP6. It is not enforced by Starknet, but deviating from the standard interface may lead to incompatibility with standard tooling. ## Signature ```rust theme={null} pub trait AccountContract ``` ## Trait functions ### **validate\_declare** An entry point that is called to check if the account is willing to pay for the declaration of the class with the given hash. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the declaration. #### Signature ```rust theme={null} fn __validate_declare__( self: @TContractState, class_hash: felt252, ) -> felt252 ``` ### **validate** An entry point that is called to check if the account is willing to pay for executing a given set of calls. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the execution, in which case `__execute__` will be called on the same set of calls. #### Signature ```rust theme={null} fn __validate__( ref self: TContractState, calls: Array, ) -> felt252 ``` ### **execute** An entry point that is called to execute a given set of calls. This entry point should block the deprecated v0 invoke transactions as they do not go through the `__validate__` entry point. #### Signature ```rust theme={null} fn __execute__( ref self: TContractState, calls: Array, ) -> Array> ``` # core::starknet::account::AccountContractDispatcher Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractDispatcher ## Signature ```rust theme={null} #[derive(Copy, Drop, Serde)] pub struct AccountContractDispatcher { pub contract_address: ContractAddress, } ``` ## Members ### contract\_address #### Signature ```rust theme={null} pub contract_address: ContractAddress ``` # core::starknet::account::AccountContractDispatcherTrait Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractDispatcherTrait ## Signature ```rust theme={null} pub trait AccountContractDispatcherTrait ``` ## Trait functions ### **validate\_declare** An entry point that is called to check if the account is willing to pay for the declaration of the class with the given hash. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the declaration. #### Signature ```rust theme={null} fn __validate_declare__(self: T, class_hash: felt252) -> felt252 ``` ### **validate** An entry point that is called to check if the account is willing to pay for executing a given set of calls. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the execution, in which case `__execute__` will be called on the same set of calls. #### Signature ```rust theme={null} fn __validate__(self: T, calls: Array) -> felt252 ``` ### **execute** An entry point that is called to execute a given set of calls. This entry point should block the deprecated v0 invoke transactions as they do not go through the `__validate__` entry point. #### Signature ```rust theme={null} fn __execute__(self: T, calls: Array) -> Array> ``` # core::starknet::account::AccountContractLibraryDispatcher Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractLibraryDispatcher ## Signature ```rust theme={null} #[derive(Copy, Drop, Serde)] pub struct AccountContractLibraryDispatcher { pub class_hash: ClassHash, } ``` ## Members ### class\_hash #### Signature ```rust theme={null} pub class_hash: ClassHash ``` # core::starknet::account::AccountContractSafeDispatcher Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractSafeDispatcher ## Signature ```rust theme={null} #[derive(Copy, Drop, Serde)] pub struct AccountContractSafeDispatcher { pub contract_address: ContractAddress, } ``` ## Members ### contract\_address #### Signature ```rust theme={null} pub contract_address: ContractAddress ``` # core::starknet::account::AccountContractSafeDispatcherTrait Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractSafeDispatcherTrait ## Signature ```rust theme={null} pub trait AccountContractSafeDispatcherTrait ``` ## Trait functions ### **validate\_declare** An entry point that is called to check if the account is willing to pay for the declaration of the class with the given hash. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the declaration. #### Signature ```rust theme={null} fn __validate_declare__(self: T, class_hash: felt252) -> Result> ``` ### **validate** An entry point that is called to check if the account is willing to pay for executing a given set of calls. The entry point should return `starknet::VALIDATED` if the account is willing to pay for the execution, in which case `__execute__` will be called on the same set of calls. #### Signature ```rust theme={null} fn __validate__(self: T, calls: Array) -> Result> ``` ### **execute** An entry point that is called to execute a given set of calls. This entry point should block the deprecated v0 invoke transactions as they do not go through the `__validate__` entry point. #### Signature ```rust theme={null} fn __execute__(self: T, calls: Array) -> Result>, Array> ``` # core::starknet::account::AccountContractSafeLibraryDispatcher Source: https://docs.starknet.io/build/corelib/core-starknet-account-AccountContractSafeLibraryDispatcher ## Signature ```rust theme={null} #[derive(Copy, Drop, Serde)] pub struct AccountContractSafeLibraryDispatcher { pub class_hash: ClassHash, } ``` ## Members ### class\_hash #### Signature ```rust theme={null} pub class_hash: ClassHash ``` # core::starknet::account::Call Source: https://docs.starknet.io/build/corelib/core-starknet-account-Call A struct representing a call to a contract. ## Signature ```rust theme={null} #[derive(Drop, Copy, Serde, Debug)] pub struct Call { pub to: ContractAddress, pub selector: felt252, pub calldata: Span, } ``` ## Members ### to The address of the contract to call. #### Signature ```rust theme={null} pub to: ContractAddress ``` ### selector The entry point selector in the called contract. #### Signature ```rust theme={null} pub selector: felt252 ``` ### calldata The calldata to pass to entry point. #### Signature ```rust theme={null} pub calldata: Span ``` # core::starknet::class_hash::ClassHash Source: https://docs.starknet.io/build/corelib/core-starknet-class_hash-ClassHash Represents a Starknet contract class hash. The value range of this type is `[0, 2**251)`. ## Signature ```rust theme={null} pub extern type ClassHash; ``` # core::starknet::class_hash::class_hash_const Source: https://docs.starknet.io/build/corelib/core-starknet-class_hash-class_hash_const Returns a `ClassHash` given a `felt252` value. ## Signature ```rust theme={null} pub extern fn class_hash_const() -> ClassHash nopanic; ``` ## Examples ```rust theme={null} use starknet::class_hash::class_hash_const; let class_hash = class_hash_const::(); ``` # core::starknet::contract_address::ContractAddress Source: https://docs.starknet.io/build/corelib/core-starknet-contract_address-ContractAddress Represents a Starknet contract address. The value range of this type is `[0, 2**251)`. ## Signature ```rust theme={null} pub extern type ContractAddress; ``` # core::starknet::eth_address::EthAddress Source: https://docs.starknet.io/build/corelib/core-starknet-eth_address-EthAddress An Ethereum address, 20 bytes in length. ## Signature ```rust theme={null} #[derive(Copy, Drop, Hash, PartialEq)] pub struct EthAddress { address: felt252, } ``` # core::starknet::eth_signature::is_eth_signature_valid Source: https://docs.starknet.io/build/corelib/core-starknet-eth_signature-is_eth_signature_valid Validates an Ethereum signature against a message hash and Ethereum address. Similar to `verify_eth_signature` but returns a `Result` instead of panicking. Also verifies that `r` and `s` components of the signature are in the range `[1, N)`, where N is the size of the curve. ## Signature ```rust theme={null} pub fn is_eth_signature_valid( msg_hash: u256, signature: Signature, eth_address: EthAddress, ) -> Result ``` #### Arguments * `msg_hash` - The 32-byte hash of the message that was signed * `signature` - The Ethereum signature containing `r`, `s` components and `y_parity` * `eth_address` - The expected Ethereum address of the signer #### Returns Returns `Ok(())` if the signature is valid, or `Err(felt252)` containing an error message if invalid. ## Examples ```rust theme={null} use starknet::eth_address::EthAddress; use starknet::eth_signature::is_eth_signature_valid; use starknet::secp256_trait::Signature; let msg_hash = 0xe888fbb4cf9ae6254f19ba12e6d9af54788f195a6f509ca3e934f78d7a71dd85; let r = 0x4c8e4fbc1fbb1dece52185e532812c4f7a5f81cf3ee10044320a0d03b62d3e9a; let s = 0x4ac5e5c0c0e8a4871583cc131f35fb49c2b7f60e6a8b84965830658f08f7410c; let y_parity = true; let eth_address: EthAddress = 0x767410c1bb448978bd42b984d7de5970bcaf5c43_u256 .try_into() .unwrap(); assert!(is_eth_signature_valid(msg_hash, Signature { r, s, y_parity }, eth_address).is_ok()); ``` # core::starknet::eth_signature::public_key_point_to_eth_address Source: https://docs.starknet.io/build/corelib/core-starknet-eth_signature-public_key_point_to_eth_address Converts a public key point to its corresponding Ethereum address. The Ethereum address is calculated by taking the Keccak-256 hash of the public key coordinates and taking the last 20 big-endian bytes. ## Signature ```rust theme={null} pub fn public_key_point_to_eth_address, +Secp256Trait, +Secp256PointTrait, >( public_key_point: Secp256Point, ) -> EthAddress ``` #### Arguments * `public_key_point` - A point on a secp256 curve representing a public key #### Returns The 20-byte Ethereum address derived from the public key ## Examples ```rust theme={null} use starknet::eth_signature::public_key_point_to_eth_address; use starknet::secp256k1::Secp256k1Point; use starknet::secp256_trait::Secp256Trait; let public_key: Secp256k1Point = Secp256Trait::secp256_ec_get_point_from_x_syscall( 0xa9a02d48081294b9bb0d8740d70d3607feb20876964d432846d9b9100b91eefd, false, ) .unwrap() .unwrap(); let eth_address = public_key_point_to_eth_address(public_key); assert!(eth_address == 0x767410c1bb448978bd42b984d7de5970bcaf5c43.try_into().unwrap()); ``` # core::starknet::eth_signature::verify_eth_signature Source: https://docs.starknet.io/build/corelib/core-starknet-eth_signature-verify_eth_signature Asserts that an Ethereum signature is valid for a given message hash and Ethereum address. Also verifies that the `r` and `s` components of the signature are in the range `[1, N)`, where N is the size of the curve. ## Signature ```rust theme={null} pub fn verify_eth_signature(msg_hash: u256, signature: Signature, eth_address: EthAddress) ``` #### Arguments * `msg_hash` - The 32-byte hash of the message that was signed * `signature` - The Ethereum signature containing `r`, `s` components and `y_parity` * `eth_address` - The expected Ethereum address of the signer #### Panics Panics if: * The signature components are out of range (not in \[1, N) where N is the curve order) * The recovered address doesn't match the provided address ## Examples ```rust theme={null} use starknet::eth_address::EthAddress; use starknet::eth_signature::verify_eth_signature; use starknet::secp256_trait::Signature; let msg_hash = 0xe888fbb4cf9ae6254f19ba12e6d9af54788f195a6f509ca3e934f78d7a71dd85; let r = 0x4c8e4fbc1fbb1dece52185e532812c4f7a5f81cf3ee10044320a0d03b62d3e9a; let s = 0x4ac5e5c0c0e8a4871583cc131f35fb49c2b7f60e6a8b84965830658f08f7410c; let y_parity = true; let eth_address: EthAddress = 0x767410c1bb448978bd42b984d7de5970bcaf5c43_u256 .try_into() .unwrap(); verify_eth_signature(msg_hash, Signature { r, s, y_parity }, eth_address); ``` # core::starknet::event::Event Source: https://docs.starknet.io/build/corelib/core-starknet-event-Event A trait for handling serialization and deserialization of events. Events in Starknet are stored in transaction receipts as a combination of keys and data fields. This trait provides the methods needed to serialize event data into these fields and deserialize them back into their original form. This trait can easily be derived using the `#[derive(starknet::Event)]` attribute. Fields can be marked as keys using the `#[key]` attribute to serialize them as event keys. ## Signature ```rust theme={null} pub trait Event ``` ## Examples ```rust theme={null} #[derive(Drop, starknet::Event)] pub struct Transfer { #[key] pub from: ContractAddress, #[key] pub to: ContractAddress, pub amount: u256, } ``` ## Trait functions ### append\_keys\_and\_data Serializes the keys and data for event emission. The keys array will contain: * The event name selector as the first key * Any fields marked with #key as subsequent keys The data array will contain all non-key fields. #### Signature ```rust theme={null} fn append_keys_and_data(self: @T, ref keys: Array, ref data: Array) ``` ### deserialize Deserializes events keys and data back into the original event structure. Returns `None` if deserialization fails. #### Signature ```rust theme={null} fn deserialize(ref keys: Span, ref data: Span) -> Option ``` # core::starknet::event::EventEmitter Source: https://docs.starknet.io/build/corelib/core-starknet-event-EventEmitter A trait for emitting Starknet events. ## Signature ```rust theme={null} pub trait EventEmitter ``` ## Examples ```rust theme={null} #[derive(Drop, starknet::Event)] pub struct NewOwner { pub new_owner: ContractAddress, } fn emit_event(ref self: ContractState, new_owner: ContractAddress) { self.emit(NewOwner { new_owner }); } ``` ## Trait functions ### emit Emits an event. #### Signature ```rust theme={null} fn emit>(ref self: T, event: S) ``` # core::starknet::info::BlockInfo Source: https://docs.starknet.io/build/corelib/core-starknet-info-BlockInfo Information about the current block. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug, Serde)] pub struct BlockInfo { pub block_number: u64, pub block_timestamp: u64, pub sequencer_address: ContractAddress, } ``` ## Members ### block\_number The number, that is, the height, of this block. #### Signature ```rust theme={null} pub block_number: u64 ``` ### block\_timestamp The time at which the sequencer began building the block, in seconds since the Unix epoch. #### Signature ```rust theme={null} pub block_timestamp: u64 ``` ### sequencer\_address The Starknet address of the sequencer that created the block. #### Signature ```rust theme={null} pub sequencer_address: ContractAddress ``` # core::starknet::info::get_block_info Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_block_info Returns the block information for the current block. ## Signature ```rust theme={null} pub fn get_block_info() -> Box ``` ## Examples ```rust theme={null} use starknet::get_block_info; let block_info = get_block_info().unbox(); let block_number = block_info.block_number; let block_timestamp = block_info.block_timestamp; let sequencer = block_info.sequencer_address; ``` # core::starknet::info::get_block_number Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_block_number Returns the number of the current block. ## Signature ```rust theme={null} pub fn get_block_number() -> u64 ``` ## Examples ```rust theme={null} use starknet::get_block_number; let block_number = get_block_number(); ``` # core::starknet::info::get_block_timestamp Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_block_timestamp Returns the timestamp of the current block. ## Signature ```rust theme={null} pub fn get_block_timestamp() -> u64 ``` ## Examples ```rust theme={null} use starknet::get_block_timestamp; let block_timestamp = get_block_timestamp(); ``` # core::starknet::info::get_caller_address Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_caller_address Returns the address of the caller contract. Returns `0` if there is no caller—for example, when a transaction begins execution inside an account contract. Note: This function returns the direct caller. If you're interested in the account that initiated the transaction, use `get_execution_info().tx_info.unbox().account_contract_address` instead. ## Signature ```rust theme={null} pub fn get_caller_address() -> ContractAddress ``` ## Examples ```rust theme={null} use starknet::get_caller_address; let caller = get_caller_address(); ``` # core::starknet::info::get_contract_address Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_contract_address Returns the address of the contract being executed. ## Signature ```rust theme={null} pub fn get_contract_address() -> ContractAddress ``` ## Examples ```rust theme={null} use starknet::get_contract_address; let contract_address = get_contract_address(); ``` # core::starknet::info::get_execution_info Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_execution_info Returns the execution info for the current execution. ## Signature ```rust theme={null} pub fn get_execution_info() -> Box ``` ## Examples ```rust theme={null} use starknet::get_execution_info; let execution_info = get_execution_info().unbox(); // Access various execution context information let caller = execution_info.caller_address; let contract = execution_info.contract_address; let selector = execution_info.entry_point_selector; ``` # core::starknet::info::get_tx_info Source: https://docs.starknet.io/build/corelib/core-starknet-info-get_tx_info Returns the transaction information for the current transaction. ## Signature ```rust theme={null} pub fn get_tx_info() -> Box ``` ## Examples ```rust theme={null} use starknet::get_tx_info; let tx_info = get_tx_info().unbox(); let account_contract_address = tx_info.account_contract_address; let chain_id = tx_info.chain_id; let nonce = tx_info.nonce; let max_fee = tx_info.max_fee; let tx_hash = tx_info.transaction_hash; let signature = tx_info.signature; let version = tx_info.version; ``` # core::starknet::info::v2::ExecutionInfo Source: https://docs.starknet.io/build/corelib/core-starknet-info-v2-ExecutionInfo The same as `ExecutionInfo`, but with the `TxInfo` field replaced with `v2::TxInfo`. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug)] pub struct ExecutionInfo { pub block_info: Box, pub tx_info: Box, pub caller_address: ContractAddress, pub contract_address: ContractAddress, pub entry_point_selector: felt252, } ``` ## Members ### block\_info #### Signature ```rust theme={null} pub block_info: Box ``` ### tx\_info #### Signature ```rust theme={null} pub tx_info: Box ``` ### caller\_address #### Signature ```rust theme={null} pub caller_address: ContractAddress ``` ### contract\_address #### Signature ```rust theme={null} pub contract_address: ContractAddress ``` ### entry\_point\_selector #### Signature ```rust theme={null} pub entry_point_selector: felt252 ``` # core::starknet::info::v2::ResourceBounds Source: https://docs.starknet.io/build/corelib/core-starknet-info-v2-ResourceBounds V3 transactions resources used for enabling the fee market. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug, Serde)] pub struct ResourceBounds { pub resource: felt252, pub max_amount: u64, pub max_price_per_unit: u128, } ``` ## Members ### resource The name of the resource. #### Signature ```rust theme={null} pub resource: felt252 ``` ### max\_amount The maximum amount of the resource allowed for usage during the execution. #### Signature ```rust theme={null} pub max_amount: u64 ``` ### max\_price\_per\_unit The maximum price the user is willing to pay for the resource unit. #### Signature ```rust theme={null} pub max_price_per_unit: u128 ``` # core::starknet::info::v2::TxInfo Source: https://docs.starknet.io/build/corelib/core-starknet-info-v2-TxInfo Extended information about the current transaction. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug, Serde)] pub struct TxInfo { pub version: felt252, pub account_contract_address: ContractAddress, pub max_fee: u128, pub signature: Span, pub transaction_hash: felt252, pub chain_id: felt252, pub nonce: felt252, pub resource_bounds: Span, pub tip: u128, pub paymaster_data: Span, pub nonce_data_availability_mode: u32, pub fee_data_availability_mode: u32, pub account_deployment_data: Span, } ``` ## Members ### version The version of the transaction. It is fixed (currently, 1) in the OS, and should be signed by the account contract. This field allows invalidating old transactions, whenever the meaning of the other transaction fields is changed (in the OS). #### Signature ```rust theme={null} pub version: felt252 ``` ### account\_contract\_address The account contract from which this transaction originates. #### Signature ```rust theme={null} pub account_contract_address: ContractAddress ``` ### max\_fee The `max_fee` field of the transaction. #### Signature ```rust theme={null} pub max_fee: u128 ``` ### signature The signature of the transaction. #### Signature ```rust theme={null} pub signature: Span ``` ### transaction\_hash The hash of the transaction. #### Signature ```rust theme={null} pub transaction_hash: felt252 ``` ### chain\_id The identifier of the chain. This field can be used to prevent replay of testnet transactions on mainnet. #### Signature ```rust theme={null} pub chain_id: felt252 ``` ### nonce The transaction's nonce. #### Signature ```rust theme={null} pub nonce: felt252 ``` ### resource\_bounds A span of `ResourceBounds` structs used for V3 transactions. #### Signature ```rust theme={null} pub resource_bounds: Span ``` ### tip The tip of the transaction. #### Signature ```rust theme={null} pub tip: u128 ``` ### paymaster\_data If specified, the paymaster should pay for the execution of the transaction. The data includes the address of the paymaster sponsoring the transaction, followed by extra data to send to the paymaster. Used for V3 transactions. #### Signature ```rust theme={null} pub paymaster_data: Span ``` ### nonce\_data\_availability\_mode The data availability mode for the nonce. Used for V3 transactions. #### Signature ```rust theme={null} pub nonce_data_availability_mode: u32 ``` ### fee\_data\_availability\_mode The data availability mode for the account balance from which fee will be taken. Used for V3 transactions. #### Signature ```rust theme={null} pub fee_data_availability_mode: u32 ``` ### account\_deployment\_data If nonempty, will contain the required data for deploying and initializing an account contract: its class hash, address salt and constructor calldata. Used for V3 transactions. #### Signature ```rust theme={null} pub account_deployment_data: Span ``` # core::starknet::secp256_trait::Secp256PointTrait Source: https://docs.starknet.io/build/corelib/core-starknet-secp256_trait-Secp256PointTrait A trait for performing operations on Secp2561 curve points. Provides operations needed for elliptic curve cryptography, including point addition and scalar multiplication. ## Signature ```rust theme={null} pub trait Secp256PointTrait ``` ## Examples ```rust theme={null} use starknet::SyscallResultTrait; use starknet::secp256k1::Secp256k1Point; use starknet::secp256_trait::Secp256PointTrait; use starknet::secp256_trait::Secp256Trait; let generator = Secp256Trait::::get_generator_point(); assert!( Secp256PointTrait::get_coordinates(generator) .unwrap_syscall() == ( 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, ), ); let point = Secp256PointTrait::add(generator, generator); let other_point = Secp256PointTrait::mul(generator, 2); ``` ## Trait functions ### get\_coordinates Returns the x and y coordinates of the curve point. #### Signature ```rust theme={null} fn get_coordinates( self: Secp256Point, ) -> Result> ``` ### add Performs elliptic curve point addition. Adds `self` and `other` following the curve's addition law and returns the resulting point. #### Signature ```rust theme={null} fn add( self: Secp256Point, other: Secp256Point, ) -> Result> ``` ### mul Performs scalar multiplication of a curve point. Multiplies `self` by the given scalar value. #### Signature ```rust theme={null} fn mul( self: Secp256Point, scalar: u256, ) -> Result> ``` # core::starknet::secp256_trait::Secp256Trait Source: https://docs.starknet.io/build/corelib/core-starknet-secp256_trait-Secp256Trait A trait for interacting with Secp2561 curves. Provides operations needed to work with Secp256k1 and Secp256r1 elliptic curves. It includes methods for accessing curve parameters and creating curve points. ## Signature ```rust theme={null} pub trait Secp256Trait ``` ## Examples ```rust theme={null} use starknet::secp256k1::Secp256k1Point; use starknet::secp256_trait::Secp256Trait; use starknet::SyscallResultTrait; assert!( Secp256Trait::::get_curve_size() == 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141, ); let generator = Secp256Trait::::get_generator_point(); let generator = Secp256Trait::::secp256_ec_new_syscall( 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, ) .unwrap_syscall(); let random_point = Secp256Trait::::secp256_ec_get_point_from_x_syscall( 0x4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3, true, ); ``` ## Trait functions ### get\_curve\_size Returns the order (size) of the curve's underlying field. This is the number of points on the curve, also known as the curve order. #### Signature ```rust theme={null} fn get_curve_size() -> u256 ``` ### get\_generator\_point Returns the generator point (G) for the curve. The generator point is a standard base point on the curve from which other points can be generated through scalar multiplication. #### Signature ```rust theme={null} fn get_generator_point() -> Secp256Point ``` ### secp256\_ec\_new\_syscall Creates a new curve point from its x and y coordinates. Returns `None` if the provided coordinates don't represent a valid point on the curve. #### Signature ```rust theme={null} fn secp256_ec_new_syscall( x: u256, y: u256, ) -> Result, Array> ``` ### secp256\_ec\_get\_point\_from\_x\_syscall Creates a curve point on the curve given its x-coordinate and y-parity. #### Arguments * `x` - The x coordinate of the point * `y_parity` - If true, choose the odd y value; if false, choose the even y value #### Returns Returns `Some(point)` if a point exists with the given x coordinate, `None` otherwise. #### Signature ```rust theme={null} fn secp256_ec_get_point_from_x_syscall( x: u256, y_parity: bool, ) -> Result, Array> ``` # core::starknet::secp256_trait::Signature Source: https://docs.starknet.io/build/corelib/core-starknet-secp256_trait-Signature Represents a Secp2561 ECDSA signature. This struct holds the components of an ECDSA signature: `r`, `s`, and `y_parity`. ## Signature ```rust theme={null} #[derive(Copy, Drop, Debug, PartialEq, Serde, Hash)] pub struct Signature { pub r: u256, pub s: u256, pub y_parity: bool, } ``` ## Members ### r #### Signature ```rust theme={null} pub r: u256 ``` ### s #### Signature ```rust theme={null} pub s: u256 ``` ### y\_parity The parity of the y coordinate of the elliptic curve point whose x coordinate is `r`. `y_parity == true` means that the y coordinate is odd. Some places use non boolean `v` instead of `y_parity`. In that case, `signature_from_vrs` should be used. #### Signature ```rust theme={null} pub y_parity: bool ``` # core::starknet::secp256k1::Secp256k1Point Source: https://docs.starknet.io/build/corelib/core-starknet-secp256k1-Secp256k1Point A point on the secp256k1 curve. ## Signature ```rust theme={null} pub extern type Secp256k1Point; ``` # core::starknet::secp256r1::Secp256r1Point Source: https://docs.starknet.io/build/corelib/core-starknet-secp256r1-Secp256r1Point Represents a point on the secp256r1 elliptic curve. ## Signature ```rust theme={null} pub extern type Secp256r1Point; ``` # core::starknet::storage::IntoIterRange Source: https://docs.starknet.io/build/corelib/core-starknet-storage-IntoIterRange Trait for turning collection of values into an iterator over a specific range. ## Signature ```rust theme={null} pub trait IntoIterRange ``` ## Trait functions ### into\_iter\_range Creates an iterator over a range from a collection. #### Signature ```rust theme={null} fn into_iter_range(self: T, range: Range) -> IntoIterRangeIntoIter ``` ### into\_iter\_full\_range Creates an iterator over the full range of a collection. #### Signature ```rust theme={null} fn into_iter_full_range(self: T) -> IntoIterRangeIntoIter ``` ## Trait types ### IntoIter #### Signature ```rust theme={null} type IntoIter; ``` # core::starknet::storage::Mutable Source: https://docs.starknet.io/build/corelib/core-starknet-storage-Mutable A wrapper around different storage related types, indicating that the instance is mutable, i.e. originally created from a `ref` contract state. ## Signature ```rust theme={null} #[phantom] pub struct Mutable {} ``` # core::starknet::storage::PendingStoragePath Source: https://docs.starknet.io/build/corelib/core-starknet-storage-PendingStoragePath A struct for delaying the creation of a storage path, used for lazy evaluation in storage nodes. ## Signature ```rust theme={null} pub struct PendingStoragePath { __hash_state__: HashState, __pending_key__: felt252, } ``` # core::starknet::storage::PendingStoragePathTrait Source: https://docs.starknet.io/build/corelib/core-starknet-storage-PendingStoragePathTrait A trait for creating a `PendingStoragePath` from a `StoragePath` hash state and a key. ## Signature ```rust theme={null} pub trait PendingStoragePathTrait ``` ## Trait functions ### new #### Signature ```rust theme={null} fn new(storage_path: @StoragePath, pending_key: felt252) -> PendingStoragePath ``` # core::starknet::storage::StorableStoragePointerReadAccess Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StorableStoragePointerReadAccess Simple implementation of `StoragePointerReadAccess` for any type that implements `Store` for any offset. ## Signature ```rust theme={null} pub impl StorableStoragePointerReadAccess, > of StoragePointerReadAccess>; ``` ## Impl functions ### read #### Signature ```rust theme={null} fn read(self: @StoragePointer) -> T ``` ## Impl types ### Value #### Signature ```rust theme={null} type Value = T; ``` # core::starknet::storage::StorageAsPath Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StorageAsPath Trait for creating a new `StoragePath` from a storage member. ## Signature ```rust theme={null} pub trait StorageAsPath ``` ## Trait functions ### as\_path #### Signature ```rust theme={null} fn as_path(self: @TMemberState) -> StoragePathValue> ``` ## Trait types ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::StorageAsPointer Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StorageAsPointer Trait for converting a storage member to a `StoragePointer0Offset`. ## Signature ```rust theme={null} pub trait StorageAsPointer ``` ## Trait functions ### as\_ptr #### Signature ```rust theme={null} fn as_ptr(self: @TMemberState) -> StoragePointer0OffsetValue> ``` ## Trait types ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::StorageNodeDeref Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StorageNodeDeref This makes the storage node members directly accessible from a path to the parent struct. ## Signature ```rust theme={null} pub impl StorageNodeDeref> of Deref>; ``` ## Impl functions ### deref #### Signature ```rust theme={null} fn deref(self: StoragePath) -> NodeType ``` ## Impl types ### Target #### Signature ```rust theme={null} type Target = NodeType; ``` # core::starknet::storage::StorageNodeMutDeref Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StorageNodeMutDeref This makes the storage node members directly accessible from a path to the parent struct. ## Signature ```rust theme={null} pub impl StorageNodeMutDeref> of Deref>>; ``` ## Impl functions ### deref #### Signature ```rust theme={null} fn deref(self: StoragePath>) -> NodeType ``` ## Impl types ### Target #### Signature ```rust theme={null} type Target = NodeType; ``` # core::starknet::storage::StoragePath Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePath An intermediate struct to store a hash state, in order to be able to hash multiple values and get the final address. Storage path should have two interfaces, if `T` is storable then it should implement `StorageAsPointer` in order to be able to get the address of the storage path. Otherwise, if `T` is not storable then it should implement some kind of updating trait, e.g. `StoragePathEntry`. ## Signature ```rust theme={null} pub struct StoragePath { __hash_state__: HashState, } ``` # core::starknet::storage::StoragePathMutableConversion Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePathMutableConversion ## Signature ```rust theme={null} pub trait StoragePathMutableConversion ``` ## Trait functions ### as\_non\_mut Converts a `StoragePath>` to a `StoragePath`. This is useful to expose functions implemented for `StoragePath` on a `StoragePath>`. #### Signature ```rust theme={null} fn as_non_mut(self: StoragePath>) -> StoragePath ``` # core::starknet::storage::StoragePointer Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePointer A pointer to an address in storage, can be used to read and write values, if the generic type supports it (e.g. basic types like `felt252`). ## Signature ```rust theme={null} pub struct StoragePointer { pub __storage_pointer_address__: StorageBaseAddress, pub __storage_pointer_offset__: u8, } ``` ## Members ### **storage\_pointer\_address** #### Signature ```rust theme={null} pub __storage_pointer_address__: StorageBaseAddress ``` ### **storage\_pointer\_offset** #### Signature ```rust theme={null} pub __storage_pointer_offset__: u8 ``` # core::starknet::storage::StoragePointer0Offset Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePointer0Offset Same as `StoragePointer`, but with `offset` 0, which allows for some optimizations. ## Signature ```rust theme={null} pub struct StoragePointer0Offset { pub __storage_pointer_address__: StorageBaseAddress, } ``` ## Members ### **storage\_pointer\_address** #### Signature ```rust theme={null} pub __storage_pointer_address__: StorageBaseAddress ``` # core::starknet::storage::StoragePointerReadAccess Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePointerReadAccess Trait for accessing the values in storage using a `StoragePointer`. ## Signature ```rust theme={null} pub trait StoragePointerReadAccess ``` ## Examples ## Trait functions ### read #### Signature ```rust theme={null} fn read(self: @T) -> StoragePointerReadAccessValue ``` ## Trait types ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::StoragePointerWriteAccess Source: https://docs.starknet.io/build/corelib/core-starknet-storage-StoragePointerWriteAccess Trait for writing values to storage using a `StoragePointer`. ## Signature ```rust theme={null} pub trait StoragePointerWriteAccess ``` ## Examples ## Trait functions ### write #### Signature ```rust theme={null} fn write(self: T, value: Self::Value) ``` ## Trait types ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::SubPointersDeref Source: https://docs.starknet.io/build/corelib/core-starknet-storage-SubPointersDeref This makes the sub-pointers members directly accessible from a pointer to the parent struct. ## Signature ```rust theme={null} pub impl SubPointersDeref> of Deref>; ``` ## Impl functions ### deref #### Signature ```rust theme={null} fn deref(self: StoragePointer) -> SubPointersType ``` ## Impl types ### Target #### Signature ```rust theme={null} type Target = SubPointersType; ``` # core::starknet::storage::SubPointersMutDeref Source: https://docs.starknet.io/build/corelib/core-starknet-storage-SubPointersMutDeref This makes the sub-pointers members directly accessible from a pointer to the parent struct. ## Signature ```rust theme={null} pub impl SubPointersMutDeref> of Deref>>; ``` ## Impl functions ### deref #### Signature ```rust theme={null} fn deref(self: StoragePointer>) -> SubPointersType ``` ## Impl types ### Target #### Signature ```rust theme={null} type Target = SubPointersType; ``` # core::starknet::storage::ValidStorageTypeTrait Source: https://docs.starknet.io/build/corelib/core-starknet-storage-ValidStorageTypeTrait Trait that ensures a type is valid for storage in Starknet contracts. This trait is used to enforce that only specific types, such as those implementing `Store` or acting as a `StorageNode`, can be a part of a storage hierarchy. Any type that does not implement this trait cannot be used in a storage struct. ## Signature ```rust theme={null} pub trait ValidStorageTypeTrait ``` # core::starknet::storage::map::Map Source: https://docs.starknet.io/build/corelib/core-starknet-storage-map-Map A persistent key-value store in contract storage. This type cannot be instantiated as it is marked with `#[phantom]`. This is by design: `Map` is a compile-time type that only exists to provide type information for the compiler. It represents a mapping in storage, but the actual storage operations are handled by the [`StorageMapReadAccess`](./core-starknet-storage-map-StorageMapReadAccess), [`StorageMapWriteAccess`](./core-starknet-storage-map-StorageMapWriteAccess), and [`StoragePathEntry`](./core-starknet-storage-map-StoragePathEntry) traits. ## Signature ```rust theme={null} #[phantom] pub struct Map {} ``` # core::starknet::storage::map::StorageMapReadAccess Source: https://docs.starknet.io/build/corelib/core-starknet-storage-map-StorageMapReadAccess Provides direct read access to values in a storage [`Map`](./core-starknet-storage-map-Map). ## Signature ```rust theme={null} pub trait StorageMapReadAccess ``` ## Examples ```rust theme={null} use starknet::ContractAddress; use starknet::storage::{Map, StorageMapReadAccess, StoragePathEntry}; #[storage] struct Storage { balances: Map, allowances: Map>, } fn read_storage(self: @ContractState, address: ContractAddress) { // Read from single mapping let balance = self.balances.read(address); // Read from nested mapping let allowance = self.allowances.entry(owner).read(spender); } ``` ## Trait functions ### read #### Signature ```rust theme={null} fn read(self: TMemberState, key: Self::Key) -> StorageMapReadAccessValue ``` ## Trait types ### Key #### Signature ```rust theme={null} type Key; ``` ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::map::StorageMapWriteAccess Source: https://docs.starknet.io/build/corelib/core-starknet-storage-map-StorageMapWriteAccess Provides direct write access to values in a storage [`Map`](./core-starknet-storage-map-Map). Enables directly storing values in the contract's storage at the address of the given key. ## Signature ```rust theme={null} pub trait StorageMapWriteAccess ``` ## Examples ```rust theme={null} use starknet::ContractAddress; use starknet::storage::{Map, StorageMapWriteAccess, StoragePathEntry}; #[storage] struct Storage { balances: Map, allowances: Map>, } fn write_storage(ref self: ContractState, address: ContractAddress) { // Write to single mapping self.balances.write(address, 100); // Write to nested mapping self.allowances.entry(owner).write(spender, 50); } ``` ## Trait functions ### write #### Signature ```rust theme={null} fn write(self: TMemberState, key: Self::Key, value: Self::Value) ``` ## Trait types ### Key #### Signature ```rust theme={null} type Key; ``` ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::map::StoragePathEntry Source: https://docs.starknet.io/build/corelib/core-starknet-storage-map-StoragePathEntry Computes storage paths for accessing [`Map`](./core-starknet-storage-map-Map) entries. The storage path combines the variable's base path with the key's hash to create a unique identifier for the storage slot. This path can then be used for subsequent read or write operations, or advanced further by chaining the `entry` method. ## Signature ```rust theme={null} pub trait StoragePathEntry ``` ## Examples ```rust theme={null} use starknet::ContractAddress; use starknet::storage::{Map, StoragePathEntry}; #[storage] struct Storage { balances: Map, } // Get the storage path for the balance of a specific address let balance_path = self.balances.entry(address); ``` ## Trait functions ### entry #### Signature ```rust theme={null} fn entry(self: C, key: Self::Key) -> StoragePathValue> ``` ## Trait types ### Key #### Signature ```rust theme={null} type Key; ``` ### Value #### Signature ```rust theme={null} type Value; ``` # core::starknet::storage::storage_base::FlattenedStorage Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_base-FlattenedStorage A type that represents a flattened storage, i.e. a storage object which does not have any effect on the path taken into consideration when computing the address of the storage object. ## Signature ```rust theme={null} pub struct FlattenedStorage {} ``` # core::starknet::storage::storage_base::StorageBase Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_base-StorageBase A struct for holding an address to initialize a storage path with. The members (not direct members, but accessible using `deref`) of a contract state are either `StorageBase` or `FlattenedStorage` instances, with the generic type representing the type of the stored member. ## Signature ```rust theme={null} pub struct StorageBase { pub __base_address__: felt252, } ``` ## Members ### **base\_address** #### Signature ```rust theme={null} pub __base_address__: felt252 ``` # core::starknet::storage::storage_base::StorageTrait Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_base-StorageTrait A trait for creating the struct containing the `StorageBase` or `FlattenedStorage` of all the members of a contract state. ## Signature ```rust theme={null} pub trait StorageTrait ``` ## Trait functions ### storage Creates a struct containing the `StorageBase` or `FlattenedStorage` of all the members of a contract state. Should be called from the `deref` method of the contract state. #### Signature ```rust theme={null} fn storage(self: FlattenedStorage) -> StorageTraitBaseType ``` ## Trait types ### BaseType The type of the struct containing the `StorageBase` or `FlattenedStorage` of all the members of the type `T`. #### Signature ```rust theme={null} type BaseType; ``` # core::starknet::storage::storage_base::StorageTraitMut Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_base-StorageTraitMut A trait for creating the struct containing the mutable `StorageBase` or `FlattenedStorage` of all the members of a contract state. ## Signature ```rust theme={null} pub trait StorageTraitMut ``` ## Trait functions ### storage\_mut Creates a struct containing a mutable version of the `StorageBase` or `FlattenedStorage` of all the members of a contract state. Should be called from the `deref` method of the contract state. #### Signature ```rust theme={null} fn storage_mut(self: FlattenedStorage>) -> StorageTraitMutBaseType ``` ## Trait types ### BaseType The type of the struct containing the mutable `StorageBase` or `FlattenedStorage` of all the members of the type `T`. #### Signature ```rust theme={null} type BaseType; ``` # core::starknet::storage::storage_node::StorageNode Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_node-StorageNode A trait that given a storage path of a struct, generates the storage node of this struct. ## Signature ```rust theme={null} pub trait StorageNode ``` ## Trait functions ### storage\_node #### Signature ```rust theme={null} fn storage_node(self: StoragePath) -> StorageNodeNodeType ``` ## Trait types ### NodeType #### Signature ```rust theme={null} type NodeType; ``` # core::starknet::storage::storage_node::StorageNodeMut Source: https://docs.starknet.io/build/corelib/core-starknet-storage-storage_node-StorageNodeMut A mutable version of `StorageNode`, works the same way, but on `Mutable`. ## Signature ```rust theme={null} pub trait StorageNodeMut ``` ## Trait functions ### storage\_node\_mut #### Signature ```rust theme={null} fn storage_node_mut(self: StoragePath>) -> StorageNodeMutNodeType ``` ## Trait types ### NodeType #### Signature ```rust theme={null} type NodeType; ``` # core::starknet::storage::sub_pointers::SubPointers Source: https://docs.starknet.io/build/corelib/core-starknet-storage-sub_pointers-SubPointers Similar to storage node, but for structs which are stored sequentially in the storage. In contrast to storage node, the fields of the struct are just at an offset from the base address of the struct. ## Signature ```rust theme={null} pub trait SubPointers ``` ## Trait functions ### sub\_pointers Creates a sub pointers struct for the given storage pointer to a struct T. #### Signature ```rust theme={null} fn sub_pointers(self: StoragePointer) -> SubPointersSubPointersType ``` ## Trait types ### SubPointersType The type of the storage pointers, generated for the struct T. #### Signature ```rust theme={null} type SubPointersType; ``` # core::starknet::storage::sub_pointers::SubPointersForward Source: https://docs.starknet.io/build/corelib/core-starknet-storage-sub_pointers-SubPointersForward A trait for implementing `SubPointers` for types which are not a `StoragePointer`, such as `StorageBase` and `StoragePath`. ## Signature ```rust theme={null} pub trait SubPointersForward ``` ## Trait functions ### sub\_pointers #### Signature ```rust theme={null} fn sub_pointers(self: T) -> SubPointersForwardSubPointersType ``` ## Trait types ### SubPointersType #### Signature ```rust theme={null} type SubPointersType; ``` # core::starknet::storage::sub_pointers::SubPointersMut Source: https://docs.starknet.io/build/corelib/core-starknet-storage-sub_pointers-SubPointersMut A mutable version of `SubPointers`, works the same way, but on `Mutable`. ## Signature ```rust theme={null} pub trait SubPointersMut ``` ## Trait functions ### sub\_pointers\_mut Creates a sub pointers struct for the given storage pointer to a struct T. #### Signature ```rust theme={null} fn sub_pointers_mut(self: StoragePointer>) -> SubPointersMutSubPointersType ``` ## Trait types ### SubPointersType The type of the storage pointers, generated for the struct T. #### Signature ```rust theme={null} type SubPointersType; ``` # core::starknet::storage::sub_pointers::SubPointersMutForward Source: https://docs.starknet.io/build/corelib/core-starknet-storage-sub_pointers-SubPointersMutForward A trait for implementing `SubPointersMut` for types which are not a `StoragePointer`, such as `StorageBase` and `StoragePath`. ## Signature ```rust theme={null} pub trait SubPointersMutForward ``` ## Trait functions ### sub\_pointers\_mut #### Signature ```rust theme={null} fn sub_pointers_mut(self: T) -> SubPointersMutForwardSubPointersType ``` ## Trait types ### SubPointersType #### Signature ```rust theme={null} type SubPointersType; ``` # core::starknet::storage::vec::MutableVecTrait Source: https://docs.starknet.io/build/corelib/core-starknet-storage-vec-MutableVecTrait Provides mutable access to elements in a storage [`Vec`](./core-starknet-storage-vec-Vec). This trait extends the read functionality with methods to append new elements and modify existing ones. ## Signature ```rust theme={null} pub trait MutableVecTrait ``` ## Trait functions ### get Returns a mutable storage path to the element at the specified index, or `None` if out of bounds. #### Signature ```rust theme={null} fn get(self: T, index: u64) -> OptionElementType>>> ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; #[storage] struct Storage { numbers: Vec, } fn set_number(ref self: ContractState, index: u64, number: u256) -> bool { if let Some(ptr) = self.numbers.get(index) { ptr.write(number); true } else { false } } ``` ### at Returns a mutable storage path to the element at the specified index. #### Panics Panics if the index is out of bounds. #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; #[storage] struct Storage { numbers: Vec, } fn set_number(ref self: ContractState, index: u64, number: u256) { self.numbers.at(index).write(number); } ``` #### Signature ```rust theme={null} fn at(self: T, index: u64) -> StoragePathElementType>> ``` ### len Returns the number of elements in the vector. The length is stored at the vector's base storage address and is automatically updated when elements are appended. #### Signature ```rust theme={null} fn len(self: T) -> u64 ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait}; #[storage] struct Storage { numbers: Vec, } fn is_empty(self: @ContractState) -> bool { self.numbers.len() == 0 } ``` ### append Returns a mutable storage path to write a new element at the end of the vector. This operation: 1. Increments the vector's length 2. Returns a storage path to write the new element #### Signature ```rust theme={null} fn append(self: T) -> StoragePathElementType>> ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; #[storage] struct Storage { numbers: Vec, } fn push_number(ref self: ContractState, number: u256) { self.numbers.append().write(number); } ``` ### allocate Allocates space for a new element at the end of the vector, returning a mutable storage path to write the element. This function is a replacement for the deprecated `append` function, which allowed appending new elements to a vector. Unlike `push`, which gets an object to write to the vector, `allocate` is specifically useful when you need to prepare space for elements of unknown or dynamic size (e.g., appending another vector). #### Use Case `allocate` is essential when pushing a vector into another vector, as the size of the nested vector is unknown at compile time. It allows the caller to allocate the required space first, then write the nested vector into the allocated space using `.write()`. This is necessary because pushing directly (e.g., `vec.push(nested_vec)`) is not supported due to `Vec` being only a storage abstraction. #### Deprecation Note The `append` function is now deprecated. Use `allocate` to achieve the same functionality with improved clarity and flexibility. #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; #[storage] struct Storage { numbers: Vec>, } fn append_nested_vector(ref self: ContractState, elements: Array) { // Allocate space for the nested vector in the outer vector. let new_vec_storage_path = self.numbers.allocate(); for element in elements { new_vec_storage_path.push(element) } } ``` #### Signature ```rust theme={null} fn allocate(self: T) -> StoragePathElementType>> ``` ### push Pushes a new value onto the vector. This operation: 1. Increments the vector's length. 2. Writes the provided value to the new storage location at the end of the vector. #### Note If you need to allocate storage without writing a value (e.g., when appending another vector), consider using `allocate` instead. #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait}; #[storage] struct Storage { numbers: Vec, } fn push_number(ref self: ContractState, number: u256) { self.numbers.push(number); } ``` #### Signature ```rust theme={null} fn push, +starknet::Store>( self: T, value: Self::ElementType, ) ``` ### pop Pops the last value off the vector. This operation: 1. Retrieves the value stored at the last position in the vector. 2. Decrements the vector's length. 3. Returns the retrieved value or `None` if the vector is empty. #### Signature ```rust theme={null} fn pop, +starknet::Store>(self: T) -> OptionElementType> ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, MutableVecTrait}; #[storage] struct Storage { numbers: Vec, } fn pop_number(ref self: ContractState) -> Option { self.numbers.pop() } ``` ## Trait types ### ElementType #### Signature ```rust theme={null} type ElementType; ``` # core::starknet::storage::vec::Vec Source: https://docs.starknet.io/build/corelib/core-starknet-storage-vec-Vec Represents a dynamic array in contract storage. This type is zero-sized and cannot be instantiated. Vectors can only be used in storage contexts and manipulated using the associated `VecTrait` and `MutableVecTrait` traits. ## Signature ```rust theme={null} #[phantom] pub struct Vec {} ``` # core::starknet::storage::vec::VecIter Source: https://docs.starknet.io/build/corelib/core-starknet-storage-vec-VecIter An iterator struct over a `Vec` in storage. ## Signature ```rust theme={null} #[derive(Drop)] pub struct VecIter> { vec: T, current_index: IntRange, } ``` # core::starknet::storage::vec::VecTrait Source: https://docs.starknet.io/build/corelib/core-starknet-storage-vec-VecTrait Provides read-only access to elements in a storage [`Vec`](./core-starknet-storage-vec-Vec). This trait enables retrieving elements and checking the vector's length without modifying the underlying storage. ## Signature ```rust theme={null} pub trait VecTrait ``` ## Trait functions ### get Returns a storage path to the element at the specified index, or `None` if out of bounds. #### Signature ```rust theme={null} fn get(self: T, index: u64) -> OptionElementType>> ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, VecTrait, StoragePointerReadAccess}; #[storage] struct Storage { numbers: Vec, } fn maybe_number(self: @ContractState, index: u64) -> Option { self.numbers.get(index).map(|ptr| ptr.read()) } ``` ### at Returns a storage path to access the element at the specified index. #### Panics Panics if the index is out of bounds. #### Examples ```rust theme={null} use starknet::storage::{Vec, VecTrait, StoragePointerReadAccess}; #[storage] struct Storage { numbers: Vec, } fn get_number(self: @ContractState, index: u64) -> u256 { self.numbers.at(index).read() } ``` #### Signature ```rust theme={null} fn at(self: T, index: u64) -> StoragePathElementType> ``` ### len Returns the number of elements in the vector. The length is stored at the vector's base storage address and is automatically updated when elements are appended. #### Signature ```rust theme={null} fn len(self: T) -> u64 ``` #### Examples ```rust theme={null} use starknet::storage::{Vec, VecTrait}; #[storage] struct Storage { numbers: Vec, } fn is_empty(self: @ContractState) -> bool { self.numbers.len() == 0 } ``` ## Trait types ### ElementType #### Signature ```rust theme={null} type ElementType; ``` # core::starknet::storage_access::StorageAddress Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-StorageAddress Represents the address of a storage value in a Starknet contract. The value range of this type is `[0, 2**251)`. ## Signature ```rust theme={null} pub extern type StorageAddress; ``` # core::starknet::storage_access::StorageBaseAddress Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-StorageBaseAddress Represents a base storage address that can be combined with offsets. The value range of this type is `[0, 2**251 - 256)`. ## Signature ```rust theme={null} pub extern type StorageBaseAddress; ``` # core::starknet::storage_access::Store Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-Store Trait for types that can be stored in Starknet contract storage. The `Store` trait enables types to be stored in and retrieved from Starknet's contract storage. Cairo implements `Store` for most primitive types. However, collection types (arrays, dicts, etc.) do not implement `Store` directly. Instead, use specialized storage types, such as [`Vec`](./core-starknet-storage-vec-Vec) or [`Map`](./core-starknet-storage-map-Map). ## Signature ```rust theme={null} pub trait Store ``` ## Derivation To make a type storable in contract storage, simply derive the `Store` trait: ```rust theme={null} #[derive(Drop, starknet::Store)] struct Sizes { tiny: u8, // 8 bits small: u32, // 32 bits medium: u64, // 64 bits } ``` This allows the `Size` struct to be stored in a contract's storage. There's no real reason to implement this trait yourself, as it can be trivially derived. For efficiency purposes, consider manually implementing [`StorePacking`](./core-starknet-storage_access-StorePacking) to optimize storage usage. ## Trait functions ### read Reads a value from storage at the given domain and base address. #### Arguments * `address_domain` - The storage domain (currently only 0 is supported) * `base` - The base storage address to read from #### Signature ```rust theme={null} fn read(address_domain: u32, base: StorageBaseAddress) -> Result> ``` ### write Writes a value to storage at the given domain and base address. #### Arguments * `address_domain` - The storage domain (currently only 0 is supported) * `base` - The base storage address to write to * `value` - The value to store #### Signature ```rust theme={null} fn write( address_domain: u32, base: StorageBaseAddress, value: T, ) -> Result> ``` ### read\_at\_offset Reads a value from storage at a base address plus an offset. #### Arguments * `address_domain` - The storage domain (currently only 0 is supported) * `base` - The base storage address * `offset` - The offset from the base address where the value should be read #### Signature ```rust theme={null} fn read_at_offset( address_domain: u32, base: StorageBaseAddress, offset: u8, ) -> Result> ``` ### write\_at\_offset Writes a value to storage at a base address plus an offset. #### Arguments * `address_domain` - The storage domain (currently only 0 is supported) * `base` - The base storage address * `offset` - The offset from the base address where the value should be written * `value` - The value to store #### Signature ```rust theme={null} fn write_at_offset( address_domain: u32, base: StorageBaseAddress, offset: u8, value: T, ) -> Result> ``` ### size Returns the size in storage for this type. This is bounded to 255, as the offset is a u8. As such, a single type can only take up to 255 slots in storage. #### Signature ```rust theme={null} fn size() -> u8 ``` ### scrub Clears the storage area by writing zeroes to it. #### Arguments * `address_domain` - The storage domain * `base` - The base storage address to start clearing * `offset` - The offset from the base address where clearing should start The operation writes zeroes to storage starting from the specified base address and offset, and continues for the size of the type as determined by the `size()` function. #### Signature ```rust theme={null} fn scrub( address_domain: u32, base: StorageBaseAddress, offset: u8, ) -> Result> ``` # core::starknet::storage_access::StorePacking Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-StorePacking Trait for efficient packing of values into optimized storage representations. This trait enables bit-packing of complex types into simpler storage types to reduce gas costs by minimizing the number of storage slots used. When a type implements `StorePacking`, the compiler automatically uses `StoreUsingPacking` to handle storage operations. As such, a type cannot implement both `Store` and `StorePacking`. ## Signature ```rust theme={null} pub trait StorePacking ``` # Storage Optimization Each storage slot in Starknet is a `felt252`, and storage operations are expensive. By packing multiple values into fewer slots, you can significantly reduce gas costs. For example: * Multiple small integers can be packed into a single `felt252` * Structs with several fields can be compressed into a single storage slot # Implementation Requirements To implement `StorePacking`, ensure that the `PackedT` type implements [`Store`](./core-starknet-storage_access-Store). The packed representation must preserve all necessary information to allow unpacking back to the original type. Additionally, the `pack` and `unpack` operations must be reversible, meaning that packing followed by unpacking should return the original value. # Example Packing multiple integer fields into a single storage slot: ```rust theme={null} use starknet::storage_access::StorePacking; #[derive(Drop)] struct Sizes { tiny: u8, // 8 bits small: u32, // 32 bits medium: u64, // 64 bits } const TWO_POW_8: u128 = 0x100; const TWO_POW_40: u128 = 0x10000000000; impl SizesStorePacking of StorePacking { fn pack(value: Sizes) -> u128 { value.tiny.into() + (value.small.into() * TWO_POW_8) + (value.medium.into() * TWO_POW_40) } fn unpack(value: u128) -> Sizes { let tiny = value & 0xff; let small = (value / TWO_POW_8) & 0xffffffff; let medium = (value / TWO_POW_40); Sizes { tiny: tiny.try_into().unwrap(), small: small.try_into().unwrap(), medium: medium.try_into().unwrap(), } } } ``` By implementing `StorePacking` for `Sizes`, the `Sizes` will be stored in its packed form, using a single storage slot instead of 3. When retrieved, it will automatically be unpacked back into the original type. ## Trait functions ### pack Packs a value into its optimized storage representation. #### Signature ```rust theme={null} fn pack(value: T) -> PackedT ``` ### unpack Unpacks a storage representation back into the original type. #### Signature ```rust theme={null} fn unpack(value: PackedT) -> T ``` # core::starknet::storage_access::storage_address_from_base Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-storage_address_from_base Converts a `StorageBaseAddress` into a `StorageAddress`. This should be used through the high-level `Into` trait. ## Signature ```rust theme={null} pub extern fn storage_address_from_base(base: StorageBaseAddress) -> StorageAddress nopanic; ``` # core::starknet::storage_access::storage_address_from_base_and_offset Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-storage_address_from_base_and_offset Sums the base address and the offset to return a storage address. ## Signature ```rust theme={null} pub extern fn storage_address_from_base_and_offset(base: StorageBaseAddress, offset: u8) -> StorageAddress nopanic; ``` # core::starknet::storage_access::storage_base_address_const Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-storage_base_address_const Returns a `StorageBaseAddress` given a constant `felt252` value. The value is validated to be in the range `[0, 2**251 - 256)` at compile time. ## Signature ```rust theme={null} pub extern fn storage_base_address_const() -> StorageBaseAddress nopanic; ``` ## Examples ```rust theme={null} use starknet::storage_access::storage_base_address_const; let base_address = storage_base_address_const::(); ``` # core::starknet::storage_access::storage_base_address_from_felt252 Source: https://docs.starknet.io/build/corelib/core-starknet-storage_access-storage_base_address_from_felt252 Returns a `StorageBaseAddress` given a `felt252` value. Wraps around the value if it is not in the range `[0, 2**251 - 256)`. ## Signature ```rust theme={null} pub extern fn storage_base_address_from_felt252(addr: felt252) -> StorageBaseAddress implicits(RangeCheck) nopanic; ``` # core::starknet::syscalls::call_contract_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-call_contract_syscall Calls a given contract. ## Signature ```rust theme={null} pub extern fn call_contract_syscall(address: ContractAddress, entry_point_selector: felt252, calldata: Span) -> Result, Array> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `address` - The address of the called contract. * `entry_point_selector` - A selector for a function within that contract. * `calldata` - Call arguments. # core::starknet::syscalls::deploy_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-deploy_syscall Deploys a new instance of a previously declared class. ## Signature ```rust theme={null} pub extern fn deploy_syscall(class_hash: ClassHash, contract_address_salt: felt252, calldata: Span, deploy_from_zero: bool) -> Result), Array> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `class_hash` - The class hash of the contract to be deployed. * `contract_address_salt` - The salt, an arbitrary value provided by the deployer, used in the computation of the contract's address. * `calldata` - Call arguments for the constructor. * `deploy_from_zero` - Deploy the contract from the zero address. #### Returns * The address of the deployed contract. * The serialized return value of the constructor. # core::starknet::syscalls::emit_event_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-emit_event_syscall Emits an event. ## Signature ```rust theme={null} pub extern fn emit_event_syscall(keys: Span, data: Span) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `keys` - The keys of the event. * `data` - The data of the event. # core::starknet::syscalls::get_block_hash_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-get_block_hash_syscall Returns the hash of the block with the given number. ## Signature ```rust theme={null} pub extern fn get_block_hash_syscall(block_number: u64) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `block_number` - The number of the queried block. #### Returns * The hash of the block with the given number. # core::starknet::syscalls::get_class_hash_at_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-get_class_hash_at_syscall Gets the class hash of the contract at the given address. ## Signature ```rust theme={null} pub extern fn get_class_hash_at_syscall(contract_address: ContractAddress) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `contract_address` - The address of the deployed contract. #### Returns * The class hash of the contract's originating code. # core::starknet::syscalls::get_execution_info_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-get_execution_info_syscall Gets information about the currently executing block and the transactions within it. For a complete description of this information, see `Execution information`. When an account’s `__validate__`, `__validate_deploy__`, or `__validate_declare__` function calls `get_execution_info`, the return values for `block_timestamp` and `block_number` are modified as follows: * `block_timestamp` returns the hour, rounded down to the nearest hour. * `block_number` returns the block number, rounded down to the nearest multiple of 100. ## Signature ```rust theme={null} pub extern fn get_execution_info_syscall() -> Result, Array> implicits(GasBuiltin, System) nopanic; ``` #### Returns * A struct that contains information about the currently executing function, transaction, and block. # core::starknet::syscalls::get_execution_info_v2_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-get_execution_info_v2_syscall Gets information about the current execution, version 2. This syscall should not be called directly. Instead, use `starknet::info::get_execution_info`. ## Signature ```rust theme={null} pub extern fn get_execution_info_v2_syscall() -> Result, Array> implicits(GasBuiltin, System) nopanic; ``` #### Returns * A box containing the current V2 execution information. # core::starknet::syscalls::keccak_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-keccak_syscall Computes the keccak of the input. * The input must be a multiple of 1088 bits (== 17 u64 words) * The input must be pre-padded following the Keccak padding rule (pad10\*1): 1. Add a '1' bit 2. Add zero or more '0' bits 3. Add a final '1' bit The total length after padding must be a multiple of 1088 bits ## Signature ```rust theme={null} pub extern fn keccak_syscall(input: Span) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `input` - Array of 64-bit words (little endian) to be hashed. #### Returns * The keccak hash as a little-endian u256 # core::starknet::syscalls::library_call_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-library_call_syscall Calls the requested function in any previously declared class. ## Signature ```rust theme={null} pub extern fn library_call_syscall(class_hash: ClassHash, function_selector: felt252, calldata: Span) -> Result, Array> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `class_hash` - The hash of the class to be used. * `function_selector` - A selector for a function within that class. * `calldata` - Call arguments. # core::starknet::syscalls::meta_tx_v0_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-meta_tx_v0_syscall Invokes the given entry point as a v0 meta transaction. * The signature is replaced with the given signature. * The caller is the OS (address 0). * The transaction version is replaced by 0. * The transaction hash is replaced by the corresponding version-0 transaction hash. The changes apply to the called contract and the inner contracts it calls. NOTE: This syscall should only be used to allow support for old version-0 bound accounts, and should not be used for other purposes. ## Signature ```rust theme={null} pub extern fn meta_tx_v0_syscall(address: ContractAddress, entry_point_selector: felt252, calldata: Span, signature: Span) -> Result, Array> implicits(GasBuiltin, System) nopanic; ``` # core::starknet::syscalls::replace_class_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-replace_class_syscall Replaces the class hash of the current contract, instantly modifying its entrypoints. The new class becomes effective only after the current function call completes. The remaining code in the current function will continue executing from the old class. The new class will be used: * In subsequent transactions * If the contract is called via `call_contract` syscall later in the same transaction ## Signature ```rust theme={null} pub extern fn replace_class_syscall(class_hash: ClassHash) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `class_hash` - The class hash that should replace the current one. # core::starknet::syscalls::send_message_to_l1_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-send_message_to_l1_syscall Sends a message to L1. ## Signature ```rust theme={null} pub extern fn send_message_to_l1_syscall(to_address: felt252, payload: Span) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `to_address` - The recipient's L1 address. * `payload` - The content of the message. # core::starknet::syscalls::sha256_process_block_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-sha256_process_block_syscall Computes the next SHA-256 state of the input with the given state. ## Signature ```rust theme={null} pub extern fn sha256_process_block_syscall(state: Sha256StateHandle, input: Box) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `state` - The current SHA-256 state. * `input` - The input provided to compute the next SHA-256 state. #### Returns * The next SHA-256 state of the input with the givens state. The system call does not add any padding and the input needs to be a multiple of 512 bits (== 16 u32 word). # core::starknet::syscalls::storage_read_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-storage_read_syscall Gets the value of a key in the storage of the calling contract. ## Signature ```rust theme={null} pub extern fn storage_read_syscall(address_domain: u32, address: StorageAddress) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `address_domain` - The domain of the address. Only `address_domain` 0 is currently supported, in the future it will enable access to address spaces with different data availability guarantees. * `address` - The address of the storage key to read. # core::starknet::syscalls::storage_write_syscall Source: https://docs.starknet.io/build/corelib/core-starknet-syscalls-storage_write_syscall Sets the value of a key in the storage of the calling contract. ## Signature ```rust theme={null} pub extern fn storage_write_syscall(address_domain: u32, address: StorageAddress, value: felt252) -> Result> implicits(GasBuiltin, System) nopanic; ``` #### Arguments * `address_domain` - The domain of the address. Only `address_domain` 0 is currently supported, in the future it will enable access to address spaces with different data availability guarantees. * `address` - The address of the storage key to write. * `value` - The value to write to the key. # core::starknet::testing::cheatcode Source: https://docs.starknet.io/build/corelib/core-starknet-testing-cheatcode A general cheatcode function used to simplify implementation of Starknet testing functions. This is the base function used by testing utilities to interact with the test environment. External users can implement custom cheatcodes by injecting a custom `CairoHintProcessor` in the runner. ## Signature ```rust theme={null} pub extern fn cheatcode(input: Span) -> Span nopanic; ``` #### Arguments `selector` - The cheatcode identifier. `input` - Input parameters for the cheatcode. #### Returns * A span containing the cheatcode's output # core::starknet::testing::pop_l2_to_l1_message Source: https://docs.starknet.io/build/corelib/core-starknet-testing-pop_l2_to_l1_message Pop the earliest unpopped l2 to l1 message for the contract. ## Signature ```rust theme={null} pub fn pop_l2_to_l1_message(address: ContractAddress) -> Option)> ``` #### Arguments `address` - The contract address from which to pop a l2-L1 message. The returned value is a tuple of the l1 address the message was sent to as a `felt252`, and the message data as a span. May be called multiple times to pop multiple messages. Useful for testing the contract's l2 to l1 message emission. # core::starknet::testing::pop_log Source: https://docs.starknet.io/build/corelib/core-starknet-testing-pop_log Pop the earliest unpopped logged event for the contract as the requested type. ## Signature ```rust theme={null} pub fn pop_log>(address: ContractAddress) -> Option ``` #### Arguments `address` - The contract address from which to pop an event. Should be used when the type of the event is known. Type of the event should be the event defined within the contract. Useful for testing the contract's event emission. May be called multiple times to pop multiple events. If called until `None` is returned, all events have been popped. ## Examples ```rust theme={null} #[starknet::contract] mod contract { #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub enum Event { Event1: felt252, Event2: u128, } ... } #[test] fn test_event() { let contract_address = somehow_get_contract_address(); call_code_causing_events(contract_address); assert_eq!( starknet::testing::pop_log(contract_address), Some(contract::Event::Event1(42)) ); assert_eq!( starknet::testing::pop_log(contract_address), Some(contract::Event::Event2(41)) ); assert_eq!( starknet::testing::pop_log(contract_address), Some(contract::Event::Event1(40)) ); assert_eq!(starknet::testing::pop_log_raw(contract_address), None); } ``` # core::starknet::testing::pop_log_raw Source: https://docs.starknet.io/build/corelib/core-starknet-testing-pop_log_raw Pop the earliest unpopped logged event for the contract. ## Signature ```rust theme={null} pub fn pop_log_raw(address: ContractAddress) -> Option, Span)> ``` #### Arguments `address` - The contract address from which to pop an event. The value is returned as a tuple of two spans, the first for the keys and the second for the data. May be called multiple times to pop multiple events. If called until `None` is returned, all events have been popped. # core::starknet::testing::set_account_contract_address Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_account_contract_address Sets the account contract address. ## Signature ```rust theme={null} pub fn set_account_contract_address(address: ContractAddress) ``` #### Arguments `address` - The account contract to set. After a call to `set_account_contract_address`, `starknet::get_execution_info().tx_info.account_contract_address` will return the set value. # core::starknet::testing::set_block_hash Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_block_hash Set the hash for a block. ## Signature ```rust theme={null} pub fn set_block_hash(block_number: u64, value: felt252) ``` #### Arguments `block_number` - The targeted block number. `value` - The block hash to set. After a call to `set_block_hash`, `starknet::syscalls::get_block_hash_syscall` for the block\_number will return the set value. Unset blocks values call would fail. # core::starknet::testing::set_block_number Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_block_number Sets the block number to the provided value. ## Signature ```rust theme={null} pub fn set_block_number(block_number: u64) ``` #### Arguments `block_number` - The block number to set. After a call to `set_block_number`, `starknet::get_execution_info().block_info.block_number` will return the set value. # core::starknet::testing::set_block_timestamp Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_block_timestamp Sets the block timestamp to the provided value. ## Signature ```rust theme={null} pub fn set_block_timestamp(block_timestamp: u64) ``` #### Arguments `block_timestamp` - The block timestamp to set. After a call to `set_block_timestamp`, `starknet::get_execution_info().block_info.block_timestamp` will return the set value. # core::starknet::testing::set_caller_address Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_caller_address Sets the caller address to the provided value. ## Signature ```rust theme={null} pub fn set_caller_address(address: ContractAddress) ``` #### Arguments `address` - The caller address to set. After a call to `set_caller_address`, `starknet::get_execution_info().caller_address` will return the set value. # core::starknet::testing::set_chain_id Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_chain_id Set the transaction chain id. ## Signature ```rust theme={null} pub fn set_chain_id(chain_id: felt252) ``` #### Arguments `chain_id` - The chain id to set. After a call to `set_chain_id`, `starknet::get_execution_info().tx_info.chain_id` will return the set value. # core::starknet::testing::set_contract_address Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_contract_address Sets the contract address to the provided value. ## Signature ```rust theme={null} pub fn set_contract_address(address: ContractAddress) ``` #### Arguments `address` - The contract address to set. After a call to `set_contract_address`, `starknet::get_execution_info().contract_address` will return the set value. # core::starknet::testing::set_max_fee Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_max_fee Sets the transaction max fee. ## Signature ```rust theme={null} pub fn set_max_fee(fee: u128) ``` #### Arguments `fee` - The max fee to set. After a call to `set_max_fee`, `starknet::get_execution_info().tx_info.max_fee` will return the set value. # core::starknet::testing::set_nonce Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_nonce Set the transaction nonce. ## Signature ```rust theme={null} pub fn set_nonce(nonce: felt252) ``` #### Arguments `non` - The nonce to set. After a call to `set_nonce`, `starknet::get_execution_info().tx_info.nonce` will return the set value. # core::starknet::testing::set_sequencer_address Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_sequencer_address Sets the sequencer address to the provided value. ## Signature ```rust theme={null} pub fn set_sequencer_address(address: ContractAddress) ``` #### Arguments `address` - The sequencer address to set. After a call to `set_sequencer_address`, `starknet::get_execution_info().block_info.sequencer_address` will return the set value. # core::starknet::testing::set_signature Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_signature Set the transaction signature. ## Signature ```rust theme={null} pub fn set_signature(signature: Span) ``` #### Arguments `signature` - The signature to set. After a call to `set_signature`, `starknet::get_execution_info().tx_info.signature` will return the set value. # core::starknet::testing::set_transaction_hash Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_transaction_hash Sets the transaction hash. ## Signature ```rust theme={null} pub fn set_transaction_hash(hash: felt252) ``` #### Arguments `hash` - The transaction hash to set. After a call to `set_transaction_hash`, `starknet::get_execution_info().tx_info.transaction_hash` will return the set value. # core::starknet::testing::set_version Source: https://docs.starknet.io/build/corelib/core-starknet-testing-set_version Sets the version to the provided value. ## Signature ```rust theme={null} pub fn set_version(version: felt252) ``` #### Arguments `version` - The version to set. After a call to `set_version`, `starknet::get_execution_info().tx_info.version` will return the set value. # Core string StringLiteral Source: https://docs.starknet.io/build/corelib/core-string-StringLiteral # StringLiteral Fully qualified path: [core](./intro.md)::[string](./core-string.md)::[StringLiteral](./core-string-StringLiteral.md)
pub trait StringLiteral\
# core::testing::get_available_gas Source: https://docs.starknet.io/build/corelib/core-testing-get_available_gas Returns the amount of gas available in the `GasBuiltin`. Useful for asserting that a certain amount of gas was consumed. Note: The actual gas consumption observed by calls to `get_available_gas` is only exact immediately before calls to `withdraw_gas`. ## Examples ```rust theme={null} use core::testing::get_available_gas; fn gas_heavy_function() { // ... some gas-intensive code } fn test_gas_consumption() { let gas_before = get_available_gas(); // Making sure `gas_before` is exact. core::gas::withdraw_gas().unwrap(); gas_heavy_function(); let gas_after = get_available_gas(); // Making sure `gas_after` is exact core::gas::withdraw_gas().unwrap(); assert!(gas_after - gas_before u128 implicits(GasBuiltin) nopanic; ``` # core::testing::get_unspent_gas Source: https://docs.starknet.io/build/corelib/core-testing-get_unspent_gas Returns the amount of gas available in the `GasBuiltin`, as well as the amount of gas unused in the local wallet. Useful for asserting that a certain amount of gas was used. Note: This function call costs exactly `2300` gas, so this may be ignored in calculations. ## Examples ```rust theme={null} use core::testing::get_unspent_gas; fn gas_heavy_function() { // ... some gas-intensive code } fn test_gas_consumption() { let gas_before = get_unspent_gas(); gas_heavy_function(); let gas_after = get_unspent_gas(); assert!(gas_after - gas_before u128 implicits(GasBuiltin) nopanic; ``` # core::to_byte_array::AppendFormattedToByteArray Source: https://docs.starknet.io/build/corelib/core-to_byte_array-AppendFormattedToByteArray A trait for appending the ASCII representation of a number to an existing `ByteArray`. ## Signature ```rust theme={null} pub trait AppendFormattedToByteArray ``` ## Trait functions ### append\_formatted\_to\_byte\_array Appends the ASCII representation of the value to the provided `ByteArray`. #### Signature ```rust theme={null} fn append_formatted_to_byte_array(self: @T, ref byte_array: ByteArray, base: NonZero) ``` #### Examples ```rust theme={null} use core::to_byte_array::AppendFormattedToByteArray; let mut buffer = "Count: "; let num: u32 = 42; num.append_formatted_to_byte_array(ref buffer, 10); assert!(buffer == "Count: 42"); ``` # core::to_byte_array::FormatAsByteArray Source: https://docs.starknet.io/build/corelib/core-to_byte_array-FormatAsByteArray A trait for formatting values into their ASCII string representation in a `ByteArray`. ## Signature ```rust theme={null} pub trait FormatAsByteArray ``` ## Trait functions ### format\_as\_byte\_array Returns a new `ByteArray` containing the ASCII representation of the value. #### Signature ```rust theme={null} fn format_as_byte_array(self: @T, base: NonZero) -> ByteArray ``` #### Examples ```rust theme={null} use core::to_byte_array::FormatAsByteArray; let num: u32 = 42; let formatted = num.format_as_byte_array(16); assert!(formatted, "2a"); ``` # core::traits::Add Source: https://docs.starknet.io/build/corelib/core-traits-Add The addition operator `+`. ## Signature ```rust theme={null} pub trait Add ``` ## Examples `Add`able types: ```rust theme={null} assert!(1_u8 + 2_u8 == 3_u8); ``` Implementing `Add` for a type: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Point { x: u32, y: u32, } impl PointAdd of Add { fn add(lhs: Point, rhs: Point) -> Point { Point { x: lhs.x + rhs.x, y: lhs.y + rhs.y, } } } let p1 = Point { x: 1, y: 0 }; let p2 = Point { x: 2, y: 3 }; let p3 = p1 + p2; assert!(p3 == Point { x: 3, y: 3 }); ``` ## Trait functions ### add Performs the `+` operation. #### Signature ```rust theme={null} fn add(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(12 + 1 == 13); ``` # core::traits::AddEq Source: https://docs.starknet.io/build/corelib/core-traits-AddEq ## Signature ```rust theme={null} pub trait AddEq ``` ## Trait functions ### add\_eq #### Signature ```rust theme={null} fn add_eq(ref self: T, other: T) ``` # core::traits::BitAnd Source: https://docs.starknet.io/build/corelib/core-traits-BitAnd The bitwise AND operator `&`. ## Signature ```rust theme={null} pub trait BitAnd ``` ## Examples An implementation of `BitAnd` for a wrapper around `bool`. ```rust theme={null} use core::traits::BitAnd; #[derive(Drop, PartialEq)] struct Scalar { inner: bool, } impl BitAndScalar of BitAnd { fn bitand(lhs: Scalar, rhs: Scalar) -> Scalar { Scalar { inner: lhs.inner & rhs.inner } } } assert!(Scalar { inner: true } & Scalar { inner: true } == Scalar { inner: true }); assert!(Scalar { inner: true } & Scalar { inner: false } == Scalar { inner: false }); assert!(Scalar { inner: false } & Scalar { inner: true } == Scalar { inner: false }); assert!(Scalar { inner: false } & Scalar { inner: false } == Scalar { inner: false }); ``` ## Trait functions ### bitand Performs the `&` operation. #### Signature ```rust theme={null} fn bitand(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert_eq!(true & false, false); assert_eq!(5_u8 & 1_u8, 1); assert_eq!(true & true, true); assert_eq!(5_u8 & 2_u8, 0); ``` # core::traits::BitNot Source: https://docs.starknet.io/build/corelib/core-traits-BitNot The bitwise NOT operator `~`. ## Signature ```rust theme={null} pub trait BitNot ``` ## Examples An implementation of `BitNot` for a wrapper around `u8`. ```rust theme={null} use core::traits::BitNot; #[derive(Drop, PartialEq)] struct Wrapper { u8: u8, } impl BitNotWrapper of BitNot { fn bitnot(a: Wrapper) -> Wrapper { Wrapper { u8: ~a.u8 } } } assert!(~Wrapper { u8: 0 } == Wrapper { u8 : 255 }); assert!(~Wrapper { u8: 1 } == Wrapper { u8 : 254 }); ``` ## Trait functions ### bitnot Performs the `~` operation. #### Signature ```rust theme={null} fn bitnot(a: T) -> T ``` #### Examples ```rust theme={null} assert!(~1_u8 == 254); ``` # core::traits::BitOr Source: https://docs.starknet.io/build/corelib/core-traits-BitOr The bitwise OR operator `|`. ## Signature ```rust theme={null} pub trait BitOr ``` ## Examples An implementation of `BitOr` for a wrapper around `bool`. ```rust theme={null} use core::traits::BitOr; #[derive(Drop, PartialEq)] struct Scalar { inner: bool, } impl BitOrScalar of BitOr { fn bitor(lhs: Scalar, rhs: Scalar) -> Scalar { Scalar { inner: lhs.inner | rhs.inner } } } assert!(Scalar { inner: true } | Scalar { inner: true } == Scalar { inner: true }); assert!(Scalar { inner: true } | Scalar { inner: false } == Scalar { inner: true }); assert!(Scalar { inner: false } | Scalar { inner: true } == Scalar { inner: true }); assert!(Scalar { inner: false } | Scalar { inner: false } == Scalar { inner: false }); ``` ## Trait functions ### bitor Performs the `|` operation. #### Signature ```rust theme={null} fn bitor(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(1_u8 | 2_u8 == 3); ``` # core::traits::BitXor Source: https://docs.starknet.io/build/corelib/core-traits-BitXor The bitwise XOR operator `^`. ## Signature ```rust theme={null} pub trait BitXor ``` ## Examples An implementation of `BitXor` for a wrapper around `bool`. ```rust theme={null} use core::traits::BitXor; #[derive(Drop, PartialEq)] struct Scalar { inner: bool, } impl BitXorScalar of BitXor { fn bitxor(lhs: Scalar, rhs: Scalar) -> Scalar { Scalar { inner: lhs.inner ^ rhs.inner } } } assert!(Scalar { inner: true } ^ Scalar { inner: true } == Scalar { inner: false }); assert!(Scalar { inner: true } ^ Scalar { inner: false } == Scalar { inner: true }); assert!(Scalar { inner: false } ^ Scalar { inner: true } == Scalar { inner: true }); assert!(Scalar { inner: false } ^ Scalar { inner: false } == Scalar { inner: false }); ``` ## Trait functions ### bitxor Performs the `^` operation. #### Signature ```rust theme={null} fn bitxor(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(1_u8 ^ 2_u8 == 3); ``` # core::traits::Copy Source: https://docs.starknet.io/build/corelib/core-traits-Copy A trait for copying values. By default, variables in Cairo have 'move semantics', meaning they are moved when used. However, types implementing `Copy` have 'copy semantics', allowing the value to be duplicated instead of moved. ## Signature ```rust theme={null} pub trait Copy ``` ## Examples Without `Copy` (move semantics): ```rust theme={null} #[derive(Drop)] struct Point { x: u128, y: u128, } fn main() { let p1 = Point { x: 5, y: 10 }; foo(p1); foo(p1); // error: Variable was previously moved. } fn foo(p: Point) {} ``` With `Copy` (copy semantics): ```rust theme={null} #[derive(Copy, Drop)] struct Point { x: u128, y: u128, } fn main() { let p1 = Point { x: 5, y: 10 }; foo(p1); foo(p1); // works: `p1` is copied when passed to `foo` } fn foo(p: Point) {} ``` # core::traits::Default Source: https://docs.starknet.io/build/corelib/core-traits-Default A trait for giving a type a useful default value. Cairo implements `Default` for various primitives types. This trait can be used with `#[derive]` if all of the type's fields implement `Default`. When `derive`d, it will use the default value for each field's type. When using `#[derive(Default)]` on an `enum`, you need to choose which unit variant will be default. You do this by placing the `#[default]` attribute on the variant. ```rust theme={null} #[derive(Default)] enum Kind { #[default] A, B, C, } ``` You can even use the `#[default]` attribute even on non-unit variants, provided that the associated type implements `Default`. ## Signature ```rust theme={null} pub trait Default ``` ## Examples ```rust theme={null} #[derive(Drop, Default, PartialEq)] struct SomeOptions { foo: i32, bar: u32, } assert!(Default::default() == SomeOptions { foo: 0, bar: 0 }); ``` To implement `Default`, provide an implementation for the `default()` method that returns the value of your type that should be the default: ```rust theme={null} #[derive(Copy, Drop)] enum Kind { A, B, C, } impl DefaultKind of Default { fn default() -> Kind { Kind::A } } ``` ## Trait functions ### default Returns the "default value" for a type. Default values are often some kind of initial value, identity value, or anything else that may make sense as a default. #### Signature ```rust theme={null} fn default() -> T ``` #### Examples ```rust theme={null} let i: i8 = Default::default(); let (x, y): (Option, u64) = Default::default(); let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default(); ``` # core::traits::Destruct Source: https://docs.starknet.io/build/corelib/core-traits-Destruct A trait that allows for custom destruction behavior of a type. In Cairo, values must be explicitly handled - they cannot be silently dropped. Types can only go out of scope in two ways: 1. Implement `Drop` - for types that can be discarded trivially 2. Implement `Destruct` - for types that need cleanup when destroyed. Typically, any type that contains a `Felt252Dict` must implement `Destruct`, as the `Felt252Dict` needs to be "squashed" when going out of scope to ensure a program is sound. Generally, `Destruct` does not need to be implemented manually. It can be derived from the `Drop` and `Destruct` implementations of the type's fields. ## Signature ```rust theme={null} pub trait Destruct ``` ## Examples Here's a simple type that wraps a `Felt252Dict` and needs to be destructed: ```rust theme={null} use core::dict::Felt252Dict; // A struct containing a Felt252Dict must implement Destruct #[derive(Destruct, Default)] struct ResourceManager { resources: Felt252Dict, count: u32, } #[generate_trait] impl ResourceManagerImpl of ResourceManagerTrait{ fn add_resource(ref self: ResourceManager, resource_id: felt252, amount: u32){ assert!(self.resources.get(resource_id) == 0, "Resource already exists"); self.resources.insert(resource_id, amount); self.count += amount; } } let mut manager = Default::default(); // Add some resources manager.add_resource(1, 100); // When manager goes out of scope here, Destruct is automatically called, // which ensures the dictionary is properly squashed ``` ## Trait functions ### destruct #### Signature ```rust theme={null} fn destruct(self: T) ``` # core::traits::Div Source: https://docs.starknet.io/build/corelib/core-traits-Div The division operator `/`. Types implementing this trait support the division operation via the `/` operator. ## Signature ```rust theme={null} pub trait Div ``` ## Examples `Div`isible types: ```rust theme={null} assert!(4_u8 / 2_u8 == 2_u8); ``` Implementing `Div` for a type: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Point { x: u32, y: u32, } impl PointDiv of Div { fn div(lhs: Point, rhs: Point) -> Point { Point { x: lhs.x / rhs.x, y: lhs.y / rhs.y, } } } let p1 = Point { x: 2, y: 4 }; let p2 = Point { x: 2, y: 2 }; let p3 = p1 / p2; assert!(p3 == Point { x: 1, y: 2 }); ``` ## Trait functions ### div Performs the `/` operation. #### Signature ```rust theme={null} fn div(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(12 / 2 == 6); ``` # core::traits::DivEq Source: https://docs.starknet.io/build/corelib/core-traits-DivEq ## Signature ```rust theme={null} pub trait DivEq ``` ## Trait functions ### div\_eq #### Signature ```rust theme={null} fn div_eq(ref self: T, other: T) ``` # core::traits::DivRem Source: https://docs.starknet.io/build/corelib/core-traits-DivRem Performs truncated division and remainder. This trait provides a way to efficiently compute both the quotient and remainder in a single operation. The division truncates towards zero, matching the behavior of the `/` and `%` operators. ## Signature ```rust theme={null} pub trait DivRem ``` ## Examples ```rust theme={null} assert!(DivRem::div_rem(7_u32, 3) == (2, 1)); ``` ## Trait functions ### div\_rem Performs the `/` and the `%` operations, returning both the quotient and remainder. #### Signature ```rust theme={null} fn div_rem(lhs: T, rhs: NonZero) -> (T, T) ``` #### Examples ```rust theme={null} assert!(DivRem::div_rem(12_u32, 10) == (1, 2)); ``` # core::traits::Drop Source: https://docs.starknet.io/build/corelib/core-traits-Drop A trait for types that can be safely dropped. Types implementing `Drop` can be automatically discarded when they go out of scope. The drop operation is a no-op - it simply indicates to the compiler that this type can be safely discarded. ## Signature ```rust theme={null} pub trait Drop ``` ## Examples Without `Drop`: ```rust theme={null} struct Point { x: u128, y: u128, } fn foo(p: Point) {} // Error: `p` cannot be dropped ``` With `Drop`: ```rust theme={null} #[derive(Drop)] struct Point { x: u128, y: u128, } fn foo(p: Point) {} // OK: `p` is dropped at the end of the function ``` # core::traits::Felt252DictValue Source: https://docs.starknet.io/build/corelib/core-traits-Felt252DictValue A trait that must be implemented for any type that will be stored as a value in a `Felt252Dict`. When working with dictionaries in Cairo, we need a way to represent "empty" or "uninitialized" slots. This trait provides a zero-like default value that is returned when accessing a key that hasn't been explicitly set. The `Felt252Dict` implementation needs to handle cases where a key hasn't been assigned a value yet. Instead of using `Option` or similar constructs, it uses a zero-like value specific to each type. This trait is only implemented for primitive scalar types and `Nullable`. It cannot be implemented manually. Instead, if you want to use a custom type as a value in a dictionary, you can wrap your type in a [`Nullable`](./core-nullable-Nullable), which implements `Felt252DictValue` for any wrapped type. ## Signature ```rust theme={null} pub trait Felt252DictValue ``` ## Examples ```rust theme={null} use core::dict::Felt252Dict; #[derive(Copy, Drop, Default)] struct Counter { value: u32, } // u8 already implements Felt252DictValue let mut dict: Felt252Dict = Default::default(); assert!(dict.get(123) == 0); // Counter is wrapped in a Nullable, as it doesn't implement Felt252DictValue let mut counters: Felt252Dict> = Default::default(); // If the key is not set, `deref` would panic. `deref_or` returns the default value. let maybe_counter: Nullable = counters.get(123); assert!(maybe_counter.deref_or(Default::default()).value == 0); ``` ## Trait functions ### zero\_default Returns the default value for this type when used in a `Felt252Dict`. This value should be logically equivalent to zero or an "empty" state. #### Signature ```rust theme={null} fn zero_default() -> T ``` # core::traits::Index Source: https://docs.starknet.io/build/corelib/core-traits-Index ## Signature ```rust theme={null} pub trait Index ``` ## Trait functions ### index #### Signature ```rust theme={null} fn index(ref self: C, index: I) -> V ``` # core::traits::IndexView Source: https://docs.starknet.io/build/corelib/core-traits-IndexView ## Signature ```rust theme={null} pub trait IndexView ``` ## Trait functions ### index #### Signature ```rust theme={null} fn index(self: @C, index: I) -> V ``` # core::traits::Into Source: https://docs.starknet.io/build/corelib/core-traits-Into A value-to-value conversion that consumes the input value. Note: This trait must not fail. If the conversion can fail, use [`TryInto`](./core-traits-TryInto). ## Signature ```rust theme={null} pub trait Into ``` # Generic Implementations * [`Into`](./core-traits-Into) is reflexive, which means that `Into` is implemented ## Examples Converting from RGB components to a packed color value: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Color { // Packed as 0x00RRGGBB value: u32, } impl RGBIntoColor of Into { fn into(self: (u8, u8, u8)) -> Color { let (r, g, b) = self; let value = (r.into() * 0x10000_u32) + (g.into() * 0x100_u32) + b.into(); Color { value } } } // Convert RGB(255, 128, 0) to 0x00FF8000 let orange: Color = (255_u8, 128_u8, 0_u8).into(); assert!(orange == Color { value: 0x00FF8000_u32 }); ``` ## Trait functions ### into Converts the input type T into the output type S. #### Signature ```rust theme={null} fn into(self: T) -> S ``` #### Examples ```rust theme={null} let a: u8 = 1; let b: u16 = a.into(); ``` # core::traits::Mul Source: https://docs.starknet.io/build/corelib/core-traits-Mul The multiplication operator `*`. ## Signature ```rust theme={null} pub trait Mul ``` ## Examples Multipliable types: ```rust theme={null} assert!(3_u8 * 2_u8 == 6_u8); ``` Implementing `Mul` for a type: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Point { x: u32, y: u32, } impl PointMul of Mul { fn mul(lhs: Point, rhs: Point) -> Point { Point { x: lhs.x * rhs.x, y: lhs.y * rhs.y, } } } let p1 = Point { x: 2, y: 3 }; let p2 = Point { x: 1, y: 0 }; let p3 = p1 * p2; assert!(p3 == Point { x: 2, y: 0 }); ``` ## Trait functions ### mul Performs the `*` operation. #### Signature ```rust theme={null} fn mul(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(12 * 2 == 24); ``` # core::traits::MulEq Source: https://docs.starknet.io/build/corelib/core-traits-MulEq ## Signature ```rust theme={null} pub trait MulEq ``` ## Trait functions ### mul\_eq #### Signature ```rust theme={null} fn mul_eq(ref self: T, other: T) ``` # core::traits::Neg Source: https://docs.starknet.io/build/corelib/core-traits-Neg The unary negation operator `-`. ## Signature ```rust theme={null} pub trait Neg ``` ## Examples An implementation of `Neg` for `Sign`, which allows the use of `-` to negate its value. ```rust theme={null} #[derive(Copy, Drop, PartialEq)] enum Sign { Negative, Zero, Positive, } impl SignNeg of Neg { fn neg(a: Sign) -> Sign { match a { Sign::Negative => Sign::Positive, Sign::Zero => Sign::Zero, Sign::Positive => Sign::Negative, } } } // A negative positive is a negative assert!(-Sign::Positive == Sign::Negative); // A double negative is a positive assert!(-Sign::Negative == Sign::Positive); // Zero is its own negation assert!(-Sign::Zero == Sign::Zero); ``` ## Trait functions ### neg Performs the unary `-` operation. #### Signature ```rust theme={null} fn neg(a: T) -> T ``` #### Examples ```rust theme={null} let x: i8 = 1; assert!(-x == -1); ``` # core::traits::Not Source: https://docs.starknet.io/build/corelib/core-traits-Not The unary logical negation operator `!`. ## Signature ```rust theme={null} pub trait Not ``` ## Examples An implementation of `Not` for `Answer`, which enables the use of `!` to invert its value. ```rust theme={null} #[derive(Drop, PartialEq)] enum Answer { Yes, No, } impl AnswerNot of Not { fn not(a: Answer) -> Answer { match a { Answer::Yes => Answer::No, Answer::No => Answer::Yes, } } } assert!(!Answer::Yes == Answer::No); assert!(!Answer::No == Answer::Yes); ``` ## Trait functions ### not Performs the unary `!` operation. #### Signature ```rust theme={null} fn not(a: T) -> T ``` #### Examples ```rust theme={null} assert!(!true == false); assert!(!false == true); ``` # core::traits::PanicDestruct Source: https://docs.starknet.io/build/corelib/core-traits-PanicDestruct A trait that allows for destruction of a value in case of a panic. This trait is automatically implemented from the `Destruct` implementation for a type. ## Signature ```rust theme={null} pub trait PanicDestruct ``` ## Trait functions ### panic\_destruct #### Signature ```rust theme={null} fn panic_destruct(self: T, ref panic: Panic) ``` # core::traits::PartialEq Source: https://docs.starknet.io/build/corelib/core-traits-PartialEq Trait for comparisons using the equality operator. Implementing this trait for types provides the `==` and `!=` operators for those types. This trait can be used with `#[derive]`. When `derive`d on structs, two instances are equal if all fields are equal, and not equal if any fields are not equal. When `derive`d on enums, two instances are equal if they are the same variant and all fields are equal. ## Signature ```rust theme={null} pub trait PartialEq ``` ## Examples An example in which two points are equal if their x and y coordinates are equal. ```rust theme={null} #[derive(Copy, Drop)] struct Point { x: u32, y: u32 } impl PointEq of PartialEq { fn eq(lhs: @Point, rhs: @Point) -> bool { lhs.x == rhs.x && lhs.y == rhs.y } } let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 1, y: 2 }; assert!(p1 == p2); assert!(!(p1 != p2)); ``` ## Trait functions ### eq Returns whether `lhs` and `rhs` equal, and is used by `==`. #### Signature ```rust theme={null} fn eq(lhs: @T, rhs: @T) -> bool ``` #### Examples ```rust theme={null} assert!(1 == 1); ``` ### ne Returns whether `lhs` and `rhs` are not equal, and is used by `!=`. #### Signature ```rust theme={null} fn ne(lhs: @T, rhs: @T) -> bool ``` #### Examples ```rust theme={null} assert!(0 != 1); ``` # core::traits::PartialOrd Source: https://docs.starknet.io/build/corelib/core-traits-PartialOrd Trait for comparing types that form a partialorder. The `lt`, `le`, `gt`, and `ge` methods of this trait can be called using the \`\`, and `>=` operators, respectively. PartialOrd is not derivable, but can be implemented manually ## Signature ```rust theme={null} pub trait PartialOrd ``` # Implementing `PartialOrd` Here's how to implement `PartialOrd` for a custom type. This example implements comparison operations for a 2D point where points are compared based on their squared Euclidean distance from the origin (0,0): ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Point { x: u32, y: u32, } impl PointPartialOrd of PartialOrd { fn lt(lhs: Point, rhs: Point) -> bool { let lhs_dist = lhs.x * lhs.x + lhs.y * lhs.y; let rhs_dist = rhs.x * rhs.x + rhs.y * rhs.y; lhs_dist p1); assert!(p2 >= p1); ``` Note that only the `lt` method needs to be implemented. The other comparison operations (`le`, `gt`, `ge`) are automatically derived from `lt`. However, you can override them for better performance if needed. ## Trait functions ### lt Tests less than (for `self` and `other`) and is used by the \`(lhs: T, rhs: T) -> bool ``` ### ge Tests less than or equal to (for `self` and `other`) and is used by the `(lhs: T, rhs: T) -> bool ``` ### gt Tests greater than (for `self` and `other`) and is used by the `>` operator. #### Signature ```rust theme={null} fn gt(lhs: T, rhs: T) -> bool ``` #### Examples ```rust theme={null} assert_eq!(1 > 1, false); assert_eq!(1 > 2, false); assert_eq!(2 > 1, true); ``` ### le Tests greater than or equal to (for `self` and `other`) and is used by the `>=` operator. #### Signature ```rust theme={null} fn le(lhs: T, rhs: T) -> bool ``` #### Examples ```rust theme={null} assert_eq!(1 >= 1, true); assert_eq!(1 >= 2, false); assert_eq!(2 >= 1, true); ``` # core::traits::Rem Source: https://docs.starknet.io/build/corelib/core-traits-Rem The remainder operator `%`. Types implementing this trait support the remainder operation via the `%` operator. ## Signature ```rust theme={null} pub trait Rem ``` ## Examples ```rust theme={null} assert!(3_u8 % 2_u8 == 1_u8); ``` ## Trait functions ### rem Performs the `%` operation. #### Signature ```rust theme={null} fn rem(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(12_u8 % 10_u8 == 2_u8); ``` # core::traits::RemEq Source: https://docs.starknet.io/build/corelib/core-traits-RemEq ## Signature ```rust theme={null} pub trait RemEq ``` ## Trait functions ### rem\_eq #### Signature ```rust theme={null} fn rem_eq(ref self: T, other: T) ``` # core::traits::Sub Source: https://docs.starknet.io/build/corelib/core-traits-Sub The subtraction operator `-`. ## Signature ```rust theme={null} pub trait Sub ``` ## Examples `Sub`tractable types: ```rust theme={null} assert!(3_u8 - 2_u8 == 1_u8); ``` Implementing `Sub` for a type: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Point { x: u32, y: u32, } impl PointSub of Sub { fn sub(lhs: Point, rhs: Point) -> Point { Point { x: lhs.x - rhs.x, y: lhs.y - rhs.y, } } } let p1 = Point { x: 2, y: 3 }; let p2 = Point { x: 1, y: 0 }; let p3 = p1 - p2; assert!(p3 == Point { x: 1, y: 3 }); ``` ## Trait functions ### sub Performs the `-` operation. #### Signature ```rust theme={null} fn sub(lhs: T, rhs: T) -> T ``` #### Examples ```rust theme={null} assert!(12 - 1 == 11); ``` # core::traits::SubEq Source: https://docs.starknet.io/build/corelib/core-traits-SubEq ## Signature ```rust theme={null} pub trait SubEq ``` ## Trait functions ### sub\_eq #### Signature ```rust theme={null} fn sub_eq(ref self: T, other: T) ``` # core::traits::TryInto Source: https://docs.starknet.io/build/corelib/core-traits-TryInto Simple and safe type conversions that may fail in a controlled way under some circumstances. This is useful when you are doing a type conversion that may trivially succeed but may also need special handling. For example, there is no way to convert an [`i64`](./core-integer-i64) into an [`i32`](./core-integer-i32) using the [`Into`](./core-traits-Into) trait, because an [`i64`](./core-integer-i64) may contain a value that an [`i32`](./core-integer-i32) cannot represent and so the conversion would lose data. This might be handled by truncating the [`i64`](./core-integer-i64) to an [`i32`](./core-integer-i32) or by simply returning `Bounded::::MAX`, or by some other method. The [`Into`](./core-traits-Into) trait is intended for perfect conversions, so the `TryInto` trait informs the programmer when a type conversion could go bad and lets them decide how to handle it. ## Signature ```rust theme={null} pub trait TryInto ``` # Generic Implementations * [`TryInto`](./core-traits-TryInto) is reflexive, which means that `TryInto` is implemented * [`TryInto`](./core-traits-TryInto) is implemented for all types that implement [`Into`](./core-traits-Into) ## Examples Converting chess coordinates (like 'e4') into a validated position: ```rust theme={null} #[derive(Copy, Drop, PartialEq)] struct Position { file: u8, // Column a-h (0-7) rank: u8, // Row 1-8 (0-7) } impl TupleTryIntoPosition of TryInto { fn try_into(self: (u8, u8)) -> Option { let (file_char, rank) = self; // Validate rank is between 1 and 8 if rank 8 { return None; } // Validate and convert file character (a-h) to number (0-7) if file_char 'h' { return None; } let file = file_char - 'a'; Some(Position { file, rank: rank - 1 // Convert 1-8 (chess notation) to 0-7 (internal index) }) } } // Valid positions let e4 = ('e', 4).try_into(); assert!(e4 == Some(Position { file: 4, rank: 3 })); // Invalid positions let invalid_file = ('x', 4).try_into(); let invalid_rank = ('a', 9).try_into(); assert!(invalid_file == None); assert!(invalid_rank == None); ``` ## Trait functions ### try\_into Attempts to convert the input type T into the output type S. In the event of a conversion error, returns [`None`](./core-option#none). #### Signature ```rust theme={null} fn try_into(self: T) -> Option ``` #### Examples ```rust theme={null} let a: Option = 1_u16.try_into(); assert!(a == Some(1)); let b: Option = 256_u16.try_into(); assert!(b == None); ``` # core::usize Source: https://docs.starknet.io/build/corelib/core-usize `usize` is an alias for `u32` type. ## Signature ```rust theme={null} pub type usize = u32; ``` # core::zeroable::NonZero Source: https://docs.starknet.io/build/corelib/core-zeroable-NonZero A wrapper type for non-zero values of type T. This type guarantees that the wrapped value is never zero. ## Signature ```rust theme={null} pub extern type NonZero; ``` # Introduction to the Corelib documentation Source: https://docs.starknet.io/build/corelib/intro ## What is the Corelib? The Cairo core library, also known as *Corelib*, provides the foundational building blocks for writing provable programs in Cairo. It offers essential utilities, data structures, mathematical functions, cryptographic tools, and system interactions making it suitable for both onchain and offchain development. Whether you are working on Starknet smart contracts, cryptographic applications, or general-purpose Cairo programs, the corelib provides the fundamental tools needed. The corelib is available to all Cairo packages by default, meaning its features are available by simply importing specific modules into your program as follows: ```rust theme={null} use core::array::Array; fn main() { let mut arr = Array::new(); arr.append(42); } ``` ## How to use this documentation? This documentation serves as a comprehensive reference for all components of the Corelib. It organizes functionality into modules, constants, functions, types, and traits, allowing developers to explore and understand available features efficiently. It is based on the auto-generated documentation of the corelib's codebase using [Scarb](https://docs.swmansion.com/scarb/docs/extensions/documentation-generation.html), so if you find a bug, have a feature request, or want to improve the documentation, simply report an issue or submit a pull request on either [Cairo's](https://github.com/starkware-libs/cairo) or [Scarb's](https://github.com/software-mansion/scarb) GitHub repositories. If you already know what you are looking for, the fastest way to find it is to use the search bar at the top of the page. Otherwise, you can take [a quick tour of the documentation](#a-quick-tour-of-the-documentation) and start clicking on anything the seems interesting. If this is your first time, we recommend starting with browsing the library's modules and experimenting with their functionality in your Cairo programs. Happy coding! 𓅃 ## A quick tour of the documentation ### Modules | | | | :- | :- | | core::traits | Core traits for various operations. This module provides a collection of essential traits that define common behavior patterns for Cairo types. | | core::boolean | Boolean operations. The `bool` type is a primitive type in Cairo representing a boolean value that can be either `true` or `false`. | | core::circuit | Efficient modular arithmetic computations using arithmetic circuits. This module provides a type-safe way to perform modular arithmetic operations using arithmetic circuits. | | core::blake | — | | core::box | `Box` is a smart pointer that allows for heap allocation. | | core::nullable | A wrapper type for handling optional values. `Nullable` is a wrapper type that can either contain a value stored in a `Box` or be null. | | core::array | A contiguous collection of elements of the same type in memory, written `Array`. Arrays have O(1) indexing, O(1) push and O(1) pop (from the front). | | core::dict | A dictionary-like data structure that maps `felt252` keys to values of any type. The `Felt252Dict` provides efficient key-value storage with operations for inserting, retrieving, and updating values. | | core::result | Error handling with the `Result` type. `Result` is the type used for returning and propagating errors. It is an enum with the variants, `Ok(T)`, representing success and containing a value, and `Err(E)`, representing error and containing an error value. | | core::option | Optional values. The `Option` type represents an optional value: every `Option` is either `Some` and contains a value, or `None`, and does not. | | core::clone | The `Clone` trait provides the ability to duplicate instances of types that cannot be 'implicitly copied'. In Cairo, some simple types are "implicitly copyable": when you assign them or pass them as arguments, the receiver will get a copy, leaving the original value in place. | | core::ec | Functions and constructs related to elliptic curve operations on the STARK curve. This module provides implementations for various elliptic curve operations tailored for the STARK curve. | | core::ecdsa | Elliptic Curve Digital Signature Algorithm (ECDSA) for the STARK curve. This module provides implementations for ECDSA signature verification and public key recovery. | | core::integer | Integer types and operations. This module provides the built-in integer types and their associated operations. | | core::cmp | Utilities for comparing and ordering values. This module contains functions that rely on the `PartialOrd` trait for comparing values. | | core::gas | Utilities for handling gas in Cairo code. | | core::math | Mathematical operations and utilities. Provides extended GCD, modular inverse, and modular arithmetic operations. | | core::num | — | | core::ops | Overloadable operators. Implementing these traits allows you to overload certain operators. Note: Other overloadable operators are also defined in the `core::traits` module. | | core::panics | Core panic mechanism. This module provides the core panic functionality used for error handling in Cairo. It defines the basic types and functions used to trigger and manage panics, which are used when an unrecoverable error is encountered. | | core::hash | Generic hashing support. This module provides a hash state abstraction that can be updated with values and finalized to produce a hash digest. | | core::keccak | Keccak-256 cryptographic hash function implementation. | | core::pedersen | Pedersen hash related traits implementations. This module provides an implementation of the Pedersen hash function, which is a collision-resistant cryptographic hash function. The `HashState` allows for efficient computation of Pedersen hashes. | | core::qm31 | Definition for the `qm31` type. Only available for local proofs. The implementations defined in this module can be accessed by using the traits directly. | | core::serde | Serialization and deserialization of data structures. This module provides traits and implementations for converting Cairo types into a sequence of `felt252` values and back. | | core::sha256 | Implementation of the SHA-256 cryptographic hash function. This module provides functions to compute SHA-256 hashes of data. The input data can be an array of 32-bit words, or a `ByteArray`. | | core::poseidon | Poseidon hash related traits implementations and functions. This module provides cryptographic hash functions based on the Poseidon permutation. | | core::debug | Utilities related to printing of values at runtime. The recommended way of printing values is by using the `Display` and `Debug` traits available in the `fmt` module. | | core::fmt | Functionality for formatting values. The main components of this module are the formatting traits and the `Formatter` struct. | | core::starknet | Functionalities for interacting with the Starknet network. | | core::internal | — | | core::zeroable | Types and traits for handling non-zero values and zero checking operations. This module provides the `NonZero` wrapper type which guarantees that a value is never zero. | | core::bytes\_31 | Definitions and utilities for the `bytes31` type. The `bytes31` type is a compact, indexable 31-byte type. | | core::byte\_array | `ByteArray` is designed to handle large sequences of bytes with operations like appending, concatenation, and accessing individual bytes. It uses a structure that combines an `Array` of `bytes31` chunks with a pending word for efficient storage. | | core::string | — | | core::iter | Composable external iteration. If you've found yourself with a collection of some kind, and needed to perform an operation on the elements of said collection, you'll quickly run into 'iterators'. | | core::metaprogramming | Metaprogramming utilities. | | core::testing | Measurement of gas consumption for testing purpose. This module provides the `get_available_gas` function, useful for asserting the amount of gas consumed by a particular operation or function call. | | core::to\_byte\_array | ASCII representation of numeric types for `ByteArray` manipulation. This module enables conversion of numeric values into their ASCII string representation, which can then be used to build `ByteArray` instances. | ### Miscellaneous | | | | :- | :- | | core::panic\_with\_felt252 | Panics with the given `felt252` as error message. | | core::panic\_with\_const\_felt252 | Panics with the given const argument `felt252` as error message. | | core::assert | Panics if `cond` is false with the given `felt252` as error message. | | core::bool | `bool` enum representing either `false` or `true`. | | core::never | — | | core::usize | `usize` is an alias for `u32` type. | | core::RangeCheck | General purpose implicits. | | core::SegmentArena | — | | core::felt252 | `felt252` is the basic field element used in Cairo. It corresponds to an integer in the range 0 ≤ x \< P where P is a very large prime number currently equal to 2^251 + 17⋅2^192 + 1. | | core::felt252\_div | Performs division on `felt252` values in Cairo's finite field. Unlike regular integer division, `felt252` division returns a field element n that satisfies the equation a = b \* n, where a is the dividend and b is the divisor. | # Deploy your first Starknet contract guide appendix Source: https://docs.starknet.io/build/quickstart/appendix ## Setting up your environment manually on MacOS and Linux ### Installing WSL and Ubuntu Setting up Scarb and Starknet Foundry on Windows requires configuring the Windows Subsystem for Linux (WSL) and installing the tools inside a Linux distribution such as Ubuntu: 1. Open PowerShell as administrator and run: ```bash theme={null} wsl --install ``` This command installs WSL along with the default Ubuntu distribution. If WSL or virtualization is not yet enabled, reboot and re-run the command as needed. If `wsl --install` does not work, enable WSL manually by running: ```bash theme={null} dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` and installing Ubuntu from the [Microsoft Store](https://aka.ms/wslstore). 2. Restart your computer when prompted. 3. After reboot, launch Ubuntu from the Start menu. On the first launch, create a UNIX username and password when prompted. 4. Open the Ubuntu terminal and run: ```bash theme={null} sudo apt update sudo apt install -y curl git build-essential ``` ### Installing Homebrew 1. Run the Homebrew install script: ```bash theme={null} /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` 2. Add Homebrew to your shell environment: ```bash theme={null} echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile source ~/.profile ``` 3. Verify that Homebrew was installed correctly: ```bash theme={null} brew --version ``` ### Installing `asdf` Using `asdf` allows you to easily switch between versions of Scarb, Starknet Foundry, and Starknet Devnet globally or per project. 1. Install `asdf` using Homebrew: ```bash theme={null} brew install asdf ``` 2. Add `asdf` to your shell: ```bash theme={null} echo '. "$(brew --prefix asdf)/libexec/asdf.sh"' >> ~/.bashrc source ~/.bashrc ``` 3. Verify that `asdf` is installed correctly: ```bash theme={null} asdf --version ``` ### Installing Scarb, Starknet Foundry, and Starknet Devnet 1. Add the Scarb plugin and install the latest Scarb version: ```bash theme={null} asdf plugin add scarb asdf install scarb latest asdf set -u scarb latest ``` 2. Add the Starknet Foundry plugin and install the latest Starknet Foundry version: ```bash theme={null} asdf plugin add starknet-foundry asdf install starknet-foundry latest asdf set -u starknet-foundry latest ``` 3. Add the Starknet Devnet plugin and install the latest Starknet Devnet version: ```bash theme={null} asdf plugin add starknet-devnet asdf install starknet-devnet latest asdf set -u starknet-devnet latest ``` 4. Restart your terminal and verify that Scarb, Starknet Foundry, and Starknet Devnet were installed correctly: ```bash theme={null} scarb --version snforge --version && sncast --version starknet-devnet --version ``` If `scarb`, `snforge`, or `starknet-devnet` are not recognized, try running `source ~/.bashrc` or restarting your terminal. ## Fetching a predeployed Sepolia account **Procedure:** 1. Export the private key from your wallet by: * For Ready wallets: navigating to `Settings` -> `` -> `Export Private Key`. * For Braavos wallets: navigating to `Settings` -> `Privacy and Security` -> `Export Private Key`. 2. Create a keystore file by running: ```terminal theme={null} starkli signer keystore from-key keystore.json ``` and entering the private key of your smart wallet, along with a password that will be used to encrypt it. 3. Fetch the account by running: ```terminal theme={null} starkli account fetch \ \ --output account.json \ --network=sepolia ``` ## Troubleshooting ### Starkli unable to detect shell **Procedure:** 1. Detect whether your shell is `zsh` or `bash`: ```terminal theme={null} echo $SHELL ``` 2. Add: ```terminal theme={null} . /Users//.starkli/env ``` to either `~/.zshrc` or `~/.bashrc`. 3. Restart the terminal, and run either: ```terminal theme={null} source ~/.zshrc ``` or: ```terminal theme={null} source ~/.bashrc ``` ### `scarb build` fails to run version command for Rust Starting from Scarb version 2.10 and Starknet Foundry version 0.37.0, Rust is longer required for projects with the following line in their `Scarb.toml` file: ```cairo theme={null} [tool.scarb] allow-prebuilt-plugins = ["snforge_std"] ``` If not all three conditions are met and Rust is not installed, running `scarb build` (and `scarb test`) will result in a compilation error. To resolve this, either update Scarb, Starknet Foundry, and your `Scarb.toml` file accordingly or [install Rust](https://www.rust-lang.org/tools/install). ### `starkli declare` unable to identify compiler version When using `starkli declare`, Starkli will do its best to identify the compiler version of the declared class. In case it fails, the `--compiler-version` flag can be used to specify the version of the compiler. **Procedure:** 1. Find the compiler versions supported by Starkli by running: ```terminal theme={null} starkli declare --help ``` and looking for the possible values of the `--compiler-version` flag. 2. Find the current Scarb version in use: ```terminal theme={null} scarb --version ``` 3. In case a different compiler version is required, switch to a different Scarb version using `asdf`: a. Install the desired Scarb version: ```terminal theme={null} asdf install scarb ``` b. Select the desired Scarb version as the local version for the project: ```terminal theme={null} asdf set scarb ``` # Deploying the HelloStarknet contract locally Source: https://docs.starknet.io/build/quickstart/devnet If you encounter an issue while following this tutorial, see [Troubleshooting](/build/quickstart/appendix#troubleshooting). ## Introduction Welcome to the third installment of the *Deploy your first contract* guide! 🥇 Local networks, also known as development networks or *devnets*, enable a fast and private development process, making them ideal for taking your first Starknet steps. This installment of the series will therefore guide you through the steps necessary to declare, deploy, and interact with the `HelloStarknet` contract on your very own local instance of Starknet. ## Initializing a local Starknet instance A local Starknet instance can be easily initialized using Starknet Devnet by simply running: ```bash theme={null} starknet-devnet --seed=0 ``` The `--seed` option is used to force consistent addresses of predeployed account (see more details below). If successful, the result should resemble the following: ```console theme={null} Chain ID: SN_SEPOLIA (0x534e5f5345504f4c4941) Predeployed FeeToken ETH Address: 0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7 Class Hash: 0x9524A94B41C4440A16FD96D7C1EF6AD6F44C1C013E96662734502CD4EE9B1F STRK Address: 0x4718F5A0FC34CC1AF16A1CDEE98FFB20C31F5CD61D6AB07201858F4287C938D Class Hash: 0x76791EF97C042F81FBF352AD95F39A22554EE8D7927B2CE3C681F3418B5206A Predeployed UDC Address: 0x41A78E741E5AF2FEC34B695679BC6891742439F7AFB8484ECD7766661AD02BF Class Hash: 0x7B3E05F48F0C69E4A65CE5E076A66271A527AFF2C34CE1083EC6E1526997A69 ... ``` Starknet Devnet should keep running for the following sections to work. ## Fetching a predeployed account To learn more about Starknet accounts, check out the [Accounts section](/learn/protocol/accounts/). To interact with your local Starknet instance, you first need an account. Luckily, the result of initializing a local Starknet instance using Starknet Devnet should also include a list of predeployed accounts that resembles the following: ```console theme={null} | Account address | 0x064b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691 | Private key | 0x0000000000000000000000000000000071d7bb07b9a64f6f78ac4c816aff4da9 | Public key | 0x039d9e6ce352ad4530a0ef5d5a18fd3303c3606a7fa6ac5b620020ad681cc33b | Account address | 0x078662e7352d062084b0010068b99288486c2d8b914f6e2a55ce945f8792c8b1 | Private key | 0x000000000000000000000000000000000e1406455b7d66b1690803be066cbe5e | Public key | 0x007a1bb2744a7dd29bffd44341dbd78008adb4bc11733601e7eddff322ada9cb ... ``` This allows to avoid creating and deploying new accounts, and instead simply importing them to Starknet Foundry. To import your local node's first predeployed account, use a new terminal window to navigate into the `hello_starknet` directory created in [Generating `HelloStarknet`](/build/quickstart/hellostarknet#creating-hellostarknet) and run: ```bash theme={null} sncast account import \ --address=0x064b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691 \ --type=oz \ --url=http://127.0.0.1:5050 \ --private-key=0x0000000000000000000000000000000071d7bb07b9a64f6f78ac4c816aff4da9 \ --add-profile=devnet \ --silent ``` To learn more about `sncast account import`, see the [Starknet Foundry documentation](https://foundry-rs.github.io/starknet-foundry/appendix/sncast/account/import.html). If successful, the result should resemble the following: ```console theme={null} Success: Account imported successfully Account Name: account-1 Add Profile: Profile devnet successfully added to /app/snfoundry.toml ``` ## Declaring `HelloStarknet` locally Before a contract can be deployed on Starknet, its compiled code needs to be submitted to the network (also known as *declaring* it). To learn more about distinction between deploying a contract and declaring it, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). To declare the `HelloStarknet` contract, run: ```bash theme={null} sncast --profile=devnet declare \ --contract-name=HelloStarknet ``` If successful, the result should resemble the following: ```console theme={null} Success: Declaration completed Class Hash: 0x51e0d3b26fb79035afdc64d2214eb18291629b4f2ef132e79c3f3fbe1ba57c4 Transaction Hash: 0x76e28641d96ee0788f5a960931ee717c5dae36f95f227c02d5578a6898d8af3 ``` where `class_hash` is the contract's class hash, which can then be used to deploy an instance of it. To learn more about the class hashes, check out [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). ## Deploying `HelloStarknet` locally With `HelloStarknet` declared, you can now deploy an instance of it by running: ```bash theme={null} sncast --profile=devnet deploy \ --class-hash=0x051e0d3b26fb79035afdc64d2214eb18291629b4f2ef132e79c3f3fbe1ba57c4 \ --salt=0 ``` The `--salt` option is used to force a consistent address for the deployed contract. If successful, the result should resemble the following: ```console theme={null} Success: Deployment completed Contract Address: 0x04035c4db7822523478996bfc2b80d925e671279bb99ed1fb4e4fcc222e344e6 Transaction Hash: 0x04d11358b809c7b8a5aae1a1cf0e72e0b436fb2d0a2dd1a26d009fec7aa74280 ``` ## Interacting with `HelloStarknet` locally Now that your instance of `HelloStarknet` is deployed, you can interact with via its functions by either calling or invoking them. Calling is used for read functions that don't modify their contract's storage, and allows querying a smart contract function without sending a transaction. For example, you can call `HelloStarknet`'s `get_balance` function by running: ```bash theme={null} sncast --profile=devnet call \ --contract-address=0x04035c4db7822523478996bfc2b80d925e671279bb99ed1fb4e4fcc222e344e6 \ --function=get_balance ``` If successful, the result should resemble the following: ```console theme={null} Success: Call completed Response: 0x0 Response Raw: [0x0] ``` Invoking is used for write functions that modify their contract's storage, and submits a transaction to the network that changes its state. For example, you can invoke `HelloStarknet`'s `increase_balance` function by running: ```bash theme={null} sncast --profile=devnet invoke \ --contract-address=0x04035c4db7822523478996bfc2b80d925e671279bb99ed1fb4e4fcc222e344e6 \ --function=increase_balance \ --arguments=42 ``` If successful, the result should resemble the following: ```console theme={null} Success: Invoke completed Transaction Hash: 0x02691af0fb2b720b27ae17c844d72244b8fc35f3a3d57b6549736d9a88f8f014 ``` You can verify that your deployed contract's storage — and by extension, the state of your local Starknet instance — has indeed changed by calling `get_balance` again. If all goes well, the result should resemble the following ($42_{10} = 2a_{16}$): ```console theme={null} Success: Call completed Response: 0x2a Response Raw: [0x2a] ``` # Setting up your development environment Source: https://docs.starknet.io/build/quickstart/environment-setup If you encounter an issue while following this tutorial, see [Troubleshooting](/build/quickstart/appendix#troubleshooting). ## Introduction Welcome to the first installment of the Deploy your first contract guide! 🥇 As a popular phrase (often attributed to Abraham Lincoln) says, "Give me six hours to chop down a tree, and I will spend the first four sharpening the axe". The first installment of the series will therefore guide you through setting up your development environment, which will include the three most recommended tools to begin developing on Starknet: * *Scarb*, a build toolchain and package manager for Cairo and Starknet ecosystems * *Starknet Foundry*, the go-to framework for building and testing Starknet Smart Contracts * *Starknet Devnet*, a Rust implementation of a local Starknet node To review all Starknet developer tools, see [Developer tools](/learn/cheatsheets/tools). ## Setting up your environment on MacOS and Linux On MacOS and Linux, Scarb, Starknet Foundry and Starknet Devnet can be easily installed using the [Starkup installer](https://github.com/software-mansion/starkup) by running: ```bash theme={null} curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.sh | sh ``` and following the onscreen instructions. If you prefer to install the tools manually or encounter issues with Starkup, see [Setting up your environment manually on MacOS and Linux](/build/quickstart/appendix#setting-up-your-environment-manually-on-macos-and-linux). You can verify that all three tools are installed correctly by running: ```bash theme={null} scarb --version snforge --version && sncast --version starknet-devnet --version ``` If the installation was successful, the result should resemble the following: ```console theme={null} scarb 2.11.4 (c0ef5ec6a 2025-04-09) cairo: 2.11.4 (https://crates.io/crates/cairo-lang-compiler/2.11.4) sierra: 1.7.0 snforge 0.48.1 sncast 0.48.1 starknet-devnet 0.4.3 ``` Starkup installs Scarb, Starknet Foundry, and Starknet Devnet on MacOS and Linux via the [`asdf` version manager](https://asdf-vm.com/), which allows to easily switch between their different versions, both globally and per project (see full details in the [`asdf` documentation](https://asdf-vm.com/manage/commands.html) or by running `asdf --help`). Alongside Scarb and Starknet Foundry, Starkup uses `asdf` to install additional useful tools, including the [Universal Sierra Compiler](https://github.com/software-mansion/universal-sierra-compiler), [Cairo Profiler](https://github.com/software-mansion/cairo-profiler), [Cairo Coverage](https://github.com/software-mansion/cairo-coverage), and [CairoLS](https://github.com/software-mansion/cairols). If you encounter any issues while using it or have any requests, please help by [submitting an issue](https://github.com/software-mansion/starkup/issues/new). ## Setting up your environment on Windows Setting up Scarb and Starknet Foundry on Windows requires configuring the Windows Subsystem for Linux (WSL) and installing the tools inside a Linux distribution such as Ubuntu. ### Installing WSL and Ubuntu 1. Open PowerShell as administrator and run: ```bash theme={null} wsl --install ``` This command installs WSL along with the default Ubuntu distribution. If WSL or virtualization is not yet enabled, reboot and re-run the command as needed. 2. Restart your computer when prompted. 3. After reboot, launch Ubuntu from the Start menu. On the first launch, create a UNIX username and password when prompted. If `wsl --install` does not work, enable WSL manually by running: ```bash theme={null} dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` and installing Ubuntu from the [Microsoft Store](https://aka.ms/wslstore). ### Installing prerequisites in Ubuntu 1. Open the Ubuntu terminal and run: ```bash theme={null} sudo apt update sudo apt install -y curl git build-essential ``` ### Installing Homebrew 1. Run the Homebrew install script: ```bash theme={null} /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` 2. Add Homebrew to your shell environment: ```bash theme={null} echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile source ~/.profile ``` 3. Verify that Homebrew was installed correctly: ```bash theme={null} brew --version ``` ### Installing `asdf` Using `asdf` allows you to easily switch between versions of Scarb, Starknet Foundry, and Starknet Devnet globally or per project. 1. Install `asdf` using Homebrew: ```bash theme={null} brew install asdf ``` 2. Add `asdf` to your shell: ```bash theme={null} echo '. "$(brew --prefix asdf)/libexec/asdf.sh"' >> ~/.bashrc source ~/.bashrc ``` 3. Verify that `asdf` is installed correctly: ```bash theme={null} asdf --version ``` ### Installing Scarb, Starknet Foundry, and Starknet Devnet 1. Add the Scarb plugin and install the latest Scarb version: ```bash theme={null} asdf plugin add scarb asdf install scarb latest asdf set -u scarb latest ``` 2. Add the Starknet Foundry plugin and install the latest Starknet Foundry version: ```bash theme={null} asdf plugin add starknet-foundry asdf install starknet-foundry latest asdf set -u starknet-foundry latest ``` 3. Add the Starknet Devnet plugin and install the latest Starknet Devnet version: ```bash theme={null} asdf plugin add starknet-devnet asdf install starknet-devnet latest asdf set -u starknet-devnet latest ``` 4. Restart your terminal and verify that Scarb, Starknet Foundry, and Starknet Devnet were installed correctly: ```bash theme={null} scarb --version snforge --version && sncast --version starknet-devnet --version ``` If `scarb`, `snforge`, or `starknet-devnet` are not recognized, try running `source ~/.bashrc` or restarting your terminal. # Creating and understanding the HelloStarknet contract Source: https://docs.starknet.io/build/quickstart/hellostarknet If you encounter an issue while following this tutorial, see [Troubleshooting](/build/quickstart/appendix#troubleshooting). ## Introduction Welcome to the second installment of the *Deploy your first contract* guide! 🥇 Starknet contracts are a special superset of Cairo programs that are run by the Starknet sequencer, and as such, have access to Starknet's state. This installment of the series will therefore walk you though generating and understanding Scarb's default `HelloStarknet` contract, which will be used throughout the following installments. To learn more about Starknet smart contracts, see the [Cairo book](https://book.cairo-lang.org/ch100-00-introduction-to-smart-contracts.html). ## Creating `HelloStarknet` Scarb's default `HelloStarknet` contract can be generated by simply running: ```bash theme={null} scarb new hello_starknet ``` and selecting to set up the `Starknet Foundry (default)` test runner. If successful, this should create a new `hello_starknet` directory with the following structure: ``` hello_starknet |- Scarb.lock |- Scarb.toml |- snfoundry.toml |- src |- lib.cairo |- tests |- test_contract.cairo ``` ## Understanding `HelloStarknet` For the purpose of this tutorial, you can ignore all files in the `hello_starknet` directory other than `hello_starknet/src/lib.cairo`, which holds the contract's code: ```rust theme={null} /// Interface representing `HelloContract`. /// This interface allows modification and retrieval of the contract balance. #[starknet::interface] pub trait IHelloStarknet { /// Increase contract balance. fn increase_balance(ref self: TContractState, amount: felt252); /// Retrieve contract balance. fn get_balance(self: @TContractState) -> felt252; } /// Simple contract for managing balance. #[starknet::contract] mod HelloStarknet { use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { balance: felt252, } #[abi(embed_v0)] impl HelloStarknetImpl of super::IHelloStarknet { fn increase_balance(ref self: ContractState, amount: felt252) { assert(amount != 0, 'Amount cannot be 0'); self.balance.write(self.balance.read() + amount); } fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } } } ``` As its comments read, `HelloStarknet` is a simple contract for managing balance. Specifically: 1. The contract is defined by encapsulating state and logic within a module annotated with the `#[starknet::contract]` attribute. 2. The logic that the contract exposes to the outside world is represented by its interface trait, annotated with the `#[starknet::interface]` attribute. Here, our contract defines and publicly exposes the functions `increase_balance` and `get_balance`. 3. The state is defined within the `Storage` struct, which is always initialized empty. Here, our struct contains a single field called `balance` of type `felt252`. 4. The logic itself is defined in the implementation block and annotated with the `#[abi(embed_v0)]` attribute to expose the implementations to the outside world. Here, `increase_balance` uses the `write` method to increase `balance` by `amount` and `get_balance` uses the `read` method to return the value of `balance`. Once deployed, each value stored in the `HelloStarknet` contract's storage will be recorded in Starknet's history. To review examples of more advanced contracts, see [Starknet By Example](/build/starknet-by-example/index). # Recommended next steps after deploying your first contract Source: https://docs.starknet.io/build/quickstart/next-steps # Introduction Welcome to the fifth and final installment of the *Deploy your first contract* guide! 🥇 By now you've successfully declared, deployed, and interacted with the `HelloStarknet`, both on Starknet Sepolia and locally (way to go!). This last installment of the series will therefore list the various resources you can dive into next. ## Interactive tutorials * [Starklings](https://starklings.app/) * [Speedrun Stark](https://speedrunstark.com/) ## Essential learning materials * [Starknet By Example](/build/starknet-by-example/index) * [Corelib documentation](/build/corelib/intro) * [Cairo Book](https://book.cairo-lang.org) * [Starknet protocol](/learn/protocol/intro) * [Starknet cheatsheets](/learn/cheatsheets/chain-info) * [Starknet Basecamp Hub](https://starknet.notion.site/Starknet-Basecamp-Hub-1541b3c1f49f439da872d3d71647d834) ## Core tools documentations * [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry) * [Scarb](https://docs.swmansion.com/scarb/docs.html) * [Starknet Devnet](https://0xspaceshard.github.io/starknet-devnet/docs/intro) # Summary Good luck and welcome aboard! We can't wait to see the new and innovative things you'll bring to Starknet 🌟 The journey from your first contract to becoming a Starknet expert is exciting. Take it step by step, stay curious, and don't hesitate to ask for help along the way! # Deploy your first contract guide overview Source: https://docs.starknet.io/build/quickstart/overview If you encounter an issue while following this tutorial, see [Troubleshooting](/build/quickstart/appendix/#troubleshooting). ## Introduction Welcome to the *Deploy your first contract* guide! 🥇 This comprehensive tutorial will walk you through the entire process of setting up your development environment, creating your first smart contract, and deploying it on Starknet. By the end of this guide, you'll have hands-on experience with the core tools and workflows used by Starknet developers. ## What you'll learn This guide is structured as a series of interconnected tutorials that build upon each other: * [Setting up your Starknet development environment](/build/quickstart/environment-setup/) * [Generating and understanding the `HelloStarknet` contract](/build/quickstart/hellostarknet/) * [Declaring, deploying, and interacting with the `HelloStarknet` contract locally](/build/quickstart/devnet/) * [Deploying and interacting with the `HelloStarknet` contract on Starknet Sepolia](/build/quickstart/sepolia/) * [Recommended next steps after deploying your first contract](/build/quickstart/next-steps/) ## Prerequisites Before starting this tutorial, you should have: * Basic familiarity with command-line interfaces * A computer running macOS, Linux, or Windows with WSL * An internet connection for downloading tools and interacting with Starknet No prior experience with Cairo or Starknet is required - this guide is designed for beginners! ## Getting help If you encounter any issues while following this guide: 1. Check the [troubleshooting section](/build/quickstart/appendix/#troubleshooting) for common problems and solutions 2. Ask for help in the [Starknet Discord community](https://discord.gg/starknet-community) 3. Browse the [Starknet community forum](https://community.starknet.io/) for additional resources # Deploying the HelloStarknet contract on Starknet Sepolia Source: https://docs.starknet.io/build/quickstart/sepolia If you encounter an issue while following this tutorial, see [Troubleshooting](/build/quickstart/appendix#troubleshooting). ## Introduction Welcome to the fourth installment of the *Deploy your first contract* guide! 🥇 Starknet Sepolia is Starknet's testnet environment designed to provide developers with a testing ground that mirrors the behavior of the Starknet Mainnet while being connected to the Ethereum Sepolia testnet, making it ideal for debugging and optimizing your code before deploying it to production. This installment of the series will therefore guide you through the steps necessary to deploy and interact with the `HelloStarknet` contract on Starknet Sepolia. ## Deploying a new Sepolia account Similar to interacting with a Starknet Devnet instance, to interact with Starknet Sepolia you first need an account. However, instead fetching a predeployed Sepolia account, we will create a *new* account and deploy it using `sncast` ourselves. To learn how to fetch a predeployed Sepolia account, see [Fetching a predeployed Sepolia account](/build/quickstart/appendix#fetching-a-predeployed-sepolia-account). To create the account's information (private key, address, etc.), navigate into the `hello_starknet` directory created in [Generating `HelloStarknet`](/build/quickstart/hellostarknet#generating-hellostarknet) and run: ```bash theme={null} sncast account create \ --network=sepolia \ --name=sepolia ``` When run, the command shows instructions on how to prefund the account before proceeding, which can be done using the [Starknet Sepolia faucet](https://starknet-faucet.vercel.app/). Prefunding the account is required because deploying an account involves sending a `DEPLOY_ACCOUNT` transaction, which requires the account to contain enough STRK to pay for the transaction fee. Once your account is funded, you can deploy it by running: ```bash theme={null} sncast account deploy \ --network sepolia \ --name sepolia \ --silent ``` If successful, the result should resemble the following: ```console theme={null} Success: Account deployed Transaction Hash: 0x01340c0328b037bab85d53dd1b3b8040b0e0f4be58a42a94f554c9bf6e5bf30d To see invocation details, visit: transaction: https://sepolia.starkscan.co/tx/0x01340c0328b037bab85d53dd1b3b8040b0e0f4be58a42a94f554c9bf6e5bf30d ``` ## Deploying `HelloStarknet` on Sepolia Unlike when using a Starknet Devnet instance, there's no need for us to declare `HelloStarknet` on Sepolia as it has already been declared before (remember: declaration is a one-time process for each unique contract code). To verify that, you can try declaring it by navigating into the `hello_starknet` directory created in [Generating `HelloStarknet`](/build/quickstart/hellostarknet#generating-hellostarknet), running: ```bash theme={null} sncast --account=sepolia declare \ --contract-name=HelloStarknet \ --network=sepolia ``` The result should resemble to the following: ```console theme={null} command: declare error: Transaction execution error = TransactionExecutionErrorData { transaction_index: 0, execution_error: Message( "Class with hash 0x051e0d3b26fb79035afdc64d2214eb18291629b4f2ef132e79c3f3fbe1ba57c4 is already declared." ) } ``` With `HelloStarknet` already declared, you can deploy an instance of it by running: ```bash theme={null} sncast --account=sepolia deploy \ --class-hash=0x051e0d3b26fb79035afdc64d2214eb18291629b4f2ef132e79c3f3fbe1ba57c4 \ --network=sepolia ``` If successful, the result should resemble the following: ```console theme={null} Success: Deployment completed Contract Address: 0x05fe561f0907f61b1099ba64ee18a5250606d43d00d4f296ba622d287ceb2538 Transaction Hash: 0x0723a63261d2df60f571df8a2b8c8c64694278aae66481a0584445c03234d83f To see deployment details, visit: contract: https://sepolia.starkscan.co/contract/0x05fe561f0907f61b1099ba64ee18a5250606d43d00d4f296ba622d287ceb2538 transaction: https://sepolia.starkscan.co/tx/0x0723a63261d2df60f571df8a2b8c8c64694278aae66481a0584445c03234d83f ``` Your deployed contract's address will be different than the one listed above. Make sure to use the address of your own deployed contract in the following section. ## Interacting with `HelloStarknet` on Sepolia Once your instance of `HelloStarknet` is deployed, you can invoke its `increase_balance` function by running: ```bash theme={null} sncast --account=sepolia invoke \ --contract-address= \ --function=increase_balance \ --arguments=66 \ --network=sepolia ``` If successful, the result should resemble the following: ```console theme={null} Success: Invoke completed Transaction Hash: 0x02b900ba6bfb6a7d256d34c5d3a895abbfa0805d23f80253958e101069700020 To see invocation details, visit: transaction: https://sepolia.starkscan.co/tx/0x02b900ba6bfb6a7d256d34c5d3a895abbfa0805d23f80253958e101069700020 ``` Once the invoke transaction is accepted on Starknet Sepolia, you can call your deployed contract's `get_balance` function to confirm that your deployed contract's storage — and by extension, the state of Starknet Sepolia — has indeed changed, by running: ```bash theme={null} sncast call \ --contract-address= \ --function=get_balance \ --network=sepolia ``` If all goes well, the result should resemble the following ($66_{10} = 42_{16}$): ```console theme={null} Success: Call completed Response: 0x42 Response Raw: [0x42] ``` # Account abstraction Source: https://docs.starknet.io/build/starknet-by-example/advanced/account-abstraction An account is an unique entity that can send transactions, users usually use wallets to manage their accounts. Historically, in Ethereum, all accounts were Externally Owned Accounts (*EOA*) and were controlled by private keys. This is a simple and secure way to manage accounts, but it has limitations as the account logic is hardcoded in the protocol. Account Abstraction (*AA*) is the concept behind abstracting parts of the account logic to allow for a more flexible account system. This replaces EOA with Account Contracts, which are smart contracts that implement the account logic. This opens up a lot of possibilities that can significantly improve the user experience when dealing with accounts. On Starknet, Account Abstraction is natively supported, and all accounts are Account Contracts. ## Account Contract A smart contract must follow the Standard Account Interface specification defined in the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md). In practice, this means that the contract must implement the `SRC6` and `SRC5` interfaces to be considered an account contract. ### SNIP-6: SRC6 + SRC5 ```rust theme={null} /// @title Represents a call to a target contract /// @param to The target contract address /// @param selector The target function selector /// @param calldata The serialized function parameters struct Call { to: ContractAddress, selector: felt252, calldata: Array } ``` The `Call` struct is used to represent a call to a function (`selector`) in a target contract (`to`) with parameters (`calldata`). It is available under the `starknet::account` module. ```rust theme={null} /// @title SRC-6 Standard Account trait ISRC6 { /// @notice Execute a transaction through the account /// @param calls The list of calls to execute /// @return The list of each call's serialized return value fn __execute__(calls: Array) -> Array>; /// @notice Assert whether the transaction is valid to be executed /// @param calls The list of calls to execute /// @return The string 'VALID' represented as felt when is valid fn __validate__(calls: Array) -> felt252; /// @notice Assert whether a given signature for a given hash is valid /// @param hash The hash of the data /// @param signature The signature to validate /// @return The string 'VALID' represented as felt when the signature is valid fn is_valid_signature(hash: felt252, signature: Array) -> felt252; } ``` A transaction can be represented as a list of calls `Array` to other contracts, with atleast one call. * `__execute__`: Executes a transaction after the validation phase. Returns an array of the serialized return of value (`Span`) of each call. * `__validate__`: Validates a transaction by verifying some predefined rules, such as the signature of the transaction. Returns the `VALID` short string (as a felt252) if the transaction is valid. * `is_valid_signature`: Verify that a given signature is valid. This is mainly used by applications for authentication purposes. Both `__execute__` and `__validate__` functions are exclusively called by the Starknet protocol. ```rust theme={null} /// @title SRC-5 Standard Interface Detection trait ISRC5 { /// @notice Query if a contract implements an interface /// @param interface_id The interface identifier, as specified in SRC-5 /// @return `true` if the contract implements `interface_id`, `false` otherwise fn supports_interface(interface_id: felt252) -> bool; } ``` The interface identifiers of both `SRC5` and `SRC6` must be published with `supports_interface`. ### Minimal account contract Executing Transactions In this example, we will implement a minimal account contract that can validate and execute transactions. ```rust theme={null} use starknet::account::Call; #[starknet::interface] trait ISRC6 { fn execute_calls(self: @TContractState, calls: Array) -> Array>; fn validate_calls(self: @TContractState, calls: Array) -> felt252; fn is_valid_signature( self: @TContractState, hash: felt252, signature: Array, ) -> felt252; } #[starknet::contract] mod simpleAccount { use super::ISRC6; use starknet::account::Call; use core::num::traits::Zero; use core::ecdsa::check_ecdsa_signature; use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; // Implement SRC5 with openzeppelin use openzeppelin_account::interface; use openzeppelin_introspection::src5::SRC5Component; component!(path: SRC5Component, storage: src5, event: SRC5Event); #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; impl SRC5InternalImpl = SRC5Component::InternalImpl; #[storage] struct Storage { #[substorage(v0)] src5: SRC5Component::Storage, public_key: felt252, } #[constructor] fn constructor(ref self: ContractState, public_key: felt252) { self.src5.register_interface(interface::ISRC6_ID); self.public_key.write(public_key); } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] SRC5Event: SRC5Component::Event, } #[abi(embed_v0)] impl SRC6 of ISRC6 { fn execute_calls(self: @ContractState, calls: Array) -> Array> { assert(starknet::get_caller_address().is_zero(), "Not Starknet Protocol"); let Call { to, selector, calldata } = calls.at(0); let res = starknet::syscalls::call_contract_syscall(*to, *selector, *calldata).unwrap(); array![res] } fn validate_calls(self: @ContractState, calls: Array) -> felt252 { assert(starknet::get_caller_address().is_zero(), "Not Starknet Protocol"); let tx_info = starknet::get_tx_info().unbox(); let tx_hash = tx_info.transaction_hash; let signature = tx_info.signature; if self._is_valid_signature(tx_hash, signature) { starknet::VALIDATED } else { 0 } } fn is_valid_signature( self: @ContractState, hash: felt252, signature: Array, ) -> felt252 { if self._is_valid_signature(hash, signature.span()) { starknet::VALIDATED } else { 0 } } } #[generate_trait] impl SignatureVerificationImpl of SignatureVerification { fn _is_valid_signature( self: @ContractState, hash: felt252, signature: Span, ) -> bool { check_ecdsa_signature( hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32), ) } } } ``` # Commit-Reveal Source: https://docs.starknet.io/build/starknet-by-example/advanced/commit-reveal The Commit-Reveal pattern is a fundamental blockchain pattern that enables to: 1. Commit to a value without revealing it *(commit phase)* 2. Reveal the value later to prove they knew it in advance *(reveal phase)* Some use cases: * **Blind Auctions**: Bidders commit to their bids first, then reveal them after the bidding period * **Voting Systems**: Voters commit their votes early, revealing them only after voting ends * **Knowledge Proofs/Attestations**: Proving you knew information at a specific time without revealing it immediately * **Fair Random Number Generation**: Players commit to random numbers that get combined later, making it harder to manipulate the outcome ## How It Works 1. **Commit Phase**: * User generates a value (`secret`) * User creates a hash of this value * User submits only the hash on-chain (`commit`) 2. **Reveal Phase**: * User submits the original value (`reveal`) * Contract verifies that the hash of the submitted value matches the previously committed hash * If it matches then it proves that the user knew the value at the commitment time ## Minimal commit-reveal contract: ```rust theme={null} #[starknet::interface] pub trait ICommitmentRevealTrait { fn commit(ref self: T, commitment: felt252); fn reveal(self: @T, secret: felt252) -> bool; } #[starknet::contract] mod CommitmentRevealTraits { use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; use core::hash::HashStateTrait; use core::pedersen::PedersenTrait; #[storage] struct Storage { commitment: felt252, } #[abi(embed_v0)] impl CommitmentRevealTrait of super::ICommitmentRevealTrait { fn commit(ref self: ContractState, commitment: felt252) { self.commitment.write(commitment); } fn reveal(self: @ContractState, secret: felt252) -> bool { let hash = PedersenTrait::new(secret).finalize(); self.commitment.read() == hash } } } ``` Usage example: ```rust theme={null} // Off-chain, compute the commitment hash for secret let secret = "My secret"; let offchain_commitment = PedersenTrait::new(secret).finalize(); // Commit on-chain contract.commit(offchain_commitment); // Reveal on-chain and assert the result let reveal_result = contract.reveal(secret); ``` Some considerations: * The commit phase must complete before any reveals can start * Users might choose not to reveal if the outcome is unfavorable (consider adding stake/slashing mechanics to ensure reveals) # Solidity compatible hashing Source: https://docs.starknet.io/build/starknet-by-example/advanced/hash-solidity-compatible This contract demonstrates Keccak hashing in Cairo to match Solidity's keccak256. While both use Keccak, their endianness differs: Cairo is little-endian, Solidity big-endian. The contract achieves compatibility by hashing in big-endian using `keccak_u256s_be_inputs`, and reversing the bytes of the result with `u128_byte_reverse`. For example: ```rust theme={null} #[starknet::interface] pub trait ISolidityHashExample { fn hash_data(ref self: TContractState, input_data: Span) -> u256; } #[starknet::contract] mod SolidityHashExample { use core::keccak::keccak_u256s_be_inputs; use core::integer; #[storage] struct Storage {} #[abi(embed_v0)] impl SolidityHashExample of super::ISolidityHashExample { fn hash_data(ref self: ContractState, input_data: Span) -> u256 { let hashed = keccak_u256s_be_inputs(input_data); // Split the hashed value into two 128-bit segments let low: u128 = hashed.low; let high: u128 = hashed.high; // Reverse each 128-bit segment let reversed_low = integer::u128_byte_reverse(low); let reversed_high = integer::u128_byte_reverse(high); // Reverse merge the reversed segments back into a u256 value let compatible_hash = u256 { low: reversed_high, high: reversed_low }; compatible_hash } } } ``` # Hashing Source: https://docs.starknet.io/build/starknet-by-example/advanced/hashing Hashing is a cryptographic technique that allows you to transform a variable length input into a fixed length output. The resulting output is called a hash and it's completely different from the input. Hash functions are deterministic, meaning that the same input will always produce the same output. The two hash functions provided by the Cairo library are `Poseidon` and `Pedersen`. Pedersen hashes were used in the past (but are still used in some scenarios for backward compatibility), while Poseidon hashes are the standard nowadays since they were designed to be very efficient for Zero Knowledge proof systems. In Cairo, it's possible to hash all types that can be converted to `felt252` since they natively implement the `Hash` trait. It's also possible to hash more complex types, like structs, by deriving the Hash trait with the `#[derive(Hash)]` attribute, but only if all the struct's fields are themselves hashable. To hash a value, you first need to initialize a hash state with the `new` method of the `HashStateTrait`. Then, you can update the hash state with the `update` method. You can accumulate multiple updates if necessary. Finally, the `finalize` method returns the final hash value as a `felt252`. ```rust theme={null} #[starknet::interface] pub trait IHashTrait { fn save_user_with_poseidon( ref self: T, id: felt252, username: felt252, password: felt252, ) -> felt252; fn save_user_with_pedersen( ref self: T, id: felt252, username: felt252, password: felt252, ) -> felt252; } #[starknet::contract] mod HashTraits { use starknet::storage::StoragePointerWriteAccess; use core::hash::{HashStateTrait, HashStateExTrait}; use core::{pedersen::PedersenTrait, poseidon::PoseidonTrait}; #[storage] struct Storage { user_hash_poseidon: felt252, user_hash_pedersen: felt252, } #[derive(Drop, Hash)] struct LoginDetails { username: felt252, password: felt252, } #[derive(Drop, Hash)] struct UserDetails { id: felt252, login: LoginDetails, } #[abi(embed_v0)] impl HashTrait of super::IHashTrait { fn save_user_with_poseidon( ref self: ContractState, id: felt252, username: felt252, password: felt252, ) -> felt252 { let login = LoginDetails { username, password }; let user = UserDetails { id, login }; let poseidon_hash = PoseidonTrait::new().update_with(user).finalize(); self.user_hash_poseidon.write(poseidon_hash); poseidon_hash } fn save_user_with_pedersen( ref self: ContractState, id: felt252, username: felt252, password: felt252, ) -> felt252 { let login = LoginDetails { username, password }; let user = UserDetails { id, login }; let pedersen_hash = PedersenTrait::new(0).update_with(user).finalize(); self.user_hash_pedersen.write(pedersen_hash); pedersen_hash } } } ``` # Library calls Source: https://docs.starknet.io/build/starknet-by-example/advanced/library-calls External calls can be made on Starknet by two means: Contract dispatchers or Library dispatchers. Dispatchers are automatically created and exported by the compiler when a contract interface is defined. With Contract dispatcher we are calling an already deployed contract (with associated state), therefore contract address is passed to the dispatcher to make the call. However, with library dispatcher we are simply making function calls to declared contract **classes** (stateless). Contract dispatcher call is synonymous to external calls in Solidity, while library dispatcher call is synonymous to delegate call. For further reading: [Cairo book](https://book.cairo-lang.org/ch15-03-executing-code-from-another-class.html#library-calls) ```rust theme={null} #[starknet::interface] pub trait IMathUtils { fn add(ref self: T, x: u32, y: u32) -> u32; fn set_class_hash(ref self: T, class_hash: starknet::ClassHash); } // contract A #[starknet::contract] pub mod MathUtils { #[storage] struct Storage {} #[abi(embed_v0)] impl ImathUtilsImpl of super::IMathUtils { fn add(ref self: ContractState, x: u32, y: u32) -> u32 { x + y } fn set_class_hash(ref self: ContractState, class_hash: starknet::ClassHash) {} } } // contract B to make library call to the class of contract A #[starknet::contract] pub mod MathUtilsLibraryCall { use super::{IMathUtilsDispatcherTrait, IMathUtilsLibraryDispatcher}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { value: u32, lib_class_hash: starknet::ClassHash, } #[abi(embed_v0)] impl MathUtils of super::IMathUtils { fn add(ref self: ContractState, x: u32, y: u32) -> u32 { IMathUtilsLibraryDispatcher { class_hash: self.lib_class_hash.read() }.add(x, y) } #[abi(embed_v0)] fn set_class_hash(ref self: ContractState, class_hash: starknet::ClassHash) { self.lib_class_hash.write(class_hash); } } } ``` # Plugins Source: https://docs.starknet.io/build/starknet-by-example/advanced/plugins Compiler plugins in Cairo are a way to generate additional code for specific items during the compilation process. There are already a set of core plugins that you may have already used, such as `#[derive]`, `#[cfg]`, `#[generate_trait]`, etc. It is possible to create your own plugins in Cairo. You can learn more about how to do that in the [hello-cairo-plugin](https://github.com/piwonskp/hello-cairo-plugin) repository. # Understanding Sierra: From High-Level Cairo to Safe CASM Source: https://docs.starknet.io/build/starknet-by-example/advanced/sierra-ir Sierra (Safe Intermediate REpresentAtion) is a linear intermediate representation of Cairo instructions, designed to bridge the gap between high-level Cairo 1 intermdiate and low-level Cairo Assembly (CASM). Sierra can be compiled to a subset of CASM, known as `Safe CASM{:md}`. Sierra ensures that programs are always **provable** by preventing the compilation of programs with infinite loops or invalid constraints. ### From Cairo 1 to Sierra Before Starknet Alpha v0.11.0, developers wrote contracts in Cairo 0, which compiled directly to Cairo Assembly (CASM), and contract classes were submitted to the sequencer via `DECLARE` transactions. This approach had risks, as the sequencer could not determine whether a given transaction would fail without executing it, and therefore could not charge fees for failed transactions. Cairo 1 introduced contract class compilation to this new Sierra intermediate representation instead of directly compiling to CASM. The Sierra code is then submitted to the sequencer, compiled down to CASM, and finally executed by the Starknet OS. Using Sierra ensures that all transactions (including failed ones) are provable, and allows sequencers to charge fees for all submitted transactions, making DoS attacks significantly more expensive. ### Compilation Pipeline ``` Cairo 1.0 Source (High-level) | | with Cairo-to-Sierra Compiler V Sierra IR (Safe, Provable Code) | | with Sierra-to-CASM Compiler (Run by Sequencer) V CASM (Cairo Assembly) | | with CairoVM Execution V STARK Proofs (Proof Generation) ``` At its core, Sierra's compilation process is focused on safety and efficiency. Cairo 1 uses a linear type system and a non-deterministic, immutable, contiguous memory model, which guarantees that dereferencing never fails. Sierra transforms loops into recursion and keeps track of gas usage, which prevents infinite loops. If you're interested in really understanding how the compilation works under the hood, check out the [cairo-compiler-workshop](https://github.com/software-mansion-labs/cairo-compiler-workshop). ### Anatomy of a Sierra Program ### Type Declarations Sierra, as a Cairo representation, also uses a **linear type system**, where each value **must be used exactly once**. During the compilation, a unique identifier is assigned to each type. When types can safely be used multiple times, they need to be duplicated using the `dup` instruction, which will assign two new identifiers to preserve linearity. Type declaration is done with the following syntax: ```rust theme={null} type type_id = concrete_type; ``` In addition, each type has a set of attributes that describe how it can be used: * storable * droppable * duplicatable * zero\_sized They are added in the type declaration: ```rust theme={null} type type_id = concrete_type [storable: bool, drop: bool, dup: bool, zero_sized: bool] ``` ### Library Function Declarations Sierra comes with a set of built-in functions (`libfuncs`) that represent the call to low-level units of code known to be safe. After type declarations, a Sierra program must define all the libfuncs used in the program along with their expected input types. Libfunc declaration is done with the following syntax: ```rust theme={null} libfunc libfunc_id = libfunc_name; ``` While this section is generic, Starknet uses an allowed [list](https://github.com/starkware-libs/cairo/tree/main/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists) of libfuncs. ### Statements This section shows the sequence of operations that occur during execution, describing the actual logic of the program. A statement either invokes a libfunc or returns a value. Statements are declared with the following syntax: ```rust theme={null} libfunc_id(input_variables) -> (output_variables); ``` To return a value, we use the `return(variable_id)` statement. ### User Defined Functions Declarations At the end of a Sierra program, each user-defined function is declared with a unique identifier and the statement index where the function starts. This provides information about the function, such as its signature, while the implementation is defined in the statements section. An user defined function is declared with the following syntax: ```rust theme={null} function_id@statement_index(parameters: types) -> (return_types); ``` ## Simple Sierra Program Breakdown Let's go through the following Cairo program: ```rust theme={null} fn add_numbers(a: felt252, b: felt252) -> felt252 { a + b } ``` It compiles to the following Sierra code: ```rust theme={null} type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; libfunc felt252_add = felt252_add; libfunc store_temp = store_temp; felt252_add([0], [1]) -> ([2]); // 0 store_temp([2]) -> ([2]); // 1 return([2]); // 2 sierra_ir::add_numbers@0([0]: felt252, [1]: felt252) -> (felt252); ``` Type Declarations: * `felt252`: Represents the field element type Libfunc Declarations: * `felt252_add`: Performs addition on field elements * `store_temp`: Temporarily stores the result Statements Section: * Statement 0: calls the `felt252_add` libfunc to add the values from memory cells 0 and 1, storing the result in memory cell 2 * Statement 1: calls the `store_temp` libfunc to prepare the result for the return statement * Statement 2: returns the value from memory cell 2 User Defined Functions: * `add_numbers`: Takes two `felt252` types in memory cells 0 and 1 and returns a `felt252` value by starting at statement 0 To enable Sierra code generation in a human-readable format, add the `sierra-text` flag to the library target in your `Scarb.toml{:md}` file: ```toml theme={null} [lib] sierra-text = true ``` ### Storage Variables Smart Contract Sierra Code You can find a more complex example of the [compiled Sierra code](./sierra_ir_storage_contract) of the [Storage Variables Example](../basic/variables#storage-variables). ## Further Reading * [Under the hood of Cairo 1.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-1) * [Under the hood of Cairo 2.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-2) * [Under the hood of Cairo 1.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-3) * [Cairo and Sierra](https://docs.starknet.io/architecture-and-learn/architecture/smart-contracts/cairo-and-sierra/) * [Sierra - Deep Dive](https://www.starknet.io/blog/sierra-deep-dive-video/) * [Cairo and MLIR](https://blog.lambdaclass.com/cairo-and-mlir/) # ECDSA verification Source: https://docs.starknet.io/build/starknet-by-example/advanced/signature-verification This is the Cairo adaptation of the [Solidity by Example - Verifying Signature](https://solidity-by-example.org/signature/). Messages can be signed off chain and then verified on chain using a smart contract. ```rust theme={null} use core::starknet::eth_address::EthAddress; use starknet::secp256_trait::{Signature}; // How to Sign and Verify // # Signing // 1. Create message to sign // 2. Hash the message // 3. Sign the hash (off chain, keep your private key secret) #[starknet::interface] trait IVerifySignature { fn get_signature(self: @TContractState, r: u256, s: u256, v: u32) -> Signature; fn verify_eth_signature( self: @TContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32, ); fn recover_public_key( self: @TContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32, ); } #[starknet::contract] mod verifySignature { use super::IVerifySignature; use core::starknet::eth_address::EthAddress; use starknet::secp256k1::Secp256k1Point; use starknet::secp256_trait::{Signature, signature_from_vrs, recover_public_key}; use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; #[storage] struct Storage { msg_hash: u256, signature: Signature, eth_address: EthAddress, } #[abi(embed_v0)] impl VerifySignature of IVerifySignature { /// This function returns the signature struct for the given parameters. /// /// # Arguments /// /// * `r` - The R component of the signature. /// * `s` - The S component of the signature. /// * `v` - The V component of the signature. /// /// # Returns /// /// * `Signature` - The signature struct. fn get_signature(self: @ContractState, r: u256, s: u256, v: u32) -> Signature { // Create a Signature object from the given v, r, and s values. let signature: Signature = signature_from_vrs(v, r, s); signature } /// Verifies an Ethereum signature. /// /// # Arguments /// /// * `eth_address` - The Ethereum address to verify the signature against. /// * `msg_hash` - The hash of the message that was signed. /// * `r` - The R component of the signature. /// * `s` - The S component of the signature. /// * `v` - The V component of the signature. fn verify_eth_signature( self: @ContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32, ) { let signature = self.get_signature(r, s, v); verify_eth_signature(:msg_hash, :signature, :eth_address); } /// Recovers the public key from an Ethereum signature and verifies that it matches the /// given Ethereum address. /// /// # Arguments /// /// * `eth_address` - The Ethereum address to verify the signature against. /// * `msg_hash` - The hash of the message that was signed. /// * `r` - The R component of the signature. /// * `s` - The S component of the signature. /// * `v` - The V component of the signature. fn recover_public_key( self: @ContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32, ) { let signature = self.get_signature(r, s, v); let public_key_point = recover_public_key::(msg_hash, signature) .unwrap(); let calculated_eth_address = public_key_point_to_eth_address(:public_key_point); assert(calculated_eth_address == eth_address, 'Invalid Address'); } } } ``` # Storage optimisation Source: https://docs.starknet.io/build/starknet-by-example/advanced/store-using-packing A smart contract has a limited amount of **storage slots**. Each slot can store a single `felt252` value. Writing to a storage slot has a cost, so we want to use as few storage slots as possible. In Cairo, every type is derived from the `felt252` type, which uses 252 bits to store a value. This design is quite simple, but it does have a drawback: it is not storage efficient. For example, if we want to store a `u8` value, we need to use an entire slot, even though we only need 8 bits. ## Packing When storing multiple values, we can use a technique called **packing**. Packing is a technique that allows us to store multiple values in a single `felt252` value. This is done by using the bits of the `felt252` value to store multiple values. For example, if we want to store two `u8` values, we can use the first 8 bits of the `felt252` value to store the first `u8` value, and the last 8 bits to store the second `u8` value. This way, we can store two `u8` values in a single `felt252` value. Cairo provides a built-in store using packing that you can use with the `StorePacking` trait. ```rust theme={null} trait StorePacking { fn pack(value: T) -> PackedT; fn unpack(value: PackedT) -> T; } ``` This allows us to store the type `T` by first packing it into the type `PackedT` with the `pack` function, and then storing the `PackedT` value with it's `Store` implementation. When reading the value, we first retrieve the `PackedT` value, and then unpack it into the type `T` using the `unpack` function. Here's an example of storing a `Time` struct with two `u8` values using the `StorePacking` trait: ```rust theme={null} #[derive(Copy, Serde, Drop)] pub struct Time { pub hour: u8, pub minute: u8, } #[starknet::interface] pub trait ITime { fn set(ref self: TContractState, value: Time); fn get(self: @TContractState) -> Time; } #[starknet::contract] mod TimeContract { use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; use super::Time; use starknet::storage_access::StorePacking; #[storage] struct Storage { time: Time, } impl TimePackable of StorePacking { fn pack(value: Time) -> felt252 { let msb: felt252 = 256 * value.hour.into(); let lsb: felt252 = value.minute.into(); msb + lsb } fn unpack(value: felt252) -> Time { let value: u16 = value.try_into().unwrap(); let (q, r) = DivRem::div_rem(value, 256_u16.try_into().unwrap()); let hour: u8 = Into::::into(q).try_into().unwrap(); let minute: u8 = Into::::into(r).try_into().unwrap(); Time { hour, minute } } } #[abi(embed_v0)] impl TimeContract of super::ITime { fn set(ref self: ContractState, value: Time) { // This will call the pack method of the TimePackable trait // and store the resulting felt252 self.time.write(value); } fn get(self: @ContractState) -> Time { // This will read the felt252 value from storage // and return the result of the unpack method of the TimePackable trait return self.time.read(); } } } ``` # Structs as mapping keys Source: https://docs.starknet.io/build/starknet-by-example/advanced/struct-mapping-key In order to use structs as mapping keys, you can use `#[derive(Hash)]` on the struct definition. This will automatically generate a hash function for the struct that can be used to represent the struct as a key in a `Map`. Consider the following example in which we would like to use an object of type `Pet` as a key in a `Map`. The `Pet` struct has three fields: `name`, `age` and `owner`. We consider that the combination of these three fields uniquely identifies a pet. ```rust theme={null} #[derive(Copy, Drop, Serde, Hash)] pub struct Pet { pub name: felt252, pub age: u8, pub owner: felt252, } #[starknet::interface] pub trait IPetRegistry { fn register_pet(ref self: TContractState, key: Pet, timestamp: u64); fn get_registration_date(self: @TContractState, key: Pet) -> u64; } #[starknet::contract] pub mod PetRegistry { use super::Pet; use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; #[storage] struct Storage { registration_time: Map::, } #[abi(embed_v0)] impl PetRegistry of super::IPetRegistry { fn register_pet(ref self: ContractState, key: Pet, timestamp: u64) { self.registration_time.write(key, timestamp); } fn get_registration_date(self: @TContractState, key: Pet) -> u64 { self.registration_time.read(key) } } } ``` # Verifying ZK proofs Source: https://docs.starknet.io/build/starknet-by-example/advanced/verify-proofs This example shows how to verify SNARK proofs on Starknet using a practical example of a token minting system that requires proof of knowledge of a secret. ## ZK-SNARKs **zk-SNARKs** (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge) are cryptographic proofs that enable one party (the *prover*) to demonstrate knowledge of specific information to another party (the *verifier*) without revealing the information itself. * **Zero-Knowledge (Privacy)**: Ensures computation inputs remain private while proving correctness. The proof only reveals the statement's validity, not the underlying data. * **Succinctness**: Proofs remain small regardless of statement complexity, with verification being computationally cheaper than proof generation. This enables efficient verification of large computations. * **Non-Interactivity**: Proofs require no further communication between prover and verifier after generation, ideal for decentralized environments. * **Integrity**: Guarantees computation correctness without requiring re-execution. ### Common Use Cases * **Identity Verification**: Prove attributes (age, nationality, membership) without revealing actual details. Enables trustless verification without storing sensitive data. * **Scalable Rollups**: Bundle multiple transaction proofs into a single proof, eliminating the need for re-execution. * **Proof of Reserves**: Demonstrate sufficient funds for service eligibility without disclosing actual balances. ## Example: Proof of Secret with Replay Attack Protection This example shows how to implement a token minting system where users can mint tokens by proving knowledge of a secret password without revealing it. The system includes protection against replay attacks, ensuring each proof is unique to its generator. We will use the following: * **Circom**: Domain-specific language for defining arithmetic circuits, the foundation of zk-SNARKs. * **Groth16**: A pairing-based zk-SNARK system that provides the mathematical framework for proof generation and verification. * **Snarkjs**: JavaScript library for generating and verifying zk-SNARK proofs. * **Garaga**: Enables efficient elliptic curve operations on Starknet, including Groth16 smart contract verifier generation. ### 1. Circuit Definition Create a circuit that: * Takes 3 inputs: * User address (public) * Password hash (public) * Password in plain text (private) * Computes the hash of the plain text password * Compares it with the public hash * Generates a user-specific proof to prevent replay attacks ```solidity theme={null} pragma circom 2.0.0; include "../node_modules/circomlib/circuits/poseidon.circom"; template PasswordCheck() { // Public inputs signal input userAddress; signal input pwdHash; // Private input signal input pwd; // (Public) output signal output uniqueToUser; // Make sure password is the correct one by comparing its hash to the expected known hash component hasher = Poseidon(1); hasher.inputs[0] <== pwd; hasher.out === pwdHash; // Compute a number unique to user so that other users can't simply copy and use same proof // but instead have to execute this circuit to generate a proof unique to them component uniqueHasher = Poseidon(2); uniqueHasher.inputs[0] <== pwdHash; uniqueHasher.inputs[1] <== userAddress; uniqueToUser <== uniqueHasher.out; } component main {public [userAddress, pwdHash]} = PasswordCheck(); ``` ### 2. Circuit Compilation The circuit computes the hash of the plain text password and compares the result to the publicly known hash of the password. This equality assertion is one of the constraints set by the circuit. The rest of the code is to generate a proof unique to the user to avoid replay attacks (more about it later). ```bash [Terminal] theme={null} mkdir target circom src/circuit/circuit.circom -l node_modules --r1cs --wasm --output target ``` ### 3. Trusted Setup The **trusted setup** is a phase in the zk-SNARK protocol where cryptographic parameters, known as a *proving key* and a *verification key*, are generated. These keys are essential for the prover to create proofs and for the verifier to validate them. #### Phase 1: "Powers of Tau" Ceremony A trusted setup ceremony is a collaborative process where multiple participants contribute randomness to create the cryptographic parameters for a proof system (the proving and verification keys), with the goal to provide additional security. You can provide additional contributions if you wish to do so. * Initialize powers of tau ceremony: ```bash [Terminal] theme={null} mkdir target/ptau && cd target/ptau snarkjs powersoftau new bn128 12 pot12_0000.ptau -v ``` ```bash [Terminal] theme={null} snarkjs powersoftau contribute pot12_0000.ptau pot12.ptau --name="My contribution to part 1" -v -e="some random text for the contribution to part 1" ``` #### Phase 2: Circuit Dependent * Finalize ptau file: ```bash [Terminal] theme={null} snarkjs powersoftau prepare phase2 pot12.ptau pot12_final.ptau -v ``` * Generate a zkey file: ```bash [Terminal] theme={null} cd .. snarkjs groth16 setup circuit.r1cs ptau/pot12_final.ptau circuit_0000.zkey ``` * Contribute to Phase 2: ```bash [Terminal] theme={null} snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="My contribution to part 2" -v -e="some random text for the contribution to part 2" ``` We now have our proving key (`circuit_0001.zkey{:md}`) that we will use, along with the compiled circuit and the input to the circuit, to generate proofs. * Export verification key: ```bash [Terminal] theme={null} snarkjs zkey export verificationkey circuit_0001.zkey circuit_verification_key.json ``` We have our verification key (`circuit_verification_key.json{:md}`) that we will use, along with the generated proof and its outputs, to verify proofs. ### 4. Proof Generation #### Generate witness The **witness** refers to the private input and intermediate values that the prover knows and uses to generate the proof. The intermediate values correspond to the values computed during the circuit execution. These are also part of the witness and are necessary for proving the correctness of the computation. In short, the witness is a complete set of values that satisfies the constraints defined by the zk-SNARK circuit. ```bash [Terminal] theme={null} node circuit_js/generate_witness.js circuit_js/circuit.wasm ../src/circuit/input.json witness.wtns ``` #### Generate proof ```bash [Terminal] theme={null} snarkjs groth16 prove circuit_0001.zkey witness.wtns proof.json public.json ``` To generate a proof, 3 information are needed: * compiled circuit * circuit inputs * proving key To verify a proof, 3 information are also needed: * proof * circuit outputs (obtained when generating proof) * verification key ### 5. Generate verifier contract ```bash [Terminal] theme={null} garaga gen --system groth16 --vk circuit_verification_key.json ``` This above command will generate a cairo project with the verifier contract, with the main endpoint `verify_groth16_proof_[curve_name]{:md}`. Garaga also provides some command utilities to deploy it on-chain. Else, you can deploy it like any other contract (using starkli or sncast for example). Here is the generated starknet contract: ```rust theme={null} use super::groth16_verifier_constants::{N_PUBLIC_INPUTS, vk, ic, precomputed_lines}; #[starknet::interface] trait IGroth16VerifierBN254 { fn verify_groth16_proof_bn254( self: @TContractState, full_proof_with_hints: Span, ) -> Option>; } #[starknet::contract] mod Groth16VerifierBN254 { use starknet::SyscallResultTrait; use garaga::definitions::{G1Point, G1G2Pair}; use garaga::groth16::{multi_pairing_check_bn254_3P_2F_with_extra_miller_loop_result}; use garaga::ec_ops::{G1PointTrait, ec_safe_add}; use garaga::ec_ops_g2::{G2PointTrait}; use garaga::utils::calldata::{deserialize_full_proof_with_hints_bn254}; use super::{N_PUBLIC_INPUTS, vk, ic, precomputed_lines}; const ECIP_OPS_CLASS_HASH: felt252 = 0x70c1d1c709c75e3cf51d79d19cf7c84a0d4521f3a2b8bf7bff5cb45ee0dd289; use starknet::ContractAddress; #[storage] struct Storage {} #[abi(embed_v0)] impl IGroth16VerifierBN254 of super::IGroth16VerifierBN254 { fn verify_groth16_proof_bn254( self: @ContractState, full_proof_with_hints: Span, ) -> Option> { // DO NOT EDIT THIS FUNCTION UNLESS YOU KNOW WHAT YOU ARE DOING. // This function returns an Option for the public inputs if the proof is valid. // If the proof is invalid, the execution will either fail or return None. // Read the documentation to learn how to generate the full_proof_with_hints array given // a proof and a verifying key. let fph = deserialize_full_proof_with_hints_bn254(full_proof_with_hints); let groth16_proof = fph.groth16_proof; let mpcheck_hint = fph.mpcheck_hint; let small_Q = fph.small_Q; let msm_hint = fph.msm_hint; groth16_proof.a.assert_on_curve(0); groth16_proof.b.assert_on_curve(0); groth16_proof.c.assert_on_curve(0); let ic = ic.span(); let vk_x: G1Point = match ic.len() { 0 => panic!("Malformed VK"), 1 => *ic.at(0), _ => { // Start serialization with the hint array directly to avoid copying it. let mut msm_calldata: Array = msm_hint; // Add the points from VK and public inputs to the proof. Serde::serialize(@ic.slice(1, N_PUBLIC_INPUTS), ref msm_calldata); Serde::serialize(@groth16_proof.public_inputs, ref msm_calldata); // Complete with the curve identifier (0 for BN254): msm_calldata.append(0); // Call the multi scalar multiplication endpoint on the Garaga ECIP ops contract // to obtain vk_x. let mut _vx_x_serialized = core::starknet::syscalls::library_call_syscall( ECIP_OPS_CLASS_HASH.try_into().unwrap(), selector!("msm_g1"), msm_calldata.span(), ) .unwrap_syscall(); ec_safe_add( Serde::::deserialize(ref _vx_x_serialized).unwrap(), *ic.at(0), 0, ) }, }; // Perform the pairing check. let check = multi_pairing_check_bn254_3P_2F_with_extra_miller_loop_result( G1G2Pair { p: vk_x, q: vk.gamma_g2 }, G1G2Pair { p: groth16_proof.c, q: vk.delta_g2 }, G1G2Pair { p: groth16_proof.a.negate(0), q: groth16_proof.b }, vk.alpha_beta_miller_loop_result, precomputed_lines.span(), mpcheck_hint, small_Q, ); if check == true { return Option::Some(groth16_proof.public_inputs); } else { return Option::None; } } } } ``` ### 6. Generate calldata & call on-chain verifier contract This step is useful for generating calldata from the proof & circuit execution outputs, which can then be sent to the verifier contract to verify the proof on-chain. In this example, there is an intermediary contract, `ZkERC20Token`, which will itself call the verifier contract (more about it below). ```bash [Terminal] theme={null} garaga calldata --system groth16 --vk circuit_verification_key.json --proof proof.json --public-inputs public.json --format starkli | xargs starkli invoke --account ~/.starkli-wallets/deployer/account.json --keystore ~/.starkli-wallets/deployer/keystore.json --network sepolia --watch 0x00375cf5081763e1f2a7ed5e28d4253c6135243385f432492dda00861ec5e58f mint_with_proof ``` Garaga also provides some command utilities to call the verifier contract directly abstracting the calldata generation part, simplifying the above command. ### 7. `ZkERC20Token` contract This contract allows anyone to mint free tokens if they know a secret password (`2468`). You can submit your proof calldata to this contract, which will itself call the generated verifier contract. If the proof verification passes and the proof is indeed unique to you (ie, you generated it yourself), you can receive the free tokens. Otherwise, the endpoint execution will revert. You can mint free tokens only once per user. Contract Address (Sepolia testnet): `0x00375cf5081763e1f2a7ed5e28d4253c6135243385f432492dda00861ec5e58f{:md}` ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] trait IZkERC20Token { fn mint_with_proof(ref self: TContractState, full_proof: Span); fn has_user_minted(self: @TContractState, address: ContractAddress) -> bool; } #[starknet::interface] trait IGroth16VerifierBN254 { fn verify_groth16_proof_bn254( self: @TContractState, full_proof_with_hints: Span, ) -> Option>; } mod errors { pub const ALREADY_MINTED: felt252 = "User has already minted tokens"; pub const PROOF_NOT_VERIFIED: felt252 = "Proof is not correct"; pub const PROOF_ALREADY_USED: felt252 = "Generate a proof unique to you"; } #[starknet::contract] pub mod ZkERC20Token { use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; use starknet::{ContractAddress, get_caller_address}; use super::{errors, IGroth16VerifierBN254Dispatcher, IGroth16VerifierBN254DispatcherTrait}; use starknet::storage::{ Map, StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry, }; component!(path: ERC20Component, storage: erc20, event: ERC20Event); #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; const MINT_WITH_PROOF_TOKEN_REWARD: u8 = 100; // used in the front end to generate the proof const PASSWORD_HASH: u256 = 16260938803047823847354854419633652218467975114284208787981985448019235110758; #[storage] struct Storage { #[substorage(v0)] erc20: ERC20Component::Storage, verifier_contract: IGroth16VerifierBN254Dispatcher, users_who_minted: Map, } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] ERC20Event: ERC20Component::Event, } #[constructor] fn constructor( ref self: ContractState, initial_supply: u256, recipient: ContractAddress, name: ByteArray, symbol: ByteArray, proof_verifier_address: ContractAddress, ) { self.erc20.initializer(name, symbol); self.erc20.mint(recipient, initial_supply); self .verifier_contract .write(IGroth16VerifierBN254Dispatcher { contract_address: proof_verifier_address }); } #[abi(embed_v0)] impl ZkERC20TokenImpl of super::IZkERC20Token { fn mint_with_proof(ref self: ContractState, full_proof: Span) { let caller = get_caller_address(); // Prevent a user from receiving tokens twice assert(!self.users_who_minted.entry(caller).read(), errors::ALREADY_MINTED); // Verify the correctness of the proof by calling the verifier contract // If incorrect, execution of the verifier will fail or return an Option::None let proof_public_inputs = self .verifier_contract .read() .verify_groth16_proof_bn254(full_proof); assert( proof_public_inputs.is_some() && proof_public_inputs.unwrap().len() == 3, errors::PROOF_NOT_VERIFIED, ); // Verify the proof has been generated by the user calling this smart contract let user_address_dec: u256 = *proof_public_inputs.unwrap().at(1); let address_felt252: felt252 = caller.into(); assert(address_felt252.into() == user_address_dec, errors::PROOF_ALREADY_USED); // Mint tokens only if the proof is valid and has been generated by the user self.erc20.mint(caller, MINT_WITH_PROOF_TOKEN_REWARD.into()); self.users_who_minted.entry(caller).write(true); } fn has_user_minted(self: @TContractState, address: ContractAddress) -> bool { self.users_who_minted.entry(address).read() } } } ``` For more detailed information about the technologies used, refer to: * [Circom Documentation](https://docs.circom.io/) * [Snarkjs GitHub](https://github.com/iden3/snarkjs) * [Garaga Documentation](https://garaga.gitbook.io/garaga) # Writing to any storage slot Source: https://docs.starknet.io/build/starknet-by-example/advanced/write-to-any-slot On Starknet, a contract's storage is a map with $( 2^{251} )$ slots, where each slot is a `felt252` which is initialized to 0. The address of storage variables is computed at compile time using the formula: $$ storage\_variable\_address~\text{:=}~\text{pedersen}(\text{keccak}(variable\_name),~keys) $$ Interactions with storage variables are commonly performed using the `self.var.read()` and `self.var.write()` functions. Nevertheless, we can use the `storage_write_syscall` and `storage_read_syscall` syscalls, to write to and read from any storage slot. This is useful when writing to storage variables that are not known at compile time, or to ensure that even if the contract is upgraded and the computation method of storage variable addresses changes, they remain accessible. In the following example, we use the Poseidon hash function to compute the address of a storage variable. Poseidon is a ZK-friendly hash function that is cheaper and faster than Pedersen, making it an excellent choice for onchain computations. Once the address is computed, we use the storage syscalls to interact with it. ```rust theme={null} #[starknet::interface] pub trait IWriteToAnySlots { fn write_slot(ref self: TContractState, value: u32); fn read_slot(self: @TContractState) -> u32; } #[starknet::contract] pub mod WriteToAnySlot { use starknet::syscalls::{storage_read_syscall, storage_write_syscall}; use core::poseidon::poseidon_hash_span; use starknet::StorageAddress; #[storage] struct Storage {} const SLOT_NAME: felt252 = 'test_slot'; #[abi(embed_v0)] impl WriteToAnySlot of super::IWriteToAnySlots { fn write_slot(ref self: ContractState, value: u32) { storage_write_syscall(0, get_address_from_name(SLOT_NAME), value.into()).unwrap(); } fn read_slot(self: @ContractState) -> u32 { storage_read_syscall(0, get_address_from_name(SLOT_NAME)).unwrap().try_into().unwrap() } } pub fn get_address_from_name(variable_name: felt252) -> StorageAddress { let mut data: Array = array![]; data.append(variable_name); let hashed_name: felt252 = poseidon_hash_span(data.span()); let MASK_250: u256 = 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; // By taking the 250 least significant bits of the hash output, we get a valid 250bits // storage address. let result: felt252 = (hashed_name.into() & MASK_250).try_into().unwrap(); let result: StorageAddress = result.try_into().unwrap(); result } } ``` # Advanced factory Source: https://docs.starknet.io/build/starknet-by-example/applications/advanced-factory This is an example of an advanced factory contract that manages crowdfunding Campaign contracts created in the ["Crowdfunding" chapter](./crowdfunding). The advanced factory allows for a centralized creation and management of `Campaign` contracts on the Starknet blockchain, ensuring that they adhere to a standard interface and can be easily upgraded. Key Features 1. **Campaign Creation**: Users can create new crowdfunding campaigns with specific details such as title, description, goal, and duration. 2. **Campaign Management**: The factory contract stores and manages the campaigns, allowing for upgrades and tracking. 3. **Upgrade Mechanism**: The factory owner can update the implementation of the campaign contract, ensuring that all campaigns benefit from improvements and bug fixes. * the factory only updates it's `Campaign` class hash and emits an event to notify any listeners, but the `Campaign` creators are in the end responsible for actually upgrading their contracts. ```rust theme={null} pub use starknet::{ContractAddress, ClassHash}; #[starknet::interface] pub trait ICampaignFactory { fn create_campaign( ref self: TContractState, title: ByteArray, description: ByteArray, goal: u256, start_time: u64, end_time: u64, token_address: ContractAddress, ) -> ContractAddress; fn get_campaign_class_hash(self: @TContractState) -> ClassHash; fn update_campaign_class_hash(ref self: TContractState, new_class_hash: ClassHash); fn upgrade_campaign( ref self: TContractState, campaign_address: ContractAddress, new_end_time: Option, ); } #[starknet::contract] pub mod CampaignFactory { use core::num::traits::Zero; use starknet::{ContractAddress, ClassHash, syscalls::deploy_syscall, get_caller_address}; use crowdfunding::campaign::{ICampaignDispatcher, ICampaignDispatcherTrait}; use components::ownable::ownable_component; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; component!(path: ownable_component, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] impl OwnableImpl = ownable_component::Ownable; impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; #[storage] struct Storage { #[substorage(v0)] ownable: ownable_component::Storage, /// Store all of the created campaign instances' addresses and their class hashes campaigns: Map<(ContractAddress, ContractAddress), ClassHash>, /// Store the class hash of the contract to deploy campaign_class_hash: ClassHash, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { #[flat] OwnableEvent: ownable_component::Event, CampaignClassHashUpgraded: CampaignClassHashUpgraded, CampaignCreated: CampaignCreated, ClassHashUpdated: ClassHashUpdated, } #[derive(Drop, starknet::Event)] pub struct ClassHashUpdated { pub new_class_hash: ClassHash, } #[derive(Drop, starknet::Event)] pub struct CampaignClassHashUpgraded { pub campaign: ContractAddress, } #[derive(Drop, starknet::Event)] pub struct CampaignCreated { pub creator: ContractAddress, pub contract_address: ContractAddress, } pub mod Errors { pub const CLASS_HASH_ZERO: felt252 = "Class hash cannot be zero"; pub const ZERO_ADDRESS: felt252 = "Zero address"; pub const SAME_IMPLEMENTATION: felt252 = "Implementation is unchanged"; pub const CAMPAIGN_NOT_FOUND: felt252 = "Campaign not found"; } #[constructor] fn constructor(ref self: ContractState, class_hash: ClassHash) { assert(class_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); self.campaign_class_hash.write(class_hash); self.ownable._init(get_caller_address()); } #[abi(embed_v0)] impl CampaignFactory of super::ICampaignFactory { fn create_campaign( ref self: ContractState, title: ByteArray, description: ByteArray, goal: u256, start_time: u64, end_time: u64, token_address: ContractAddress, ) -> ContractAddress { let creator = get_caller_address(); // Create constructor arguments let mut constructor_calldata: Array:: = array![]; ((creator, title, description, goal), start_time, end_time, token_address) .serialize(ref constructor_calldata); // Contract deployment let (contract_address, _) = deploy_syscall( self.campaign_class_hash.read(), 0, constructor_calldata.span(), false, ) .unwrap(); // track new campaign instance self.campaigns.write((creator, contract_address), self.campaign_class_hash.read()); self.emit(Event::CampaignCreated(CampaignCreated { creator, contract_address })); contract_address } fn get_campaign_class_hash(self: @ContractState) -> ClassHash { self.campaign_class_hash.read() } fn update_campaign_class_hash(ref self: ContractState, new_class_hash: ClassHash) { self.ownable._assert_only_owner(); assert(new_class_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); self.campaign_class_hash.write(new_class_hash); self.emit(Event::ClassHashUpdated(ClassHashUpdated { new_class_hash })); } fn upgrade_campaign( ref self: ContractState, campaign_address: ContractAddress, new_end_time: Option, ) { assert(campaign_address.is_non_zero(), Errors::ZERO_ADDRESS); let creator = get_caller_address(); let old_class_hash = self.campaigns.read((creator, campaign_address)); assert(old_class_hash.is_non_zero(), Errors::CAMPAIGN_NOT_FOUND); assert(old_class_hash != self.campaign_class_hash.read(), Errors::SAME_IMPLEMENTATION); let campaign = ICampaignDispatcher { contract_address: campaign_address }; campaign.upgrade(self.campaign_class_hash.read(), new_end_time); } } } ``` # Constant product AMM Source: https://docs.starknet.io/build/starknet-by-example/applications/constant-product-amm This is the Cairo adaptation of the [Solidity by Example - Constant Product AMM](https://solidity-by-example.org/defi/constant-product-amm/). In this contract, we implement a simple Automated Market Maker (AMM) following the **constant product formula**: $( x \cdot y = k )$. This formula ensures that the product of the two token reserves ($x$ and $y$ representing the tokens being swapper) remains constant, regardless of trades. Here, we provide liquidity pools that allow users to trade between two tokens or add and remove liquidity from the pool. ## Key Concepts 1. **approve() before swap or adding liquidity**: Before interacting with the AMM (whether through swaps or adding liquidity), the user must approve the contract to spend their tokens. This is done by calling the `approve()` function on the ERC20 token contracts, allowing the AMM to transfer the required tokens on behalf of the user. 2. **Constant Product Formula for Swaps**: The swap function operates based on the constant product formula $( x \cdot y = k )$, where $x$ and $y$ are the token reserves. When a user swaps one token for another, the product of the reserves remains constant, which determines how much of the other token the user will receive. 3. **Shares and Token Ratios for Liquidity**: When adding liquidity, users provide both tokens in the ratio of the current reserves. The number of shares (liquidity tokens) the user receives represents their contribution to the pool. Similarly, when removing liquidity, users receive back tokens proportional to the number of shares they burn. ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IConstantProductAmm { fn swap(ref self: TContractState, token_in: ContractAddress, amount_in: u256) -> u256; fn add_liquidity(ref self: TContractState, amount0: u256, amount1: u256) -> u256; fn remove_liquidity(ref self: TContractState, shares: u256) -> (u256, u256); } #[starknet::contract] pub mod ConstantProductAmm { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ContractAddress, get_caller_address, get_contract_address}; use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; use core::num::traits::Sqrt; #[storage] struct Storage { token0: IERC20Dispatcher, token1: IERC20Dispatcher, reserve0: u256, reserve1: u256, total_supply: u256, balance_of: Map::, // Fee 0 - 1000 (0% - 100%, 1 decimal places) // E.g. 3 = 0.3% fee: u16, } #[constructor] fn constructor( ref self: ContractState, token0: ContractAddress, token1: ContractAddress, fee: u16, ) { // assert(fee <= 1000, 'fee > 1000'); self.token0.write(IERC20Dispatcher { contract_address: token0 }); self.token1.write(IERC20Dispatcher { contract_address: token1 }); self.fee.write(fee); } #[generate_trait] impl PrivateFunctions of PrivateFunctionsTrait { fn _mint(ref self: ContractState, to: ContractAddress, amount: u256) { self.balance_of.write(to, self.balance_of.read(to) + amount); self.total_supply.write(self.total_supply.read() + amount); } fn _burn(ref self: ContractState, from: ContractAddress, amount: u256) { self.balance_of.write(from, self.balance_of.read(from) - amount); self.total_supply.write(self.total_supply.read() - amount); } fn _update(ref self: ContractState, reserve0: u256, reserve1: u256) { self.reserve0.write(reserve0); self.reserve1.write(reserve1); } #[inline(always)] fn select_token(self: @ContractState, token: ContractAddress) -> bool { assert( token == self.token0.read().contract_address || token == self.token1.read().contract_address, "invalid token", ); token == self.token0.read().contract_address } #[inline(always)] fn min(x: u256, y: u256) -> u256 { if (x <= y) { x } else { y } } } #[abi(embed_v0)] impl ConstantProductAmm of super::IConstantProductAmm { fn swap(ref self: ContractState, token_in: ContractAddress, amount_in: u256) -> u256 { assert(amount_in > 0, "amount in = 0"); let is_token0: bool = self.select_token(token_in); let (token0, token1): (IERC20Dispatcher, IERC20Dispatcher) = ( self.token0.read(), self.token1.read(), ); let (reserve0, reserve1): (u256, u256) = (self.reserve0.read(), self.reserve1.read()); let ( token_in, token_out, reserve_in, reserve_out, ): (IERC20Dispatcher, IERC20Dispatcher, u256, u256) = if (is_token0) { (token0, token1, reserve0, reserve1) } else { (token1, token0, reserve1, reserve0) }; let caller = get_caller_address(); let this = get_contract_address(); token_in.transfer_from(caller, this, amount_in); // How much dy for dx? // xy = k // (x + dx)(y - dy) = k // y - dy = k / (x + dx) // y - k / (x + dx) = dy // y - xy / (x + dx) = dy // (yx + ydx - xy) / (x + dx) = dy // ydx / (x + dx) = dy let amount_in_with_fee = (amount_in * (1000 - self.fee.read().into()) / 1000); let amount_out = (reserve_out * amount_in_with_fee) / (reserve_in + amount_in_with_fee); token_out.transfer(caller, amount_out); self._update(self.token0.read().balance_of(this), self.token1.read().balance_of(this)); amount_out } fn add_liquidity(ref self: ContractState, amount0: u256, amount1: u256) -> u256 { let caller = get_caller_address(); let this = get_contract_address(); let (token0, token1): (IERC20Dispatcher, IERC20Dispatcher) = ( self.token0.read(), self.token1.read(), ); token0.transfer_from(caller, this, amount0); token1.transfer_from(caller, this, amount1); // How much dx, dy to add? // // xy = k // (x + dx)(y + dy) = k' // // No price change, before and after adding liquidity // x / y = (x + dx) / (y + dy) // // x(y + dy) = y(x + dx) // x * dy = y * dx // // x / y = dx / dy // dy = y / x * dx let (reserve0, reserve1): (u256, u256) = (self.reserve0.read(), self.reserve1.read()); if (reserve0 > 0 || reserve1 > 0) { assert(reserve0 * amount1 == reserve1 * amount0, "x / y != dx / dy"); } // How many shares to mint? // // f(x, y) = value of liquidity // We will define f(x, y) = sqrt(xy) // // L0 = f(x, y) // L1 = f(x + dx, y + dy) // T = total shares // s = shares to mint // // Total shares should increase proportional to increase in liquidity // L1 / L0 = (T + s) / T // // L1 * T = L0 * (T + s) // // (L1 - L0) * T / L0 = s // Claim // (L1 - L0) / L0 = dx / x = dy / y // // Proof // --- Equation 1 --- // (L1 - L0) / L0 = (sqrt((x + dx)(y + dy)) - sqrt(xy)) / sqrt(xy) // // dx / dy = x / y so replace dy = dx * y / x // // --- Equation 2 --- // Equation 1 = (sqrt(xy + 2ydx + dx^2 * y / x) - sqrt(xy)) / sqrt(xy) // // Multiply by sqrt(x) / sqrt(x) // Equation 2 = (sqrt(x^2y + 2xydx + dx^2 * y) - sqrt(x^2y)) / sqrt(x^2y) // = (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2))) / (sqrt(y)sqrt(x^2)) // sqrt(y) on top and bottom cancels out // // --- Equation 3 --- // Equation 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / sqrt(x^2) // = (sqrt((x + dx)^2) - sqrt(x^2)) / sqrt(x^2) // = ((x + dx) - x) / x // = dx / x // Since dx / dy = x / y, // dx / x = dy / y // // Finally // (L1 - L0) / L0 = dx / x = dy / y let total_supply = self.total_supply.read(); let shares = if (total_supply == 0) { (amount0 * amount1).sqrt().into() } else { PrivateFunctions::min( amount0 * total_supply / reserve0, amount1 * total_supply / reserve1, ) }; assert(shares > 0, "shares = 0"); self._mint(caller, shares); self._update(self.token0.read().balance_of(this), self.token1.read().balance_of(this)); shares } fn remove_liquidity(ref self: ContractState, shares: u256) -> (u256, u256) { let caller = get_caller_address(); let this = get_contract_address(); let (token0, token1): (IERC20Dispatcher, IERC20Dispatcher) = ( self.token0.read(), self.token1.read(), ); // Claim // dx, dy = amount of liquidity to remove // dx = s / T * x // dy = s / T * y // // Proof // Let's find dx, dy such that // v / L = s / T // // where // v = f(dx, dy) = sqrt(dxdy) // L = total liquidity = sqrt(xy) // s = shares // T = total supply // // --- Equation 1 --- // v = s / T * L // sqrt(dxdy) = s / T * sqrt(xy) // // Amount of liquidity to remove must not change price so // dx / dy = x / y // // replace dy = dx * y / x // sqrt(dxdy) = sqrt(dx * dx * y / x) = dx * sqrt(y / x) // // Divide both sides of Equation 1 with sqrt(y / x) // dx = s / T * sqrt(xy) / sqrt(y / x) // = s / T * sqrt(x^2) = s / T * x // // Likewise // dy = s / T * y // bal0 >= reserve0 // bal1 >= reserve1 let (bal0, bal1): (u256, u256) = (token0.balance_of(this), token1.balance_of(this)); let total_supply = self.total_supply.read(); let (amount0, amount1): (u256, u256) = ( (shares * bal0) / total_supply, (shares * bal1) / total_supply, ); assert(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0"); self._burn(caller, shares); self._update(bal0 - amount0, bal1 - amount1); token0.transfer(caller, amount0); token1.transfer(caller, amount1); (amount0, amount1) } } } ``` # Crowdfunding Source: https://docs.starknet.io/build/starknet-by-example/applications/crowdfunding Crowdfunding is a method of raising capital through the collective effort of many individuals. It allows project creators to raise funds from a large number of people, usually through small contributions. 1. Contract admin creates a campaign in some user's name (i.e. creator). 2. Users can pledge, transferring their token to a campaign. 3. Users can "unpledge", retrieving their tokens. 4. The creator can at any point refund any of the users. 5. Once the total amount pledged is more than the campaign goal, the campaign funds are "locked" in the contract, meaning the users can no longer unpledge; they can still pledge though. 6. After the campaign ends, the campaign creator can claim the funds if the campaign goal is reached. 7. Otherwise, campaign did not reach it's goal, pledgers can retrieve their funds. 8. The creator can at any point cancel the campaign for whatever reason and refund all of the pledgers. 9. The contract admin can upgrade the contract implementation, refunding all of the users and resetting the campaign state (we will use this in the [Advanced Factory chapter](./advanced-factory)). Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `Map`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IPledgeable { fn add(ref self: TContractState, pledger: ContractAddress, amount: u256); fn get(self: @TContractState, pledger: ContractAddress) -> u256; fn get_pledger_count(self: @TContractState) -> u32; fn array(self: @TContractState) -> Array; fn get_total(self: @TContractState) -> u256; fn remove(ref self: TContractState, pledger: ContractAddress) -> u256; } #[starknet::component] pub mod pledgeable_component { use core::array::ArrayTrait; use core::num::traits::Zero; use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; #[storage] pub struct Storage { index_to_pledger: Map, pledger_to_amount: Map, pledger_count: u32, total_amount: u256, } #[event] #[derive(Drop, starknet::Event)] pub enum Event {} mod Errors { pub const INCONSISTENT_STATE: felt252 = "Non-indexed pledger found"; } #[embeddable_as(Pledgeable)] pub impl PledgeableImpl< TContractState, +HasComponent, > of super::IPledgeable> { fn add(ref self: ComponentState, pledger: ContractAddress, amount: u256) { let old_amount: u256 = self.pledger_to_amount.read(pledger); if old_amount == 0 { let index = self.pledger_count.read(); self.index_to_pledger.write(index, pledger); self.pledger_count.write(index + 1); } self.pledger_to_amount.write(pledger, old_amount + amount); self.total_amount.write(self.total_amount.read() + amount); } fn get(self: @ComponentState, pledger: ContractAddress) -> u256 { self.pledger_to_amount.read(pledger) } fn get_pledger_count(self: @ComponentState) -> u32 { self.pledger_count.read() } fn array(self: @ComponentState) -> Array { let mut result = array![]; let mut index = self.pledger_count.read(); while index != 0 { index -= 1; let pledger = self.index_to_pledger.read(index); result.append(pledger); }; result } fn get_total(self: @ComponentState) -> u256 { self.total_amount.read() } fn remove(ref self: ComponentState, pledger: ContractAddress) -> u256 { let amount: u256 = self.pledger_to_amount.read(pledger); // check if the pledge even exists if amount == 0 { return 0; } let last_index = self.pledger_count.read() - 1; // if there are other pledgers, we need to update our indices if last_index != 0 { let mut pledger_index = last_index; loop { if self.index_to_pledger.read(pledger_index) == pledger { break; } // if pledger_to_amount contains a pledger, then so does index_to_pledger // thus this will never underflow pledger_index -= 1; }; self.index_to_pledger.write(pledger_index, self.index_to_pledger.read(last_index)); } // last_index == new pledger count self.pledger_count.write(last_index); self.pledger_to_amount.write(pledger, 0); self.index_to_pledger.write(last_index, Zero::zero()); self.total_amount.write(self.total_amount.read() - amount); amount } } } ``` Now we can create the `Campaign` contract. ```rust theme={null} pub mod pledgeable; use starknet::{ClassHash, ContractAddress}; #[derive(Drop, Serde)] pub struct Details { pub canceled: bool, pub claimed: bool, pub creator: ContractAddress, pub description: ByteArray, pub end_time: u64, pub goal: u256, pub start_time: u64, pub title: ByteArray, pub token: ContractAddress, pub total_pledges: u256, } #[starknet::interface] pub trait ICampaign { fn claim(ref self: TContractState); fn cancel(ref self: TContractState, reason: ByteArray); fn pledge(ref self: TContractState, amount: u256); fn get_pledge(self: @TContractState, pledger: ContractAddress) -> u256; fn get_pledgers(self: @TContractState) -> Array; fn get_details(self: @TContractState) -> Details; fn refund(ref self: TContractState, pledger: ContractAddress, reason: ByteArray); fn upgrade(ref self: TContractState, impl_hash: ClassHash, new_end_time: Option); fn unpledge(ref self: TContractState, reason: ByteArray); } #[starknet::contract] pub mod Campaign { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::ownable::ownable_component::OwnableInternalTrait; use core::num::traits::Zero; use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ ClassHash, ContractAddress, get_block_timestamp, get_caller_address, get_contract_address, }; use components::ownable::ownable_component; use super::pledgeable::pledgeable_component; use super::Details; component!(path: ownable_component, storage: ownable, event: OwnableEvent); component!(path: pledgeable_component, storage: pledges, event: PledgeableEvent); #[abi(embed_v0)] pub impl OwnableImpl = ownable_component::Ownable; impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; #[abi(embed_v0)] impl PledgeableImpl = pledgeable_component::Pledgeable; #[storage] struct Storage { canceled: bool, claimed: bool, creator: ContractAddress, description: ByteArray, end_time: u64, goal: u256, #[substorage(v0)] ownable: ownable_component::Storage, #[substorage(v0)] pledges: pledgeable_component::Storage, start_time: u64, title: ByteArray, token: IERC20Dispatcher, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { Claimed: Claimed, Canceled: Canceled, #[flat] OwnableEvent: ownable_component::Event, PledgeableEvent: pledgeable_component::Event, PledgeMade: PledgeMade, Refunded: Refunded, RefundedAll: RefundedAll, Unpledged: Unpledged, Upgraded: Upgraded, } #[derive(Drop, starknet::Event)] pub struct Canceled { pub reason: ByteArray, } #[derive(Drop, starknet::Event)] pub struct Claimed { pub amount: u256, } #[derive(Drop, starknet::Event)] pub struct PledgeMade { #[key] pub pledger: ContractAddress, pub amount: u256, } #[derive(Drop, starknet::Event)] pub struct Refunded { #[key] pub pledger: ContractAddress, pub amount: u256, pub reason: ByteArray, } #[derive(Drop, starknet::Event)] pub struct RefundedAll { pub reason: ByteArray, } #[derive(Drop, starknet::Event)] pub struct Unpledged { #[key] pub pledger: ContractAddress, pub amount: u256, pub reason: ByteArray, } #[derive(Drop, starknet::Event)] pub struct Upgraded { pub implementation: ClassHash, } pub mod Errors { pub const CANCELED: felt252 = "Campaign canceled"; pub const CLAIMED: felt252 = "Campaign already claimed"; pub const CLASS_HASH_ZERO: felt252 = "Class hash zero"; pub const CREATOR_ZERO: felt252 = "Creator address zero"; pub const ENDED: felt252 = "Campaign already ended"; pub const END_BEFORE_NOW: felt252 = "End time < now"; pub const END_BEFORE_START: felt252 = "End time < start time"; pub const END_BIGGER_THAN_MAX: felt252 = "End time > max duration"; pub const NOTHING_TO_REFUND: felt252 = "Nothing to refund"; pub const NOTHING_TO_UNPLEDGE: felt252 = "Nothing to unpledge"; pub const NOT_CREATOR: felt252 = "Not creator"; pub const NOT_STARTED: felt252 = "Campaign not started"; pub const PLEDGES_LOCKED: felt252 = "Goal reached, pledges locked"; pub const START_TIME_IN_PAST: felt252 = "Start time < now"; pub const STILL_ACTIVE: felt252 = "Campaign not ended"; pub const GOAL_NOT_REACHED: felt252 = "Goal not reached"; pub const TITLE_EMPTY: felt252 = "Title empty"; pub const TRANSFER_FAILED: felt252 = "Transfer failed"; pub const ZERO_ADDRESS_CALLER: felt252 = "Caller address zero"; pub const ZERO_ADDRESS_PLEDGER: felt252 = "Pledger address zero"; pub const ZERO_ADDRESS_TOKEN: felt252 = "Token address zero"; pub const ZERO_DONATION: felt252 = "Donation must be > 0"; pub const ZERO_GOAL: felt252 = "Goal must be > 0"; pub const ZERO_PLEDGES: felt252 = "No pledges to claim"; } const NINETY_DAYS: u64 = 90 * 24 * 60 * 60; #[constructor] fn constructor( ref self: ContractState, creator: ContractAddress, title: ByteArray, description: ByteArray, goal: u256, start_time: u64, end_time: u64, token_address: ContractAddress, ) { assert(creator.is_non_zero(), Errors::CREATOR_ZERO); assert(title.len() > 0, Errors::TITLE_EMPTY); assert(goal > 0, Errors::ZERO_GOAL); assert(start_time >= get_block_timestamp(), Errors::START_TIME_IN_PAST); assert(end_time >= start_time, Errors::END_BEFORE_START); assert(end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX); assert(token_address.is_non_zero(), Errors::ZERO_ADDRESS_TOKEN); self.creator.write(creator); self.title.write(title); self.goal.write(goal); self.description.write(description); self.start_time.write(start_time); self.end_time.write(end_time); self.token.write(IERC20Dispatcher { contract_address: token_address }); self.ownable._init(get_caller_address()); } #[abi(embed_v0)] impl Campaign of super::ICampaign { fn cancel(ref self: ContractState, reason: ByteArray) { self._assert_only_creator(); assert(!self.canceled.read(), Errors::CANCELED); assert(!self.claimed.read(), Errors::CLAIMED); self.canceled.write(true); self._refund_all(reason.clone()); self.emit(Event::Canceled(Canceled { reason })); } /// Sends the funds to the campaign creator. /// It leaves the pledge data intact as a testament to campaign success fn claim(ref self: ContractState) { self._assert_only_creator(); assert(self._is_started(), Errors::NOT_STARTED); assert(self._is_ended(), Errors::STILL_ACTIVE); assert(!self.claimed.read(), Errors::CLAIMED); assert(self._is_goal_reached(), Errors::GOAL_NOT_REACHED); // no need to check if canceled; if it was, then the goal wouldn't have been reached let this = get_contract_address(); let token = self.token.read(); let amount = token.balance_of(this); assert(amount > 0, Errors::ZERO_PLEDGES); self.claimed.write(true); let owner = get_caller_address(); let success = token.transfer(owner, amount); assert(success, Errors::TRANSFER_FAILED); self.emit(Event::Claimed(Claimed { amount })); } fn get_details(self: @ContractState) -> Details { Details { canceled: self.canceled.read(), claimed: self.claimed.read(), creator: self.creator.read(), description: self.description.read(), end_time: self.end_time.read(), goal: self.goal.read(), start_time: self.start_time.read(), title: self.title.read(), token: self.token.read().contract_address, total_pledges: self.pledges.get_total(), } } fn get_pledge(self: @ContractState, pledger: ContractAddress) -> u256 { self.pledges.get(pledger) } fn get_pledgers(self: @ContractState) -> Array { self.pledges.array() } fn pledge(ref self: ContractState, amount: u256) { assert(self._is_started(), Errors::NOT_STARTED); assert(!self._is_ended(), Errors::ENDED); assert(!self.canceled.read(), Errors::CANCELED); assert(amount > 0, Errors::ZERO_DONATION); let pledger = get_caller_address(); let this = get_contract_address(); let success = self.token.read().transfer_from(pledger, this, amount); assert(success, Errors::TRANSFER_FAILED); self.pledges.add(pledger, amount); self.emit(Event::PledgeMade(PledgeMade { pledger, amount })); } fn refund(ref self: ContractState, pledger: ContractAddress, reason: ByteArray) { self._assert_only_creator(); assert(self._is_started(), Errors::NOT_STARTED); assert(!self.claimed.read(), Errors::CLAIMED); assert(!self.canceled.read(), Errors::CANCELED); assert(pledger.is_non_zero(), Errors::ZERO_ADDRESS_PLEDGER); assert(self.pledges.get(pledger) != 0, Errors::NOTHING_TO_REFUND); let amount = self._refund(pledger); self.emit(Event::Refunded(Refunded { pledger, amount, reason })) } fn unpledge(ref self: ContractState, reason: ByteArray) { assert(self._is_started(), Errors::NOT_STARTED); assert(!self._is_goal_reached(), Errors::PLEDGES_LOCKED); assert(self.pledges.get(get_caller_address()) != 0, Errors::NOTHING_TO_UNPLEDGE); let pledger = get_caller_address(); let amount = self._refund(pledger); self.emit(Event::Unpledged(Unpledged { pledger, amount, reason })); } fn upgrade(ref self: ContractState, impl_hash: ClassHash, new_end_time: Option) { self.ownable._assert_only_owner(); assert(impl_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); // only active campaigns have pledges to refund and an end time to update if self._is_started() { if let Option::Some(end_time) = new_end_time { assert(end_time >= get_block_timestamp(), Errors::END_BEFORE_NOW); assert( end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX, ); self.end_time.write(end_time); }; self._refund_all("contract upgraded"); } starknet::syscalls::replace_class_syscall(impl_hash).unwrap(); self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })); } } #[generate_trait] impl CampaignInternalImpl of CampaignInternalTrait { fn _assert_only_creator(self: @ContractState) { let caller = get_caller_address(); assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); assert(caller == self.creator.read(), Errors::NOT_CREATOR); } fn _is_ended(self: @ContractState) -> bool { get_block_timestamp() >= self.end_time.read() } fn _is_goal_reached(self: @ContractState) -> bool { self.pledges.get_total() >= self.goal.read() } fn _is_started(self: @ContractState) -> bool { get_block_timestamp() >= self.start_time.read() } #[inline(always)] fn _refund(ref self: ContractState, pledger: ContractAddress) -> u256 { let amount = self.pledges.remove(pledger); let success = self.token.read().transfer(pledger, amount); assert(success, Errors::TRANSFER_FAILED); amount } fn _refund_all(ref self: ContractState, reason: ByteArray) { let mut pledges = self.pledges.array(); while let Option::Some(pledger) = pledges.pop_front() { self._refund(pledger); }; self.emit(Event::RefundedAll(RefundedAll { reason })); } } } ``` # ERC20 token Source: https://docs.starknet.io/build/starknet-by-example/applications/erc20 Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. They are used to represent fungible assets. To create an ERC20 contract, it must implement the following interface: ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IERC20 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; fn get_total_supply(self: @TContractState) -> felt252; fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; fn allowance( self: @TContractState, owner: ContractAddress, spender: ContractAddress, ) -> felt252; fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); fn transfer_from( ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252, ); fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); fn decrease_allowance( ref self: TContractState, spender: ContractAddress, subtracted_value: felt252, ); } ``` In Starknet, function names should be written in *snake\_case*. This is not the case in Solidity, where function names are written in *camelCase*. The Starknet ERC20 interface is therefore slightly different from the Solidity ERC20 interface. Here's an implementation of the ERC20 interface in Cairo: ```rust theme={null} #[starknet::contract] pub mod erc20 { use core::num::traits::Zero; use starknet::get_caller_address; use starknet::contract_address_const; use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; #[storage] struct Storage { name: felt252, symbol: felt252, decimals: u8, total_supply: felt252, balances: Map::, allowances: Map::<(ContractAddress, ContractAddress), felt252>, } #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub enum Event { Transfer: Transfer, Approval: Approval, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct Transfer { pub from: ContractAddress, pub to: ContractAddress, pub value: felt252, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct Approval { pub owner: ContractAddress, pub spender: ContractAddress, pub value: felt252, } mod Errors { pub const APPROVE_FROM_ZERO: felt252 = "ERC20: approve from 0"; pub const APPROVE_TO_ZERO: felt252 = "ERC20: approve to 0"; pub const TRANSFER_FROM_ZERO: felt252 = "ERC20: transfer from 0"; pub const TRANSFER_TO_ZERO: felt252 = "ERC20: transfer to 0"; pub const BURN_FROM_ZERO: felt252 = "ERC20: burn from 0"; pub const MINT_TO_ZERO: felt252 = "ERC20: mint to 0"; } #[constructor] fn constructor( ref self: ContractState, recipient: ContractAddress, name: felt252, decimals: u8, initial_supply: felt252, symbol: felt252, ) { self.name.write(name); self.symbol.write(symbol); self.decimals.write(decimals); self.mint(recipient, initial_supply); } #[abi(embed_v0)] impl IERC20Impl of super::IERC20 { fn get_name(self: @ContractState) -> felt252 { self.name.read() } fn get_symbol(self: @ContractState) -> felt252 { self.symbol.read() } fn get_decimals(self: @ContractState) -> u8 { self.decimals.read() } fn get_total_supply(self: @ContractState) -> felt252 { self.total_supply.read() } fn balance_of(self: @ContractState, account: ContractAddress) -> felt252 { self.balances.read(account) } fn allowance( self: @ContractState, owner: ContractAddress, spender: ContractAddress, ) -> felt252 { self.allowances.read((owner, spender)) } fn transfer(ref self: ContractState, recipient: ContractAddress, amount: felt252) { let sender = get_caller_address(); self._transfer(sender, recipient, amount); } fn transfer_from( ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252, ) { let caller = get_caller_address(); self.spend_allowance(sender, caller, amount); self._transfer(sender, recipient, amount); } fn approve(ref self: ContractState, spender: ContractAddress, amount: felt252) { let caller = get_caller_address(); self.approve_helper(caller, spender, amount); } fn increase_allowance( ref self: ContractState, spender: ContractAddress, added_value: felt252, ) { let caller = get_caller_address(); self .approve_helper( caller, spender, self.allowances.read((caller, spender)) + added_value, ); } fn decrease_allowance( ref self: ContractState, spender: ContractAddress, subtracted_value: felt252, ) { let caller = get_caller_address(); self .approve_helper( caller, spender, self.allowances.read((caller, spender)) - subtracted_value, ); } } #[generate_trait] impl InternalImpl of InternalTrait { fn _transfer( ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252, ) { assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO); assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO); self.balances.write(sender, self.balances.read(sender) - amount); self.balances.write(recipient, self.balances.read(recipient) + amount); self.emit(Transfer { from: sender, to: recipient, value: amount }); } fn spend_allowance( ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: felt252, ) { let allowance = self.allowances.read((owner, spender)); self.allowances.write((owner, spender), allowance - amount); } fn approve_helper( ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: felt252, ) { assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO); self.allowances.write((owner, spender), amount); self.emit(Approval { owner, spender, value: amount }); } fn mint(ref self: ContractState, recipient: ContractAddress, amount: felt252) { assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO); let supply = self.total_supply.read() + amount; self.total_supply.write(supply); let balance = self.balances.read(recipient) + amount; self.balances.write(recipient, balance); self .emit( Event::Transfer( Transfer { from: contract_address_const::<0>(), to: recipient, value: amount, }, ), ); } } } ``` There's several other implementations, such as the [Open Zeppelin](https://docs.openzeppelin.com/contracts-cairo/erc20) or the [Cairo By Example](https://cairo-by-example.com/examples/erc20/) ones. # ERC721 token Source: https://docs.starknet.io/build/starknet-by-example/applications/erc721 Contracts that follow the [ERC721 Standard](https://eips.ethereum.org/EIPS/eip-721) are called ERC721 tokens. They are used to represent non-fungible assets. For a deeper understanding of the ERC721 interface specifications and its functionality, we highly recommend reading the EIP in detail. To create an ERC721 contract, it must implement the following interface: ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IERC721 { fn balance_of(self: @TContractState, owner: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; // The function `safe_transfer_from(address _from, address _to, uint256 _tokenId)` // is not included because the same behavior can be achieved by calling // `safe_transfer_from(from, to, tokenId, data)` with an empty `data` // parameter. This approach reduces redundancy in the contract's interface. fn safe_transfer_from( ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span, ); fn transfer_from( ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, ); fn approve(ref self: TContractState, approved: ContractAddress, token_id: u256); fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; fn is_approved_for_all( self: @TContractState, owner: ContractAddress, operator: ContractAddress, ) -> bool; } #[starknet::interface] pub trait IERC721Mintable { fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); } #[starknet::interface] pub trait IERC721Burnable { fn burn(ref self: TContractState, token_id: u256); } pub const IERC721_RECEIVER_ID: felt252 = 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; #[starknet::interface] pub trait IERC721Receiver { fn on_erc721_received( self: @TContractState, operator: ContractAddress, from: ContractAddress, token_id: u256, data: Span, ) -> felt252; } // The `IERC721Metadata` and `IERC721Enumerable` interfaces are included here // as optional extensions to the ERC721 standard. While they provide additional // functionality (such as token metadata and enumeration), they are not // implemented in this example. Including these interfaces demonstrates how they // can be integrated and serves as a starting point for developers who wish to // extend the functionality. #[starknet::interface] pub trait IERC721Metadata { fn name(self: @TContractState) -> ByteArray; fn symbol(self: @TContractState) -> ByteArray; fn token_uri(self: @TContractState, token_id: u256) -> ByteArray; } #[starknet::interface] pub trait IERC721Enumerable { fn total_supply(self: @TContractState) -> u256; fn token_by_index(self: @TContractState, index: u256) -> u256; fn token_of_owner_by_index(self: @TContractState, owner: ContractAddress, index: u256) -> u256; } ``` Because function names in Starknet should be written in *snake\_case*, the Starknet ERC721 interface is slightly different from the Solidity ERC721 interface which uses *camelCase*. Here's an implementation of the ERC721 interface in Cairo: ```rust theme={null} #[starknet::contract] pub mod ERC721 { use core::num::traits::Zero; use starknet::get_caller_address; use starknet::ContractAddress; use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; use openzeppelin_introspection::interface::{ISRC5Dispatcher, ISRC5DispatcherTrait}; use erc721::interfaces::{ IERC721, IERC721ReceiverDispatcher, IERC721ReceiverDispatcherTrait, IERC721_RECEIVER_ID, IERC721Mintable, IERC721Burnable, }; #[storage] pub struct Storage { pub owners: Map, pub balances: Map, pub approvals: Map, pub operator_approvals: Map<(ContractAddress, ContractAddress), bool>, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { Transfer: Transfer, Approval: Approval, ApprovalForAll: ApprovalForAll, } #[derive(Drop, starknet::Event)] pub struct Transfer { pub from: ContractAddress, pub to: ContractAddress, pub token_id: u256, } #[derive(Drop, starknet::Event)] pub struct Approval { pub owner: ContractAddress, pub approved: ContractAddress, pub token_id: u256, } #[derive(Drop, starknet::Event)] pub struct ApprovalForAll { pub owner: ContractAddress, pub operator: ContractAddress, pub approved: bool, } pub mod Errors { pub const INVALID_TOKEN_ID: felt252 = "ERC721: invalid token ID"; pub const INVALID_ACCOUNT: felt252 = "ERC721: invalid account"; pub const INVALID_OPERATOR: felt252 = "ERC721: invalid operator"; pub const UNAUTHORIZED: felt252 = "ERC721: unauthorized caller"; pub const INVALID_RECEIVER: felt252 = "ERC721: invalid receiver"; pub const INVALID_SENDER: felt252 = "ERC721: invalid sender"; pub const SAFE_TRANSFER_FAILED: felt252 = "ERC721: safe transfer failed"; pub const ALREADY_MINTED: felt252 = "ERC721: token already minted"; } #[abi(embed_v0)] impl ERC721 of IERC721 { fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { self._require_owned(token_id) } fn balance_of(self: @ContractState, owner: ContractAddress) -> u256 { assert(!owner.is_zero(), Errors::INVALID_ACCOUNT); self.balances.read(owner) } fn set_approval_for_all( ref self: ContractState, operator: ContractAddress, approved: bool, ) { assert(!operator.is_zero(), Errors::INVALID_OPERATOR); let owner = get_caller_address(); self.operator_approvals.write((owner, operator), approved); self.emit(ApprovalForAll { owner, operator, approved }); } fn approve(ref self: ContractState, approved: ContractAddress, token_id: u256) { let owner = self._require_owned(token_id); let caller = get_caller_address(); assert( caller == owner || self.is_approved_for_all(owner, caller), Errors::UNAUTHORIZED, ); self.approvals.write(token_id, approved); self.emit(Approval { owner, approved, token_id }); } fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { self._require_owned(token_id); self.approvals.read(token_id) } fn transfer_from( ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, ) { let previous_owner = self._require_owned(token_id); assert(from == previous_owner, Errors::INVALID_SENDER); assert(!to.is_zero(), Errors::INVALID_RECEIVER); assert( self._is_approved_or_owner(from, get_caller_address(), token_id), Errors::UNAUTHORIZED, ); self.balances.write(from, self.balances.read(from) - 1); self.balances.write(to, self.balances.read(to) + 1); self.owners.write(token_id, to); self.approvals.write(token_id, Zero::zero()); self.emit(Transfer { from, to, token_id }); } fn safe_transfer_from( ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span, ) { Self::transfer_from(ref self, from, to, token_id); assert( self._check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED, ); } fn is_approved_for_all( self: @ContractState, owner: ContractAddress, operator: ContractAddress, ) -> bool { self.operator_approvals.read((owner, operator)) } } #[abi(embed_v0)] pub impl ERC721Burnable of IERC721Burnable { fn burn(ref self: ContractState, token_id: u256) { self._burn(token_id) } } #[abi(embed_v0)] pub impl ERC721Mintable of IERC721Mintable { fn mint(ref self: ContractState, to: ContractAddress, token_id: u256) { self._mint(to, token_id) } } #[generate_trait] pub impl InternalImpl of InternalTrait { fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { assert(!to.is_zero(), Errors::INVALID_RECEIVER); assert(self.owners.read(token_id).is_zero(), Errors::ALREADY_MINTED); self.balances.write(to, self.balances.read(to) + 1); self.owners.write(token_id, to); self.emit(Transfer { from: Zero::zero(), to, token_id }); } fn _burn(ref self: ContractState, token_id: u256) { let owner = self._require_owned(token_id); self.balances.write(owner, self.balances.read(owner) - 1); self.owners.write(token_id, Zero::zero()); self.approvals.write(token_id, Zero::zero()); self.emit(Transfer { from: owner, to: Zero::zero(), token_id }); } fn _require_owned(self: @ContractState, token_id: u256) -> ContractAddress { let owner = self.owners.read(token_id); assert(!owner.is_zero(), Errors::INVALID_TOKEN_ID); owner } fn _is_approved_or_owner( self: @ContractState, owner: ContractAddress, spender: ContractAddress, token_id: u256, ) -> bool { !spender.is_zero() && (owner == spender || self.is_approved_for_all(owner, spender) || spender == self.get_approved(token_id)) } fn _check_on_erc721_received( self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span, ) -> bool { let src5_dispatcher = ISRC5Dispatcher { contract_address: to }; if src5_dispatcher.supports_interface(IERC721_RECEIVER_ID) { IERC721ReceiverDispatcher { contract_address: to } .on_erc721_received( get_caller_address(), from, token_id, data, ) == IERC721_RECEIVER_ID } else { src5_dispatcher.supports_interface(openzeppelin_account::interface::ISRC6_ID) } } } } ``` There are other implementations, such as the [Open Zeppelin](https://docs.openzeppelin.com/contracts-cairo/erc721) one. # Factory pattern Source: https://docs.starknet.io/build/starknet-by-example/applications/factory The factory pattern is a well known pattern in object oriented programming. It provides an abstraction on how to instantiate a class. In the case of smart contracts, we can use this pattern by defining a factory contract that has the sole responsibility of creating and managing other contracts. ## Class hash and contract instance In Starknet, there's a separation between contract's classes and instances. A contract class serves as a blueprint, defined by the underlying Cairo bytecode, contract's entrypoints, ABI and Sierra program hash. The contract class is identified by a class hash. When you want to add a new class to the network, you first need to declare it. When deploying a contract, you need to specify the class hash of the contract you want to deploy. Each instance of a contract has their own storage regardless of the class hash. Using the factory pattern, we can deploy multiple instances of the same contract class and handle upgrades easily. ## Minimal example Here's a minimal example of a factory contract that deploys the `SimpleCounter` contract: ```rust theme={null} #[starknet::interface] trait IFactory { fn create_counter(ref self: TContractState) -> ContractAddress; fn create_counter_at(ref self: TContractState, salt: felt252) -> ContractAddress; fn update_counter_class_hash(ref self: TContractState, new_class_hash: ClassHash); } #[starknet::contract] mod Factory { use starknet::{ contract_address_const, class_hash_const, get_caller_address, ContractAddress, ClassHash, }; #[storage] struct Storage { counter_class_hash: ClassHash, } #[constructor] fn constructor(ref self: ContractState, counter_class_hash: ClassHash) { self.counter_class_hash.write(counter_class_hash); } #[abi(embed_v0)] impl Factory of super::IFactory { fn create_counter(ref self: ContractState) -> ContractAddress { let salt = starknet::contract_address_const::<0x123>(); self.create_counter_at(salt) } fn create_counter_at(ref self: ContractState, salt: felt252) -> ContractAddress { let counter_class_hash = self.counter_class_hash.read(); let constructor_calldata = array![]; let (contract_address, _) = starknet::deploy_contract_syscall( counter_class_hash, constructor_calldata.span(), salt, false, ).unwrap(); contract_address } fn update_counter_class_hash(ref self: ContractState, new_class_hash: ClassHash) { self.counter_class_hash.write(new_class_hash); } } } ``` This factory can be used to deploy multiple instances of the `SimpleCounter` contract by calling the `create_counter` and `create_counter_at` functions. The `SimpleCounter` class hash is stored inside the factory, and can be upgraded with the `update_counter_class_hash` function which allows to reuse the same factory contract when the `SimpleCounter` contract is upgraded. This minimal example lacks several useful features such as access control, tracking of deployed contracts, events etc. # L1 ↔ L2 token bridge Source: https://docs.starknet.io/build/starknet-by-example/applications/l1-l2-token-bridge In Starknet, it is possible to interact with Ethereum by using the L1 ↔ L2 messaging system. In this example, we will demonstrate the usage of this messaging system to send and receive messages from L1 to L2 for a token bridge. It will require creating two contracts, one on Starknet and one on Ethereum, that will communicate cross-chain, and notify each other how many tokens to mint and who to assign them to. We will also create very simple mock token implementations that will simply emit appropriate events on burning and minting. This will allow us to verify that the cross-chain communication actually succeeded. First, we create the `TokenBridge` contract on Starknet: ```rust theme={null} use starknet::{ContractAddress, EthAddress}; /// Represents any time of token that can be minted/burned /// In a real contract this would probably be an ERC20 contract, /// but here it's represented as a generic token for simplicity. #[starknet::interface] pub trait IMintableToken { fn mint(ref self: TContractState, account: ContractAddress, amount: u256); fn burn(ref self: TContractState, account: ContractAddress, amount: u256); } #[starknet::interface] pub trait ITokenBridge { fn bridge_to_l1(ref self: TContractState, l1_recipient: EthAddress, amount: u256); fn set_l1_bridge(ref self: TContractState, l1_bridge_address: EthAddress); fn set_token(ref self: TContractState, l2_token_address: ContractAddress); fn governor(self: @TContractState) -> ContractAddress; fn l1_bridge(self: @TContractState) -> felt252; fn l2_token(self: @TContractState) -> ContractAddress; } #[starknet::contract] pub mod TokenBridge { use core::num::traits::Zero; use starknet::{ContractAddress, EthAddress, get_caller_address, syscalls, SyscallResultTrait}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::{IMintableTokenDispatcher, IMintableTokenDispatcherTrait}; #[storage] pub struct Storage { // The address of the L2 governor of this contract. Only the governor can set the other // storage variables. pub governor: ContractAddress, // The L1 bridge address. Zero when unset. pub l1_bridge: felt252, // The L2 token contract address. Zero when unset. pub l2_token: ContractAddress, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { WithdrawInitiated: WithdrawInitiated, DepositHandled: DepositHandled, L1BridgeSet: L1BridgeSet, L2TokenSet: L2TokenSet, } // An event that is emitted when bridge_to_l1 is called. // * l1_recipient is the l1 recipient address. // * amount is the amount to withdraw. // * caller_address is the address from which the call was made. #[derive(Drop, starknet::Event)] pub struct WithdrawInitiated { pub l1_recipient: EthAddress, pub amount: u256, pub caller_address: ContractAddress, } // An event that is emitted when handle_deposit is called. // * account is the recipient address. // * amount is the amount to deposit. #[derive(Drop, starknet::Event)] pub struct DepositHandled { pub account: ContractAddress, pub amount: u256, } // An event that is emitted when set_l1_bridge is called. // * l1_bridge_address is the new l1 bridge address. #[derive(Drop, starknet::Event)] struct L1BridgeSet { l1_bridge_address: EthAddress, } // An event that is emitted when set_token is called. // * l2_token_address is the new l2 token address. #[derive(Drop, starknet::Event)] struct L2TokenSet { l2_token_address: ContractAddress, } pub mod Errors { pub const EXPECTED_FROM_BRIDGE_ONLY: felt252 = "Expected from bridge only"; pub const INVALID_ADDRESS: felt252 = "Invalid address"; pub const INVALID_AMOUNT: felt252 = "Invalid amount"; pub const UNAUTHORIZED: felt252 = "Unauthorized"; pub const TOKEN_NOT_SET: felt252 = "Token not set"; pub const L1_BRIDGE_NOT_SET: felt252 = "L1 bridge address not set"; } #[constructor] fn constructor(ref self: ContractState, governor: ContractAddress) { assert(governor.is_non_zero(), Errors::INVALID_ADDRESS); self.governor.write(governor); } #[abi(embed_v0)] impl TokenBridge of super::ITokenBridge { /// Initiates a withdrawal of tokens on the L1 contract. fn bridge_to_l1(ref self: ContractState, l1_recipient: EthAddress, amount: u256) { assert(l1_recipient.is_non_zero(), Errors::INVALID_ADDRESS); assert(amount.is_non_zero(), Errors::INVALID_AMOUNT); self._assert_l1_bridge_set(); self._assert_token_set(); // burn tokens on L2 let caller_address = get_caller_address(); IMintableTokenDispatcher { contract_address: self.l2_token.read() } .burn(caller_address, amount); // Send the message to L1 to mint tokens there. let mut payload: Array = array![ l1_recipient.into(), amount.low.into(), amount.high.into(), ]; syscalls::send_message_to_l1_syscall(self.l1_bridge.read(), payload.span()) .unwrap_syscall(); self.emit(WithdrawInitiated { l1_recipient, amount, caller_address }); } fn set_l1_bridge(ref self: ContractState, l1_bridge_address: EthAddress) { self._assert_only_governor(); assert(l1_bridge_address.is_non_zero(), Errors::INVALID_ADDRESS); self.l1_bridge.write(l1_bridge_address.into()); self.emit(L1BridgeSet { l1_bridge_address }); } fn set_token(ref self: ContractState, l2_token_address: ContractAddress) { self._assert_only_governor(); assert(l2_token_address.is_non_zero(), Errors::INVALID_ADDRESS); self.l2_token.write(l2_token_address); self.emit(L2TokenSet { l2_token_address }); } // Getters fn governor(self: @ContractState) -> ContractAddress { self.governor.read() } fn l1_bridge(self: @ContractState) -> felt252 { self.l1_bridge.read() } fn l2_token(self: @ContractState) -> ContractAddress { self.l2_token.read() } } #[generate_trait] impl Internal of InternalTrait { fn _assert_only_governor(self: @ContractState) { assert(get_caller_address() == self.governor.read(), Errors::UNAUTHORIZED); } fn _assert_token_set(self: @ContractState) { assert(self.l2_token.read().is_non_zero(), Errors::TOKEN_NOT_SET); } fn _assert_l1_bridge_set(self: @ContractState) { assert(self.l1_bridge.read().is_non_zero(), Errors::L1_BRIDGE_NOT_SET); } } #[l1_handler] pub fn handle_deposit( ref self: ContractState, from_address: felt252, account: ContractAddress, amount: u256, ) { assert(from_address == self.l1_bridge.read(), Errors::EXPECTED_FROM_BRIDGE_ONLY); assert(account.is_non_zero(), Errors::INVALID_ADDRESS); assert(amount.is_non_zero(), Errors::INVALID_AMOUNT); self._assert_token_set(); // Call mint on l2_token contract. IMintableTokenDispatcher { contract_address: self.l2_token.read() }.mint(account, amount); self.emit(Event::DepositHandled(DepositHandled { account, amount })); } } ``` The `IMintableToken` interface represents any mintable/burnable token, and will allow our contract to perform these operations without regard for the actual token it's working with. Let's immediately create the mock token implementation that we'll use with the contract: ```rust theme={null} // [!include ~/listings/applications/l1_l2_token_bridge/src/mintable_token_mock.cairo] ``` Next, we'll implement the mock token on the Ethereum side, which behaves more or less the same as its Starknet counterpart. Let's just quickly define the interfaces we'll need: ```solidity theme={null} // [!include ~/listings/applications/l1_l2_token_bridge/solidity/src/IMintableTokenEvents.sol] ``` ```solidity theme={null} // [!include ~/listings/applications/l1_l2_token_bridge/solidity/src/IMintableToken.sol] ``` Now we can create a mock token implementation: ```solidity theme={null} // [!include ~/listings/applications/l1_l2_token_bridge/solidity/src/MintableTokenMock.sol] ``` Finally, we will implement the `TokenBridge` contract on Ethereum: ```solidity theme={null} // [!include ~/listings/applications/l1_l2_token_bridge/solidity/src/TokenBridge.sol] ``` > Note: Bridging tokens from Ethereum to Starknet usually takes a couple of minutes, and the `#[l1_handler]` function is automatically > invoked by the Sequencer, minting tokens to our Starknet wallet address. On the other hand, for bridging transactions to be > successfully sent from Starknet to Ethereum can take a couple of hours, and will require us to *manually consume the withdrawal* in > order for the tokens to be sent to our Ethereum wallet address. # Merkle tree contract Source: https://docs.starknet.io/build/starknet-by-example/applications/merkle-tree A Merkle tree, also known as a hash tree, is a data structure used in cryptography and computer science to verify data integrity and consistency. It is a binary tree where each leaf node represents the cryptographic hash of some data (a transaction for example), and each non-leaf node represents the cryptographic hash of its child nodes. This hierarchical structure allows efficient and secure verification of the data integrity. Here's a quick summary of how it operates and what functionalities it supports: ### How it works: 1. Leaves Creation: * Some data is hashed to create a leaf node. 2. Intermediate Nodes Creation: * Pairwise hashes of the leaf nodes are combined and hashed again to create parent nodes. * This process continues until only one hash remains, known as the Merkle root. 3. Merkle Root: * The final hash at the top of the tree, representing the entire dataset. * Changing any single data block will change its corresponding leaf node, which will propagate up the tree, altering the Merkle root. ### Key Features: 1. Efficient Verification: * Only a small subset of the tree (the Merkle proof) is needed to verify the inclusion of a particular data block, reducing the amount of data that must be processed. 2. Data Integrity: * The Merkle root ensures the integrity of all the underlying data blocks. * Any alteration in the data will result in a different root hash. ### Examples of use cases: 1. Fundamental use case: Ethereum blockchain integrity * Cryptocurrencies like Ethereum use Merkle trees to efficiently verify and maintain transaction integrity within blocks. * Each transaction in a block is hashed to form leaf nodes, and these hashes are recursively combined to form a single Merkle root, summarizing all transactions. * The Merkle root is stored in the block header, which is hashed to generate the block's unique identifier. * Guaranteed Integrity: Any change to a transaction alters the Merkle root, block header, and block hash, making it easy for nodes to detect tampering. * Transaction verification: Nodes can verify specific transactions via Merkle proofs without downloading the entire block. 2. Whitelist inclusion * Merkle trees allow efficient whitelist verification without storing the full list on-chain, reducing storage costs. * The Merkle root of the whitelist is stored on-chain, while the full list remains off-chain. * To verify if an address is on the whitelist, a user provides a Merkle proof and the address. The Merkle root is recalculated using the provided data and compared to the stored on-chain root. If they match, the address is included; if not, it's excluded. 3. Decentralized Identity Verification * Merkle trees can be used in decentralized identity systems to verify credentials. * Off-chain data: a user's credentials. * On-chain data: the Merkle root representing the credentials. ### Visual example The above diagram represents a merkle tree.\ Each leaf node is the hash of some data.\ Each other node is the hash of the combination of both children nodes. If we were to `verify{:md}` the `hash 6{:md}`, the merkle proof would need to contain the `hash 5{:md}`, `hash 12{:md}`and `hash 13{:md}`: 1. The `hash 5{:md}` would be combined with the `hash 6{:md}` to re-compute the `hash 11{:md}`. 2. The newly computed `hash 11{:md}` in step 1 would be combined with `hash 12{:md}` to re-compute `hash 14{:md}`. 3. The `hash 13{:md}` would be combined with the newly computed `hash 14{:md}` in step 2 to re-compute the merkle root. 4. We can then compare the computed resultant merkle root with the one provided to the `verify{:md}` function. ### Code The following implementation is the Cairo adaptation of the [Solidity by Example - Merkle Tree contract](https://solidity-by-example.org/app/merkle-tree/). ```rust theme={null} #[generate_trait] pub impl ByteArrayHashTraitImpl of ByteArrayHashTrait { fn hash(self: @ByteArray) -> felt252 { let mut serialized_byte_arr: Array = ArrayTrait::new(); self.serialize(ref serialized_byte_arr); core::poseidon::poseidon_hash_span(serialized_byte_arr.span()) } } #[starknet::interface] pub trait IMerkleTree { fn build_tree(ref self: TContractState, data: Array) -> Array; fn get_root(self: @TContractState) -> felt252; // function to verify if leaf node exists in the merkle tree fn verify( self: @TContractState, proof: Array, root: felt252, leaf: felt252, index: usize, ) -> bool; } mod errors { pub const NOT_POW_2: felt252 = "Data length is not a power of 2"; pub const NOT_PRESENT: felt252 = "No element in merkle tree"; } #[starknet::contract] pub mod MerkleTree { use core::poseidon::PoseidonTrait; use core::hash::{HashStateTrait, HashStateExTrait}; use starknet::storage::{ StoragePointerWriteAccess, StoragePointerReadAccess, Vec, MutableVecTrait, VecTrait, }; use super::ByteArrayHashTrait; #[storage] struct Storage { pub hashes: Vec, } #[derive(Drop, Serde, Copy)] struct Vec2 { x: u32, y: u32, } #[abi(embed_v0)] impl IMerkleTreeImpl of super::IMerkleTree { fn build_tree(ref self: ContractState, mut data: Array) -> Array { let data_len = data.len(); assert(data_len > 0 && (data_len & (data_len - 1)) == 0, super::errors::NOT_POW_2); let mut _hashes: Array = ArrayTrait::new(); // first, hash every leaf for value in data { _hashes.append(value.hash()); }; // then, hash all levels above leaves let mut current_nodes_lvl_len = data_len; let mut hashes_offset = 0; while current_nodes_lvl_len > 0 { let mut i = 0; while i < current_nodes_lvl_len - 1 { let left_elem = *_hashes.at(hashes_offset + i); let right_elem = *_hashes.at(hashes_offset + i + 1); let hash = PoseidonTrait::new().update_with((left_elem, right_elem)).finalize(); _hashes.append(hash); i += 2; }; hashes_offset += current_nodes_lvl_len; current_nodes_lvl_len /= 2; }; // write to the contract state (useful for the get_root function) for hash in _hashes.span() { self.hashes.append().write(*hash); }; _hashes } fn get_root(self: @ContractState) -> felt252 { let merkle_tree_length = self.hashes.len(); assert(merkle_tree_length > 0, super::errors::NOT_PRESENT); self.hashes.at(merkle_tree_length - 1).read() } fn verify( self: @ContractState, mut proof: Array, root: felt252, leaf: felt252, mut index: usize, ) -> bool { let mut current_hash = leaf; while let Option::Some(value) = proof.pop_front() { current_hash = if index % 2 == 0 { PoseidonTrait::new().update_with((current_hash, value)).finalize() } else { PoseidonTrait::new().update_with((value, current_hash)).finalize() }; index /= 2; }; current_hash == root } } } ``` # NFT Dutch auction Source: https://docs.starknet.io/build/starknet-by-example/applications/nft-dutch-auction This is the Cairo adaptation (with some modifications) of the [Solidity by example NFT Dutch Auction](https://solidity-by-example.org/app/dutch-auction/). Here's how it works: * The seller of the NFT deploys this contract with a startingPrice. * The auction lasts for a specified duration. * The price decreases over time. * Participants can purchase NFTs at any time as long as the totalSupply has not been reached. * The auction ends when either the totalSupply is reached or the duration has elapsed. ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IERC20 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; fn get_total_supply(self: @TContractState) -> felt252; fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; fn allowance( self: @TContractState, owner: ContractAddress, spender: ContractAddress, ) -> felt252; fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); fn transfer_from( ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252, ); fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); fn decrease_allowance( ref self: TContractState, spender: ContractAddress, subtracted_value: felt252, ); } #[starknet::interface] trait IERC721 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; fn is_approved_for_all( self: @TContractState, owner: ContractAddress, operator: ContractAddress, ) -> bool; fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); fn transfer_from( ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, ); fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); } #[starknet::interface] pub trait INFTDutchAuction { fn buy(ref self: TContractState, token_id: u256); fn get_price(self: @TContractState) -> u64; } #[starknet::contract] pub mod NFTDutchAuction { use super::{IERC20Dispatcher, IERC20DispatcherTrait, IERC721Dispatcher, IERC721DispatcherTrait}; use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { erc20_token: ContractAddress, erc721_token: ContractAddress, starting_price: u64, seller: ContractAddress, duration: u64, discount_rate: u64, start_at: u64, expires_at: u64, purchase_count: u128, total_supply: u128, } mod Errors { pub const AUCTION_ENDED: felt252 = "auction has ended"; pub const LOW_STARTING_PRICE: felt252 = "low starting price"; pub const INSUFFICIENT_BALANCE: felt252 = "insufficient balance"; } #[constructor] fn constructor( ref self: ContractState, erc20_token: ContractAddress, erc721_token: ContractAddress, starting_price: u64, seller: ContractAddress, duration: u64, discount_rate: u64, total_supply: u128, ) { assert(starting_price >= discount_rate * duration, Errors::LOW_STARTING_PRICE); self.erc20_token.write(erc20_token); self.erc721_token.write(erc721_token); self.starting_price.write(starting_price); self.seller.write(seller); self.duration.write(duration); self.discount_rate.write(discount_rate); self.start_at.write(get_block_timestamp()); self.expires_at.write(get_block_timestamp() + duration * 1000); self.total_supply.write(total_supply); } #[abi(embed_v0)] impl NFTDutchAuction of super::INFTDutchAuction { fn get_price(self: @ContractState) -> u64 { let time_elapsed = (get_block_timestamp() - self.start_at.read()) / 1000; // Ignore milliseconds let discount = self.discount_rate.read() * time_elapsed; self.starting_price.read() - discount } fn buy(ref self: ContractState, token_id: u256) { // Check duration assert(get_block_timestamp() < self.expires_at.read(), Errors::AUCTION_ENDED); // Check total supply assert(self.purchase_count.read() < self.total_supply.read(), Errors::AUCTION_ENDED); let erc20_dispatcher = IERC20Dispatcher { contract_address: self.erc20_token.read() }; let erc721_dispatcher = IERC721Dispatcher { contract_address: self.erc721_token.read(), }; let caller = get_caller_address(); // Get NFT price let price: u256 = self.get_price().into(); let buyer_balance: u256 = erc20_dispatcher.balance_of(caller).into(); // Ensure buyer has enough token for payment assert(buyer_balance >= price, Errors::INSUFFICIENT_BALANCE); // Transfer payment token from buyer to seller erc20_dispatcher.transfer_from(caller, self.seller.read(), price.try_into().unwrap()); // Mint token to buyer's address erc721_dispatcher.mint(caller, token_id); // Increase purchase count self.purchase_count.write(self.purchase_count.read() + 1); } } } ``` # Random number generator Source: https://docs.starknet.io/build/starknet-by-example/applications/random-number-generator Randomness plays a crucial role in blockchain and smart contract development. In the context of blockchain, randomness is about generating unpredictable values using some source of entropy that is fair and resistant to manipulation. In blockchain and smart contracts, randomness is needed for: * **Gaming:** Ensuring fair outcomes in games of chance. * **Lotteries:** Selecting winners in a verifiable and unbiased manner. * **Security:** Generating cryptographic keys and nonces that are hard to predict. * **Consensus Protocols:** Selecting validators or block producers in some proof-of-stake systems. However, achieving true randomness on a decentralized platform poses significant challenges. There are numerous sources of entropy, each with its strengths and weaknesses. ### Sources of Entropy #### 1. Block Properties * **Description:** Using properties of the blockchain itself, like the hash of a block, or a block timestamp, as a source of randomness. * **Example:** A common approach is to use the hash of a recent block as a seed for random number generation. * **Risks:** * **Predictability:** Miners can influence future block hashes by controlling the nonce they use during mining. * **Manipulation:** Many of the blockchain properties (block hash, timestamp etc.) can be manipulated by some entities, especially if they stand to gain from a specific random outcome. #### 2. User-Provided Inputs * **Description:** Allowing users to provide entropy directly, often combined with other sources to generate a random number. * **Example:** Users submitting their own random values which are then hashed together with other inputs. * **Risks:** * **Collusion:** Users may collude to provide inputs that skew the randomness in their favor. * **Front-Running:** Other participants might observe a user's input and act on it before it gets included in the block, affecting the outcome. #### 3. External Oracles * **Description:** Using a trusted third-party service to supply randomness. Oracles are off-chain services that provide data to smart contracts. * **Example:** Pragma VRF (Verifiable Random Function) is a service that provides cryptographically secure randomness. * **Risks:** * **Trust:** Reliance on a third party undermines the trustless nature of blockchain. * **Centralization:** If the oracle service is compromised or shut down, so is the randomness it provides. * **Cost:** Using an oracle often involves additional transaction fees. #### 4. Commit-Reveal Schemes * **Description:** A multi-phase protocol where participants commit to a value in the first phase and reveal it in the second. * **Example:** Participants submit a hash of their random value (commitment) first and reveal the actual value later. The final random number is derived from all revealed values. * **Risks:** * **Dishonest Behavior:** Participants may choose not to reveal their values if the outcome is unfavorable. * **Coordination:** Requires honest participation from multiple parties, which can be hard to guarantee. :::info There are other ways to generate randomness on-chain, for more information read the ["Public Randomness and Randomness Beacons"](https://a16zcrypto.com/posts/article/public-randomness-and-randomness-beacons/) article. ::: ## CoinFlip using Pragma VRF Below is an implementation of a `CoinFlip` contract that utilizes a [Pragma Verifiable Random Function (VRF)](https://docs.pragma.build/v1/Resources/randomness/randomness) to generate random numbers on-chain. * Players can flip a virtual coin and receive a random outcome of `Heads` or `Tails` * The contract needs to be funded with enough ETH to perform the necessary operations, including paying fees to Pragma's Randomness Oracle which returns a random value * When the coin is "flipped", the contract makes a call to the Randomness Oracle to request a random value and the `Flipped` event is emitted * Randomness is generated off-chain, and then submitted to the contract using the `receive_random_words` callback * Based on this random value, the contract determines whether the coin "landed" on `Heads` or on `Tails`, and the `Landed` event is emitted ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait ICoinFlip { fn flip(ref self: TContractState); } // declares just the pragma_lib::abi::IRandomness.receive_random_words function #[starknet::interface] pub trait IPragmaVRF { fn receive_random_words( ref self: TContractState, requestor_address: ContractAddress, request_id: u64, random_words: Span, calldata: Array, ); } #[starknet::contract] pub mod CoinFlip { use core::num::traits::zero::Zero; use starknet::{ContractAddress, get_caller_address, get_contract_address}; use starknet::storage::{ Map, StoragePointerReadAccess, StoragePathEntry, StoragePointerWriteAccess, }; use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait}; use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; #[storage] struct Storage { eth_dispatcher: IERC20Dispatcher, flips: Map, nonce: u64, randomness_contract_address: ContractAddress, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { Flipped: Flipped, Landed: Landed, } #[derive(Drop, starknet::Event)] pub struct Flipped { pub flip_id: u64, pub flipper: ContractAddress, } #[derive(Drop, starknet::Event)] pub struct Landed { pub flip_id: u64, pub flipper: ContractAddress, pub side: Side, } #[derive(Drop, Debug, PartialEq, Serde)] pub enum Side { Heads, Tails, } pub mod Errors { pub const CALLER_NOT_RANDOMNESS: felt252 = "Caller not randomness contract"; pub const INVALID_ADDRESS: felt252 = "Invalid address"; pub const INVALID_FLIP_ID: felt252 = "No flip with the given ID"; pub const REQUESTOR_NOT_SELF: felt252 = "Requestor is not self"; pub const TRANSFER_FAILED: felt252 = "Transfer failed"; } pub const PUBLISH_DELAY: u64 = 1; // return the random value asap pub const NUM_OF_WORDS: u64 = 1; // one random value is sufficient pub const CALLBACK_FEE_LIMIT: u128 = 100_000_000_000_000; // 0.0001 ETH pub const MAX_CALLBACK_FEE_DEPOSIT: u256 = 500_000_000_000_000; // CALLBACK_FEE_LIMIT * 5; needs to cover the Premium fee #[constructor] fn constructor( ref self: ContractState, randomness_contract_address: ContractAddress, eth_address: ContractAddress, ) { assert(randomness_contract_address.is_non_zero(), Errors::INVALID_ADDRESS); assert(eth_address.is_non_zero(), Errors::INVALID_ADDRESS); self.randomness_contract_address.write(randomness_contract_address); self.eth_dispatcher.write(IERC20Dispatcher { contract_address: eth_address }); } #[abi(embed_v0)] impl CoinFlip of super::ICoinFlip { /// The contract needs to be funded with some ETH in order for this function /// to be callable. For simplicity, anyone can fund the contract. fn flip(ref self: ContractState) { let flip_id = self._request_my_randomness(); let flipper = get_caller_address(); self.flips.entry(flip_id).write(flipper); self.emit(Event::Flipped(Flipped { flip_id, flipper })); } } #[abi(embed_v0)] impl PragmaVRF of super::IPragmaVRF { fn receive_random_words( ref self: ContractState, requestor_address: ContractAddress, request_id: u64, random_words: Span, calldata: Array, ) { let caller = get_caller_address(); assert( caller == self.randomness_contract_address.read(), Errors::CALLER_NOT_RANDOMNESS, ); let this = get_contract_address(); assert(requestor_address == this, Errors::REQUESTOR_NOT_SELF); self._process_coin_flip(request_id, random_words.at(0)); } } #[generate_trait] impl Private of PrivateTrait { fn _request_my_randomness(ref self: ContractState) -> u64 { let randomness_contract_address = self.randomness_contract_address.read(); let randomness_dispatcher = IRandomnessDispatcher { contract_address: randomness_contract_address, }; let this = get_contract_address(); // Approve the randomness contract to transfer the callback deposit/fee let eth_dispatcher = self.eth_dispatcher.read(); eth_dispatcher.approve(randomness_contract_address, MAX_CALLBACK_FEE_DEPOSIT); let nonce = self.nonce.read(); // Request the randomness to be used to construct the winning combination let request_id = randomness_dispatcher .request_random( nonce, this, CALLBACK_FEE_LIMIT, PUBLISH_DELAY, NUM_OF_WORDS, array![], ); self.nonce.write(nonce + 1); request_id } fn _process_coin_flip(ref self: ContractState, flip_id: u64, random_value: @felt252) { let flipper = self.flips.entry(flip_id).read(); assert(flipper.is_non_zero(), Errors::INVALID_FLIP_ID); let random_value: u256 = (*random_value).into(); let side = if random_value % 2 == 0 { Side::Heads } else { Side::Tails }; self.emit(Event::Landed(Landed { flip_id, flipper, side })); } } } ``` # Simple storage Source: https://docs.starknet.io/build/starknet-by-example/applications/simple-storage In this example, we will use a SimpleStorage Cairo contract deployed on Starknet Sepolia Testnet and show how you can interact with the contract using Starknet-js. ## Writing SimpleStorage contract in Cairo The SimpleStorage contract has only one purpose: storing a number. We want the users to interact with the stored number by **writing** to the currently stored number and **reading** the number in the contract. We will use the following SimpleStorage contract. In the [Storage Variables](../basic/variables) page, you can find explanations for each component of the contract: ```cairo theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/src/storage.cairo:contract] ``` Because we want to interact with the get and set functions of the SimpleStorage contract using Starknet-js, we define the function signatures in `#[starknet::interface]`. The functions are defined under the macro `#[abi(embed_v0)]` where external functions are written. Only deployed instances of the contract can be interacted with. You can refer to the [How to Deploy](/build/quickstart/hellostarknet) page. Note down the address of your contract, as it is needed for the following part. ## Interacting with SimpleStorage contract We will interact with the SimpleStorage contract using Starknet-js. Firstly, create a new folder and inside the directory of the new folder, initialize the npm package (click Enter several items, you can skip adding the package info): ```bash [Terminal] theme={null} npm init ``` Now, `package.json{:md}` file is created. Change the type of the package to a module. ```json theme={null} "type": "module" ``` Let's add Starknet-js as a dependency: ```bash [Terminal] theme={null} npm install starknet@next ``` Create a file named `index.js{:md}` where we will write JavaScript code to interact with our contract. Let's start our code by importing from Starknet-js, and from other libraries we will need: ```js theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/index.js:imports] ``` Let's create our provider object, and add our account address as a constant variable. We need the provider in order to send our queries and transactions to a Starknet node that is connected to the Starknet network: ```js theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/index.js:provider] const accountAddress = // 'PASTE_ACCOUNT_ADDRESS_HERE'; ``` The next step is creating an `Account` object that will be used to sign transactions, so we need to import the account private key. You can access it directly from your keystore with the following command using Starkli: ```bash [Terminal] theme={null} starkli signer keystore inspect-private /path/to/starkli-wallet/keystore.json --raw ``` Create a `.env{:md}` file in your project folder, and paste your private key as shown in the following line: ```bash [Terminal] theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/.env.example] ``` :::warning Using `.env{:md}` files is not recommended for production environments, please use `.env{:md}` files only for development purposes! It is HIGHLY recommended to add `.gitignore{:md}`, and include your .env file there if you will be pushing your project to GitHub. ::: Now, import your private key from the environment variables and create your Account object. ```js theme={null} const accountAddress = // 'PASTE_ACCOUNT_PUBLIC_ADDRESS_HERE'; // [!include ~/listings/applications/simple_storage_starknetjs/index.js:account] ``` Now, let's create a Contract object in order to interact with our contract. In order to create the Contract object, we need the ABI and the address of our contract. The ABI contains information about what kind of data structures and functions there are in our contract so that we can interact with them using SDKs like Starknet-js. We will copy `./target/simple_storage_SimpleStorage.contract_class.json{:md}` to `abi.json{:md}` in the Scarb project folder. The beginning of the content of the ABI file should look like this: ```json theme={null} {"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18", //... ``` We can then create the Account object and the Contract object in our `index.js{:md}` file: ```js theme={null} const contractAddress = 'PASTE_CONTRACT_ADDRESS_HERE'; // [!include ~/listings/applications/simple_storage_starknetjs/index.js:contract] ``` The setup is finished! By calling the `fn get(self: @ContractState) -> u128` function, we will be able to read the `stored_data` variable from the contract: ```js theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/index.js:get] ``` In order to run your code, run the command `node index.js{:md}` in your project directory. After a short amount of time, you should see a "0" as the stored data. Now, we will set a new number to the `stored_data` variable by calling the `fn set(self: @mut ContractState, new_data: u128)` function. This is an `INVOKE{:md}` transaction, so we need to sign the transaction with our account's private key and pass along the calldata. The transaction is signed and broadcasted to the network and it can takes a few seconds for the transaction to be confirmed. ```js theme={null} // [!include ~/listings/applications/simple_storage_starknetjs/index.js:set] ``` # Simple DeFi vault Source: https://docs.starknet.io/build/starknet-by-example/applications/simple-vault This is the Cairo adaptation of the [Solidity by Example - Vault](https://solidity-by-example.org/defi/vault/). Here's how it works: * When a user deposits a token, the contract calculates the amount of shares to mint. * When a user withdraws, the contract burns their shares, calculates the yield, and withdraws both the yield and the initial amount of tokens deposited. ```rust theme={null} use starknet::ContractAddress; // In order to make contract calls within our Vault, // we need to have the interface of the remote ERC20 contract defined to import the Dispatcher. #[starknet::interface] pub trait IERC20 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; fn get_total_supply(self: @TContractState) -> felt252; fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; fn allowance( self: @TContractState, owner: ContractAddress, spender: ContractAddress, ) -> felt252; fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); fn transfer_from( ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252, ); fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); fn decrease_allowance( ref self: TContractState, spender: ContractAddress, subtracted_value: felt252, ); } #[starknet::interface] pub trait ISimpleVault { fn deposit(ref self: TContractState, amount: u256); fn withdraw(ref self: TContractState, shares: u256); fn user_balance_of(self: @TContractState, account: ContractAddress) -> u256; fn contract_total_supply(self: @TContractState) -> u256; } #[starknet::contract] pub mod SimpleVault { use super::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ContractAddress, get_caller_address, get_contract_address}; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; #[storage] struct Storage { token: IERC20Dispatcher, total_supply: u256, balance_of: Map, } #[constructor] fn constructor(ref self: ContractState, token: ContractAddress) { self.token.write(IERC20Dispatcher { contract_address: token }); } #[generate_trait] impl PrivateFunctions of PrivateFunctionsTrait { fn _mint(ref self: ContractState, to: ContractAddress, shares: u256) { self.total_supply.write(self.total_supply.read() + shares); self.balance_of.write(to, self.balance_of.read(to) + shares); } fn _burn(ref self: ContractState, from: ContractAddress, shares: u256) { self.total_supply.write(self.total_supply.read() - shares); self.balance_of.write(from, self.balance_of.read(from) - shares); } } #[abi(embed_v0)] impl SimpleVault of super::ISimpleVault { fn user_balance_of(self: @ContractState, account: ContractAddress) -> u256 { self.balance_of.read(account) } fn contract_total_supply(self: @ContractState) -> u256 { self.total_supply.read() } fn deposit(ref self: ContractState, amount: u256) { // a = amount // B = balance of token before deposit // T = total supply // s = shares to mint // // (T + s) / T = (a + B) / B // // s = aT / B let caller = get_caller_address(); let this = get_contract_address(); let mut shares = 0; if self.total_supply.read() == 0 { shares = amount; } else { let balance: u256 = self.token.read().balance_of(this).try_into().unwrap(); shares = (amount * self.total_supply.read()) / balance; } PrivateFunctions::_mint(ref self, caller, shares); let amount_felt252: felt252 = amount.low.into(); self.token.read().transfer_from(caller, this, amount_felt252); } fn withdraw(ref self: ContractState, shares: u256) { // a = amount // B = balance of token before withdraw // T = total supply // s = shares to burn // // (T - s) / T = (B - a) / B // // a = sB / T let caller = get_caller_address(); let this = get_contract_address(); let balance = self.user_balance_of(this); let amount = (shares * balance) / self.total_supply.read(); PrivateFunctions::_burn(ref self, caller, shares); let amount_felt252: felt252 = amount.low.into(); self.token.read().transfer(caller, amount_felt252); } } } ``` # Staking Source: https://docs.starknet.io/build/starknet-by-example/applications/staking The following staking contract is designed to allow users to stake tokens in exchange for reward tokens over a specified duration. Here's a quick summary of how it operates and what functionalities it supports: ### Key Features: 1. Token staking and unstaking: * Users can stake an ERC20 token, specified at deployment. * Users can withdraw their staked tokens at any time. 2. Reward calculation and distribution: * The rewards are distributed as an ERC20, also specified at deployment (can be different from the staking token). * Rewards are calculated based on the duration of staking and the amount the user staked relative to the total staked amount by all users. * A user’s reward accumulates over time up until the reward period's end and can be claimed anytime by the user. 3. Dynamic reward rates: * The reward rate is determined by the total amount of reward tokens over a set period (duration). * The reward rate can be adjusted during the rewards period if new rewards are added before the current reward period finishes. * Even after a reward period finishes, a new reward duration and new rewards can be set up if desired. 4. Ownership and administration: * Only the owner of the contract can set the rewards amount and duration. The reward mechanism ensures that rewards are distributed fairly based on the amount and duration of tokens staked by each user. The following implementation is the Cairo adaptation of the [Solidity by Example - Staking Rewards contract](https://solidity-by-example.org/defi/staking-rewards/). It includes a small adaptation to keep track of the amount of total distributed reward tokens and emit an event when the remaining reward token amount reaches 0. ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IStakingContract { fn set_reward_amount(ref self: TContractState, amount: u256); fn set_reward_duration(ref self: TContractState, duration: u256); fn stake(ref self: TContractState, amount: u256); fn withdraw(ref self: TContractState, amount: u256); fn get_rewards(self: @TContractState, account: ContractAddress) -> u256; fn claim_rewards(ref self: TContractState); } mod Errors { pub const NULL_REWARDS: felt252 = "Reward amount must be > 0"; pub const NOT_ENOUGH_REWARDS: felt252 = "Reward amount must be > balance"; pub const NULL_AMOUNT: felt252 = "Amount must be > 0"; pub const NULL_DURATION: felt252 = "Duration must be > 0"; pub const UNFINISHED_DURATION: felt252 = "Reward duration not finished"; pub const NOT_OWNER: felt252 = "Caller is not the owner"; pub const NOT_ENOUGH_BALANCE: felt252 = "Balance too low"; } #[starknet::contract] pub mod StakingContract { use core::starknet::event::EventEmitter; use core::num::traits::Zero; use starknet::{ContractAddress, get_caller_address, get_block_timestamp, get_contract_address}; use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; #[storage] struct Storage { pub staking_token: IERC20Dispatcher, pub reward_token: IERC20Dispatcher, pub owner: ContractAddress, pub reward_rate: u256, pub duration: u256, pub current_reward_per_staked_token: u256, pub finish_at: u256, // last time an operation (staking / withdrawal / rewards claimed) was registered pub last_updated_at: u256, pub last_user_reward_per_staked_token: Map::, pub unclaimed_rewards: Map::, pub total_distributed_rewards: u256, // total amount of staked tokens pub total_supply: u256, // amount of staked tokens per user pub balance_of: Map::, } #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub enum Event { Deposit: Deposit, Withdrawal: Withdrawal, RewardsFinished: RewardsFinished, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct Deposit { pub user: ContractAddress, pub amount: u256, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct Withdrawal { pub user: ContractAddress, pub amount: u256, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct RewardsFinished { pub msg: felt252, } #[constructor] fn constructor( ref self: ContractState, staking_token_address: ContractAddress, reward_token_address: ContractAddress, ) { self.staking_token.write(IERC20Dispatcher { contract_address: staking_token_address }); self.reward_token.write(IERC20Dispatcher { contract_address: reward_token_address }); self.owner.write(get_caller_address()); } #[abi(embed_v0)] impl StakingContract of super::IStakingContract { fn set_reward_duration(ref self: ContractState, duration: u256) { self.only_owner(); assert(duration > 0, super::Errors::NULL_DURATION); // can only set duration if the previous duration has already finished assert( self.finish_at.read() < get_block_timestamp().into(), super::Errors::UNFINISHED_DURATION, ); self.duration.write(duration); } fn set_reward_amount(ref self: ContractState, amount: u256) { self.only_owner(); self.update_rewards(Zero::zero()); assert(amount > 0, super::Errors::NULL_REWARDS); assert(self.duration.read() > 0, super::Errors::NULL_DURATION); let block_timestamp: u256 = get_block_timestamp().into(); let rate = if self.finish_at.read() < block_timestamp { amount / self.duration.read() } else { let remaining_rewards = self.reward_rate.read() * (self.finish_at.read() - block_timestamp); (remaining_rewards + amount) / self.duration.read() }; assert( self.reward_token.read().balance_of(get_contract_address()) >= rate * self.duration.read(), super::Errors::NOT_ENOUGH_REWARDS, ); self.reward_rate.write(rate); // even if the previous reward duration has not finished, we reset the finish_at // variable self.finish_at.write(block_timestamp + self.duration.read()); self.last_updated_at.write(block_timestamp); // reset total distributed rewards self.total_distributed_rewards.write(0); } fn stake(ref self: ContractState, amount: u256) { assert(amount > 0, super::Errors::NULL_AMOUNT); let user = get_caller_address(); self.update_rewards(user); self.balance_of.write(user, self.balance_of.read(user) + amount); self.total_supply.write(self.total_supply.read() + amount); self.staking_token.read().transfer_from(user, get_contract_address(), amount); self.emit(Deposit { user, amount }); } fn withdraw(ref self: ContractState, amount: u256) { assert(amount > 0, super::Errors::NULL_AMOUNT); let user = get_caller_address(); assert( self.staking_token.read().balance_of(user) >= amount, super::Errors::NOT_ENOUGH_BALANCE, ); self.update_rewards(user); self.balance_of.write(user, self.balance_of.read(user) - amount); self.total_supply.write(self.total_supply.read() - amount); self.staking_token.read().transfer(user, amount); self.emit(Withdrawal { user, amount }); } fn get_rewards(self: @ContractState, account: ContractAddress) -> u256 { self.unclaimed_rewards.read(account) + self.compute_new_rewards(account) } fn claim_rewards(ref self: ContractState) { let user = get_caller_address(); self.update_rewards(user); let rewards = self.unclaimed_rewards.read(user); if rewards > 0 { self.unclaimed_rewards.write(user, 0); self.reward_token.read().transfer(user, rewards); } } } #[generate_trait] impl PrivateFunctions of PrivateFunctionsTrait { // call this function every time a user (including owner) performs a state-modifying action fn update_rewards(ref self: ContractState, account: ContractAddress) { self .current_reward_per_staked_token .write(self.compute_current_reward_per_staked_token()); self.last_updated_at.write(self.last_time_applicable()); if account.is_non_zero() { self.distribute_user_rewards(account); self .last_user_reward_per_staked_token .write(account, self.current_reward_per_staked_token.read()); self.send_rewards_finished_event(); } } fn distribute_user_rewards(ref self: ContractState, account: ContractAddress) { // compute earned rewards since last update for the user `account` let user_rewards = self.get_rewards(account); self.unclaimed_rewards.write(account, user_rewards); // track amount of total rewards distributed self .total_distributed_rewards .write(self.total_distributed_rewards.read() + user_rewards); } fn send_rewards_finished_event(ref self: ContractState) { // check whether we should send a RewardsFinished event if self.last_updated_at.read() == self.finish_at.read() { let total_rewards = self.reward_rate.read() * self.duration.read(); if total_rewards != 0 && self.total_distributed_rewards.read() == total_rewards { // owner should set up NEW rewards into the contract self.emit(RewardsFinished { msg: "Rewards all distributed" }); } else { // owner should set up rewards into the contract (or add duration by setting up // rewards) self.emit(RewardsFinished { msg: "Rewards not active yet" }); } } } fn compute_current_reward_per_staked_token(self: @ContractState) -> u256 { if self.total_supply.read() == 0 { self.current_reward_per_staked_token.read() } else { self.current_reward_per_staked_token.read() + self.reward_rate.read() * (self.last_time_applicable() - self.last_updated_at.read()) / self.total_supply.read() } } fn compute_new_rewards(self: @ContractState, account: ContractAddress) -> u256 { self.balance_of.read(account) * (self.current_reward_per_staked_token.read() - self.last_user_reward_per_staked_token.read(account)) } #[inline(always)] fn last_time_applicable(self: @ContractState) -> u256 { Self::min(self.finish_at.read(), get_block_timestamp().into()) } #[inline(always)] fn min(x: u256, y: u256) -> u256 { if (x <= y) { x } else { y } } fn only_owner(self: @ContractState) { let caller = get_caller_address(); assert(caller == self.owner.read(), super::Errors::NOT_OWNER); } } } ``` # TimeLock Source: https://docs.starknet.io/build/starknet-by-example/applications/timelock This is the Cairo adaptation of the [Solidity by example TimeLock](https://solidity-by-example.org/app/time-lock/). ```rust theme={null} use starknet::account::Call; #[starknet::interface] pub trait ITimeLock { fn get_tx_id(self: @TState, call: Call, timestamp: u64) -> felt252; fn queue(ref self: TState, call: Call, timestamp: u64) -> felt252; fn execute(ref self: TState, call: Call, timestamp: u64) -> Span; fn cancel(ref self: TState, tx_id: felt252); } #[starknet::contract] pub mod TimeLock { use core::poseidon::{PoseidonTrait, poseidon_hash_span}; use core::hash::HashStateTrait; use starknet::{get_caller_address, get_block_timestamp, syscalls}; use starknet::account::Call; use components::ownable::ownable_component; use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; component!(path: ownable_component, storage: ownable, event: OwnableEvent); // Ownable #[abi(embed_v0)] impl OwnableImpl = ownable_component::Ownable; impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; #[storage] struct Storage { #[substorage(v0)] ownable: ownable_component::Storage, queued: Map::, } #[event] #[derive(Drop, starknet::Event)] pub enum Event { #[flat] OwnableEvent: ownable_component::Event, Queue: Queue, Execute: Execute, Cancel: Cancel, } #[derive(Drop, starknet::Event)] pub struct Queue { #[key] pub tx_id: felt252, pub call: Call, pub timestamp: u64, } #[derive(Drop, starknet::Event)] pub struct Execute { #[key] pub tx_id: felt252, pub call: Call, pub timestamp: u64, } #[derive(Drop, starknet::Event)] pub struct Cancel { #[key] pub tx_id: felt252, } pub const MIN_DELAY: u64 = 10; // seconds pub const MAX_DELAY: u64 = 1000; // seconds pub const GRACE_PERIOD: u64 = 1000; // seconds pub mod Errors { pub const ALREADY_QUEUED: felt252 = "TimeLock: already queued"; pub const TIMESTAMP_NOT_IN_RANGE: felt252 = "TimeLock: timestamp range"; pub const NOT_QUEUED: felt252 = "TimeLock: not queued"; pub const TIMESTAMP_NOT_PASSED: felt252 = "TimeLock: timestamp not passed"; pub const TIMESTAMP_EXPIRED: felt252 = "TimeLock: timestamp expired"; } #[constructor] fn constructor(ref self: ContractState) { self.ownable._init(get_caller_address()); } #[abi(embed_v0)] impl TimeLockImpl of super::ITimeLock { fn get_tx_id(self: @ContractState, call: Call, timestamp: u64) -> felt252 { PoseidonTrait::new() .update(call.to.into()) .update(call.selector.into()) .update(poseidon_hash_span(call.calldata)) .update(timestamp.into()) .finalize() } fn queue(ref self: ContractState, call: Call, timestamp: u64) -> felt252 { self.ownable._assert_only_owner(); let tx_id = self.get_tx_id(self._copy_call(@call), timestamp); assert(!self.queued.read(tx_id), Errors::ALREADY_QUEUED); // ---|------------|---------------|------- // block block + min block + max let block_timestamp = get_block_timestamp(); assert( timestamp >= block_timestamp + MIN_DELAY && timestamp <= block_timestamp + MAX_DELAY, Errors::TIMESTAMP_NOT_IN_RANGE, ); self.queued.write(tx_id, true); self.emit(Queue { tx_id, call: self._copy_call(@call), timestamp }); tx_id } fn execute(ref self: ContractState, call: Call, timestamp: u64) -> Span { self.ownable._assert_only_owner(); let tx_id = self.get_tx_id(self._copy_call(@call), timestamp); assert(self.queued.read(tx_id), Errors::NOT_QUEUED); // ----|-------------------|------- // timestamp timestamp + grace period let block_timestamp = get_block_timestamp(); assert(block_timestamp >= timestamp, Errors::TIMESTAMP_NOT_PASSED); assert(block_timestamp <= timestamp + GRACE_PERIOD, Errors::TIMESTAMP_EXPIRED); self.queued.write(tx_id, false); let result = syscalls::call_contract_syscall(call.to, call.selector, call.calldata) .unwrap(); self.emit(Execute { tx_id, call: self._copy_call(@call), timestamp }); result } fn cancel(ref self: ContractState, tx_id: felt252) { self.ownable._assert_only_owner(); assert(self.queued.read(tx_id), Errors::NOT_QUEUED); self.queued.write(tx_id, false); self.emit(Cancel { tx_id }); } } #[generate_trait] impl InternalImpl of InternalTrait { fn _copy_call(self: @ContractState, call: @Call) -> Call { Call { to: *call.to, selector: *call.selector, calldata: *call.calldata } } } } ``` # Upgradeable contract Source: https://docs.starknet.io/build/starknet-by-example/applications/upgradeable-contract In Starknet, contracts are divided into two parts: contract classes and contract instances. This division follows a similar concept used in object-oriented programming languages, where we distinguish between the definition and implementation of objects. A contract class is the definition of a contract: it specifies how the contract behaves. It contains essential information like the Cairo byte code, hint information, entry point names, and everything that defines its semantics unambiguously. To identify different contract classes, Starknet assigns a unique identifier to each class: the class hash. A contract instance is a deployed contract that corresponds to a specific contract class. Think of it as an instance of an object in languages like Java. Each class is identified by its class hash, which is analogous to a class name in an object-oriented programming language. A contract instance is a deployed contract corresponding to a class. You can upgrade a deployed contract to a newer version by calling the `replace_class_syscall` function. By using this function, you can update the class hash associated with a deployed contract, effectively upgrading its implementation. However, this will not modify the contract's storage, so all the data stored in the contract will remain the same. To illustrate this concept, let's consider an example with two contracts: `UpgradeableContract_V0`, and `UpgradeableContract_V1`. Start by deploying `UpgradeableContract_V0` as the initial version. Next, send a transaction that invokes the `upgrade` function, with the class hash of `UpgradeableContract_V1` as parameter to upgrade the class hash of the deployed contract to the `UpgradeableContract_V1` one. Then, call the `version` method on the contract to see that the contract was upgraded to the V1 version. ```rust theme={null} use starknet::class_hash::ClassHash; #[starknet::interface] pub trait IUpgradeableContract { fn upgrade(ref self: TContractState, impl_hash: ClassHash); fn version(self: @TContractState) -> u8; } #[starknet::contract] pub mod UpgradeableContract_V0 { use starknet::class_hash::ClassHash; use core::num::traits::Zero; #[storage] struct Storage {} #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub enum Event { Upgraded: Upgraded, } #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct Upgraded { pub implementation: ClassHash, } #[abi(embed_v0)] impl UpgradeableContract of super::IUpgradeableContract { fn upgrade(ref self: ContractState, impl_hash: ClassHash) { assert(impl_hash.is_non_zero(), "Class hash cannot be zero"); starknet::syscalls::replace_class_syscall(impl_hash).unwrap(); self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })) } fn version(self: @ContractState) -> u8 { 0 } } } ``` ```rust theme={null} use starknet::class_hash::ClassHash; #[starknet::interface] pub trait IUpgradeableContract { fn upgrade(ref self: TContractState, impl_hash: ClassHash); fn version(self: @TContractState) -> u8; } #[starknet::contract] pub mod UpgradeableContract_V1 { use starknet::class_hash::ClassHash; use core::num::traits::Zero; #[storage] struct Storage {} #[event] #[derive(Drop, starknet::Event)] enum Event { Upgraded: Upgraded, } #[derive(Drop, starknet::Event)] struct Upgraded { implementation: ClassHash, } #[abi(embed_v0)] impl UpgradeableContract of super::IUpgradeableContract { fn upgrade(ref self: ContractState, impl_hash: ClassHash) { assert(impl_hash.is_non_zero(), "Class hash cannot be zero"); starknet::syscalls::replace_class_syscall(impl_hash).unwrap(); self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })) } fn version(self: @ContractState) -> u8 { 1 } } } ``` # Calling other contracts Source: https://docs.starknet.io/build/starknet-by-example/basic/calling-other-contracts In Starknet, contracts can interact with each other through contract calls. The recommended way to make these calls is using the dispatcher pattern, which provides type safety and better error handling. ## Understanding Dispatchers A dispatcher is an automatically generated struct that handles the serialization and deserialization of contract calls. To use dispatchers: 1. Define the target contract's interface as a trait with `#[starknet::interface]` (`IContract`) 2. Import the generated dispatcher types (`IContractDispatcher` and `IContractDispatcherTrait`) 3. Create a dispatcher instance with the target contract's address Let's look at a practical example where one contract (`Caller`) interacts with another (`Callee`). The `Callee` contract stores a value that can be set and retrieved: ```rust theme={null} #[starknet::interface] trait ICallee { fn set_value(ref self: TContractState, value: u128); fn get_value(self: @TContractState) -> u128; } #[starknet::contract] mod Callee { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { value: u128, } #[abi(embed_v0)] impl Callee of super::ICallee { fn set_value(ref self: ContractState, value: u128) { self.value.write(value); } fn get_value(self: @ContractState) -> u128 { self.value.read() } } } ``` The `Caller` contract demonstrates how to use the dispatcher to interact with `Callee`: ```rust theme={null} #[starknet::interface] trait ICaller { fn call_callee_set_value(ref self: TContractState, callee_address: ContractAddress, value: u128); fn call_callee_get_value(self: @TContractState, callee_address: ContractAddress) -> u128; } #[starknet::contract] mod Caller { use starknet::storage::StoragePointerReadAccess; use super::ICaller; #[storage] struct Storage {} #[abi(embed_v0)] impl Caller of super::ICaller { fn call_callee_set_value(ref self: ContractState, callee_address: ContractAddress, value: u128) { // Create a dispatcher for the callee contract let callee_dispatcher = ICalleeDispatcher { contract_address: callee_address }; // Call the callee contract's set_value function callee_dispatcher.set_value(value); } fn call_callee_get_value(self: @ContractState, callee_address: ContractAddress) -> u128 { // Create a dispatcher for the callee contract let callee_dispatcher = ICalleeDispatcher { contract_address: callee_address }; // Call the callee contract's get_value function callee_dispatcher.get_value() } } } ``` ### Key Points: * The `#[starknet::interface]` attribute automatically generates the dispatcher types * Dispatchers handle all the low-level details of contract interaction * Contract calls are type-safe and checked at compile time * Each contract maintains its own storage and state For more details about dispatchers, check out the [Cairo Book](https://book.cairo-lang.org/ch102-02-interacting-with-another-contract.html). While you can use the low-level `call_contract_syscall` directly, it's not recommended as it: * Requires manual serialization/deserialization * Lacks compile-time type checking * Is more easy to make mistakes # Component contract storage collisions Source: https://docs.starknet.io/build/starknet-by-example/basic/components/collisions Components can declare their own storage variables. When a contract uses a component, the component storage is merged with the contract storage. The storage layout is only determined by the variables names, so variables with the same name will collide. In a future release, the `#[substorage(v1)]` will determine the storage layout based on the component as well, so collisions will be avoided. A good practice is to prefix the component storage variables with the component name, as shown in the [Switchable component example](./intro). #### Example Here's an example of a collision on the `switchable_value` storage variable of the `Switchable` component. Interface: ```rust theme={null} #[starknet::interface] pub trait ISwitchCollision { fn set(ref self: TContractState, value: bool); fn get(ref self: TContractState) -> bool; } ``` Here's the storage of the contract: ```rust theme={null} #[storage] #[allow(starknet::colliding_storage_paths)] struct Storage { switchable_value: bool, #[substorage(v0)] switch: switchable_component::Storage, } ``` Both the contract and the component have a `switchable_value` storage variable, so they collide: ```rust theme={null} #[test] fn test_collision() { let (mut contract, mut contract_iswitch) = deploy(); assert_eq!(contract.get(), false); assert_eq!(contract_iswitch.is_on(), false); contract_iswitch.switch(); assert_eq!(contract_iswitch.is_on(), true); assert_eq!(contract.get(), true); // `collision` between component storage 'value' and contract storage 'value' assert_eq!(contract.get(), contract_iswitch.is_on()); contract.set(false); assert_eq!(contract.get(), contract_iswitch.is_on()); } ``` # Component dependencies Source: https://docs.starknet.io/build/starknet-by-example/basic/components/dependencies A component with a dependency on a trait `T` can be used in a contract as long as the contract implements the trait `T`. We will use a new `Countable` component as an example: ```rust theme={null} #[starknet::interface] pub trait ICountable { fn get(self: @TContractState) -> u32; fn increment(ref self: TContractState); } #[starknet::component] pub mod countable_component { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] pub struct Storage { countable_value: u32, } #[embeddable_as(Countable)] impl CountableImpl< TContractState, +HasComponent, > of super::ICountable> { fn get(self: @ComponentState) -> u32 { self.countable_value.read() } fn increment(ref self: ComponentState) { self.countable_value.write(self.countable_value.read() + 1); } } } ``` We want to add a way to enable or disable the counter, in a way that calling `increment` on a disabled counter will not increment it. But we don't want to add this switch logic to the `Countable` component itself. Instead, we add the trait `Switchable` as a dependency to the `Countable` component. #### Implementation of the trait in the contract First, we import the `ISwitchable` trait defined in chapter ["Components How-To"](./intro): ```rust theme={null} #[starknet::interface] pub trait ISwitchable { fn is_on(self: @TContractState) -> bool; fn switch(ref self: TContractState); } ``` Then we can modify the implementation of the `Countable` component to depend on the `ISwitchable` trait: ```rust theme={null} #[starknet::component] pub mod countable_component { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::countable::ICountable; use components::switchable::ISwitchable; #[storage] pub struct Storage { countable_value: u32, } #[embeddable_as(Countable)] impl CountableImpl< TContractState, +HasComponent, +ISwitchable, > of ICountable> { fn get(self: @ComponentState) -> u32 { self.countable_value.read() } fn increment(ref self: ComponentState) { if (self.get_contract().is_on()) { self.countable_value.write(self.countable_value.read() + 1); } } } } ``` A contract that uses the `Countable` component must implement the `ISwitchable` trait: ```rust theme={null} #[starknet::contract] mod MockContract { use super::countable_component; use components::switchable::ISwitchable; component!(path: countable_component, storage: counter, event: CountableEvent); #[storage] struct Storage { #[substorage(v0)] counter: countable_component::Storage, switch: bool, } #[event] #[derive(Drop, starknet::Event)] enum Event { CountableEvent: countable_component::Event, } #[abi(embed_v0)] impl CountableImpl = countable_component::Countable; #[abi(embed_v0)] impl Switchable of ISwitchable { fn switch(ref self: ContractState) {} fn is_on(self: @ContractState) -> bool { true } } } ``` #### Implementation of the trait in another component In the previous example, we implemented the `ISwitchable` trait in the contract. We already implemented a [`Switchable`](./intro) component that provides an implementation of the `ISwitchable` trait. By using the `Switchable` component in a contract, we can embed the implementation of the `ISwitchable` trait in the contract and resolve the dependency on the `ISwitchable` trait. ```rust theme={null} #[starknet::contract] mod CountableContract { use super::countable_component; use components::switchable::switchable_component; component!(path: countable_component, storage: counter, event: CountableEvent); component!(path: switchable_component, storage: switch, event: SwitchableEvent); #[storage] struct Storage { #[substorage(v0)] counter: countable_component::Storage, #[substorage(v0)] switch: switchable_component::Storage, } #[event] #[derive(Drop, starknet::Event)] enum Event { CountableEvent: countable_component::Event, SwitchableEvent: switchable_component::Event, } #[abi(embed_v0)] impl CountableImpl = countable_component::Countable; #[abi(embed_v0)] impl SwitchableImpl = switchable_component::Switchable; } ``` #### Dependency on other component's internal functions The previous example shows how to use the `ISwitchable` trait implementation from the `Switchable` component inside the `Countable` component by embedding the implementation in the contract. However, suppose we would like to turn off the switch after each increment. There's no `set` function in the `ISwitchable` trait, so we can't do it directly. But the `Switchable` component implements the internal function `_off` from the `SwitchableInternalTrait` that set the switch to `false`. We can't embed `SwitchableInternalImpl`, but we can add `switchable::HasComponent` as a dependency inside `CountableImpl`. We make the `Countable` component depend on the `Switchable` component. This will allow to do `switchable::ComponentState` -> `TContractState` -> `countable::ComponentState` and access the internal functions of the `Switchable` component inside the `Countable` component: ```rust theme={null} #[starknet::contract] mod CountableContract { use super::countable_component; use components::switchable::switchable_component; component!(path: countable_component, storage: counter, event: CountableEvent); component!(path: switchable_component, storage: switch, event: SwitchableEvent); #[storage] struct Storage { #[substorage(v0)] counter: countable_component::Storage, #[substorage(v0)] switch: switchable_component::Storage, } #[event] #[derive(Drop, starknet::Event)] enum Event { CountableEvent: countable_component::Event, SwitchableEvent: switchable_component::Event, } #[abi(embed_v0)] impl CountableImpl = countable_component::Countable; #[abi(embed_v0)] impl SwitchableImpl = switchable_component::Switchable; impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; } ``` The `CountableContract` contract remains the same as in the previous example, only the implementation of the `Countable` component is different. # Introduction to components Source: https://docs.starknet.io/build/starknet-by-example/basic/components/intro Components are like modular addons that can be snapped into contracts to add reusable logic, storage, and events. They are used to separate the core logic from common functionalities, simplifying the contract's code and making it easier to read and maintain. It also reduces the risk of bugs and vulnerabilities by using well-tested components. Key characteristics: * Modularity: Easily pluggable into multiple contracts. * Reusable Logic: Encapsulates specific functionalities. * Not Standalone: Cannot be declared or deployed independently. ## How to create a component The following example shows a simple `Switchable` component that can be used to add a switch that can be either on or off. It contains a storage variable `switchable_value`, a function `switch` and an event `Switch`. It is a good practice to prefix the component storage variables with the component name to [avoid collisions](./collisions). ```rust theme={null} #[starknet::interface] pub trait ISwitchable { fn is_on(self: @TContractState) -> bool; fn switch(ref self: TContractState); } #[starknet::component] pub mod switchable_component { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] pub struct Storage { switchable_value: bool, } #[derive(Drop, Debug, PartialEq, starknet::Event)] pub struct SwitchEvent {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { SwitchEvent: SwitchEvent, } #[embeddable_as(Switchable)] impl SwitchableImpl< TContractState, +HasComponent, > of super::ISwitchable> { fn is_on(self: @ComponentState) -> bool { self.switchable_value.read() } fn switch(ref self: ComponentState) { self.switchable_value.write(!self.switchable_value.read()); self.emit(Event::SwitchEvent(SwitchEvent {})); } } #[generate_trait] pub impl SwitchableInternalImpl< TContractState, +HasComponent, > of SwitchableInternalTrait { fn _off(ref self: ComponentState) { self.switchable_value.write(false); } } } ``` A component is really similar to a contract and can also have: * An interface defining entrypoints (`ISwitchableComponent`) * A Storage struct * Events * Internal functions It doesn't have a constructor, but you can create an `_init` internal function and call it from the contract's constructor. In the previous example, the `_off` function will be used this way. It's currently not possible to use the same component multiple times in the same contract. This is a known limitation that may be lifted in the future. For now, you can view components as implementations of specific interfaces or features (`Ownable`, `Upgradeable`, ... \~`able`). This is why we called the component in the above example `Switchable`, and not `Switch`; the contract *is switchable*, it does not *have a switch*. ## How to use a component Now that we have a component, we can use it in a contract. The following contract incorporates the `Switchable` component: ```rust theme={null} #[starknet::contract] pub mod SwitchContract { use super::switchable_component; component!(path: switchable_component, storage: switch, event: SwitchableEvent); #[abi(embed_v0)] impl SwitchableImpl = switchable_component::Switchable; #[storage] struct Storage { #[substorage(v0)] switch: switchable_component::Storage, } #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { SwitchableEvent: switchable_component::Event, } // You can optionally use the internal implementation of the component as well impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; #[constructor] fn constructor(ref self: ContractState) { // Internal function call self.switch._off(); } } ``` ## How to test a component In order to effectively test a component, you need to test it in the context of a contract. A common practice is to declare a `Mock` contract that has the only purpose of testing the component. To test the `Switchable` component, we can use the previous `SwitchableContract`: ```rust theme={null} #[cfg(test)] mod test { use super::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; use snforge_std::{ ContractClassTrait, DeclareResultTrait, declare, spy_events, EventSpyAssertionsTrait, }; use super::SwitchContract; // Used as a mock contract use super::switchable_component; fn deploy() -> ISwitchableDispatcher { let contract = declare("SwitchContract").unwrap().contract_class(); let (contract_address, _) = contract.deploy(@array![]).unwrap(); ISwitchableDispatcher { contract_address } } #[test] fn test_constructor() { let switchable = deploy(); assert_eq!(switchable.is_on(), false); } #[test] fn test_switch() { let switchable = deploy(); let mut spy = spy_events(); switchable.switch(); assert_eq!(switchable.is_on(), true); spy .assert_emitted( @array![ ( switchable.contract_address, SwitchContract::Event::SwitchableEvent( switchable_component::Event::SwitchEvent( switchable_component::SwitchEvent {}, ), ), ), ], ); } #[test] fn test_multiple_switches() { let switchable = deploy(); switchable.switch(); assert_eq!(switchable.is_on(), true); switchable.switch(); assert_eq!(switchable.is_on(), false); switchable.switch(); assert_eq!(switchable.is_on(), true); } } ``` ## Deep dive into components You can find more in-depth information about components in [The Cairo book - Components](https://book.cairo-lang.org/ch16-02-00-composability-and-components.html). # The Ownable component Source: https://docs.starknet.io/build/starknet-by-example/basic/components/ownable The following `Ownable` component is a simple component that allows the contract to set an owner and provides an `_assert_is_owner` function that can be used to ensure that the caller is the owner. It can also be used to renounce ownership of a contract, meaning that no one will be able to satisfy the `_assert_is_owner` function. ```rust theme={null} use starknet::ContractAddress; #[starknet::interface] pub trait IOwnable { fn owner(self: @TContractState) -> ContractAddress; fn transfer_ownership(ref self: TContractState, new: ContractAddress); fn renounce_ownership(ref self: TContractState); } pub mod Errors { pub const UNAUTHORIZED: felt252 = "Not owner"; pub const ZERO_ADDRESS_OWNER: felt252 = "Owner cannot be zero"; pub const ZERO_ADDRESS_CALLER: felt252 = "Caller cannot be zero"; } #[starknet::component] pub mod ownable_component { use super::Errors; use starknet::{ContractAddress, get_caller_address}; use core::num::traits::Zero; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] pub struct Storage { ownable_owner: ContractAddress, } #[derive(Drop, Debug, PartialEq, starknet::Event)] pub struct OwnershipTransferredEvent { pub previous: ContractAddress, pub new: ContractAddress, } #[derive(Drop, Debug, PartialEq, starknet::Event)] pub struct OwnershipRenouncedEvent { pub previous: ContractAddress, } #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { OwnershipTransferredEvent: OwnershipTransferredEvent, OwnershipRenouncedEvent: OwnershipRenouncedEvent, } #[embeddable_as(Ownable)] pub impl OwnableImpl< TContractState, +HasComponent, > of super::IOwnable> { fn owner(self: @ComponentState) -> ContractAddress { self.ownable_owner.read() } fn transfer_ownership(ref self: ComponentState, new: ContractAddress) { self._assert_only_owner(); self._transfer_ownership(new); } fn renounce_ownership(ref self: ComponentState) { self._assert_only_owner(); self._renounce_ownership(); } } #[generate_trait] pub impl OwnableInternalImpl< TContractState, +HasComponent, > of OwnableInternalTrait { fn _assert_only_owner(self: @ComponentState) { let caller = get_caller_address(); assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); assert(caller == self.ownable_owner.read(), Errors::UNAUTHORIZED); } fn _init(ref self: ComponentState, owner: ContractAddress) { assert(owner.is_non_zero(), Errors::ZERO_ADDRESS_OWNER); self.ownable_owner.write(owner); } fn _transfer_ownership(ref self: ComponentState, new: ContractAddress) { assert(new.is_non_zero(), Errors::ZERO_ADDRESS_OWNER); let previous = self.ownable_owner.read(); self.ownable_owner.write(new); self .emit( Event::OwnershipTransferredEvent(OwnershipTransferredEvent { previous, new }), ); } fn _renounce_ownership(ref self: ComponentState) { let previous = self.ownable_owner.read(); self.ownable_owner.write(Zero::zero()); self.emit(Event::OwnershipRenouncedEvent(OwnershipRenouncedEvent { previous })); } } } ``` A mock contract that uses the `Ownable` component: ```rust theme={null} #[starknet::contract] pub mod OwnedContract { use super::{ownable_component, ownable_component::OwnableInternalTrait}; component!(path: ownable_component, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] impl OwnableImpl = ownable_component::Ownable; #[storage] struct Storage { #[substorage(v0)] ownable: ownable_component::Storage, } #[constructor] fn constructor(ref self: ContractState) { self.ownable._init(starknet::get_caller_address()); } #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { OwnableEvent: ownable_component::Event, } } ``` # Constructor Source: https://docs.starknet.io/build/starknet-by-example/basic/constructor A constructor is a special function that initializes a contract's state during deployment. It has several key characteristics: * Runs exactly once when the contract is deployed * Must be annotated with `#[constructor]` * Up to one constructor per contract * Function is conventionally named `constructor` Here's an example that shows how to initialize storage variables during contract deployment: ```rust theme={null} #[starknet::interface] trait IConstructorExample { fn get_a(self: @TContractState) -> u128; fn get_b(self: @TContractState) -> u8; fn get_c(self: @TContractState) -> u256; } #[starknet::contract] mod ConstructorExample { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { a: u128, b: u8, c: u256, } #[constructor] fn constructor(ref self: ContractState, a: u128, b: u8, c: u256) { // Initialize storage variables with constructor parameters self.a.write(a); self.b.write(b); self.c.write(c); } #[abi(embed_v0)] impl ConstructorExample of super::IConstructorExample { fn get_a(self: @ContractState) -> u128 { self.a.read() } fn get_b(self: @ContractState) -> u8 { self.b.read() } fn get_c(self: @ContractState) -> u256 { self.c.read() } } } ``` In this example: * The constructor takes three parameters: `a`, `b`, and `c` * Each parameter corresponds to a storage variable of the same name, but you can specify any argument variable name * The values are written to storage using the `write()` method. You need to import the `StoragePointerWriteAccess` trait to be able to write to a specific storage pointer Constructors are ideal for: * Setting initial contract state * Storing deployment-time parameters * Initializing access control (e.g., setting an owner) **Constructor values cannot be changed after deployment unless you specifically implement functions to modify them.** # Simple counter Source: https://docs.starknet.io/build/starknet-by-example/basic/counter We now understand how to create a contract with state variables and functions. Let's create a simple counter contract that increments and decrements a counter. Here's how it works: * The contract has a state variable `counter` that is initialized to `0`. * When a user calls the `increment` entrypoint, the contract increments `counter` by `1`. * When a user calls the `decrement`, the contract decrements `counter` by `1`. ```rust theme={null} #[starknet::interface] trait ISimpleCounter { fn get_current_count(self: @TContractState) -> u128; fn increment(ref self: TContractState); fn decrement(ref self: TContractState); } #[starknet::contract] mod SimpleCounter { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { // Counter variable counter: u128, } #[constructor] fn constructor(ref self: ContractState, init_value: u128) { // Store initial value self.counter.write(init_value); } #[abi(embed_v0)] impl SimpleCounter of super::ISimpleCounter { fn get_current_count(self: @ContractState) -> u128 { return self.counter.read(); } fn increment(ref self: ContractState) { // Store counter value + 1 let counter = self.counter.read() + 1; self.counter.write(counter); } fn decrement(ref self: ContractState) { // Store counter value - 1 let counter = self.counter.read() - 1; self.counter.write(counter); } } } ``` # Custom types in entrypoints Source: https://docs.starknet.io/build/starknet-by-example/basic/custom-types-in-entrypoints When using custom types in Starknet contract entrypoints, you need to handle serialization and deserialization of data. This is because: 1. Input parameters are sent to entrypoints as arrays of `felt252` 2. Return values must be converted back to arrays of `felt252` 3. Custom types need to be converted between these formats automatically ## Using the Serde Trait The `Serde` trait provides the necessary serialization and deserialization capabilities for your custom types. For most simple types, you can derive this trait automatically: ```rust theme={null} #[derive(Drop, starknet::Serde)] struct Person { name: felt252, age: u8, is_active: bool, } #[starknet::interface] trait ICustomTypeEntrypoints { fn set_person(ref self: TContractState, person: Person); fn get_person(self: @TContractState) -> Person; } #[starknet::contract] mod CustomTypeEntrypoints { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { person: Person, } #[abi(embed_v0)] impl CustomTypeEntrypoints of super::ICustomTypeEntrypoints { fn set_person(ref self: ContractState, person: Person) { // The Serde trait automatically handles serialization of the Person struct self.person.write(person); } fn get_person(self: @ContractState) -> Person { // The Serde trait automatically handles deserialization of the Person struct self.person.read() } } } ``` For some complex types, you might need to implement the `Serde` trait manually. This gives you control over how your type is serialized and deserialized. The `Serde` trait is distinct from the `Store` trait - `Serde` is for passing data in and out of entrypoints, while `Store` is for persisting data in contract storage. # Documentation Source: https://docs.starknet.io/build/starknet-by-example/basic/documentation It's important to take the time to document your code. It will help developers and users to understand the contract and its functionalities. In Cairo, you can add comments with `//`. ### Best Practices Since Cairo 1, the community has adopted a [Rust-like documentation style](https://doc.rust-lang.org/rust-by-example/meta/doc.html). ### Contract Interface In smart contracts, you will often have a trait that defines the contract's interface (with `#[starknet::interface]`). This is the perfect place to include detailed documentation explaining the purpose and functionality of the contract entry points. You can follow this template: ```rust theme={null} #[starknet::interface] trait IContract { /// High-level description of the function /// /// # Arguments /// /// * `arg_1` - Description of the argument /// * `arg_n` - ... /// /// # Returns /// /// High-level description of the return value fn do_something(ref self: TContractState, arg_1: T_arg_1) -> T_return; } ``` Keep in mind that this should not describe the implementation details of the function, but rather the high-level purpose and functionality of the contract from the perspective of a user. ### Implementation Details When writing the contract logic, you can add comments to describe the technical implementation details of the functions. Avoid over-commenting: Comments should provide additional value and clarity. # Error handling Source: https://docs.starknet.io/build/starknet-by-example/basic/errors Cairo provides robust error handling mechanisms for smart contracts. When an error occurs during contract execution, the transaction is immediately reverted and all state changes are undone. ## Basic Error Functions Cairo offers two main functions for error handling: ### 1. `assert` * Used for condition validation (similar to Solidity's `require`) * Stops execution if the condition is false * Supports two formats: ```rust theme={null} assert(condition, 'error message'); // Basic assertion assert!(condition, "formatted error: {}", x); // Formatted string error ``` ### 2. `panic` * Used for immediate execution halt (similar to Solidity's `revert`) * Best for complex conditions or internal errors * Supports multiple formats: ```rust theme={null} panic_with_felt252('error message'); // Basic panic panic!("formatted error: value={}", value); // Formatted string error ``` While Cairo provides assertion macros like `assert_eq!` and `assert_ne!`, these are **only for testing**. In contract code, always use the standard `assert` function. ## Simple Example Here's a basic example demonstrating both error handling approaches: ```rust theme={null} #[starknet::interface] trait ISimpleErrors { fn check_positive(self: @TContractState, value: u128) -> bool; fn set_value(ref self: TContractState, value: u128); } #[starknet::contract] mod SimpleErrors { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { value: u128, } #[abi(embed_v0)] impl SimpleErrors of super::ISimpleErrors { fn check_positive(self: @ContractState, value: u128) -> bool { // Using assert for validation assert(value > 0, 'Value must be positive'); true } fn set_value(ref self: ContractState, value: u128) { // Using panic for immediate halt if value == 0 { panic_with_felt252('Value cannot be zero'); } self.value.write(value); } } } ``` ## Custom Error Codes For better organization and consistency, you can define error messages in a dedicated module: ```rust theme={null} mod Errors { const INSUFFICIENT_BALANCE: felt252 = "Insufficient balance"; const INVALID_AMOUNT: felt252 = "Invalid amount"; const ACCESS_DENIED: felt252 = "Access denied"; } #[starknet::interface] trait ICustomErrors { fn withdraw(ref self: TContractState, amount: u128); } #[starknet::contract] mod CustomErrors { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::Errors; #[storage] struct Storage { balance: u128, } #[abi(embed_v0)] impl CustomErrors of super::ICustomErrors { fn withdraw(ref self: ContractState, amount: u128) { // Using custom error constants assert(amount > 0, Errors::INVALID_AMOUNT); assert(self.balance.read() >= amount, Errors::INSUFFICIENT_BALANCE); let new_balance = self.balance.read() - amount; self.balance.write(new_balance); } } } ``` ## Real-World Example: Vault Contract Here's a practical example showing error handling in a vault contract that manages deposits and withdrawals: ```rust theme={null} mod Errors { const INSUFFICIENT_BALANCE: felt252 = "Insufficient balance"; const INVALID_AMOUNT: felt252 = "Invalid amount"; const ACCESS_DENIED: felt252 = "Access denied"; } #[starknet::interface] trait IVaultErrors { fn deposit(ref self: TContractState, amount: u128); fn withdraw(ref self: TContractState, amount: u128); fn get_balance(self: @TContractState) -> u128; } #[starknet::contract] mod VaultErrors { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::Errors; #[storage] struct Storage { balance: u128, owner: ContractAddress, } #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress) { self.owner.write(owner); } #[abi(embed_v0)] impl VaultErrors of super::IVaultErrors { fn deposit(ref self: ContractState, amount: u128) { assert(amount > 0, Errors::INVALID_AMOUNT); let current_balance = self.balance.read(); self.balance.write(current_balance + amount); } fn withdraw(ref self: ContractState, amount: u128) { assert(amount > 0, Errors::INVALID_AMOUNT); let current_balance = self.balance.read(); if current_balance < amount { panic_with_felt252(Errors::INSUFFICIENT_BALANCE); } self.balance.write(current_balance - amount); } fn get_balance(self: @ContractState) -> u128 { self.balance.read() } } } ``` In this example: 1. Custom errors are defined in a separate module 2. The `withdraw` function demonstrates both `assert` and `panic` approaches 3. Balance checks protect against underflow conditions # Events Source: https://docs.starknet.io/build/starknet-by-example/basic/events Events in Cairo smart contracts allow you to emit and record data on the Starknet blockchain. They are essential for tracking important state changes and providing transparency to users and other contracts. Events are also useful when building interfaces, to be notified about important state changes. To use events in your contract: 1. Create event structs that derive the `starknet::Event` trait 2. Define an `Event` enum in the contract, annotated with `#[event]`, where each variant is linked to an event struct 3. Emit events with the `emit` function You can make events searchable by adding the `#[key]` attribute to specific fields, which indexes them for efficient querying later. Events variant names and structs are recommended to be named consistently, even if it create some redundancy when emitting events. Here's a practical example of a contract that emits events when incrementing a counter: ```rust theme={null} #[starknet::interface] trait ICounterEvents { fn increment(ref self: TContractState); fn get_count(self: @TContractState) -> u128; } // Event structs defined outside the contract for better organization #[derive(Drop, starknet::Event)] enum CounterEvent { Incremented: Incremented, Reset: Reset, } #[derive(Drop, starknet::Event)] struct Incremented { #[key] user: ContractAddress, new_count: u128, timestamp: u64, } #[derive(Drop, starknet::Event)] struct Reset { #[key] user: ContractAddress, old_count: u128, timestamp: u64, } #[starknet::contract] mod CounterEvents { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::{ICounterEvents, CounterEvent, Incremented, Reset}; #[storage] struct Storage { count: u128, } #[event] #[derive(Drop, starknet::Event)] enum Event { Incremented: Incremented, Reset: Reset, } #[abi(embed_v0)] impl CounterEvents of super::ICounterEvents { fn increment(ref self: ContractState) { let current_count = self.count.read(); let new_count = current_count + 1; self.count.write(new_count); // Emit the Incremented event self.emit(CounterEvent::Incremented(Incremented { user: starknet::get_caller_address(), new_count, timestamp: starknet::get_block_timestamp(), })); } fn get_count(self: @ContractState) -> u128 { self.count.read() } } // Internal function to reset counter fn reset_counter(ref self: ContractState) { let old_count = self.count.read(); self.count.write(0); // Emit the Reset event self.emit(CounterEvent::Reset(Reset { user: starknet::get_caller_address(), old_count, timestamp: starknet::get_block_timestamp(), })); } } ``` For better code organization, especially in larger contracts, you can define event structs outside of the contract module, as shown in the example here. While this allows you to group related events in separate modules or files, remember that you must still include all event variants in the contract's `Event` enum. # Mappings Source: https://docs.starknet.io/build/starknet-by-example/basic/mappings Maps are a fundamental key-value data structure in Cairo smart contracts that allow you to store and retrieve values using unique keys. The `Map` type in `starknet::storage` is specifically designed for contract storage for this purpose. Here's a simple example that demonstrates how to use a `Map`: ```rust theme={null} #[starknet::interface] trait IMappings { fn set_value(ref self: TContractState, key: u32, value: u128); fn get_value(self: @TContractState, key: u32) -> u128; } #[starknet::contract] mod Mappings { use starknet::storage::{StorageMapReadAccess, StorageMapWriteAccess}; #[storage] struct Storage { // Simple mapping from u32 to u128 values: Map, } #[abi(embed_v0)] impl Mappings of super::IMappings { fn set_value(ref self: ContractState, key: u32, value: u128) { // Write a value to the map self.values.write(key, value); } fn get_value(self: @ContractState, key: u32) -> u128 { // Read a value from the map self.values.read(key) } } } ``` Let's break down the key components: * **Declaration**: Maps are declared using `Map` syntax * **Storage**: Maps must be declared inside the contract's `Storage` struct * You need to import the `StorageMapReadAccess` and `StorageMapWriteAccess` traits from `starknet::storage` * **Operations**: * `write(key, value)`: Stores a value for a given key * `read(key)`: Retrieves the value associated with a key * Maps automatically initialize all values to zero * Keys and values must be of valid storage types, see [Storing Custom Types](./storing-custom-types) ### Composite Keys For more complex scenarios, you can use composite keys by combining multiple values: ```rust theme={null} // Example: ERC20 allowance mapping Map<(ContractAddress, ContractAddress), felt252> // (owner, spender) -> amount ``` ### Storage Layout (advanced) Under the hood, Cairo maps use a deterministic storage layout: * Each key-value pair is stored at a unique address calculated using Pedersen hashes * The address formula is: $\text{h}(...\text{h}(\text{h}(\text{sn\_keccak}(name),k_1),k_2),...,k_n)$ mod $2^{251} - 256$ * Learn more in the [Cairo Book](https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html) # Storage Source: https://docs.starknet.io/build/starknet-by-example/basic/storage ## Introduction You can define storage variables to store and retrieve data in your contract. Storage in Cairo contracts is implemented as a key-value store using a struct marked with the `#[storage]` attribute. Every contract must have exactly one storage definition, which serves as the contract's persistent state on the blockchain and is kept between contract executions. ## Storage space and pointers The contract's storage space consists of $2^{251}$ *storage slots*, where each slot: * Can store a single `felt252` value * Is initialized to 0 Storage variables are stored in storage slots using Starknet's memory model abstraction called **storage pointers**. A storage pointer is a tuple `(base_address, offset)` where: * `base_address` is the address of the first slot where the variable is stored * `offset` is the distance from the base address where the variable is stored To get the base address of a storage variable, you can use the `selector!` macro to derive it from the variable name (i.e., `selector!("variable§_name")`). ## Example ```rust theme={null} mod Contract { #[storage] struct Storage { a: u128, b: u8, c: u256, } } ``` The storage layout of this contract is: * Variable `a` (`u128`): * Base address: `selector!(`"a")\` * Uses lowest 128 bits of the slot at offset 0 * Leaves 124 bits unused * Variable `b` (`u8`): * Base address: `selector!("b")` * Uses lowest 8 bits of the slot at offset 0 * Leaves 244 bits unused * Variable `c` (`u256`): * Base address: `selector!("c")` * Too large for a single slot, uses two consecutive slots: * First slot: lower 128 bits at offset 0 * Second slot: lower 128 bits at offset 1 * Leaves 248 bits unused, 124 in each slot For more complex data structures, see [storing custom types](/build/starknet-by-example/basic/storing-custom-types). Notice how many bits are left unused in each slot? This can make storage operations expensive. To optimize storage usage, you can pack multiple variables together. Learn more in [storage optimization](/build/starknet-by-example/advanced/store-using-packing). # Storing custom types Source: https://docs.starknet.io/build/starknet-by-example/basic/storing-custom-types In Starknet contracts, storing custom types in contract storage requires implementing the `Store` trait. While native types (like `felt252`, `u128`, etc.) can be stored directly, custom types need this additional step to generate the necessary implementation on how to handle their storage. To make a custom type storable: 1. Derive the `starknet::Store` trait for your struct 2. Add any other necessary traits like `Drop`, `Serde`, and `Copy` 3. Define your storage variables using the custom type Here's an example showing how to store a custom `Person` struct: ```rust theme={null} #[derive(Drop, starknet::Store)] struct Person { name: felt252, age: u8, is_active: bool, } #[starknet::interface] trait ICustomTypes { fn set_person(ref self: TContractState, person: Person); fn get_person(self: @TContractState) -> Person; } #[starknet::contract] mod CustomTypes { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { person: Person, } #[abi(embed_v0)] impl CustomTypes of super::ICustomTypes { fn set_person(ref self: ContractState, person: Person) { self.person.write(person); } fn get_person(self: @ContractState) -> Person { self.person.read() } } } ``` For more complex types, you might need to implement the `Store` trait manually instead of deriving it. ## Accessing Struct Members When you derive the `Store` trait, Cairo automatically generates the necessary storage pointers for each struct member. This allows you to access and modify individual fields of your stored struct directly: ```rust theme={null} #[starknet::interface] trait ICustomTypesAccess { fn set_name(ref self: TContractState, name: felt252); fn get_name(self: @TContractState) -> felt252; } #[starknet::contract] mod CustomTypesAccess { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { person: Person, } #[abi(embed_v0)] impl CustomTypesAccess of super::ICustomTypesAccess { fn set_name(ref self: ContractState, name: felt252) { // Access individual struct member self.person.name.write(name); } fn get_name(self: @ContractState) -> felt252 { // Read individual struct member self.person.name.read() } } } ``` # Variables Source: https://docs.starknet.io/build/starknet-by-example/basic/variables Cairo contracts support three types of variables, each serving a different purpose: 1. **Local Variables** * Temporary variables within functions * Exist only during function execution * Not stored on the blockchain 2. **Storage Variables** * Defined in the contract's [Storage](/build/starknet-by-example/basic/storage) * Persist between contract executions * Stored on the blockchain 3. **Global Variables** * Provide blockchain context and information * Accessible anywhere in the contract * Read-only system variables ## Local Variables Local variables are temporary variables that exist only within their defined scope (a function or code block). Key characteristics: * Stored in memory, not on the blockchain * Used for intermediate calculations and temporary data * Available only during function execution * Help improve code readability by naming values Here's an example demonstrating local variable scope: ```rust theme={null} #[starknet::contract] mod LocalVariablesContract { #[storage] struct Storage {} pub fn do_something(value: u32) -> u32 { // This variable is local to the current block. // It can't be accessed once it goes out of scope. let increment = 10; { // The scope of a code block allows for local variable declaration // We can access variables defined in higher scopes. let sum = value + increment; sum } // We can't access the variable `sum` here, as it's out of scope. } }} ``` ## Storage Variables Storage variables provide persistent state for your contract on the blockchain. They have these properties: * Persist between contract executions * Can be read for free (no transaction needed) * Require a transaction to write to them * Must be defined in the contract's Storage struct Here's an example showing storage variable usage: ```rust theme={null} #[starknet::interface] trait IStorageVariable { fn set(ref self: TContractState, value: u32); fn get(self: @TContractState) -> u32; } #[starknet::contract] mod StorageVariablesContract { // You need to import these storage functions to read and write to storage variables use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::IStorageVariable; // All storage variables are contained in a struct called Storage // annotated with the `#[storage]` attribute #[storage] struct Storage { // Storage variable holding a number value: u32, } #[abi(embed_v0)] impl StorageVariables of IStorageVariable { // Write to storage variables by sending a transaction // that calls an external function fn set(ref self: ContractState, value: u32) { self.value.write(value); } // Read from storage variables without sending transactions fn get(self: @ContractState) -> u32 { self.value.read() } } } ``` Reading is a free operation, with no transaction needed. Writing requires a transaction and costs gas. ## Global Variables Global variables provide access to blockchain context and system information. In Starknet: * Accessed through core library functions * Available anywhere in the contract * Provide critical blockchain context (e.g., caller address, block info) Example using global variables: ```rust theme={null} #[starknet::contract] pub mod GlobalVariablesContract { // import the required functions from the starknet core library use starknet::get_caller_address; #[storage] struct Storage {} pub fn foo(ref self: ContractState) { // Call the get_caller_address function to get the sender address let _caller = get_caller_address(); // ... } } ``` # Functions Source: https://docs.starknet.io/build/starknet-by-example/basic/visibility-mutability ## Function Visibility In Starknet contracts, functions can have two types of visibility: * **External Functions**: Can be called by anyone, including other contracts and users * **Internal Functions**: Can only be called by other functions within the same contract ## State Mutability Every function in a contract can either modify or just read the contract's state. This behavior is determined by how we pass the `ContractState` parameter: * **State-Modifying Functions**: Use `ref self: ContractState` * Can read and write to storage * Require a transaction to execute * Cost gas to run * **View Functions**: Use `self: @ContractState` * Can only read from storage * Can be called directly through an RPC node * Free to call (no transaction needed) Internal functions follow the same state mutability rules as external functions. The only difference is who can call them. ## Implementation ### External Functions For external functions (both state-modifying and view), you need: 1. **Interface Definition** * Defined with `#[starknet::interface]` attribute * Lists all functions that can be called externally * Functions can be called as transactions or view calls * Part of the contract's public API 2. **Interface Implementation** * Uses `#[abi(embed_v0)]` attribute * Becomes part of the contract's ABI (Application Binary Interface) * ABI defines how to interact with the contract from outside * Must implement all functions defined in the interface ### Internal Functions For internal functions, there are two options: 1. **Implementation Block** * Can use `#[generate_trait]` attribute * Recommended for functions that need `ContractState` access * Sometimes prefixed with `_` to indicate internal use 2. **Direct Contract Body** * Functions defined directly in the contract * Recommended for pure functions * Useful for helper functions and calculations ## Example Here's a complete example demonstrating these concepts: ```rust theme={null} // This trait defines the public interface of our contract // All functions declared here will be accessible externally #[starknet::interface] trait ContractInterface { fn set(ref self: TContractState, value: u32); fn get(self: @TContractState) -> u32; } #[starknet::contract] mod Contract { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::ContractInterface; #[storage] pub struct Storage { pub value: u32, } // External Functions Implementation // The `#[abi(embed_v0)]` attribute makes these functions callable from outside the contract // This is where we implement our public interface defined in ContractInterface #[abi(embed_v0)] pub impl ContractImpl of ContractInterface { // External function that can modify state // - Takes `ref self` to allow state modifications // - Calls internal `increment` function to demonstrate internal function usage fn set(ref self: ContractState, value: u32) { self.value.write(increment(value)); } // External view function (cannot modify state) // - Takes `@self` (snapshot) to prevent state modifications // - Demonstrates calling an internal function (_read_value) fn get(self: @ContractState) -> u32 { self._read_value() } } // Internal Functions Implementation // These functions can only be called from within the contract // The #[generate_trait] attribute creates a trait for these internal functions #[generate_trait] pub impl Internal of InternalTrait { // Internal view function // - Takes `@self` as it only needs to read state // - Can only be called by other functions within the contract fn _read_value(self: @ContractState) -> u32 { self.value.read() } } // Pure Internal Function // - Doesn't access contract state // - Defined directly in the contract body // - Considered good practice to keep pure functions outside impl blocks // It's also possible to use ContractState here, but it's not recommended // as it'll require to pass the state as a parameter pub fn increment(value: u32) -> u32 { value + 1 } } ``` Cairo contracts can implement multiple interfaces and have multiple internal implementation blocks. This is not only possible but recommended because it: * Keeps each implementation block focused on a single responsibility * Makes the code more maintainable and easier to test * Simplifies the implementation of standard interfaces * Allows for better organization of related functionality # Introduction to Starknet By Example Source: https://docs.starknet.io/build/starknet-by-example/index ## What is Starknet By Example? Starknet By Example provides practical examples for building smart contracts on Starknet using the Cairo programming language. The examples in Starknet By Example are for educational purposes and have not been audited. Do not use this code in production. The authors are not liable for any damages resulting from the use of this code. ## Who is Starknet By Example for? Starknet By Example is designed for developers with basic programming and blockchain knowledge who want to learn Starknet smart contract development using Cairo. The content is structured progressively - early chapters cover Cairo and Starknet fundamentals, while later chapters explore advanced topics and complex smart contract patterns. ## How to use Starknet By Example? Each chapter of Starknet By Exampleis self-contained and focuses on a specific Starknet feature or smart contract use case. While you can jump to any topic, we recommend following the chapters in order if you're new to Starknet. You can try out the examples instantly in the [Cairo Playground](https://www.cairo-lang.org/cairovm/). # Anonymous DeFi and open notes Source: https://docs.starknet.io/build/starknet-privacy/anonymous-defi Anonymizing contracts, Invoke, and open-note fills for swaps and lending Standard AMMs and lending vaults expose **amounts** through reserves, share mint/burn, and transfers. Starknet Privacy still lets you route value **through** those protocols while targeting **anonymity** (who is interacting, how notes chain in the pool)—not full **amount** hiding on the leg that must be filled on-chain after the fact. **Open notes** carry that tradeoff by design; see [Notes and nullifiers — Open notes](/build/starknet-privacy/notes-and-nullifiers#open-notes). The paper is authoritative for semantics: [IACR 2026/474](https://eprint.iacr.org/2026/474.pdf) §10. ## End-to-end pattern (swap or analogous DeFi) 1. **Spend** the source-token notes you are using. 2. **Create** an **open note** for the **output** token (the amount is not fixed in ciphertext until the external step completes). 3. **Withdraw** from the pool into a **helper** contract address (so the helper holds the input tokens for the next step). 4. **Invoke** the helper from the pool: the helper talks to the external protocol (AMM, lending vault, …), then returns instructions for the pool to **credit** the open note with the measured output. Steps 3–4 are why you need a small, auditable **adapter** contract: the core pool stays generic; the helper encodes “how to call Vesu / an AMM / …” and how to turn the **actual** token delta into an **open-note deposit**. ## What an anonymizing contract is responsible for In this model the **privacy pool** remains the authority that verifies proofs and updates note state. The **helper** is a separate contract that: * **Receives** tokens the pool withdrew for this leg (the pool is the orchestrator; the proof authorizes the overall flow). * **Performs** one or more calls into external DeFi (approve, deposit, swap, withdraw, …) according to that protocol’s interfaces. * **Measures** the **output** amount in a robust way (often a **balance delta** on the output token before vs after the operation), so the correct quantity is known **on-chain** even when it could not be fixed inside the ZK proof. * **Approves** the **privacy pool** to pull the output tokens it received, so the pool can complete the accounting and **fill** the open note. * **Returns** a structured description of **which open note** gets **which token** and **how much**—in the reference Cairo this is expressed as deposits such as `OpenNoteDeposit { note_id, token, amount }` for the pool to apply. Helpers are meant to be **small and reviewable**: they should not hold user funds long-term; they execute a single orchestrated step when invoked by the pool. ## Example reference shape: Vesu lending helper A **Vesu lending helper** is deployed on mainnet as a reference invoke helper. It illustrates the same **open-note + Invoke** pattern as a swap helper, but the external calls are **`deposit`** / **`withdraw`** on Vesu vToken contracts instead of a swap router. The anonymizing contract source is readable on [Voyager](https://voyager.online/contract/0x028b49bc7a48b92d06d436d90e889729d7161dfc2fef3f16b674029bf7abc336). For Vesu's own vToken interface, see [vToken deposit & withdraw](https://docs.vesu.xyz/developers/core/vtoken#deposit--withdraw). ### What the Vesu helper does To enable users to anonymously use Vesu directly from the privacy pool, the helper: 1. **Receives tokens** from the pool (e.g. USDC you want to lend). 2. **Deposits them into Vesu** and receives vault shares (vTokens) in return — or the reverse for a withdrawal. 3. **Measures how many output tokens** it actually received (a balance check before and after the Vesu call). 4. **Approves the pool** to pull those output tokens and **tells it which open note to credit** with the result. The helper is needed because the output amount is only known after the Vesu call executes on-chain — it cannot be fixed inside the ZK proof ahead of time. That is exactly what open notes are for: the proof creates a placeholder, and the helper fills it with the real amount. ### Interface sketch (Cairo) The following excerpt shows the **shape** of an invoke helper for reference. **Most users will interact through existing deployed helpers** (like the Vesu lending helper above), not write their own. Teams building new DeFi integrations can use this as a starting point—for working helper integrations (Vesu lending, swaps, escrow), see the [DeFi helper examples in STRK20 by Example](https://strk20-by-example.org/helpers/vesu-lending-helper): ```cairo theme={null} pub enum LendingOperation { Deposit, Withdraw } #[starknet::interface] pub trait IVToken { fn deposit(ref self: T, assets: u256, receiver: ContractAddress) -> u256; fn withdraw( ref self: T, assets: u256, receiver: ContractAddress, owner: ContractAddress, ) -> u256; } #[starknet::interface] pub trait IVesuLendingHelper { /// Called by the privacy pool via the protocol invoke path. fn privacy_invoke( ref self: T, operation: LendingOperation, in_token: ContractAddress, out_token: ContractAddress, assets: u256, note_id: felt252, ) -> Span; } ``` The live contract also performs the approve / call / balance-delta / approve-pool sequence described above. ## Swaps vs lending * **Swap flow:** withdraw to helper → invoke AMM (or router) → measure output → return open-note deposit(s). * **Lending flow (Vesu example):** same orchestration, but the external protocol is **mint/burn of vTokens** and movement of **underlying**, still ending in a measured **output amount** credited to an open note. ## Beyond single-purpose helpers Helpers suit shared, stateless operations like swaps. When a dApp needs a stable caller or a position that persists across transactions, use [shadow accounts](/build/starknet-privacy/shadow-accounts): per-user, per-dApp accounts that run arbitrary calls through the same open-note settlement. # System architecture Source: https://docs.starknet.io/build/starknet-privacy/architecture Pool contract, proof verifier path, client-side proving, and compliance The design has three pillars (paper §3): 1. **Privacy pool state contract** 2. **Proof verification path** 3. **Client-side proving + compliance data model** ## Pool and verifier The privacy pool contract handles state transitions (note creation, nullifier publishing, deposits, withdrawals). Proof verification happens at the **sequencer level**, not in a separate smart contract—the Starknet sequencer verifies the ZK proof before executing the transaction. The mainnet pool contract is deployed at [`0x0403…812a`](https://voyager.online/contract/0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a) and its source is readable on Voyager. ## Client-side proving A transaction requires a ZK proof. The proof shows that it was generated in a virtual Starknet execution environment (SNOS) anchored to a recent block. In the current phase, **designated operators generate proofs on behalf of users**. Proofs can also be created client-side using Stwo locally; they typically take ±30 seconds on a strong laptop. ## Compliance model Each user registers a public viewing key and an auditor-encrypted private viewing key, enabling selective traceability under lawful requests. Next: [Notes and nullifiers](/build/starknet-privacy/notes-and-nullifiers). # Compliance framework Source: https://docs.starknet.io/build/starknet-privacy/compliance Auditing entity, viewing key escrow, and forward/backward tracing Privacy without accountability is hard to deploy in regulated contexts. The protocol bakes in **selective disclosure** to a designated **auditing entity**: only the user under a lawful request is traceable; everyone else remains private. ## Registration Users encrypt their **private viewing key** to the auditor’s **public key** and store it on-chain. The auditor can decrypt **only** subjects of a lawful request. ## What the auditor can do With a user’s viewing key, the auditor can: * Scan **incoming channels** (who sent to this user). * Scan **outgoing channels** (where this user sent). * Decrypt **note amounts** along those graphs and follow **nullifiers** / **withdrawals** to trace funds. Other users’ keys are not decrypted. ## On-chain transparency hooks The proof enforces emission of events useful for tracing, for example: * **Registration** — encrypted viewing key (+ event). * **Deposit** — depositor address in clear. * **Withdraw** — encryption of user address to auditor. * **UseNote** — nullifier published. Together with balance conservation and sequential discovery, this supports **forward tracing** (from deposit to current notes or withdrawals) and **backward tracing** (from withdrawal toward deposits). See Theorem 3 in [IACR 2026/474](https://eprint.iacr.org/2026/474.pdf) §9. ## Threshold auditing The paper notes **threshold encryption** for the auditor key so no single party can decrypt arbitrary users’ keys. # Channels, subchannels, and note discovery Source: https://docs.starknet.io/build/starknet-privacy/discovery Location-indexed discovery and directional channels Starknet Privacy uses **location-indexed discovery**: recipients know where to read encrypted notes, instead of trial-decrypting all pool activity. ## Channels are directional A channel is directional: **sender -> recipient**. * Channel key derivation uses sender private viewing key + recipient public viewing key. * This directionality is intentional. * If Alice and Bob both send privately to each other, there are two channels: Alice->Bob and Bob->Alice. ## Subchannels Within each channel, notes are organized into **subchannels** by token type. A subchannel is opened the first time a sender sends a particular token to a particular recipient through that channel. * Subchannel ID: derived from the channel key and a sequential index. * Each subchannel holds an encrypted token identifier and a dense list of notes for that token. * This structure lets the recipient discover notes per-token without scanning unrelated assets. So the hierarchy is: **channel** (sender → recipient) → **subchannel** (one per token) → **notes** (sequential within subchannel). ## Why this matters Directional channels simplify sender authorization and preserve deterministic recipient discovery. ## Sequential indices Outgoing channels, subchannels, and notes are dense sequential lists. This prevents hidden gaps and enables complete scanning until first empty slot. ## Discovery algorithm (recipient) 1. Scan channel entries for recipient. 2. For each channel, scan subchannels. 3. For each (channel, token), scan notes and check spent status via nullifier set. Complexity is proportional to recipient activity, not global pool volume. # Encryption and viewing keys Source: https://docs.starknet.io/build/starknet-privacy/encryption-and-keys Symmetric note encryption, ECDH channel encryption, and compliance key escrow The protocol uses **domain-separated hashes** (H\_0, H\_1, …) for distinct purposes. ## Viewing keys Each user has a **viewing key pair**: * **Private viewing key** (k) — decrypt notes and channel data; needed for nullifier computation when spending. * **Public viewing key** (K) — others encrypt to you (STARK curve; public key encoded from k·G). Viewing keys are **registered once** and treated as **immutable** for simplicity. ## Symmetric encryption (notes, subchannels) * **Note amounts** — hash-and-add style: salt plus a masked value (H\_9(c, token, i, salt) + v) mod 2^128 packed in a felt (see paper §6.1.1). * **Subchannel token** — similar masking with channel key and subchannel index. Without the **channel key**, ciphertexts are pseudorandom to outsiders. ## Asymmetric encryption (channels, compliance) **Channel open** uses **ECDH** on the STARK curve: ephemeral r, shared secret from r·lift(K\_recipient), then mask channel key and sender address with H\_10, H\_11 of the shared secret. **Compliance:** at registration, the user encrypts **private viewing key** (k) to the auditor’s public key (K\_audit) (same ECDH pattern, Definition 8 in the paper). ## Open notes (DeFi) **Open notes** fix the note salt to the protocol’s reserved value so the packed storage cell is **not** masked like a confidential note. In the paper’s packing, that corresponds to **plaintext amount** in the lower part of the felt (upper bits carry the reserved salt), which is why observers can read the filled amount on-chain. That design lets a proof commit to **creating** a note **before** an external step (for example an AMM) knows the exact output quantity; the helper fills the note afterward. More context: [Notes and nullifiers — Open notes](/build/starknet-privacy/notes-and-nullifiers#open-notes) · [Anonymous DeFi](/build/starknet-privacy/anonymous-defi) · paper [§6.1.1](https://eprint.iacr.org/2026/474.pdf). # Glossary Source: https://docs.starknet.io/build/starknet-privacy/glossary Terms used across Starknet Privacy documentation | Term | Meaning | | - | - | | **Privacy pool** | Smart contract holding pooled ERC-20s and enforcing proofs + write-once state. | | **Note** | Immutable record of ownership of an amount of a token; spent via **nullifier**. | | **Nullifier** | One-time value revealed when spending a note; prevents double-spend; unlinkable without viewing key. | | **Channel** | Unidirectional link sender → recipient; first send opens it and shares **channel key** (encrypted to recipient). | | **Channel key** | Shared secret derived from sender/recipient viewing material; indexes note locations. | | **Subchannel** | Per-channel, per-token lane for notes (avoids extra channel opens per asset). | | **Viewing key** | Key pair: private (k) for decrypt/trace/nullifier; public (K) for encrypt-to-user. | | **Client-side action** | High-level user intent (open channel, use note, withdraw, …). | | **Server-side action** | Primitive the contract runs (WriteOnce, append channel, ERC-20 transfer). | | **WriteOnce** | Storage write only if cell is zero—immutability. | | **Open note** | Note whose encoding uses a reserved salt so the filled amount is **plaintext** on-chain; used when an external contract must set the amount after the proof (e.g. AMM output). | | **Invoke** | Final transaction phase: one call from the pool to an external contract (`InvokeExternal` or `ComputeAndInvoke`) that can fill open notes. | | **Compute-and-invoke** | Invoke variant where the pool first passes the user's **identity key** to the target's `privacy_compute`, then calls `privacy_invoke_with_computation` with the result. | | **Identity key** | Per-user, per-target value derived inside the proof from the user's address and private viewing key; never published on-chain. | | **Shadow account** | Per-user, per-dApp account contract, driven only by the shadow account anonymizer, that runs dApp calls without revealing the user. | | **Paymaster** | Relayer that submits txs and is reimbursed from the pool. | | **Auditing entity** | Holder of k\_audit; decrypts escrowed viewing keys under compliance process. | | **Stwo** | STARK prover used for client-side Cairo proofs. | # Notes and nullifiers Source: https://docs.starknet.io/build/starknet-privacy/notes-and-nullifiers UTXO-style notes, note identifiers, and double-spend prevention on Starknet Privacy Starknet Privacy uses a **note** model related to Zerocash/Zcash: each note is an immutable record of ownership of a token amount. ## Note structure A note encodes: * **Owner** — Starknet address authorized to spend (via account signature + viewing key knowledge). * **Token** — ERC-20 contract address. * **Amount** — 128-bit value; on-chain storage often holds only **encrypted** amount data. Notes are **immutable**. To “transfer part” of a note, the owner **spends** the full note and **creates** new notes (change + outputs)—like Bitcoin UTXOs. ## Open notes Most notes hide the **amount** (and use encryption keyed to the channel) so observers learn little from ciphertext alone. **Open notes** are a deliberate exception for flows where the **output amount is not fixed at proof time**—for example, an AMM swap where the execution price is determined on-chain **after** the privacy proof is built. Conceptually: * **Same note object** — still an owner, token, and amount slot in pool storage; still spent with a **nullifier** like any other note. * **Reserved encoding** — the protocol uses a fixed **salt** value (see paper §6.1.1) so the stored payload is **not** ciphertext in the usual sense. After the note is **filled**, the **amount** (and related fields the spec exposes for this mode, such as token and depositor where applicable) appear **in plaintext** on-chain—anyone can read them. * **Lifecycle** — you typically **create** an open note in an “empty” or partial state inside a transaction whose proof already authorizes the shape of the flow, then an **external contract** (for example via **Invoke**) completes the swap and **writes** the final amount into that note. **Privacy tradeoff:** open notes protect **who** is interacting with DeFi and how notes chain through the pool, but they **do not** hide the filled amount on that leg. Use them only where the protocol needs an on-chain fill after proving. For ordinary transfers, use confidential notes. See [Anonymous DeFi and open notes](/build/starknet-privacy/anonymous-defi) for the high-level swap pattern and [Encryption and viewing keys](/build/starknet-privacy/encryption-and-keys) for how encoding differs from standard note encryption. ## Note identifier and storage The **note id** determines where the encrypted note is stored. It is derived from a **channel key** (c), **token**, and a **sequential index** (i) inside that channel’s subchannel for that token. Only parties who know the channel layout can locate and decrypt notes efficiently. ## Nullifiers Spending reveals a **nullifier** computed from the note parameters and the **owner’s private viewing key**. Properties: * **Deterministic** — one valid nullifier per note. * **Unique** — prevents double-spend when the contract records used nullifiers. * **Unlinkable** — observers cannot map a nullifier back to a specific note without the viewing key. The **sender** of a note cannot compute the nullifier (they don’t know the recipient’s private viewing key when the note is for someone else), so they can’t see when the note is spent. See also: [Discovery](/build/starknet-privacy/discovery) (sequential indices), [Encryption](/build/starknet-privacy/encryption-and-keys). # Overview Source: https://docs.starknet.io/build/starknet-privacy/overview Private transfers on Starknet with compliant privacy—notes, ZK proofs, and efficient discovery **Starknet Privacy** is a protocol for private asset transfers on Starknet: sender, receiver, and amounts are hidden from outside observers, while validity is enforced with client-side zero-knowledge proofs (Stwo / Cairo). A compliance layer allows an auditing entity to trace activity when legally required, without exposing unrelated users. ## What these docs are for These docs describe the **protocol**—notes, channels, discovery, compliance, transaction ordering, and security considerations—so developers, partners, and enterprises can understand how Starknet Privacy works. * **Live on mainnet:** the privacy pool contract is deployed at [`0x0403…812a`](https://voyager.online/contract/0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a). The contract source is readable on Voyager. * **SDK now open source:** the TypeScript SDK and proving stack are available in the [starknet-privacy repo](https://github.com/starkware-libs/starknet-privacy). For hands-on integration guides (install, quickstart, end-to-end flows), see [STRK20 by Example](https://strk20-by-example.org/). ## Why Starknet Privacy? If you care about private transfers, you have probably seen mixers (fixed amounts, one-time use) or privacy chains that require a separate ecosystem. Starknet Privacy takes a different path: * **Stay on Starknet** — no new chain; access existing Starknet DeFi and accounts. * **Arbitrary amounts, reusable notes** — no fixed denominations. * **Discovery that scales with you** — recipients discover notes in time proportional to their own activity. * **Compliance by design** — selective disclosure for lawful requests, without exposing unrelated users. * **One account for everything** — existing Starknet account signs spends. ## Comparison For the latest maintained cross-protocol comparison grid, use [privacygrid.dev](https://privacygrid.dev/). > Note: this page keeps the protocol summary concise and defers detailed protocol-to-protocol scoring to the dedicated grid. ## Resources *Scalable Compliant Privacy on Starknet* (IACR ePrint 2026/474) Architecture, flows, and cryptography in these docs Hands-on integration hub: runnable SDK examples, DeFi helpers, and wallet integration guides TypeScript SDK and proving stack in the starknet-privacy repo ## What you get | Property | Summary | | - | - | | **Private transfers** | Sender, receiver, and amount hidden from observers; transacting parties and authorized auditors can see details. | | **Compliance** | Designated auditor can selectively trace history under legitimate regulatory request. | | **Efficient discovery** | Recipients find notes in time scaling with *their* activity, not total pool activity. | | **Multi-asset** | One pool supports multiple token types. | | **DeFi** | Anonymous interaction with existing Starknet DeFi via open notes, atomic flows, and per-dApp [shadow accounts](/build/starknet-privacy/shadow-accounts). | | **Account abstraction** | Spend authority uses your Starknet account (multisig, smart wallet, etc.). | ## Next steps Pool, verifier, and high-level design Client actions and ordering # Paymasters and transaction submission Source: https://docs.starknet.io/build/starknet-privacy/paymasters Hiding the user as on-chain sender and reimbursing fees from the pool Private transfers aim to hide **who** is transacting. On Starknet, the **transaction sender** is visible—so users often submit through a **paymaster** service. ## Flow 1. User builds proof + server-side actions and sends them to the **paymaster**. 2. Paymaster **broadcasts** the transaction from **its** account and pays **fee** in ETH/strk. 3. User includes a **Withdraw** (or similar) in the same transaction to send **fee reimbursement** from the pool to the paymaster’s **public** address. On-chain, observers see the paymaster as sender; they do not see the user’s Starknet address as the transaction origin. ## Trust model * Paymaster learns **IP** and whatever the client sends; it should not learn more than necessary if the payload is minimal. * Choose paymasters with clear **privacy** and **retention** policies. Related: [Security](/build/starknet-privacy/security), [Transaction flow](/build/starknet-privacy/transaction-flow). # Proof generation Source: https://docs.starknet.io/build/starknet-privacy/proving Virtual execution, Stwo proving, and how this differs from live Starknet Proofs are generated by executing Cairo in a **virtual Starknet environment** anchored to a recent block, then proving that execution with Stwo. ## How integrators run the prover (today) **Operational docs are intentionally not duplicated here** while the reference implementation, SDK, and proving stack are still **private**. When that code is open sourced (planned for a later phase of the rollout), install and end-to-end “generate proof → submit actions” guides will live next to the repository—not as placeholders on this site. Until then, this page is **conceptual**: it explains what is being proved and why the model looks the way it does. ## Simulated vs real execution The virtual environment is chain-anchored but not a full sequencer runtime. In practice: * storage and contract calls are evaluated against an anchored state snapshot, * only the syscalls and builtins implemented by the **virtual Starknet OS dedicated to the privacy pool's implementation** are available (a subset of what the Starknet sequencer supports), * unsupported features fail at proving time (not at post-submit execution). **Supported builtins:** `range_check`, `bitwise`, `pedersen`, `poseidon`, `ec_op`, `segment_arena`, `keccak`. **Unsupported builtins:** `range_check96`, `add_mod`, `mul_mod`, `ecdsa`. ## Why this design * native storage model, * account abstraction signature checks (`is_valid_signature`), * shared Cairo logic between proving and verification paths. # Privacy, security, and limitations Source: https://docs.starknet.io/build/starknet-privacy/security Guarantees, fund safety, and known privacy caveats for Starknet Privacy ## Privacy (informal) What observers see (or don't) in practice: | Topic | What leaks / what’s protected | | - | - | | **Sender** | External observers see **paymaster** as sender; paymaster may see IP. | | **Receiver** | Hidden **except** when a **new channel** is opened (recipient address on-chain once per sender–recipient pair). | | **Amounts** | Encrypted in normal notes; **deposits**, **withdrawals**, and **open notes** expose amounts (and tokens) as designed. | | **Auditor** | Can decrypt registered users’ viewing keys when authorized; cannot sign spends. | ## Fund security * **Double-spend** — nullifiers are unique and recorded; proof ties nullifier to existing note. * **Unauthorized spend** — requires valid **account signature** in proof; auditor has viewing key only, not signing authority. ## Limitations 1. **New channel + deposit/withdraw in same tx or tight timing** — can link recipient to public address activity; wallets should separate these flows when possible. 2. **Deposit/withdraw heuristics** — distinctive amounts or immediate in/out weaken privacy (common to all pools). 3. **Reverted txs** — if a note slot was touched then reverted, **reusing the same note id** can link txs; mitigations include burning dummy writes to exposed ids (paper §11.3.3). For the full analysis see [IACR 2026/474](https://eprint.iacr.org/2026/474.pdf) §11. # Shadow accounts Source: https://docs.starknet.io/build/starknet-privacy/shadow-accounts Per-user, per-dApp accounts that let the privacy pool call any dApp anonymously, built on compute-and-invoke **Shadow accounts** let any Starknet account control **unlinkable** accounts. A shadow account can make the same contract calls as a regular Starknet transaction (approve, swap, lend, stake, and so on), and nothing on-chain links it to the main account that controls it. **Example: private lending.** Alice wants to lend USDC on a lending protocol such as Vesu without revealing that the position is hers. She moves USDC from her balance in the privacy pool to her shadow account for that protocol, and the shadow account deposits it. The protocol and outside observers see the shadow account supply liquidity and hold the vault shares, but they cannot link it to Alice's main Starknet account. Later, the shadow account withdraws and the proceeds go back into Alice's notes in the pool. An [anonymizing helper](/build/starknet-privacy/anonymous-defi) is written for one protocol and holds nothing between calls. A shadow account instead runs **arbitrary calls** and can **hold positions** across transactions (vault shares, LP tokens, open orders, …). Each user gets a separate shadow account per dApp, and can open more as needed. Shadow accounts are built on the pool's **compute-and-invoke** action. ## Invoke and compute-and-invoke A privacy transaction can end with one call to an external contract, made by the pool after all note operations. There are two variants, and a transaction can include **at most one** of them: | Client action | What the pool calls | Typical target | | - | - | - | | `InvokeExternal` | `privacy_invoke(calldata)` | Stateless helpers, such as a swap or lending anonymizer | | `ComputeAndInvoke` | `privacy_compute(identity_key, …)`, then `privacy_invoke_with_computation(compute_result, …)` | Contracts that need a stable, user-specific identity, such as the shadow account anonymizer | In both cases the target returns the **open-note deposits** to fill, and the pool pulls the tokens from the target into the [open notes](/build/starknet-privacy/notes-and-nullifiers#open-notes) created earlier in the same transaction. ### How compute-and-invoke works 1. **Derive an identity key.** Inside the proof, the pool computes `identity_key = H(tag, user address, private viewing key, target address)`. Only the user can reproduce it, and it is different for every target contract. 2. **Compute.** The pool calls the target's `privacy_compute` with `[identity_key, ...compute_additional_data]`. The target turns the key into a value it can act on—for shadow accounts, a commitment. The result must be non-empty. 3. **Invoke.** The pool calls the target's `privacy_invoke_with_computation` with `[...compute_result, ...invoke_additional_data]`. The compute step runs during [proof generation](/build/starknet-privacy/proving), so the identity key never appears on-chain; only the compute result does. Because that result is public once used, `privacy_invoke_with_computation` **must accept calls only from the privacy pool**—otherwise anyone could replay it and act as that user. ## How shadow accounts work The **shadow account anonymizer** contract implements compute-and-invoke. On mainnet it is deployed at [`0x04f3…88a7`](https://voyager.online/contract/0x04f33230dc57855c6e7eabe66dfa0fde82c5458fd0e54827cdb7cb4c474888a7). * **Commitment** — `privacy_compute(identity_key, dapp_name, nonce)` returns `H(H(identity_key, dapp_name), nonce)`. `dapp_name` scopes accounts to one dApp; `nonce` lets a user hold several independent accounts for the same dApp. * **Deterministic address** — the commitment is the deployment salt, so each commitment maps to one address that is known before the account exists. * **Deployed on first use** — the first `privacy_invoke_with_computation` for a commitment deploys its shadow account; later calls reuse it. * **Runs the calls** — the anonymizer executes the transaction's dApp calls through the shadow account, so the dApp sees the shadow account as the caller. * **Settles into open notes** — for each open note, the anonymizer collects tokens from the shadow account according to a **collect policy**, then approves the pool to pull them into the note. | Collect policy | Amount moved into the open note | | - | - | | `All` | The shadow account's entire balance of the token | | `Diff` | Only the balance gained during this interaction | | `Exact` | A fixed amount | Each open note must receive a non-zero amount, and each token can appear in at most one open note per invoke. ### Who controls a shadow account Control passes through a chain of single-caller checks: * A shadow account has no keys and cannot sign; it executes calls only for its **owner**, the anonymizer. * The anonymizer accepts `privacy_invoke_with_computation` only from the **privacy pool**. * The pool reaches a shadow account only through a proven transaction, which requires the user's **account signature** and the **private viewing key** the identity key is derived from. Only the user who owns a shadow account can move what it holds. The [auditor](/build/starknet-privacy/compliance) can decrypt viewing keys but cannot sign, so it cannot drive a shadow account. ## End-to-end flow A typical shadow-account transaction, in the pool's [fixed action order](/build/starknet-privacy/transaction-flow): 1. **Spend** the notes that fund the interaction. 2. **Create** an open note for each token you expect back. 3. **Withdraw** the input tokens to the shadow account's address. Skip this step when the account already holds what it needs—for example, when closing a position. 4. **Compute-and-invoke** the anonymizer with the `dapp_name`, the `nonce`, the dApp calls, and the open notes to settle. 5. The anonymizer deploys the shadow account if needed, runs the calls, and returns the collected amounts; the pool fills the open notes. Positions the dApp issues to the shadow account (shares, receipts, LP tokens) stay there until a later transaction moves them. To look up a user's accounts, the anonymizer's `get_shadow_accounts` view takes the partial commitment `H(identity_key, dapp_name)` and a nonce range (up to 1,024 per call), and returns each account's address and whether it is deployed. ## Privacy properties | Topic | What's visible | | - | - | | **User** | Hidden. Observers see the shadow account address and its commitment; neither reveals the user's address or notes. | | **Linkability** | Interactions through the **same** shadow account (same dApp and nonce) are linkable to each other. Use a new nonce for an unlinkable account. | | **Amounts** | Public on this leg: withdrawals into the shadow account, the dApp calls, and filled open notes. | | **Calls** | dApp call targets and calldata appear in the transaction trace. | | **Screening** | Funds settled from a shadow account are screened against the shadow account's address, not the anonymizer's. | ## Build with shadow accounts Derive commitments and addresses, then run `shadowAccounts(dappName).invoke(nonce, { calls, collectPolicy })` Drive shadow accounts from a dApp through a privacy-enabled wallet and starknet.js For the lower-level action model, see [Actions, phases, and proofs](https://strk20-by-example.org/actions-and-proofs). The anonymizer source is in the [starknet-privacy repo](https://github.com/starkware-libs/starknet-privacy/tree/main/packages/shadow_account_anonymizer). See also: [Anonymous DeFi and open notes](/build/starknet-privacy/anonymous-defi), [Transaction flow](/build/starknet-privacy/transaction-flow), [Security](/build/starknet-privacy/security). # STRK20 by Example Source: https://docs.starknet.io/build/starknet-privacy/strk20-by-example Hands-on hub of runnable examples for integrating STRK20 private transfers These docs explain **how Starknet Privacy works**. When you are ready to build, [**STRK20 by Example**](https://strk20-by-example.org/) is the hands-on companion: a hub of runnable examples for teams integrating STRK20 into their apps, built on the open-source [starknet-privacy SDK](https://github.com/starkware-libs/starknet-privacy). ## What you'll find Setup, registration, deposits, transfers, withdrawals, note discovery, proving configuration, and multi-operation batches Privacy Invoke, Vesu lending, swaps, and escrow — working open-note helper integrations Starknet Wallet API and starknet.js patterns for privacy-enabled dApps An end-to-end anonymous airdrop application combining the pieces Persistent, per-dApp accounts for positions that live across transactions ## How it relates to these docs | You want to... | Go to | | - | - | | Understand the protocol (notes, channels, proofs, compliance) | These docs — start with the [overview](/build/starknet-privacy/overview) | | Integrate STRK20 into your app with working code | [STRK20 by Example](https://strk20-by-example.org/) | | Read the SDK and proving stack source | [starknet-privacy repo](https://github.com/starkware-libs/starknet-privacy) | # Transaction flow Source: https://docs.starknet.io/build/starknet-privacy/transaction-flow Action compilation, ordering constraints, and per-token balance invariants ## Pipeline 1. Build client-side actions. 2. Generate proof over compiled server-side actions. 3. Submit (directly or via paymaster). 4. Contract verifies and executes. ## Why fixed ordering exists Ordering (`SetViewingKey -> OpenChannel -> OpenSubchannel -> Deposit -> UseNote -> CreateNote -> Withdraw -> Invoke`) reduces state-machine ambiguity and closes classes of ordering bugs where the same semantic operation could be encoded in multiple inconsistent sequences. The final `Invoke` phase holds at most one external call per transaction: either `InvokeExternal` or `ComputeAndInvoke` (see [Shadow accounts](/build/starknet-privacy/shadow-accounts#invoke-and-compute-and-invoke)). ## Per-token temporary balance Within one transaction, each token keeps a temporary balance: * `Deposit` and `UseNote` increase it. * `CreateNote` and `Withdraw` decrease it. Invariant: * it must never go negative during execution, * and **must end at 0** at the end of transaction. "Must end at 0" means all temporary inflows are fully matched by outflows in that transaction; no unaccounted residual value remains. # Troubleshooting and FAQ Source: https://docs.starknet.io/build/starknet-privacy/troubleshooting Protocol pitfalls and where to learn more These notes are **protocol-oriented**. Build commands, version pins, and toolchain issues will be documented when the reference implementation is open sourced. ## Proof verification fails (conceptual) * **Stale anchor** — Proofs are tied to a **recent block**; regenerating is required if chain state moved materially. * **WriteOnce conflict** — Another transaction may have consumed the same nullifier or note slot; the flow may need fresh indices. ## Wallet / signature * Accounts must expose **SNIP-6** `is_valid_signature` behavior expected by the virtual execution model. See [Proof generation](/build/starknet-privacy/proving). ## Privacy UX * Avoid **opening a new channel** in the same transaction (or immediately before/after) as **public deposit or withdraw** if you want to minimize linking (see [Security](/build/starknet-privacy/security)). ## Where to learn more * **Protocol** — [IACR 2026/474](https://eprint.iacr.org/2026/474.pdf) * **Implementation** — operational troubleshooting will move to the open source repository docs when that phase ships. # API Reference Source: https://docs.starknet.io/build/starkzap/api-reference Complete API documentation for Starkzap ## StarkZap The main SDK class for initializing and managing wallet connections. ### Constructor ```typescript theme={null} new StarkZap(config: SDKConfig): StarkZap ``` ### Methods | Method | Returns | Description | | - | - | - | | `connectWallet(options)` | `Promise` | Connect with signer + account preset | | `connectCartridge(options?)` | `Promise` | Connect via Cartridge Controller popup (web only) | | `onboard(options)` | `Promise` | Onboard with strategy selection | | `stakingTokens()` | `Promise` | Get stakeable tokens | | `getStakerPools(staker)` | `Promise` | Get validator's delegation pools | | `getBridgingTokens(chain?)` | `Promise` | Get bridgeable tokens by source chain | | `getProvider()` | `RpcProvider` | Get the underlying RPC provider | | `callContract(call)` | `Promise` | Call a contract read function | ### Configuration ```typescript theme={null} interface SDKConfig { network?: NetworkName | NetworkPreset; rpcUrl?: string; chainId?: ChainId; allowInsecureHttp?: boolean; // accept plain http:// off loopback; dev only paymaster?: PaymasterOptions; // { nodeUrl?: string; default?: boolean; headers?: object } explorer?: ExplorerConfig; staking?: StakingConfig; bridging?: BridgingConfig; } ``` ### ConnectWalletOptions ```typescript theme={null} interface ConnectWalletOptions { account: AccountConfig; accountAddress?: Address; feeMode?: FeeMode; timeBounds?: PaymasterTimeBounds; swapProviders?: SwapProvider[]; defaultSwapProviderId?: string; dcaProviders?: DcaProvider[]; defaultDcaProviderId?: string; } interface AccountConfig { signer: SignerInterface; accountClass?: AccountClassConfig; } ``` ## Wallet / WalletInterface The wallet interface provides access to account operations, transactions, and token management. ### Properties | Property | Type | Description | | - | - | - | | `address` | `Address` | Wallet address | ### Methods | Method | Returns | Description | | - | - | - | | `isDeployed()` | `Promise` | Check deployment status | | `ensureReady(options?)` | `Promise` | Deploy if needed | | `deploy(options?)` | `Promise` | Deploy account contract | | `execute(calls, options?)` | `Promise` | Execute contract calls | | `signMessage(typedData)` | `Promise` | Sign typed data | | `preflight(options)` | `Promise` | Simulate transaction | | `tx()` | `TxBuilder` | Create a transaction builder | | `balanceOf(token)` | `Promise` | Get token balance | | `transfer(token, transfers, options?)` | `Promise` | Transfer ERC20 tokens | | `stake(pool, amount, options?)` | `Promise` | Smart stake (enter or add) | | `enterPool(pool, amount, options?)` | `Promise` | Enter staking pool | | `addToPool(pool, amount, options?)` | `Promise` | Add to existing stake | | `claimPoolRewards(pool, options?)` | `Promise` | Claim staking rewards | | `exitPoolIntent(pool, amount, options?)` | `Promise` | Start exit process | | `exitPool(pool, options?)` | `Promise` | Complete exit | | `isPoolMember(pool)` | `Promise` | Check pool membership | | `getPoolPosition(pool)` | `Promise` | Get staking position | | `getPoolCommission(pool)` | `Promise` | Get pool commission rate | | `estimateFee(calls)` | `Promise` | Estimate execution fee | | `getAccount()` | `Account` | Get starknet.js Account | | `getProvider()` | `RpcProvider` | Get RPC provider | | `getChainId()` | `ChainId` | Get chain ID | | `getFeeMode()` | `FeeMode` | Get default fee mode | | `erc20(token)` | `Erc20` | Get ERC20 helper for token | | `getClassHash()` | `string` | Get account class hash | | `getQuote(request)` | `Promise` | Get swap quote | | `prepareSwap(request)` | `Promise` | Prepare swap calls | | `swap(request, options?)` | `Promise` | Execute a token swap | | `registerSwapProvider(provider, makeDefault?)` | `void` | Register a swap provider | | `setDefaultSwapProvider(providerId)` | `void` | Set the default swap provider | | `getSwapProvider(providerId)` | `SwapProvider` | Get a registered swap provider | | `getDefaultSwapProvider()` | `SwapProvider` | Get the default swap provider | | `listSwapProviders()` | `string[]` | List registered swap provider IDs | | `dca()` | `DcaClientInterface` | Get DCA (recurring buy) client | | `lending()` | `LendingClient` | Get lending client | | `staking(poolAddress)` | `Promise` | Get staking helper for a pool | | `stakingInStaker(stakerAddress, token)` | `Promise` | Get staking helper from a staker | | `lstStaking(asset, options?)` | `EndurStaking` | Get Endur liquid staking helper for an asset | | `troves(options?)` | `Troves` | Get Troves strategy client | | `callContract(call)` | `Promise` | Read-only contract call | | `disconnect()` | `Promise` | Disconnect wallet | | `deposit(recipient, amount, token, externalWallet, options?)` | `Promise<{ hash: string }>` | Bridge deposit from external chain | | `getDepositBalance(token, externalWallet)` | `Promise` | Get source-chain available balance | | `getAllowance(token, externalWallet)` | `Promise` | Get source-chain allowance (if applicable) | | `getDepositFeeEstimate(token, externalWallet, options?)` | `Promise` | Estimate bridge route fees | | `initiateWithdraw(recipient, amount, token, externalWallet, options?)` | `Promise` | Initiate withdrawal from Starknet to external chain | | `completeWithdraw(recipient, amount, token, externalWallet, options?)` | `Promise<{ hash: string }>` | Complete withdrawal on external chain (Canonical & CCTP only) | | `getWithdrawBalance(token, externalWallet)` | `Promise` | Get L2 balance available to withdraw | | `getInitiateWithdrawFeeEstimate(token, externalWallet, options?)` | `Promise` | Estimate L2 fee for initiating withdrawal | | `getCompleteWithdrawFeeEstimate(amount, recipient, token, externalWallet, options?)` | `Promise` | Estimate L1 gas for completing withdrawal | | `monitorDeposit(token, externalTxHash, starknetTxHash?)` | `Promise` | Get full deposit status snapshot | | `monitorWithdrawal(token, snTxHash, externalTxHash?)` | `Promise` | Get full withdrawal status snapshot (includes CCTP attestation data) | | `getDepositState(token, param)` | `Promise` | Simplified deposit state for UI | | `getWithdrawalState(token, param)` | `Promise` | Simplified withdrawal state for UI | ## Tx Transaction object returned from `execute()`, `deploy()`, and `transfer()`. ### Properties | Property | Type | Description | | - | - | - | | `hash` | `string` | Transaction hash | | `explorerUrl` | `string` | Block explorer URL | ### Methods | Method | Returns | Description | | - | - | - | | `wait(options?)` | `Promise` | Wait for confirmation | | `watch(callback, options?)` | `TxUnsubscribe` | Watch status changes (options: pollIntervalMs, timeoutMs, onError) | | `receipt()` | `Promise` | Get transaction receipt | ## TxBuilder Fluent API for building and executing batched transactions. ### Properties | Property | Type | Description | | - | - | - | | `length` | `number` | Number of queued call groups | | `isEmpty` | `boolean` | Whether the builder has no calls | | `isSent` | `boolean` | Whether `send()` has been called | ### Methods | Method | Returns | Description | | - | - | - | | `.add(...calls)` | `TxBuilder` | Add raw `Call` objects | | `.approve(token, spender, amount)` | `TxBuilder` | ERC20 approval | | `.transfer(token, transfers)` | `TxBuilder` | ERC20 transfer(s) | | `.stake(pool, amount)` | `TxBuilder` | Smart stake (enter or add based on membership) | | `.enterPool(pool, amount)` | `TxBuilder` | Enter pool as new member | | `.addToPool(pool, amount)` | `TxBuilder` | Add to existing pool position | | `.claimPoolRewards(pool)` | `TxBuilder` | Claim staking rewards | | `.exitPoolIntent(pool, amount)` | `TxBuilder` | Start exit process | | `.exitPool(pool)` | `TxBuilder` | Complete exit after window | | `.swap(request)` | `TxBuilder` | Provider-driven token swap | | `.lendDeposit(request)` | `TxBuilder` | Lending deposit (supply) | | `.lendWithdraw(request)` | `TxBuilder` | Lending withdraw | | `.lendWithdrawMax(request)` | `TxBuilder` | Lending max withdraw | | `.lendBorrow(request)` | `TxBuilder` | Lending borrow | | `.lendRepay(request)` | `TxBuilder` | Lending repay | | `.trovesDeposit(params)` | `TxBuilder` | Troves strategy deposit | | `.trovesWithdraw(params)` | `TxBuilder` | Troves strategy withdraw | | `.dcaCreate(request)` | `TxBuilder` | Create a DCA (recurring buy) order | | `.dcaCancel(request)` | `TxBuilder` | Cancel a DCA order | | `.confidentialFund(confidential, details)` | `TxBuilder` | Fund confidential account | | `.confidentialTransfer(confidential, details)` | `TxBuilder` | Confidential transfer | | `.confidentialWithdraw(confidential, details)` | `TxBuilder` | Withdraw from confidential to public address | | `.calls()` | `Promise` | Resolve all calls without sending | | `.estimateFee()` | `Promise` | Estimate gas cost | | `.preflight()` | `Promise` | Simulate the transaction | | `.send(options?)` | `Promise` | Execute all calls atomically | ## Amount Type-safe amount handling for token values. ### Static Methods | Method | Returns | Description | | - | - | - | | `Amount.parse(value, token)` | `Amount` | From human-readable value | | `Amount.fromRaw(value, token)` | `Amount` | From raw blockchain value | ### Instance Methods | Method | Returns | Description | | - | - | - | | `toUnit()` | `string` | Human-readable string | | `toBase()` | `bigint` | Raw value for contracts | | `toFormatted(compressed?)` | `string` | Locale-formatted with symbol | | `getDecimals()` | `number` | Token decimal places | | `getSymbol()` | `string \| undefined` | Token symbol | | `add(other)` | `Amount` | Addition | | `subtract(other)` | `Amount` | Subtraction | | `multiply(scalar)` | `Amount` | Multiplication | | `divide(scalar)` | `Amount` | Division | | `eq(other)` | `boolean` | Equal | | `gt(other)` | `boolean` | Greater than | | `gte(other)` | `boolean` | Greater than or equal | | `lt(other)` | `boolean` | Less than | | `lte(other)` | `boolean` | Less than or equal | | `isZero()` | `boolean` | Is zero | | `isPositive()` | `boolean` | Is positive | ## Signers ### StarkSigner Local Stark curve signer for private key management. ```typescript theme={null} new StarkSigner(privateKey: string): StarkSigner ``` ### PrivySigner Privy server-side key management signer. ```typescript theme={null} new PrivySigner(config: { walletId: string; publicKey: string; serverUrl?: string; rawSign?: (walletId: string, hash: string) => Promise; }): PrivySigner ``` ### SignerInterface Custom signer interface for implementing your own key management. ```typescript theme={null} interface SignerInterface { getPubKey(): Promise; signRaw(hash: string): Promise; } ``` ## Types ### Address Branded string type for Starknet addresses. ```typescript theme={null} type Address = string & { readonly __type: "StarknetAddress" }; // Create from BigNumberish (string, number, or bigint) fromAddress(value: BigNumberish): Address ``` ### ChainId Chain ID utilities and constants. ```typescript theme={null} class ChainId { static MAINNET: ChainId; static SEPOLIA: ChainId; static from(literal: ChainIdLiteral): ChainId; static fromFelt252(felt: string): ChainId; isMainnet(): boolean; isSepolia(): boolean; toFelt252(): string; toLiteral(): ChainIdLiteral; } ``` ### FeeMode Transaction fee payment mode. ```typescript theme={null} type FeeMode = | "user_pays" | { type: "paymaster"; gasToken?: Address } | "sponsored"; // deprecated alias for { type: "paymaster" } ``` ### DeployMode Account deployment policy. ```typescript theme={null} type DeployMode = "never" | "if_needed" | "always"; ``` ### OnboardStrategy Onboarding strategy selection. ```typescript theme={null} const OnboardStrategy = { Signer: "signer", Privy: "privy", Cartridge: "cartridge", } as const; type OnboardStrategy = (typeof OnboardStrategy)[keyof typeof OnboardStrategy]; ``` ## Interfaces ### Token ```typescript theme={null} interface Token { name: string; address: Address; decimals: number; symbol: string; metadata?: { logoUrl?: URL }; } ``` ### BridgingConfig ```typescript theme={null} interface BridgingConfig { layerZeroApiKey?: string; // required for OFT/OFT-migrated routes layerswapApiKey?: string; // required for Layerswap routes + token discovery layerswapBaseUrl?: string; // defaults to https://api.layerswap.io layerswapAllowedContracts?: readonly Address[]; // extra contracts Layerswap may call; unset = transfer only ethereumRpcUrl?: string; solanaRpcUrl?: string; } ``` ### ExternalChain ```typescript theme={null} enum ExternalChain { ETHEREUM = "ethereum", SOLANA = "solana", } ``` ### BridgeToken ```typescript theme={null} abstract class BridgeToken { id: string; name: string; symbol: string; decimals: number; chain: ExternalChain; protocol: Protocol; address: ExternalAddress; // source-chain token bridgeAddress: ExternalAddress; // source-chain bridge starknetAddress: Address; // destination token on Starknet starknetBridge: Address; // destination bridge contract } // Concrete subclasses: EthereumBridgeToken, SolanaBridgeToken ``` ### WithdrawalState Simplified withdrawal state for use in UI polling. ```typescript theme={null} type WithdrawalState = "PENDING" | "READY_TO_CLAIM" | "COMPLETED" | "ERROR"; ``` | Value | Meaning | | - | - | | `PENDING` | Bridging in progress — no user action needed | | `READY_TO_CLAIM` | Ready to finalize on L1 — call `completeWithdraw` (CCTP/Canonical) | | `COMPLETED` | Bridge flow fully complete | | `ERROR` | Unrecoverable error | ### DepositState Simplified deposit state for use in UI polling. ```typescript theme={null} type DepositState = "PENDING" | "COMPLETED" | "ERROR"; ``` ### InitiateBridgeWithdrawOptions Discriminated union by `protocol`: ```typescript theme={null} type InitiateBridgeWithdrawOptions = | { protocol: "canonical"; autoWithdraw?: boolean; // let a relayer handle L1 completion preferredFeeToken?: Token; // fee token for the auto-withdraw relayer } | { protocol: "cctp"; fastTransfer?: boolean; // use CCTP fast transfer path } | { protocol: "oft" | "oft-migrated"; } | { protocol: "hyperlane"; }; ``` ### CompleteBridgeWithdrawOptions Options required for the L1 completion step (CCTP only). The attestation data is returned by `monitorWithdrawal`. ```typescript theme={null} type CompleteBridgeWithdrawOptions = { protocol: "cctp"; attestation: string; // Circle-issued attestation message: string; // encoded CCTP message nonce?: string; expirationBlock?: number; // attestation expiry — re-attestation is automatic }; ``` ### BridgeInitiateWithdrawFeeEstimation ```typescript theme={null} interface BridgeInitiateWithdrawFeeEstimation { l2Fee: Amount; l2FeeError?: FeeErrorCause; // Canonical with autoWithdraw only: autoWithdrawFee?: { amount: Amount; token: Token }; autoWithdrawFeeError?: FeeErrorCause; // CCTP only: fastTransferBpFee?: number; // basis points, e.g. 14 = 0.14% } ``` ### BridgeCompleteWithdrawFeeEstimation ```typescript theme={null} interface BridgeCompleteWithdrawFeeEstimation { l1Fee: Amount; l1FeeError?: FeeErrorCause; } ``` ### Connected External Wallets ```typescript theme={null} ConnectedEthereumWallet.from( { chain: ExternalChain.ETHEREUM, provider: eip1193Provider, address: "0x...", chainId: "0x1", }, starknetChainId ); ConnectedSolanaWallet.from( { chain: ExternalChain.SOLANA, provider: solanaProvider, address: "So1...", chainId: "", }, starknetChainId ); ``` ### Pool ```typescript theme={null} interface Pool { poolContract: Address; token: Token; amount: Amount; } ``` ### PoolMember ```typescript theme={null} interface PoolMember { staked: Amount; rewards: Amount; total: Amount; unpooling: Amount; unpoolTime: Date | null; commissionPercent: number; rewardAddress: Address; } ``` ### Validator ```typescript theme={null} interface Validator { name: string; stakerAddress: Address; logoUrl: URL | null; } ``` ### PreflightResult ```typescript theme={null} interface PreflightResultOk { ok: true } interface PreflightResultError { ok: false; reason: string } type PreflightResult = PreflightResultOk | PreflightResultError; ``` ## Presets ### Token Presets ```typescript theme={null} import { mainnetTokens, sepoliaTokens } from "starkzap"; // Access tokens const STRK = mainnetTokens.STRK; const USDC = mainnetTokens.USDC; ``` ### Validator Presets ```typescript theme={null} import { mainnetValidators, sepoliaValidators } from "starkzap"; // Access validators for (const validator of Object.values(mainnetValidators)) { console.log(validator.name); } ``` ### Account Presets ```typescript theme={null} import { OpenZeppelinPreset, ArgentPreset, ArgentXV050Preset, BraavosPreset, DevnetPreset, } from "starkzap"; ``` ## Utility Functions ### fromAddress Parse and validate a Starknet address. ```typescript theme={null} fromAddress(value: BigNumberish): Address ``` ### getChainId Get chain ID from a provider. ```typescript theme={null} getChainId(provider: RpcProvider): Promise ``` ### getPresets Get token or validator presets for a chain ID. ```typescript theme={null} getPresets(chainId: ChainId): Record ``` ### getTokensFromAddresses Resolve token metadata from contract addresses. ```typescript theme={null} getTokensFromAddresses( addresses: Address[], provider: RpcProvider ): Promise ``` # awesome-starkzap Source: https://docs.starknet.io/build/starkzap/awesome-starkzap Curated list of projects using Starkzap awesome-starkzap banner Open the curated list of projects built with Starkzap. # Bridging Source: https://docs.starknet.io/build/starkzap/bridging Bridge assets between Ethereum or Solana and Starknet using Starkzap bridge tokens, external wallet adapters, deposit and withdrawal APIs ## Overview Starkzap supports **bidirectional bridging** between Starknet and supported external chains: * **Ethereum** (Canonical, CCTP, OFT, OFT-migrated, Layerswap routes) * **Solana** (Hyperlane, Layerswap routes) **Deposit flow (external chain → Starknet):** 1. Configure the SDK (including optional bridging config) 2. Fetch bridgeable tokens with `sdk.getBridgingTokens(...)` 3. Connect an external wallet (`ConnectedEthereumWallet` or `ConnectedSolanaWallet`) 4. Inspect balance, allowance, and estimated fees 5. Call `wallet.deposit(...)` to submit the source-chain transaction **Withdraw flow (Starknet → external chain):** 1. Inspect L2 balance and estimated fees with `wallet.getWithdrawBalance(...)` and `wallet.getInitiateWithdrawFeeEstimate(...)` 2. Call `wallet.initiateWithdraw(...)` to burn/lock tokens on Starknet 3. Monitor status with `wallet.getWithdrawalState(...)` or `wallet.monitorWithdrawal(...)` 4. For Canonical and CCTP: call `wallet.completeWithdraw(...)` when state is `READY_TO_CLAIM` ## Install Optional Dependencies Install only what you use. For Ethereum routes: ```bash theme={null} npm install ethers ``` For Solana **Layerswap** routes (the Layerswap client is inlined — no Hyperlane packages needed): ```bash theme={null} npm install @solana/web3.js ``` For Solana **Hyperlane** routes: ```bash theme={null} npm install @solana/web3.js @hyperlane-xyz/sdk @hyperlane-xyz/registry @hyperlane-xyz/utils ``` ## SDK Configuration Use `bridging` config when you need custom external RPCs or OFT support. The SDK uses external RPCs to read source-chain state (balances/allowances), estimate bridge fees, and submit source-chain transactions reliably. Without explicit RPC URLs, these operations can be rate-limited or unavailable depending on your environment: ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", bridging: { ethereumRpcUrl: "https://eth-mainnet.g.alchemy.com/v2/", solanaRpcUrl: "https://solana-mainnet.g.alchemy.com/v2/", layerZeroApiKey: "", // required for OFT/OFT-migrated routes layerswapApiKey: "", // required for Layerswap routes + token discovery layerswapBaseUrl: "https://api.layerswap.io", // optional; this is the default layerswapAllowedContracts: [], // Starknet contracts Layerswap may call besides the token; see below }, }); ``` OFT bridging requires `bridging.layerZeroApiKey` and is supported on Starknet Mainnet routes only. Layerswap routes **and** Layerswap token discovery require `bridging.layerswapApiKey`. Without it, `getBridgingTokens(...)` omits Layerswap-bridgeable tokens and Layerswap deposits/withdrawals fail. Layerswap API keys are environment-scoped: use a **Mainnet** key with Starknet Mainnet and a **Testnet** key with Starknet Sepolia. **Layerswap withdrawals sign calls that come from the Layerswap API.** Starkzap checks every one of them before the wallet signs: a call on the bridge token must be a `transfer`, and any other call must target a contract listed in `bridging.layerswapAllowedContracts`. With that list unset or empty, only the transfer is accepted, and a deposit action carrying any other call fails before signing with an error naming the address. Before enabling a Layerswap route, run one withdrawal on Sepolia and inspect the deposit action Layerswap returns. If it includes a helper call, confirm with Layerswap which contract it is and why, then list that address. Do not list an address you have not verified — listing the bridge token itself changes nothing, since calls on it must be `transfer` regardless. ## Fetch Bridgeable Tokens ```typescript theme={null} import { ExternalChain } from "starkzap"; // All bridgeable tokens for current Starknet environment const allTokens = await sdk.getBridgingTokens(); // Filter by source chain const ethereumTokens = await sdk.getBridgingTokens(ExternalChain.ETHEREUM); const solanaTokens = await sdk.getBridgingTokens(ExternalChain.SOLANA); ``` Layerswap-bridgeable tokens are sourced from the Layerswap API and only appear when `bridging.layerswapApiKey` is configured. Without it, discovery silently omits Layerswap routes. ## Connect External Wallets Take a look at the [Examples](/build/starkzap/examples). For WalletConnect setup details, see [WalletConnect Docs](https://docs.walletconnect.network/). In practice, you establish the external wallet session first (for example with WalletConnect), then pass its provider/account/chain into `ConnectedEthereumWallet.from(...)` or `ConnectedSolanaWallet.from(...)` for bridge calls. ### Ethereum (EIP-1193) ```typescript theme={null} import { ConnectedEthereumWallet, ExternalChain } from "starkzap"; const evmProvider = window.ethereum; const [evmAddress] = await evmProvider.request({ method: "eth_requestAccounts" }); const evmChainId = await evmProvider.request({ method: "eth_chainId" }); // "0x1" or "0xaa36a7" const ethWallet = await ConnectedEthereumWallet.from( { chain: ExternalChain.ETHEREUM, provider: evmProvider, address: evmAddress, chainId: evmChainId, // evm wallet's chain id }, wallet.getChainId() // starknet wallet's chain id ); ``` ### Solana ```typescript theme={null} import { ConnectedSolanaWallet, ExternalChain } from "starkzap"; const solWallet = await ConnectedSolanaWallet.from( { chain: ExternalChain.SOLANA, provider: solanaProvider, // must implement signAndSendTransaction() address: solanaAddress, chainId: solanaChainId, // mainnet / testnet / devnet genesis hash from wallet adapter }, wallet.getChainId() ); ``` External wallet network and Starknet network must match by environment: Ethereum Mainnet with Starknet Mainnet, Ethereum Sepolia with Starknet Sepolia, and Solana Mainnet with Starknet Mainnet. On Starknet Sepolia, the connected Solana wallet may be on **either Solana Testnet or Solana Devnet** — the right cluster depends on the route (see note below). | External Network | Identifier | Starknet Network | | - | - | - | | Ethereum Mainnet | `1` | Starknet Mainnet | | Ethereum Sepolia | `11155111` | Starknet Sepolia | | Solana Mainnet | `5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Starknet Mainnet | | Solana Testnet | `4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z` | Starknet Sepolia | | Solana Devnet | `EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | Starknet Sepolia | On Starknet Sepolia the two Solana routes target different clusters: **Layerswap** uses **Solana Testnet** (`4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z`) and **Hyperlane** uses **Solana Devnet** (`EtWTRABZaYq6iMfeYKouRu166VU2xqa1`). Connect the Solana wallet to the cluster matching the route you intend to use. On Starknet Mainnet, both routes use Solana Mainnet. ## Estimate and Deposit ```typescript theme={null} import { Amount, fromAddress } from "starkzap"; const token = ethereumTokens[0]; if (!token) throw new Error("No bridge token available"); // 1) Source-chain available balance const available = await wallet.getDepositBalance(token, ethWallet); // 2) ERC20 allowance (null for native/non-allowance routes) const allowance = await wallet.getAllowance(token, ethWallet); // 3) Fee estimation (fastTransfer only applies to CCTP) const fees = await wallet.getDepositFeeEstimate(token, ethWallet, { fastTransfer: true, }); // 4) Submit deposit tx on source chain const recipient = fromAddress( "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" ); // Starknet recipient const tx = await wallet.deposit( recipient, Amount.parse("25", token.decimals, token.symbol), token, ethWallet, { fastTransfer: true } ); console.log(tx.hash); ``` On Ethereum a deposit can be two transactions: an ERC20 `approve` sized to the amount, then the deposit. Do not start a second deposit of the same token before the first has resolved. Both would read the same allowance and both would send an `approve`, and since `approve` sets rather than adds, the second deposit lands short and reverts. Serialize deposits per token, or set an allowance that covers them all before starting. ## Withdraw from Starknet ### Initiate (all protocols) ```typescript theme={null} import { Amount, fromAddress } from "starkzap"; const token = ethereumTokens[0]; if (!token) throw new Error("No bridge token available"); const recipient = "0xYourEthereumAddress"; // L1 recipient // 1) L2 balance available to withdraw const balance = await wallet.getWithdrawBalance(token, ethWallet); // 2) Estimate L2 fee for initiating the withdrawal const fees = await wallet.getInitiateWithdrawFeeEstimate(token, ethWallet, { protocol: "cctp", fastTransfer: true, }); // 3) Submit the Starknet burn/initiate transaction const tx = await wallet.initiateWithdraw( recipient, Amount.parse("25", token.decimals, token.symbol), token, ethWallet, { protocol: "cctp", fastTransfer: true } ); console.log(tx.hash); ``` ### Complete (Canonical & CCTP only) OFT, Hyperlane, and Layerswap are single-step — delivery on the destination chain happens automatically. For Canonical and CCTP, a second L1 transaction is required once the state becomes `READY_TO_CLAIM`. ```typescript theme={null} // Poll until the withdrawal is ready to claim const result = await wallet.monitorWithdrawal(token, tx.hash); if (result.protocol === "cctp" && result.attestation && result.message) { // Estimate the L1 gas cost before submitting const l1Fee = await wallet.getCompleteWithdrawFeeEstimate( Amount.parse("25", token.decimals, token.symbol), recipient, token, ethWallet, { protocol: "cctp", attestation: result.attestation, message: result.message, nonce: result.nonce, expirationBlock: result.expirationBlock, } ); // Submit the L1 completion transaction const l1Tx = await wallet.completeWithdraw( recipient, Amount.parse("25", token.decimals, token.symbol), token, ethWallet, { protocol: "cctp", attestation: result.attestation, message: result.message, nonce: result.nonce, expirationBlock: result.expirationBlock, } ); console.log(l1Tx.hash); } ``` ### Auto-withdraw (Canonical only) Canonical supports an `autoWithdraw` option where a relayer handles L1 completion — no `completeWithdraw` call needed. ```typescript theme={null} const fees = await wallet.getInitiateWithdrawFeeEstimate(token, ethWallet, { protocol: "canonical", autoWithdraw: true, }); await wallet.initiateWithdraw( recipient, Amount.parse("25", token.decimals, token.symbol), token, ethWallet, { protocol: "canonical", autoWithdraw: true, preferredFeeToken: fees.autoWithdrawFee?.token, } ); ``` ## Monitor Bridge Transfers ### Simplified state (recommended for UI) ```typescript theme={null} // Withdrawal state: "PENDING" | "READY_TO_CLAIM" | "COMPLETED" | "ERROR" const state = await wallet.getWithdrawalState(token, { starknetTxHash: tx.hash }); // Deposit state: "PENDING" | "COMPLETED" | "ERROR" const depositState = await wallet.getDepositState(token, { externalTxHash: ethTxHash }); ``` `WithdrawalState` values: * `PENDING` — bridging in progress, no user action needed * `READY_TO_CLAIM` — ready to finalize on L1; call `completeWithdraw` (CCTP/Canonical) * `COMPLETED` — bridge flow fully complete * `ERROR` — unrecoverable error ### Detailed status (advanced use) Use `monitorWithdrawal` to get the full status snapshot including CCTP attestation data needed for `completeWithdraw`: ```typescript theme={null} const result = await wallet.monitorWithdrawal(token, tx.hash); // result.status: BridgeTransferStatus // For CCTP when READY_TO_CLAIM: result.attestation, result.message, result.nonce // You can pass a previously-fetched result back to avoid redundant network calls const state = await wallet.getWithdrawalState(token, result); ``` Similarly for deposits: ```typescript theme={null} const result = await wallet.monitorDeposit(token, ethTxHash); const depositState = await wallet.getDepositState(token, result); ``` ## Protocol Notes | Protocol | Chain | Deposit | Withdrawal | | - | - | - | - | | `canonical` | Ethereum | Standard flow; approval may be required for ERC20 tokens. | Two-step: initiate on L2, complete on L1. Supports `autoWithdraw` to skip the L1 step via relayer. | | `cctp` | Ethereum | Supports `fastTransfer`; fee estimate includes CCTP fast transfer bp fee. | Two-step: wait for Circle attestation, then call `completeWithdraw`. Expired attestations are re-requested automatically. | | `oft` / `oft-migrated` | Ethereum | Requires `bridging.layerZeroApiKey`; mainnet-only route availability. | Single-step: LayerZero relayer handles L1 delivery automatically. No `completeWithdraw` needed. | | `hyperlane` | Solana | Requires Solana + Hyperlane optional dependencies. | Single-step: Hyperlane relayer handles delivery automatically. | | `layerswap` | Ethereum & Solana | Requires `bridging.layerswapApiKey`; per-swap deposit address derived from the Layerswap API. Available on mainnet and testnet. | Single-step: Layerswap delivers on the destination chain automatically. No `completeWithdraw` needed. | ## Common Errors * **Chain mismatch**: token source chain and connected external wallet chain must match. * **Missing LayerZero key**: OFT routes require `bridging.layerZeroApiKey`. * **Missing Layerswap key**: Layerswap routes and Layerswap token discovery require `bridging.layerswapApiKey`; the key must match the environment (Mainnet key for Starknet Mainnet, Testnet key for Starknet Sepolia). * **Layerswap call not in `bridging.layerswapAllowedContracts`**: the deposit action carried a call to a contract other than the bridge token. Nothing was signed. Inspect the call, and list the contract only if it is a Layerswap helper you have verified. * **Unsupported chain pair**: Ethereum mainnet must pair with Starknet mainnet; testnet pairings must match. * **CCTP options missing**: `completeWithdraw` requires `options` with attestation data for CCTP routes — the parameter is non-optional in practice. * **Attestation expired**: CCTP attestations have an expiration block; re-attestation is requested automatically during `completeWithdraw`. For additional issues, see [Troubleshooting](/build/starkzap/troubleshooting). ## Next Steps * [Configuration](/build/starkzap/configuration) — full `SDKConfig` options * [API Reference](/build/starkzap/api-reference) — exact method signatures * [Examples](/build/starkzap/examples) — web example with bridge UI flow # Network, RPC, Staking, Bridging, and Logging Source: https://docs.starknet.io/build/starkzap/configuration Configure networks, RPC providers, staking, bridging, and logging for the SDK ## Overview The `StarkZap` accepts configuration through a single options object. You can use network presets for simplicity, or provide explicit RPC URLs and chain IDs for custom setups. **New to blockchain?** An **RPC** (Remote Procedure Call) is like an API endpoint for the blockchain—it's how your app communicates with the network. A **network** is like an environment (production, staging, development). See the [Glossary](/build/starkzap/glossary) for more web2-friendly explanations. The simplest way to configure the SDK is using network presets. Think of networks like environments: * **mainnet** = Production (real money, real transactions) * **sepolia** = Staging/Testnet (fake money for testing) * **devnet** = Local development (your own test blockchain) ```typescript theme={null} import { StarkZap } from "starkzap"; // Mainnet const sdk = new StarkZap({ network: "mainnet" }); // Sepolia testnet const sdk = new StarkZap({ network: "sepolia" }); // Local devnet (starknet-devnet-rs) const sdk = new StarkZap({ network: "devnet" }); ``` ### Available Presets | Preset | Chain ID | RPC | Explorer | | - | - | - | - | | `mainnet` | `SN_MAIN` | `https://api.cartridge.gg/x/starknet/mainnet` | `https://voyager.online` | | `sepolia` | `SN_SEPOLIA` | `https://api.cartridge.gg/x/starknet/sepolia` | `https://sepolia.voyager.online` | | `devnet` | `SN_SEPOLIA` | `http://localhost:5050` | — | ### Using Network Objects You can also pass a custom `NetworkPreset` object: ```typescript theme={null} import { StarkZap, networks } from "starkzap"; const sdk = new StarkZap({ network: networks.mainnet }); ``` If you run your own blockchain node or use a different RPC provider (like using a different API endpoint): An **RPC provider** is like choosing which API server to use. Just like you might use AWS, Google Cloud, or your own servers, you can use different RPC providers to connect to the blockchain. ```typescript theme={null} import { StarkZap, ChainId } from "starkzap"; const sdk = new StarkZap({ rpcUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY", chainId: ChainId.MAINNET, }); ``` `rpcUrl` and `chainId` override values from `network` if both are provided. Plain `http://` is accepted only for loopback hosts (`localhost`, `127.0.0.1`, `[::1]`), which is what the `devnet` preset uses. Any other plain-http host is refused. If a device or emulator reaches a devnet or proxy over your LAN, set `allowInsecureHttp: true` on the SDK config. It applies to `rpcUrl`, the Cartridge URLs and the Privy `serverUrl`; the privacy module has its own flag. Never set it in a production build. Enable staking functionality to let users earn passive income by staking their tokens. When configured, users can: * 💰 **Stake tokens** in validator pools and earn rewards over time (like earning interest on a savings account) * 📈 **Grow their holdings** as rewards accumulate automatically * 🔄 **Manage their positions** by adding more tokens, claiming rewards, or exiting pools * 🏦 **Support network security** while earning passive income Staking works out of the box using chain-aware staking contract presets: ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", }); ``` If needed, you can override the default preset: ```typescript theme={null} import { StarkZap, fromAddress } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", staking: { contract: fromAddress( "0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7" ), }, }); ``` **Ready to implement staking?** See the [Staking & Delegation Guide](/build/starkzap/staking) for complete instructions on how users can stake tokens, claim rewards, and manage their positions. Configure optional cross-chain bridge settings when depositing assets from Ethereum or Solana into Starknet. ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", bridging: { ethereumRpcUrl: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY", solanaRpcUrl: "https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY", layerZeroApiKey: "YOUR_LAYERZERO_API_KEY", // for OFT routes layerswapApiKey: "YOUR_LAYERSWAP_API_KEY", // for Layerswap routes + token discovery }, }); ``` * `ethereumRpcUrl`: Custom Ethereum RPC for bridge fee/balance operations * `solanaRpcUrl`: Custom Solana RPC (falls back to cluster URL if omitted) * `layerZeroApiKey`: Required for OFT/OFT-migrated routes * `layerswapApiKey`: Required for Layerswap routes and Layerswap token discovery * `layerswapBaseUrl`: Optional Layerswap API base URL (defaults to `https://api.layerswap.io`) * `layerswapAllowedContracts`: Starknet contracts Layerswap may call in a withdrawal besides the bridge token. Unset, only the token transfer is signed. See [Bridging](/build/starkzap/bridging) before listing anything. OFT routes require `bridging.layerZeroApiKey` and are supported on mainnet bridge routes only. Layerswap routes and Layerswap token discovery require `bridging.layerswapApiKey`. Keys are environment-scoped — use a Mainnet key with Starknet Mainnet and a Testnet key with Starknet Sepolia. For the full deposit flow (`getBridgingTokens`, external wallet adapters, fee estimate, deposit), see the [Bridging Guide](/build/starkzap/bridging). Configure optional SDK diagnostics when you need visibility into wallet, bridge, and transaction flows during development or debugging. Logging is opt-in and the SDK stays silent by default. ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", logging: { logger: console, }, }); ``` StarkZap accepts any console-compatible logger with `debug`, `info`, `warn`, and `error` methods. You can also filter logs inside the SDK with `logLevel`: ```typescript theme={null} import { StarkZap } from "starkzap"; import pino from "pino"; const sdk = new StarkZap({ network: "mainnet", logging: { logger: pino(), logLevel: "warn", }, }); ``` * Omit `logging` to keep the SDK completely silent * Omit `logLevel` to forward all log levels to your logger * Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal`, and `silent` ## Complete Configuration Example Here's an example with all optional features configured: ### Using Network Preset ```typescript theme={null} import { StarkZap, fromAddress } from "starkzap"; const sdk = new StarkZap({ // Network configuration network: "mainnet", // Optional: SDK diagnostics logging: { logger: console, logLevel: "info", }, // Optional: override staking contract preset staking: { contract: fromAddress( "0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7" ), }, bridging: { ethereumRpcUrl: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY", layerZeroApiKey: "YOUR_LAYERZERO_API_KEY", }, }); ``` ### Using Custom RPC ```typescript theme={null} import { StarkZap, ChainId, fromAddress } from "starkzap"; const sdk = new StarkZap({ // Custom RPC configuration rpcUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY", chainId: ChainId.MAINNET, // Optional: SDK diagnostics logging: { logger: console, }, // Optional: override staking contract preset staking: { contract: fromAddress( "0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7" ), }, bridging: { solanaRpcUrl: "https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY", }, }); ``` For paymaster configuration, see the [Paymasters Guide](/build/starkzap/paymasters) for detailed setup instructions for AVNU Paymaster or Cartridge's built-in paymaster. ## Configuration Type Reference ```typescript theme={null} interface SDKConfig { // Network (choose one) network?: NetworkName | NetworkPreset; rpcUrl?: string; chainId?: ChainId; allowInsecureHttp?: boolean; // accept plain http:// off loopback; dev only // Optional features paymaster?: PaymasterOptions; // { nodeUrl?: string; default?: boolean; headers?: object } explorer?: ExplorerConfig; staking?: StakingConfig; bridging?: BridgingConfig; logging?: LoggerConfig; } interface StakingConfig { contract: Address; // optional override } interface BridgingConfig { layerZeroApiKey?: string; // OFT/OFT-migrated routes layerswapApiKey?: string; // Layerswap routes + token discovery layerswapBaseUrl?: string; // defaults to https://api.layerswap.io layerswapAllowedContracts?: readonly Address[]; // extra contracts Layerswap may call; unset = transfer only ethereumRpcUrl?: string; solanaRpcUrl?: string; } interface LoggerConfig { logger: { debug(message: string, ...args: unknown[]): void; info(message: string, ...args: unknown[]): void; warn(message: string, ...args: unknown[]): void; error(message: string, ...args: unknown[]): void; }; logLevel?: "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "silent"; } ``` For paymaster configuration details, see the [Paymasters Guide](/build/starkzap/paymasters). ## Next Steps * Set up [Paymasters](/build/starkzap/paymasters) for gasless transactions * Enable [Staking & Delegation](/build/starkzap/staking) so users can earn passive income * Configure [Bridging](/build/starkzap/bridging) for Ethereum/Solana deposits into and withdrawals out of Starknet * Learn about [Connecting Wallets](/build/starkzap/connecting-wallets) with different signers * Configure [Account Presets](/build/starkzap/connecting-wallets#account-presets) for your use case # Wallets Source: https://docs.starknet.io/build/starkzap/connecting-wallets Connect wallets using different signer strategies: private keys, Privy, Cartridge, and custom signers Wallets hero ## Overview Starkzap supports multiple ways to connect wallets, each suited for different use cases and security requirements. This guide covers all available connection methods. **New to wallets?** A **wallet** is like a bank account on the blockchain—it has an address (like an account number) and uses a **signer** (like authentication) to authorize transactions. A **signer** is what proves you own the wallet, similar to how OAuth tokens prove you're logged in. See the [Glossary](/build/starkzap/glossary) for more details. ## Connection Methods Comparison Choose the right method for your use case: | Method | Best For | User Experience | Gas Fees | Setup Complexity | | - | - | - | - | - | | **Private Key** | Server-side apps, development | Manual key entry | ⚠️ User pays (or configure AVNU Paymaster) | ⭐ Simple | | **Privy** | Consumer apps, mobile/web | Email/social login | ⚠️ User pays (or configure AVNU Paymaster) | ⭐⭐ Moderate | | **Cartridge** | Gaming applications | Social login, Face ID/Touch ID (web popup or native session) | ✅ **Policy-matching calls can be sponsored** | ⭐⭐ Moderate (RN: Metro + adapter) | ### Quick Decision Guide * **Building a game?** → Use **Cartridge** (web: `@cartridge/controller`; React Native: `starkzap-native` + [React Native Integration](/build/starkzap/react-native)) * **Building a consumer app?** → Use **Privy** (email/social login, configure AVNU Paymaster for gasless) * **Building a server-side service?** → Use **Private Key** (with proper security, configure AVNU Paymaster for gasless) * **Need gasless-style UX for approved game calls?** → Use **Cartridge** with policies that match your contract calls * **Need email-based auth?** → Use **Privy** * **Just testing?** → Use **Private Key** for quick setup ## Connection Methods ### 1. Local Private Key (StarkSigner) The simplest approach — sign transactions locally using a private key (like storing a password locally). Best for: * Server-side applications * Trusted environments * Development and testing A **private key** is like a password—it proves you own the wallet. Unlike a password, you should **never** expose it in client-side code. Only use this method on secure servers or for development. **For gasless transactions:** Configure [AVNU Paymaster](/build/starkzap/integrations/avnu-paymaster) separately to sponsor transaction fees. ```typescript theme={null} import { StarkZap, StarkSigner, OpenZeppelinPreset, ArgentPreset } from "starkzap"; const sdk = new StarkZap({ network: "sepolia" }); const signer = new StarkSigner("0xYOUR_PRIVATE_KEY"); // Default account type: OpenZeppelin const wallet = await sdk.connectWallet({ account: { signer }, }); // Or specify an account preset const wallet = await sdk.connectWallet({ account: { signer, accountClass: ArgentPreset }, }); ``` Never expose private keys in client-side code. Use this method only in secure server environments or for development. ### 2. Privy Signer (Server-Side Key Management) [Privy](https://privy.io) manages private keys securely on their infrastructure (like how OAuth providers manage authentication). The signing happens through your backend. Best for: * Consumer applications * Mobile and web apps * Applications requiring secure key management Think of Privy like **OAuth for wallets**—they manage the keys (like Google manages your login), and you just call their API to sign transactions (like calling Google's API to verify a token). **For gasless transactions:** Configure [AVNU Paymaster](/build/starkzap/integrations/avnu-paymaster) separately to sponsor transaction fees. For detailed Privy integration instructions, see the [Privy Integration Guide](/build/starkzap/integrations/privy). **Option A: Provide your backend URL (recommended for mobile/web clients)** ```typescript theme={null} import { StarkZap, PrivySigner, ArgentXV050Preset } from "starkzap"; const accessToken = await privy.getAccessToken(); const walletRes = await fetch("https://your-backend.com/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet: privyWallet } = await walletRes.json(); const signer = new PrivySigner({ walletId: privyWallet.id, publicKey: privyWallet.publicKey, serverUrl: "https://your-backend.com/api/wallet/sign", }); const wallet = await sdk.connectWallet({ account: { signer, accountClass: ArgentXV050Preset }, }); ``` Your backend endpoint receives `{ walletId, hash }` and must return `{ signature }`. **Option B: Custom signing function (for server-side Node.js)** ```typescript theme={null} import { PrivySigner } from "starkzap"; // Use the wallet identity returned by your /api/wallet/starknet endpoint const walletId = "wallet-id-from-backend"; const publicKey = "0xPUBLIC_KEY_FROM_BACKEND"; const signer = new PrivySigner({ walletId, publicKey, rawSign: async (walletId, messageHash) => { const response = await privyClient.wallets().rawSign(walletId, { params: { hash: messageHash }, }); return response.signature; }, }); ``` ### 3. Cartridge (web Controller or native session) [Cartridge](https://cartridge.gg) is specialized for gaming applications and provides social login and passkey authentication. * **Web:** opens a Controller popup (install `@cartridge/controller`). * **React Native / Expo:** in-app browser / deep-link **session** flow via `starkzap-native` — register the native Cartridge adapter before connecting; see [React Native Integration](/build/starkzap/react-native). Best for: * Gaming applications * Social login integration * Passkey-based authentication * Policy-approved, paymastered execution (not every arbitrary call) **Key advantage:** Cartridge can sponsor **policy-matching** transactions through its paymaster/session stack after users approve policies. Other calls may still require user fees or extra steps. See the [Cartridge Controller Integration Guide](/build/starkzap/integrations/cartridge-controller). ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectCartridge({ policies: [ { target: "0xTOKEN_CONTRACT", method: "transfer" }, { target: "0xPOOL_CONTRACT", method: "enter_delegation_pool" }, ], }); // Use just like any other wallet const tx = await wallet.execute([...]); // Access Cartridge-specific features const controller = wallet.getController(); controller.openProfile(); ``` ### 4. Custom Signer Implement `SignerInterface` to integrate any key management solution (hardware wallets, MPC, HSMs, etc.): ```typescript theme={null} import type { SignerInterface } from "starkzap"; import type { Signature } from "starknet"; class MySigner implements SignerInterface { async getPubKey(): Promise { return "0xMY_PUBLIC_KEY"; } async signRaw(hash: string): Promise { // Sign the hash using your key management solution // Return [r, s] tuple return ["0x...", "0x..."]; } } const wallet = await sdk.connectWallet({ account: { signer: new MySigner() }, }); ``` ## Account Presets Account presets define what type of wallet account to create. Think of it like choosing between "Personal" vs "Business" account types—different presets have different features and security models. An **account preset** is like selecting a user role or account type. Each preset (OpenZeppelin, Argent, Braavos) has different capabilities, just like how different user roles have different permissions in your app. ### Built-in Presets ```typescript theme={null} import { OpenZeppelinPreset, // Default — OpenZeppelin account ArgentPreset, // Argent v0.4.0 ArgentXV050Preset, // Argent v0.5.0 (used by Privy) BraavosPreset, // Braavos v1.2.0 DevnetPreset, // Local devnet accounts } from "starkzap"; // Use when connecting a wallet const wallet = await sdk.connectWallet({ account: { signer: new StarkSigner(privateKey), accountClass: ArgentPreset, }, }); ``` ### Custom Account Class ```typescript theme={null} import type { AccountClassConfig } from "starkzap"; import { CallData } from "starknet"; const MyAccountPreset: AccountClassConfig = { classHash: "0xYOUR_CLASS_HASH", buildConstructorCalldata: (publicKey) => { return CallData.compile({ owner: publicKey }); }, // Optional: custom salt derivation (default uses publicKey) getSalt: (publicKey) => publicKey, }; ``` ## Wallet Lifecycle ### Deployment Starknet accounts are smart contracts that need to be "deployed" (created on the blockchain) before they can execute transactions. Think of it like creating a new user account in your database—it needs to exist before you can use it. **Deployment** is like provisioning a new service or creating a new account. The SDK handles this automatically, but you can control when it happens with the `deploy` option. ```typescript theme={null} // Check if deployed const deployed = await wallet.isDeployed(); // Deploy explicitly if (!deployed) { const tx = await wallet.deploy(); await tx.wait(); } // Deploy with sponsored fees (gasless) const tx = await wallet.deploy({ feeMode: { type: "paymaster" } }); await tx.wait(); ``` For sponsored wallets, `execute()` automatically deploys the account in the same transaction if needed — no separate deploy step required. ### Ensure Ready A convenience method that checks deployment and optionally deploys: ```typescript theme={null} // Deploy if needed (default behavior) await wallet.ensureReady(); // Explicit options await wallet.ensureReady({ deploy: "if_needed", // "never" | "if_needed" | "always" feeMode: { type: "paymaster" }, onProgress: (event) => { console.log(event.step); // "CONNECTED" → "CHECK_DEPLOYED" → "DEPLOYING" → "READY" }, }); ``` ### Deployment Policy Options * `"never"` - Fail if account is undeployed * `"if_needed"` - Deploy undeployed accounts automatically * `"always"` - Force deployment flow even if already deployed ## Using the Onboarding API The onboarding API simplifies wallet connection by handling strategy selection, signer construction, and account readiness: ```typescript theme={null} import { StarkZap, OnboardStrategy } from "starkzap"; const sdk = new StarkZap({ network: "sepolia" }); const accessToken = await privy.getAccessToken(); const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, privy: { resolve: async () => { const walletRes = await fetch("https://your-api.example/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet } = await walletRes.json(); return { walletId: wallet.id, publicKey: wallet.publicKey, serverUrl: "https://your-api.example/api/wallet/sign", }; }, }, accountPreset: "argentXV050", deploy: "if_needed", }); const wallet = onboard.wallet; ``` ## Production Guidance * **Prefer signer separation by trust boundary**: * Server-only keys on backend * Privy for managed end-user keys * Cartridge for delegated UX * **Always call `ensureReady()`** before user actions * **Use appropriate account presets** for your use case * **Handle deployment errors gracefully** in production ## Next Steps * Learn about [Executing Transactions](/build/starkzap/transactions) * Explore [ERC20 Token Operations](/build/starkzap/erc20) * Set up [Staking & Delegation](/build/starkzap/staking) # Dollar-Cost Averaging Source: https://docs.starknet.io/build/starkzap/dollar-cost-average Schedule recurring buys with AVNU or Ekubo—create orders, preview cycles, and cancel via wallet.dca() ## Overview Starkzap supports **Dollar-Cost Averaging (DCA)** through **[AVNU](https://app.avnu.fi/en)** and **[Ekubo](https://ekubo.org/starknet/swap)**. Users can create recurring buy orders (sell token X, buy token Y on a schedule), preview the expected swap per cycle, and cancel orders. DCA providers handle order creation, execution, and cancellation independently. Swap providers are only used for `previewCycle` quoting. **What users can do with DCA:** * 📅 **Create recurring orders** - Set token pair, amount per cycle, and frequency (e.g. daily); optional min/max bounds with AVNU * 📊 **Preview a cycle** - See the expected buy amount for the next cycle before creating or on existing orders * 📋 **List orders** - Fetch active (and optionally past) DCA orders for the wallet * ❌ **Cancel orders** - Cancel by order id or order address * 📦 **Batch with other actions** - Use the transaction builder to combine DCA create/cancel with transfers or swaps DCA providers (AVNU, Ekubo) handle cycle execution on their own backend — swap providers are **not** involved in execution. However, `previewCycle` uses the wallet's default (or specified) swap provider to quote the next cycle. Register [swap providers](/build/starkzap/swap#registering-swap-providers) if you want to use `previewCycle`. ## Configuration ### Registering DCA providers Register DCA providers when connecting the wallet (or at runtime). The wallet's `dca()` client uses these providers for create/cancel and for cycle preview (which uses the swap provider to quote the next cycle). `AvnuDcaProvider` (and `AvnuSwapProvider`, used by `previewCycle`) require the optional peer dependency `@avnu/avnu-sdk` (`npm install @avnu/avnu-sdk`). It is loaded lazily on first use, so the first AVNU DCA call throws an install hint if the package is missing. Ekubo providers need no extra dependency. See [Installation](/build/starkzap/installation#peer-dependencies-by-feature). ```typescript theme={null} import { StarkZap, AvnuSwapProvider, EkuboSwapProvider, AvnuDcaProvider, EkuboDcaProvider, ArgentPreset } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer, accountClass: ArgentPreset }, swapProviders: [new AvnuSwapProvider(), new EkuboSwapProvider()], defaultSwapProviderId: "avnu", dcaProviders: [ new AvnuDcaProvider(), new EkuboDcaProvider(), ], defaultDcaProviderId: "avnu", }); ``` At runtime (e.g. after Cartridge onboard), register DCA providers and set the default the same way you do for [swap providers](/build/starkzap/swap#registering-swap-providers): `wallet.dca().registerProvider(provider)`, `wallet.dca().setDefaultProvider(providerId)`. Register swap providers and a default swap provider if you want to use `previewCycle`. Cycle execution is handled by the DCA provider backend and does not require swap providers. ## Creating a DCA order Create a recurring order with token pair, amount per cycle, frequency, and optional bounds (AVNU supports min/max buy amount per cycle; Ekubo uses TWAMM-style continuous execution). ```typescript theme={null} const tx = await wallet.dca().create({ sellToken: STRK, buyToken: USDC, sellAmount: Amount.parse("100", STRK), // total amount to sell across all cycles sellAmountPerCycle: Amount.parse("10", STRK), frequency: "P1D", // ISO 8601 duration, e.g. daily // Optional (AVNU): pricingStrategy with min/max bounds // Optional: provider, startTime, endTime }); await tx.wait(); ``` To batch DCA create with other operations, use the [Tx Builder](/build/starkzap/tx-builder) (e.g. `.dcaCreate(...)`). ## Previewing a cycle Show users the expected outcome of the next DCA cycle (quote from the swap provider) before they create the order or to display on an existing order: ```typescript theme={null} const quote = await wallet.dca().previewCycle({ sellToken: STRK, buyToken: USDC, sellAmountPerCycle: Amount.parse("10", STRK), // optional: swapProvider, slippageBps }); console.log(quote.amountOutBase); // expected buy amount for one cycle ``` ## Listing and cancelling orders List active (and optionally past) DCA orders for the wallet, then cancel by order id or order address: ```typescript theme={null} const page = await wallet.dca().getOrders({ provider: "avnu" }); for (const order of page.content) { console.log(order.id, order.sellTokenAddress, order.buyTokenAddress, order.frequency); } // Cancel by order id (provider-specific string) or by order address const tx = await wallet.dca().cancel({ orderId: page.content[0].id }); await tx.wait(); ``` ## Using the transaction builder Batch DCA create or cancel with other actions: ```typescript theme={null} const tx = await wallet .tx() .dcaCreate({ sellToken: STRK, buyToken: USDC, sellAmount: Amount.parse("100", STRK), sellAmountPerCycle: Amount.parse("10", STRK), frequency: "P1D", }) .transfer(USDC, [{ to: recipient, amount: Amount.parse("5", USDC) }]) .send(); await tx.wait(); ``` Cancel via the builder: ```typescript theme={null} const tx = await wallet.tx().dcaCancel({ orderId: myOrderId }).send(); await tx.wait(); ``` See [Tx Builder](/build/starkzap/tx-builder) for the full list of builder methods. ## DCA providers | Provider | Id | Description | | - | - | - | | **[AVNU](https://app.avnu.fi/en)** | `avnu` | Recurring orders with optional min/max buy amount per cycle; uses AVNU swap routing for execution. | | **[Ekubo](https://ekubo.org/starknet/swap)** | `ekubo` | TWAMM-style continuous DCA; no discrete per-cycle amount (continuous flow). Supports order list and cancel. | Provider-specific options (e.g. AVNU bounds, Ekubo time alignment) are documented in the SDK types. Use `wallet.dca().listProviders()` to see registered DCA provider ids and `setDefaultProvider(providerId)` to change the default. ## Best practices 1. **Register swap providers** if you want `previewCycle` to have a quote source (not required for DCA execution itself). 2. **Use token presets** from `getPresets(chainId)` or `mainnetTokens` / `sepoliaTokens` for `tokenIn` and `tokenOut` so addresses and decimals match the chain. 3. **Preview before create** — call `previewCycle` and show users the expected buy amount per cycle before they confirm the order. 4. **Handle provider differences** — AVNU supports discrete cycles and optional min/max bounds; Ekubo is continuous (TWAMM). Validate inputs per provider when building forms. ## Troubleshooting ### "No default DCA provider configured" Register at least one DCA provider and call `setDefaultProvider(providerId)` (or pass `defaultDcaProviderId` when connecting). Alternatively, pass `provider: "avnu"` or `provider: "ekubo"` on each DCA request. ### "requires optional peer dependency "@avnu/avnu-sdk"" An AVNU provider is selected but `@avnu/avnu-sdk` is not installed. Run `npm install @avnu/avnu-sdk`, or use the Ekubo providers (no extra dependency) instead. ### DCA relies on swap providers If `previewCycle` fails, ensure swap providers are registered and a default swap provider is set. Swap providers are only used for cycle quoting — actual cycle execution is handled by the DCA provider backend (AVNU/Ekubo). ### Chain or provider mismatch Ensure the wallet is on a chain supported by the chosen DCA provider (AVNU and Ekubo support mainnet and Sepolia). Align the SDK network config with the RPC/wallet chain. ## Next steps * [Swaps](/build/starkzap/swap) — One-off quotes and swaps (used by `previewCycle` for quoting) * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) — Token presets and amounts * [Tx Builder](/build/starkzap/tx-builder) — Batching DCA with other operations * [API Reference](/build/starkzap/api-reference) — Full method signatures # Stablecoins & Token Source: https://docs.starknet.io/build/starkzap/erc20 Check balances, transfer tokens, and work with amounts using type-safe primitives Bitcoin, Stablecoins, and Token hero ## Overview Starkzap provides comprehensive support for ERC20 token operations, including balance queries, transfers, and type-safe amount handling. This module allows you to work with: * **Bitcoin wrappers on Starknet** - wBTC, lBTC, tBTC (wrapped Bitcoin tokens that represent Bitcoin on Starknet) * **Stablecoins** - USDC, USDT, and other stablecoins * **Other ERC20 tokens** - Any ERC20-compatible token on Starknet This guide covers all token-related functionality for these different token types. ## Token Presets The SDK ships with pre-configured token definitions for mainnet and Sepolia: ```typescript theme={null} import { mainnetTokens, sepoliaTokens } from "starkzap"; // Access specific tokens const STRK = mainnetTokens.STRK; const USDC = mainnetTokens.USDC; const ETH = mainnetTokens.ETH; ``` ### Token Shape ```typescript theme={null} interface Token { name: string; // "Starknet Token" address: Address; // "0x04718f5a0..." decimals: number; // 18 symbol: string; // "STRK" metadata?: { logoUrl?: URL }; } ``` ### Getting Tokens for Current Network ```typescript theme={null} import { getPresets } from "starkzap"; const tokens = getPresets(wallet.getChainId()); // Record ``` ### Resolving Unknown Tokens Resolve tokens from on-chain contract addresses: ```typescript theme={null} import { getTokensFromAddresses } from "starkzap"; const tokens = await getTokensFromAddresses( [fromAddress("0xTOKEN_CONTRACT")], provider ); ``` ## Checking Balances ```typescript theme={null} const balance = await wallet.balanceOf(STRK); console.log(balance.toUnit()); // "150.25" console.log(balance.toFormatted()); // "STRK 150.25" console.log(balance.toFormatted(true)); // "STRK 150.25" (compressed, max 4 decimals) console.log(balance.toBase()); // 150250000000000000000n (raw) console.log(balance.isZero()); // false ``` ## Transferring Tokens ### Single Transfer ```typescript theme={null} const tx = await wallet.transfer(USDC, [ { to: fromAddress("0xRECIPIENT"), amount: Amount.parse("100", USDC) }, ]); await tx.wait(); ``` ### Batch Transfer (Multiple Recipients) Send to multiple recipients in a single transaction: ```typescript theme={null} const tx = await wallet.transfer(USDC, [ { to: fromAddress("0xALICE"), amount: Amount.parse("50", USDC) }, { to: fromAddress("0xBOB"), amount: Amount.parse("25", USDC) }, { to: fromAddress("0xCHARLIE"), amount: Amount.parse("10", USDC) }, ]); await tx.wait(); ``` ### With Paymaster Fees ```typescript theme={null} const tx = await wallet.transfer( USDC, [{ to: recipient, amount: Amount.parse("100", USDC) }], { feeMode: { type: "paymaster" } } ); ``` ## Working with Amounts The `Amount` class provides precision-safe handling of token amounts, preventing common errors when converting between human-readable values and raw blockchain values. ### Creating Amounts ```typescript theme={null} import { Amount } from "starkzap"; // From human-readable values (what users type) const amount = Amount.parse("1.5", STRK); // 1.5 STRK const amount = Amount.parse("100", USDC); // 100 USDC const amount = Amount.parse("0.001", 18, "ETH"); // With explicit decimals // From raw blockchain values (from contract calls, balance queries) const amount = Amount.fromRaw(1500000000000000000n, STRK); // 1.5 STRK const amount = Amount.fromRaw(100000000n, 6, "USDC"); // 100 USDC const amount = Amount.fromRaw(balance, STRK); // From balance query ``` ### Converting and Displaying ```typescript theme={null} const amount = Amount.parse("1500.50", 18, "ETH"); amount.toUnit(); // "1500.5" — human-readable string amount.toBase(); // 1500500000...0n — raw bigint for contracts amount.toFormatted(); // "ETH 1,500.5" — locale-formatted with symbol amount.toFormatted(true); // "ETH 1,500.5" — compressed (max 4 decimals) amount.getDecimals(); // 18 amount.getSymbol(); // "ETH" ``` ### Arithmetic All arithmetic operations return new `Amount` instances (immutable): ```typescript theme={null} const a = Amount.parse("10", STRK); const b = Amount.parse("3", STRK); a.add(b).toUnit(); // "13" a.subtract(b).toUnit(); // "7" a.multiply(2).toUnit(); // "20" a.multiply("0.5").toUnit(); // "5" a.divide(4).toUnit(); // "2.5" ``` Arithmetic between incompatible amounts (different decimals or symbols) throws an error. ### Comparisons ```typescript theme={null} const a = Amount.parse("10", STRK); const b = Amount.parse("5", STRK); a.eq(b); // false — equal a.gt(b); // true — greater than a.gte(b); // true — greater than or equal a.lt(b); // false — less than a.lte(b); // false — less than or equal a.isZero(); // false a.isPositive(); // true ``` Comparisons between incompatible amounts return `false` (never throw). ## Using Amounts in Transactions Amounts work seamlessly with all SDK methods: ```typescript theme={null} // Transfer const tx = await wallet.transfer(STRK, [ { to: recipient, amount: Amount.parse("10", STRK) }, ]); // Staking await wallet.enterPool(poolAddress, Amount.parse("100", STRK)); // Transaction builder await wallet .tx() .transfer(USDC, { to: alice, amount: Amount.parse("50", USDC) }) .send(); ``` ## Token Approval For operations that require spending tokens (like staking), you may need to approve first: ```typescript theme={null} // Using transaction builder await wallet .tx() .approve(STRK, spenderAddress, Amount.parse("1000", STRK)) .send(); // Or direct approval (if supported by your token) const tx = await wallet.execute([{ contractAddress: STRK.address, entrypoint: "approve", calldata: [spenderAddress, Amount.parse("1000", STRK).toBase(), "0"], }]); ``` ## Best Practices 1. **Always use Amount.parse()** for user input to avoid precision errors 2. **Use token presets** instead of hardcoding addresses 3. **Check balances before transfers** to provide better UX 4. **Use batch transfers** when sending to multiple recipients to save gas 5. **Format amounts for display** using `toFormatted()` for better UX ## Common Patterns ### Checking if User Has Enough Balance ```typescript theme={null} const balance = await wallet.balanceOf(STRK); const transferAmount = Amount.parse("100", STRK); if (balance.lt(transferAmount)) { throw new Error("Insufficient balance"); } const tx = await wallet.transfer(STRK, [ { to: recipient, amount: transferAmount }, ]); ``` ### Checking Balance Covers Transfer + Fee ```typescript theme={null} const balance = await wallet.balanceOf(STRK); const transferAmount = Amount.parse("100", STRK); const estimatedFee = await wallet.estimateFee([/* transfer call */]); // Fee is paid in the fee token (ETH or STRK depending on fee mode). // Compare transfer amount and fee separately against their respective balances. if (balance.lt(transferAmount)) { throw new Error("Insufficient STRK balance for transfer"); } // Check fee token balance separately const feeBalance = await wallet.balanceOf(ETH); const feeAmount = Amount.fromRaw(estimatedFee.overall_fee, ETH); if (feeBalance.lt(feeAmount)) { throw new Error("Insufficient ETH balance for fees"); } ``` ## Next Steps * Learn about [Staking & Delegation](/build/starkzap/staking) * Explore [Transaction Builder](/build/starkzap/transactions#transaction-builder) for complex operations * Check the [API Reference](/build/starkzap/api-reference) for detailed method signatures # Examples Source: https://docs.starknet.io/build/starkzap/examples Complete example applications demonstrating Starkzap in action ## Overview The repository includes three example applications in the [examples/](https://github.com/keep-starknet-strange/starkzap/tree/main/examples) directory: 1. **Web Example** — Svelte + Vite, with three wallet connection methods (Cartridge, Privy, private key) and panels for every SDK module. 2. **Mobile Example** — React Native + Expo, mirroring the web example across eight feature tabs. 3. **Server Example** — Express backend providing Privy wallet operations and a paymaster proxy for the other two. The web and mobile examples cover the same ground and consume the SDK straight from the repo (`file:../..` and `file:../../packages/native`), so a change to `src/` is picked up on the next build. The web example is a **Svelte + Vite** app demonstrating wallet integration in the browser. Its panels are organised by SDK module, one directory per feature under `examples/web/src/features/`. ### Features * ✅ Three wallet connection methods: * Cartridge Controller (social login / passkey) * Privy (server-managed keys) * Private Key (local signing) * ✅ Account deployment and status checking, across account presets (OpenZeppelin, Argent, Braavos, and others) * ✅ Balances, transfers, staking, and yield * ✅ Swaps and DCA (AVNU and Ekubo) * ✅ Lending (Vesu) * ✅ Bridging from Ethereum and Solana, and withdrawals out of Starknet * ✅ Privacy — both the [STRK20 pool](/build/starkzap/privacy/strk20) and [Tongo](/build/starkzap/privacy/tongo) ### Getting Started ```bash theme={null} cd examples/web npm install npm run dev ``` The app will be available at `http://localhost:5173` (or the port Vite assigns). `npm install` also builds the SDK from the repo root, since the example depends on it as `starkzap: file:../..`. ### Running with Privy Server Privy and the paymaster proxy both come from the server example. Run both at once from `examples/web`: ```bash theme={null} npm run dev:all ``` Or start them separately — Vite on 5173, Express on 3001: ```bash theme={null} npm run dev # terminal 1 npm run dev:server # terminal 2 ``` Then point the app at the server with `VITE_PRIVY_SERVER_URL` in `examples/web/.env`. ### Bridge Setup (Web Example) The web example can also demonstrate bridge flows from Ethereum/Solana into Starknet, as well as withdrawals out of Starknet. Set optional environment variables in `examples/web/.env`: ```bash theme={null} VITE_ALCHEMY_API_KEY= # external RPCs for Ethereum/Solana bridge operations VITE_OFT_PUBLIC_KEY= # LayerZero API key for OFT routes VITE_LAYERSWAP_API_KEY= # Layerswap API key for Layerswap routes + token discovery ``` Layerswap keys are environment-scoped. The web example also reads `VITE_LAYERSWAP_API_KEY_MAINNET` / `VITE_LAYERSWAP_API_KEY_TESTNET` (falling back to `VITE_LAYERSWAP_API_KEY`), plus an optional `VITE_LAYERSWAP_BASE_URL` override. SDK setup in `examples/web/src/main.ts`: ```typescript theme={null} const sdk = new StarkZap({ rpcUrl: RPC_URL, chainId: SDK_CHAIN_ID, bridging: { ethereumRpcUrl: `https://eth-mainnet.g.alchemy.com/v2/${VITE_ALCHEMY_API_KEY}`, solanaRpcUrl: `https://solana-mainnet.g.alchemy.com/v2/${VITE_ALCHEMY_API_KEY}`, layerZeroApiKey: VITE_OFT_PUBLIC_KEY, layerswapApiKey: VITE_LAYERSWAP_API_KEY, }, }); ``` ### Key Code Snippets **Connecting with Cartridge:** ```typescript theme={null} const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, deploy: "never", cartridge: { policies: [DUMMY_POLICY] }, }); wallet = onboard.wallet; ``` **Connecting with Privy:** ```typescript theme={null} const accessToken = await privy.getAccessToken(); const walletRes = await fetch(`${PRIVY_SERVER_URL}/api/privy-wallet/starknet`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet: walletData } = await walletRes.json(); const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, deploy: "never", accountPreset: preset, privy: { resolve: async () => ({ walletId: walletData.id, publicKey: walletData.publicKey, serverUrl: `${PRIVY_SERVER_URL}/api/privy-wallet/sign`, }), }, }); wallet = onboard.wallet; ``` **Executing a Transaction:** ```typescript theme={null} const tx = await wallet.execute([ { contractAddress: STRK_CONTRACT, entrypoint: "transfer", calldata: [wallet.address, "0", "0"], }, ]); await tx.wait(); ``` The mobile example is a React Native + Expo app covering the same ground as the web example, organised as eight feature tabs. Connect with **Privy** (social login) or a **private key**. It consumes the native package via `"starkzap-native": "file:../../packages/native"`. ### What the example does * **Onboarding** — Sign in with Privy (email, Google, etc.) or paste a Starknet private key; choose Mainnet or Sepolia. * **Balances** — View STRK, USDC, WBTC (and network-specific tokens) with optional USD conversion. * **Transfers** — Single and batch ERC20 transfers with amount input and explorer links. * **Staking** — Enter delegation pools, add to stake, claim rewards, exit (intent + complete). * **Yield** — LST staking positions. * **Swap** — Token swaps and DCA orders. * **Lending** — Supply, borrow, and repay with Vesu. * **Bridge** — Deposits from Ethereum and Solana, and withdrawals out of Starknet. * **Privacy** — Both the [STRK20 pool](/build/starkzap/privacy/strk20) and [Tongo](/build/starkzap/privacy/tongo), selected with a toggle. * **UX** — Themed light/dark UI, transaction toasts with explorer links, copyable errors, logs FAB. ### Configure environment (required for Privy) **`You must set up .env before running`**, or the app will show a **"Configuration required"** screen with instructions. 1. Copy the example env file: `cp .env.example .env` 2. Edit `examples/mobile/.env` and set at least: | Variable | Required | Description | | - | - | - | | `EXPO_PUBLIC_PRIVY_APP_ID` | Yes (for Privy) | From [Privy Dashboard](https://dashboard.privy.io) → your app → App ID | | `EXPO_PUBLIC_PRIVY_CLIENT_ID` | Optional | From Privy Dashboard → Clients | | `EXPO_PUBLIC_PRIVY_SERVER_URL` | Optional | Your backend URL for server-side signing (e.g. `http://localhost:3001`) | | `EXPO_PUBLIC_PAYMASTER_PROXY_URL_MAINNET` / `_SEPOLIA` | Optional | Paymaster proxy per network, e.g. `http://localhost:3001/api/paymaster/mainnet`. Per network because each AVNU deployment whitelists only its own pool, and one build switches chains at runtime | Without `EXPO_PUBLIC_PRIVY_APP_ID`, the app still runs but **Privy login is disabled**; you can use the **private key** flow only. **Privy Dashboard (Expo Go):** If you use Expo Go and see "Native app ID `host.exp.Exponent` has not been set as an allowed app identifier", add `host.exp.Exponent` in [Privy Dashboard](https://dashboard.privy.io) → **Configuration → App settings → Clients** → **Allowed app identifiers**. **OAuth (Google, Apple, etc.):** Enable login methods and set [allowed URL schemes](https://docs.privy.io/basics/get-started/dashboard/app-clients#allowed-url-schemes) for redirects. ### Getting Started Install at the **repository root** first, then in the example: ```bash theme={null} npm install cd examples/mobile npm install npx expo start ``` Then open the app in the iOS simulator, Android emulator, or Expo Go (scan QR code). If `.env` is not configured, you'll see the "Configuration required" screen. **Optional: Privy server** — For server-side signing, run `examples/server`, then set `EXPO_PUBLIC_PRIVY_SERVER_URL` in `examples/mobile/.env` to that server URL (e.g. `http://localhost:3001`). ### Project Structure ``` examples/mobile/src/ +-- app/ # Expo Router pages | +-- index.tsx # Onboarding (Privy / private key) | `-- (tabs)/ # Tab navigation | +-- balances.tsx | +-- transfers.tsx | +-- staking.tsx | +-- yield.tsx | +-- swap.tsx | +-- lending.tsx | +-- bridge.tsx | `-- privacy.tsx # STRK20 and Tongo panels +-- features/ # One directory per feature, with its store and panels +-- core/ # Config, network, wallet store +-- ui/ # Reusable components +-- theme/ # Light/dark theming `-- polyfills.ts # React Native polyfills, imported at the entry ``` ### Key Code Snippets **Checking Balances:** ```typescript theme={null} const balance = await wallet.balanceOf(STRK); console.log(balance.toFormatted()); // "STRK 150.25" ``` **Transferring Tokens:** ```typescript theme={null} const tx = await wallet.transfer(STRK, [ { to: fromAddress("0xRECIPIENT"), amount: Amount.parse("10", STRK) }, ]); await tx.wait(); ``` **Staking Operations:** ```typescript theme={null} await wallet.enterPool(poolAddress, Amount.parse("100", STRK)); await wallet.claimPoolRewards(poolAddress); ``` **React Native:** The example includes the usual polyfills (`react-native-get-random-values`, `fast-text-encoding`) and uses the SDK's optional peer deps for React Native. See the [examples/mobile README](https://github.com/keep-starknet-strange/starkzap/tree/main/examples/mobile) in the repo for full run instructions and env details. Express.js backend for Privy wallet operations: wallet creation, signing, and paymaster integration. ### Features * ✅ Privy wallet creation and management * ✅ Transaction signing endpoint * ✅ AVNU Paymaster proxy for sponsored transactions * ✅ Account registration and deployment tracking * ✅ Simple file-based storage (use a database in production) ### Getting Started [Login to Privy](https://dashboard.privy.io/) ([Privy Docs.](https://docs.privy.io/basics/get-started/dashboard/overview)) On the Privy Dashboard, create an App, and retrieve the API Keys. Configure your `env.` file. ```bash theme={null} cd examples/server cp .env.example .env # then fill in your Privy keys npm install npm start ``` The server runs on `http://localhost:3001`. ### Environment Variables * `ENABLE_PRIVY` - Enable the Privy wallet routes * `PRIVY_APP_ID` - Your Privy application ID (required for Privy routes) * `PRIVY_APP_SECRET` - Your Privy application secret (required for Privy routes) * `ENABLE_PAYMASTER` - Enable the paymaster proxy route * `AVNU_API_KEY` - AVNU Paymaster API key (optional; set it for sponsored mode) * `AVNU_PAYMASTER_URL_MAINNET` / `AVNU_PAYMASTER_URL_SEPOLIA` - Upstream paymaster per network (both default to AVNU's public endpoints) Wallet data is persisted to `wallets.json`, which is created on demand. It is a file store for development — use a real database in production. ### API Endpoints | Endpoint | Method | Auth | Description | | - | - | - | - | | `/api/health` | GET | No | Returns `{ status: "ok" }`. Used to check the server is reachable before attempting Privy operations. | | `/api/health/privy` | GET | No | Present only when the Privy routes are enabled. | | `/api/health/paymaster` | GET | No | Present only when the paymaster proxy is enabled. | | `/api/privy-wallet/starknet` | POST | Bearer token | Creates a Starknet wallet via Privy, or returns the existing one. Returns `{ wallet: { id, address, publicKey }, accounts, isNew }`. | | `/api/privy-wallet/register-account` | POST | Bearer token | Associates a computed account address with a preset. Body: `{ preset, address, deployed? }`. | | `/api/privy-wallet/set-deployed` | POST | Bearer token | Updates the deployment status of a registered account. Body: `{ preset, deployed }`. | | `/api/privy-wallet/sign` | POST | No | Signs a transaction hash. Body: `{ walletId, hash }`. Returns `{ signature }`. | | `/api/paymaster/:network` | POST | No | Proxies the body to that network's AVNU paymaster, attaching the API key header. Returns the response as-is. The bare `/api/paymaster` returns 400 naming the two valid paths, rather than silently picking a network. | #### Sign Transaction Hash ```bash theme={null} POST /api/privy-wallet/sign Content-Type: application/json { "walletId": "wallet-id", "hash": "0x..." } ``` Returns `{ signature: [...] }` #### Paymaster Proxy ```bash theme={null} POST /api/paymaster/mainnet # or /api/paymaster/sepolia Content-Type: application/json { "method": "estimate_fee", "params": { ... } } ``` Forwards requests to AVNU Paymaster with API key authentication. The network is part of the path because each AVNU deployment whitelists only its own privacy pool: send a mainnet pool to the Sepolia paymaster and it answers `156 :: privacy pool address is not whitelisted` from two layers away. One API key covers both. This route raises the JSON body limit to 4 MB. A [STRK20](/build/starkzap/privacy/strk20) proof runs to roughly 300–320 KB for a simple transfer and more for transactions carrying more actions, which is past Express's 100 KB default — and that default rejects them with an HTML 413 before any route runs, surfacing as "non-JSON response" rather than a size problem. Any proxy you put in front of a paymaster needs the same allowance. The route is **unauthenticated**, which is only safe on localhost. The reason to run a proxy at all is that it holds the API key so the key never ships in a browser bundle — which makes the route a way to *spend* that key without ever seeing it. Reachable from the internet, it is an open relay for your sponsorship budget. Gate it before deploying anything shaped like this, and gate the paymaster route specifically. `privacy.paymaster.fetch` is where a client-side credential goes. ### Key Code Snippets **Signing Endpoint:** ```typescript theme={null} app.post("/api/privy-wallet/sign", async (req, res) => { const { walletId, hash } = req.body; const result = await privy .wallets() .rawSign(walletId, { params: { hash } }); res.json({ signature: result.signature }); }); ``` **Paymaster Proxy:** ```typescript theme={null} app.post("/api/paymaster/:network", async (req, res) => { const upstream = UPSTREAMS[req.params.network]; if (!upstream) return res.status(400).json({ error: "Unknown network" }); const response = await fetch(upstream, { method: "POST", headers: { "Content-Type": "application/json", ...(AVNU_API_KEY && { "x-paymaster-api-key": AVNU_API_KEY }), }, body: JSON.stringify(req.body), }); const data = await response.json(); res.status(response.status).json(data); }); ``` ## Common Patterns ### Error Handling All examples include proper error handling: ```typescript theme={null} try { const tx = await wallet.execute(calls); await tx.wait(); console.log("Success!"); } catch (error) { console.error("Transaction failed:", error); // Show user-friendly error message } ``` ### Loading States Examples demonstrate loading state management: ```typescript theme={null} function setButtonLoading(btn: HTMLButtonElement, loading: boolean) { if (loading) { btn.disabled = true; btn.innerHTML = ''; } else { btn.disabled = false; btn.textContent = "Submit"; } } ``` ### Transaction Tracking All examples show how to track transaction status: ```typescript theme={null} const tx = await wallet.execute(calls); console.log(`Tx hash: ${tx.hash}`); console.log(`Explorer: ${tx.explorerUrl}`); await tx.wait(); console.log("Transaction confirmed!"); ``` ## Next Steps * Explore the example code in the `examples/` directory * Adapt the patterns to your own application * Check the [API Reference](/build/starkzap/api-reference) for detailed method documentation * Review the [Troubleshooting](/build/starkzap/troubleshooting) guide if you encounter issues # Glossary Source: https://docs.starknet.io/build/starkzap/glossary Web3 terminology explained in web2-friendly terms ## Key Concepts for Web2 Developers If you're new to blockchain development, here are the key terms you'll encounter, explained using familiar web2 concepts: ### Wallet **Web2 Equivalent:** Bank account or user account A wallet is like a bank account on the blockchain. It has: * An **address** (like an account number) - where you receive funds * A **private key** (like a password) - used to authorize transactions * A **public key** (like a username) - derived from the private key, can be shared Unlike a bank account, users control their wallet directly—there's no bank in the middle. ### Signer **Web2 Equivalent:** Authentication/Authorization system A signer is what proves you own a wallet and authorizes transactions. Think of it like: * **OAuth tokens** - prove you're logged in * **API keys** - authorize API calls * **Digital signatures** - prove you authorized a transaction The SDK supports different signer types: * **Private Key Signer** - Like storing a password locally (for servers only) * **Privy Signer** - Like OAuth, keys managed by a service * **Cartridge Signer** - Like social login, keys managed by Cartridge ### RPC (Remote Procedure Call) **Web2 Equivalent:** API endpoint An RPC endpoint is like a REST API endpoint, but for blockchain operations. It's how your app communicates with the blockchain network to: * Read data (like checking balances) * Submit transactions (like sending tokens) * Query the network state Think of it like calling `fetch()` to a blockchain API instead of your own backend. ### Network / Chain **Web2 Equivalent:** Environment (production, staging, development) * **Mainnet** = Production - Real money, real transactions * **Testnet (Sepolia)** = Staging - Fake money for testing * **Devnet** = Local development - Your own test blockchain Just like you have `production`, `staging`, and `localhost` environments. ### Gas / Transaction Fees **Web2 Equivalent:** Credit card processing fees or API usage costs Gas is the fee paid to process transactions on the blockchain. Think of it like: * Credit card processing fees (2-3% per transaction) * AWS API Gateway costs (pay per request) * Stripe transaction fees With the SDK, you can: * **User pays** - User covers the gas fee (like normal) * **Sponsored** - You pay the gas fee for users (like offering free shipping) ### Address **Web2 Equivalent:** Account number, email address, or user ID An address is a unique identifier for a wallet on the blockchain. It's like: * A bank account number (where you send money) * An email address (where you send messages) * A user ID in your database Example: `0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d` ### Private Key **Web2 Equivalent:** Password or secret key A private key is a secret that proves you own a wallet. It's like: * A password for your bank account * An API secret key * A JWT signing key **⚠️ Never share your private key!** It's like sharing your password—anyone with it can control your wallet. ### Public Key **Web2 Equivalent:** Username or public identifier A public key is derived from a private key and can be shared. It's like: * A username (public, can be shared) * An email address (public identifier) * A user ID (public reference) Unlike a private key, sharing your public key is safe. ### Transaction **Web2 Equivalent:** API request or database operation A transaction is an operation on the blockchain, like: * Sending tokens (like a bank transfer) * Calling a smart contract function (like an API call) * Deploying a contract (like deploying a service) Transactions are: * **Immutable** - Once confirmed, can't be changed (like a database commit) * **Public** - Anyone can see them (like public API logs) * **Atomic** - Either fully succeed or fully fail (like database transactions) ### Deploy **Web2 Equivalent:** Creating an account or provisioning a service On Starknet, wallets are smart contracts that need to be "deployed" (created on the blockchain) before they can be used. It's like: * Creating a new user account in your database * Provisioning a new AWS service * Setting up a new bank account The SDK handles this automatically with `deploy: "if_needed"`. ### Token / ERC20 **Web2 Equivalent:** Currency or digital asset A token is like a currency or digital asset on the blockchain. Think of it like: * **Bitcoin** - A cryptocurrency * **USDC** - A stablecoin (like digital dollars) * **STRK** - Starknet's native token * **Custom tokens** - Like loyalty points or in-game currency The SDK makes it easy to send and receive any token, just like sending money via Venmo or PayPal. ### Staking **Web2 Equivalent:** Savings account or investment Staking is like putting money in a savings account that earns interest. You: * Lock up tokens (like depositing money) * Earn rewards over time (like interest) * Can withdraw later (like closing the account) The SDK handles all the complexity—you just call `wallet.enterPool()`. ### Smart Contract **Web2 Equivalent:** Backend service or API A smart contract is code that runs on the blockchain. Think of it like: * A backend API endpoint * A microservice * A database stored procedure Unlike traditional code, smart contracts: * Run on the blockchain (decentralized) * Can't be changed once deployed (immutable) * Execute automatically when called ### Account Preset **Web2 Equivalent:** Account type or user role An account preset defines what type of wallet account to create. It's like: * Choosing between "Personal" vs "Business" account types * Selecting a user role (admin, user, guest) * Picking a subscription tier Different presets have different features and security models (like OpenZeppelin, Argent, Braavos). ## Privacy terms Starkzap supports two independent privacy protocols. See [Choosing a privacy protocol](/build/starkzap/privacy/overview) for which to use. ### STRK20 privacy pool **Web2 Equivalent:** A pooled clearing account One contract holding balances for every token. Instead of a public balance on your own account, your funds sit in the pool as private **notes** that only you can find. Because the pool moves the funds on your behalf, your account does not appear on the transaction. Hides amounts, sender, and recipient. See [STRK20](/build/starkzap/privacy/strk20). ### Tongo **Web2 Equivalent:** An account with the balance column encrypted A separate protocol, one contract per token, where your balance stays on your own account but is stored encrypted. Hides amounts only — you still send the transaction yourself, so your address is visible. See [Tongo](/build/starkzap/privacy/tongo). ### Note **Web2 Equivalent:** An individual banknote, rather than a balance The unit of private value in the STRK20 pool. A private balance is a *set* of notes, not a single number, and spending means proving you own specific notes. Spending part of a note produces change, called **surplus**, which returns to you as a new private note. ### Viewing key **Web2 Equivalent:** A read-only API key for your own data The key that lets you find and decrypt your own notes, and lets others send to you. Starkzap derives it from your signing key rather than storing it, so it survives a wallet reinstall with nothing to back up. The pool keeps the first key an account registers and it cannot be replaced. ### Proving service **Web2 Equivalent:** An external service that notarizes a claim The remote service that builds the cryptographic proof for each private transaction. It also screens deposits. Named `prover` in configuration. ### Discovery service **Web2 Equivalent:** A search index over encrypted data The service that indexes encrypted on-chain state so your wallet can find its own notes. Named `discovery` in configuration. ### Relayer **Web2 Equivalent:** A courier who posts your letter for you The party that submits your private transaction. Because the relayer sends it, the relayer's address appears on-chain instead of yours — that is what keeps your account off-chain. Distinct from the **forwarder**, which is the party that receives the fee. ### Pool fee **Web2 Equivalent:** A service charge, separate from postage A fee the pool charges, withdrawn from your private balance inside the proof. It is separate from gas, and transaction simulation does not include it — so quote it and show it to users explicitly. ### Screening **Web2 Equivalent:** A sanctions check at account opening A check the proving service runs against deposit sources before producing a proof. A rejection is final; if screening is unavailable, deposits fail closed rather than proceeding unchecked. ## Common Patterns ### Checking a Balance **Web2 Equivalent:** `SELECT balance FROM accounts WHERE user_id = ?` ```typescript theme={null} const balance = await wallet.balanceOf(STRK); // Like: const balance = await db.query('SELECT balance...'); ``` ### Sending Tokens **Web2 Equivalent:** `UPDATE accounts SET balance = balance - amount WHERE user_id = ?` ```typescript theme={null} await wallet.transfer(STRK, [{ to: recipient, amount }]); // Like: await paymentService.sendMoney(userId, recipientId, amount); ``` ### Waiting for Confirmation **Web2 Equivalent:** Polling an API until a job completes ```typescript theme={null} const tx = await wallet.transfer(...); await tx.wait(); // Wait for blockchain confirmation // Like: await pollUntilComplete(jobId); ``` ## Still Confused? If you're still unsure about a term, think of it this way: * **Blockchain** = A distributed database that everyone can read/write * **Wallet** = A user account on that database * **Transaction** = A write operation to that database * **Token** = A number in that database representing value The SDK abstracts away most of the complexity—you mostly just need to know: 1. Connect a wallet (like logging in) 2. Check balances (like querying a database) 3. Send tokens (like making an API call) # Installation Source: https://docs.starknet.io/build/starkzap/installation Install Starkzap and its dependencies ## Installation Install Starkzap using npm or yarn: ```bash theme={null} npm install starkzap ``` or ```bash theme={null} yarn add starkzap ``` Building for React Native/Expo? Use [React Native Integration](/build/starkzap/react-native) and install `starkzap-native` instead of using `starkzap` directly in your mobile app. ### Dependencies The SDK depends on: * [`starknet`](https://www.npmjs.com/package/starknet) (v10+) - Starknet.js core library * `@starknet-io/starknet-types-0103` - RPC spec 0.10.3 type declarations, referenced by Starkzap's published types Both are installed automatically when you install `starkzap`. #### Peer dependencies by feature All peer dependencies are **optional** to keep the package lean. Install only what you need: | Feature | Included in | Peer Dependencies | | - | - | - | | **Ethereum bridging** (deposit / withdraw to Ethereum) | `starkzap`,
`starkzap-native` | `ethers@^6.16.0` | | **Solana bridging** (deposit / withdraw to Solana via Hyperlane) | `starkzap`,
`starkzap-native` | `@solana/web3.js@^1.98.4`, `@hyperlane-xyz/sdk@^14.4.0`, `@hyperlane-xyz/registry@^19.4.0`, `@hyperlane-xyz/utils@^14.4.0` | | **Cartridge Controller wallet** (web-only) | `starkzap` | `@cartridge/controller@^0.13.13` | | **AVNU swaps & DCA** (`AvnuSwapProvider`, `AvnuDcaProvider`) | `starkzap`,
`starkzap-native` | `@avnu/avnu-sdk@^4.2.0` | | **Tongo confidential transfers** | `starkzap`,
`starkzap-native` | `@fatsolutions/tongo-sdk@^1.5.0` | | **STRK20 privacy pool** | `starkzap`,
`starkzap-native` | `@starkware-libs/starknet-privacy-sdk@^0.14.3-rc.5` — see [the extra setup below](#strk20-privacy-pool) | | **React Native / Expo** | `starkzap-native` | `react-native-get-random-values@^1.11.0`, `fast-text-encoding@^1.0.6`, `@ethersproject/shims@^5.7.0`, `buffer@^6.0.3` | Two features are reached through their own entry points rather than the package root: **Cartridge** from `starkzap/cartridge`, and the **STRK20 privacy pool** from `starkzap/privacy` (`starkzap-native/privacy` on React Native). Their types name the optional peer, and carrying them on the root entry would make that peer mandatory for everyone — so the subpath is what keeps it optional. Everything else, bridging and Tongo included, imports from the root. These are the ranges Starkzap declares as peer dependencies. Installing an older minor within the same major (`ethers@6.0.0`, say) satisfies the docs but not the peer range, and npm reports a conflict — so install at least the versions listed. ```bash theme={null} # Ethereum bridging npm install ethers # Solana bridging npm install @solana/web3.js @hyperlane-xyz/sdk @hyperlane-xyz/registry @hyperlane-xyz/utils # Cartridge Controller (only for Web) npm install @cartridge/controller # AVNU swaps & DCA (only needed for AvnuSwapProvider / AvnuDcaProvider; Ekubo needs nothing extra) npm install @avnu/avnu-sdk # Tongo confidential transfers npm install @fatsolutions/tongo-sdk # React Native / Expo (use starkzap-native instead of starkzap) npm install starkzap-native react-native-get-random-values fast-text-encoding @ethersproject/shims buffer ``` #### STRK20 privacy pool The privacy SDK is not on npm, so it needs two extra steps before `npm install` works. Installing the SDK is not enough to deposit. Deposit screening runs through Elliptic under a StarkWare API key, so the proving service that performs it is not public — contact StarkWare for deposit access. Transfers, withdrawals, and the discovery service can be self-hosted. See [STRK20 privacy pool](/build/starkzap/privacy/strk20). Point the `@starkware-libs` scope at GitHub Packages in your project's `.npmrc`: ```txt theme={null} @starkware-libs:registry=https://npm.pkg.github.com ``` Reads require authentication even though the package is public. Put a token with `read:packages` in your **`~/.npmrc`**, never in the committed project file: ```txt theme={null} //npm.pkg.github.com/:_authToken=YOUR_TOKEN ``` Then install: ```bash theme={null} # STRK20 privacy pool (requires Node 24+) npm install @starkware-libs/starknet-privacy-sdk ``` Node 24 or later is required for the WebCrypto APIs the OHTTP transport relies on. This is not enforced by the installer, so an older Node fails at runtime rather than at install time. ## Next Steps Once installed, configure the SDK: 1. Set up [Configuration](/build/starkzap/configuration) for networks, RPC providers, and optional features 2. If you are building a mobile app, follow [React Native Integration](/build/starkzap/react-native) for `starkzap-native` + Metro setup 3. Then check out the [Quick Start Guide](/build/starkzap/quick-start) to get your first wallet integration working in minutes # AVNU Paymaster Source: https://docs.starknet.io/build/starkzap/integrations/avnu-paymaster Paymaster integration for covering transaction gas costs on behalf of users AVNU Paymaster integration hero ## Overview [AVNU Paymaster](https://docs.avnu.fi/docs/paymaster/index) enables two modes of gas payment on Starknet: * **Gasfree**: You sponsor all gas costs for your users * **Gasless**: Users pay gas in any supported token (USDC, USDT, etc.) instead of STRK AVNU pioneered paymaster on Starknet and powers 50+ dApps. Starkzap integrates seamlessly with AVNU Paymaster for sponsored transactions. **For complete setup instructions and detailed configuration**, see the [Paymasters Guide](/build/starkzap/paymasters) which covers both AVNU Paymaster and Cartridge's built-in paymaster with step-by-step instructions, code examples, and best practices. ## Quick Reference ### When to Use AVNU Paymaster * ✅ Using **Privy** strategy for wallet connection * ✅ Using **Private Key** strategy (server-side) * ✅ Want to sponsor all gas fees for users (gasfree mode) * ✅ Want users to pay gas in tokens instead of STRK (gasless mode) ### Basic Configuration ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", paymaster: { nodeUrl: "https://starknet.paymaster.avnu.fi", headers: { "x-paymaster-api-key": "your-api-key-here" }, }, }); ``` ### Supported Tokens (Gasless Mode) USDC, USDT, DAI, ETH, STRK, WBTC, solvBTC, LBTC, EKUBO, NSTR, LORDS, wstETH, and more. ## Resources * [Paymasters Guide](/build/starkzap/paymasters) - Complete setup and configuration guide * [AVNU Paymaster Documentation](https://docs.avnu.fi/docs/paymaster/index) * [AVNU Portal](https://portal.avnu.fi) - Get your API key * [Propulsion Program](https://docs.avnu.fi/docs/paymaster/propulsion-program) - Gas subsidies up to \$1M # Cartridge Controller Source: https://docs.starknet.io/build/starkzap/integrations/cartridge-controller Use Cartridge Controller with network-safe session configuration Cartridge controller integration hero ## Overview [Cartridge Controller](https://docs.cartridge.gg/controller/overview) is specialized for gaming applications and provides a powerful abstraction layer for managing account access and ownership on Starknet. Cartridge enables: * **Social Login**: Users can sign in with social accounts (Google, Twitter, etc.) * **Passkey Authentication**: Biometric authentication (Face ID, Touch ID, Windows Hello) * **Policy-Based Access**: Define policies for what contracts/methods can be called * **Delegated Transactions**: Users can approve transactions without managing keys directly * **Built-in Paymaster**: **Policy-bound sponsorship** — Cartridge can sponsor fees for calls that match **approved policies** (and eligible session / SNIP-9 paths), not every arbitrary transaction. Starkzap integrates with Cartridge for gaming-style flows: **web** uses `@cartridge/controller`; **React Native / Expo** uses `starkzap-native` with a **native session adapter** (register it before connecting) when your package version supports native Cartridge. ## StarkZap integration When StarkZap connects Cartridge, it forwards your SDK network configuration (for example `rpcUrl` and `chainId`) into the Cartridge flow so you typically configure the network once on `new StarkZap(...)`. **Network alignment is not auto-validated.** You must use the same `rpcUrl` / `chainId` (or `network` preset) you intend the Cartridge session and paymaster to target. The SDK forwards these values but does **not** verify that some other tab, deep link, or preset was opened on a different chain. Mismatches often show up as SNIP-9 or paymaster errors. You do not need to pass chain settings again when calling `sdk.connectCartridge()` or `sdk.onboard({ strategy: "cartridge" })` on the same `StarkZap` instance. ## Web (browser) vs React Native | | **Web** | **React Native / Expo** | | - | - | - | | Package | `starkzap` + `@cartridge/controller` | `starkzap-native` (depends on `starkzap`) | | UX | Controller popup / embedded flow | In-app browser / deep link **session** flow | | Before connect | Install `@cartridge/controller` | [React Native setup](/build/starkzap/react-native): Metro `withStarkzap`, **register** the native Cartridge adapter when your SDK version exports it | | Import | `import { StarkZap } from "starkzap"` | `import { StarkZap } from "starkzap-native"` | ## React Native / Expo (native Cartridge session) 1. Follow [React Native Integration](/build/starkzap/react-native) for Metro (`withStarkzap`) and dependencies. 2. **Register** the native Cartridge adapter once at startup before `connectCartridge` / `onboard({ strategy: "cartridge" })` (for example `registerCartridgeTsAdapter` / `registerCartridgeNativeAdapter` — exact exports depend on your `starkzap-native` version). 3. Pass **policies** and/or a Cartridge **preset** that resolves policies for your chain. 4. **Onboarding:** native Cartridge often defaults to **`deploy: "never"`** or recommends it when deployment differs from the browser Controller; pass `deploy: "if_needed"` explicitly if you need core-style deployment checks. 5. **Sponsored execution** on native is tied to the session wallet (commonly **`feeMode: { type: "paymaster" }`** only; `gasToken` is not supported on native Cartridge). See the `examples/tic-tac-toe` app in the [Starkzap repository](https://github.com/keep-starknet-strange/starkzap) when available for a full Expo reference. ## Why Use Cartridge? * ✅ **Perfect for Gaming**: Specialized for gaming applications with session-based transactions * ✅ **Gasless-style UX for approved calls**: Built-in paymaster can sponsor **policy-matching** transactions after users approve policies * ✅ **Better UX**: No seed phrases or private key management * ✅ **Social Login**: Users sign in with familiar accounts (Google, Twitter, etc.) * ✅ **Biometric Auth**: Face ID, Touch ID, Windows Hello support * ✅ **Policy Control**: Define what contracts/methods users can interact with * ✅ **Session Management**: Users approve policies once, then transactions happen automatically **Key value:** Unlike Privy or private-key flows (which often use AVNU Paymaster for sponsorship), Cartridge can sponsor **policy-approved** calls through its paymaster/session stack—so matching game or token operations stay gasless after users approve policies. Other calls may still require user fees or extra steps. ## Setup (web browser) ### 1. Install Cartridge Controller For **web**, install the Controller peer used by `starkzap`: ```bash theme={null} npm install @cartridge/controller ``` ### 2. Initialize SDK ```typescript theme={null} import { StarkZap, OnboardStrategy } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); ``` ## Integration (web) ### Basic Connection Connect a wallet using Cartridge Controller. Define policies that specify what contracts/methods can be called, and all matching transactions will be automatically sponsored (gasless) by Cartridge's built-in paymaster: ```typescript theme={null} const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies: [ { target: "0xTOKEN_CONTRACT", method: "transfer", }, { target: "0xGAME_CONTRACT", method: "play_card", }, ], }, deploy: "if_needed", }); const wallet = onboard.wallet; ``` ### Using Policies Policies define what contracts and methods can be called in paymastered transactions. Users approve these policies once when connecting, and then all transactions matching those policies are automatically sponsored by Cartridge's paymaster: ```typescript theme={null} const policies = [ // Allow transfers from a specific token contract { target: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", // STRK method: "transfer", }, // Allow game operations { target: "0xGAME_CONTRACT", method: "play_card", }, { target: "0xGAME_CONTRACT", method: "claim_rewards", }, // Allow staking operations { target: "0xSTAKING_CONTRACT", method: "enter_delegation_pool", }, ]; const wallet = await sdk.connectCartridge({ policies }); // All transactions matching these policies are automatically paymastered // No user approval needed for individual transactions ``` ### Session Registration When users connect and approve policies, a session is automatically registered. For paymastered transactions, sessions can be registered without requiring additional signatures—the initial policy approval is sufficient. This enables seamless, gasless transaction execution: ```typescript theme={null} // User connects and approves policies const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies }, }); // Session is automatically registered // All transactions matching policies are paymastered automatically const wallet = onboard.wallet; // Execute transactions - no approval prompts, no gas fees await wallet.execute([call]); ``` ### Accessing Controller Features Get access to Cartridge-specific features: ```typescript theme={null} const wallet = await sdk.connectCartridge({ policies }); // Access the controller const controller = wallet.getController(); // Open user profile controller.openProfile(); // Check if user is authenticated const isAuthenticated = controller.isAuthenticated(); ``` ## Complete Example ```typescript theme={null} import { StarkZap, OnboardStrategy, Amount, fromAddress, mainnetTokens } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); // Define policies for what the user can do // All transactions matching these policies will be automatically paymastered const policies = [ { target: mainnetTokens.STRK.address, method: "transfer", }, { target: "0xGAME_CONTRACT", method: "play_card", }, ]; // Connect with Cartridge // User approves policies once, session is registered automatically const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies }, deploy: "if_needed", }); const wallet = onboard.wallet; // Use the wallet like any other const balance = await wallet.balanceOf(mainnetTokens.STRK); console.log(`Balance: ${balance.toFormatted()}`); // Transfer tokens - automatically paymastered (no approval, no gas fees) // Because it matches the approved policy const tx = await wallet.transfer(mainnetTokens.STRK, [ { to: fromAddress("0xRECIPIENT"), amount: Amount.parse("10", mainnetTokens.STRK), }, ]); await tx.wait(); console.log("Transfer complete! (Gas paid by Cartridge)"); ``` ## User Flow (web) 1. **User clicks "Connect with Cartridge"** 2. **Cartridge popup appears** with social login options 3. **User signs in** with Google, Twitter, or passkey 4. **Wallet is created** and connected automatically 5. **User approves policies** - Defines what contracts/methods can be called 6. **Session is registered** - Automatically registered for paymastered transactions 7. **Transactions execute automatically** - All transactions matching policies are paymastered (gasless) without additional approval ## Policy Management ### How Policies Work with Paymaster Policies serve two purposes: 1. **Security**: Define what contracts/methods can be called 2. **Paymaster eligibility**: Only transactions matching policies are automatically paymastered When a transaction matches an approved policy, it's automatically sponsored by Cartridge's paymaster. Transactions outside the policies require user approval and may not be paymastered. ### Dynamic Policies You can update policies based on user actions. Note that updating policies requires reconnection and user approval: ```typescript theme={null} // Initial connection with basic policies const wallet = await sdk.connectCartridge({ policies: [ { target: TOKEN_ADDRESS, method: "transfer" }, ], }); // Later, add more policies (requires reconnection and user approval) const updatedWallet = await sdk.connectCartridge({ policies: [ { target: TOKEN_ADDRESS, method: "transfer" }, { target: GAME_ADDRESS, method: "play_card" }, { target: STAKING_ADDRESS, method: "enter_delegation_pool" }, ], }); // New session is registered with expanded policies // All matching transactions are automatically paymastered ``` ### Policy Best Practices 1. **Be specific**: Only allow the methods users actually need 2. **Start minimal**: Begin with basic policies, add more as needed 3. **Explain policies**: Let users know what they're approving and that matching transactions will be gasless 4. **Group related operations**: Put related game actions in the same policy set 5. **Review regularly**: Update policies based on user feedback and game features ## Error Handling ```typescript theme={null} try { const wallet = await sdk.connectCartridge({ policies }); } catch (error) { if (error.message.includes("popup")) { console.error("User blocked popup or closed window"); } else if (error.message.includes("authentication")) { console.error("Authentication failed"); } else { console.error("Connection failed:", error); } } ``` ## Resources * [Cartridge Controller Documentation](https://docs.cartridge.gg/controller/overview) * [Cartridge Website](https://cartridge.gg) * [Policy Documentation](https://docs.cartridge.gg/controller/policies) ## Best Practices 1. **Request minimal permissions** - Only ask for what users need 2. **Handle popup blockers** - Guide users if popups are blocked 3. **Provide clear instructions** - Explain the connection process 4. **Test on multiple browsers** - Ensure compatibility 5. **Monitor user experience** - Track connection success rates # Privy Integration Source: https://docs.starknet.io/build/starkzap/integrations/privy Server-side key management and wallet infrastructure for Starknet applications Privy integration hero ## Overview [Privy](https://privy.io) provides secure, server-side key management for Starknet wallets. With Privy, you can: * Create and manage Starknet wallets for users * Sign transactions server-side without exposing private keys * Support social login and email-based authentication * Integrate with existing Privy authentication flows Privy supports Starknet as a [Tier 2 chain](https://docs.privy.io/recipes/use-tier-2#starknet), allowing you to use Privy's raw sign functionality for transaction signing. ## Why Use Privy? * ✅ **Security**: Private keys never leave Privy's secure infrastructure * ✅ **User Experience**: Social login and email-based authentication * ✅ **Server-Side Signing**: Sign transactions on your backend * ✅ **Multi-Chain**: Same infrastructure for multiple blockchains * ✅ **Gasless Transactions**: Configure AVNU Paymaster for sponsored transactions (see [AVNU Paymaster Integration](/build/starkzap/integrations/avnu-paymaster)) ## Wallet ownership model Privy supports two ways to use wallets. Choosing the wrong one can lead to JWT or auth errors, so use this as a guide: | Wallet type | Requires user JWT? | Best for | | - | - | - | | **Server-managed** | No | Backend signing, custodial flows | | **User-owned** | Yes | End-user auth, user-linked wallets | * **Server-managed**: Create wallets without an owner (omit `owner`). Your backend signs with `PrivyClient` (app credentials) and no end-user JWT is required. Create with just `{ chain_type: "starknet" }`. Best for custodial / backend-only flows. * **User-owned**: Create wallets with `owner: { user_id }` (the user's DID) so the wallet is tied to a Privy user; access and signing then require that user's JWT. **This is the model the examples below use** — the client authenticates with a Privy client SDK, and the backend verifies the token before creating/using the wallet. Note: `owner: { user_id }` takes the user's DID (`did:privy:...`). The separate `owner_id` field expects a **cuid2 key-quorum id**, not a user DID — passing a DID there fails with an `Invalid cuid2` error. ## Setup ### 1. Install Privy ```bash theme={null} npm install @privy-io/node ``` ### 2. Initialize Privy Client ```typescript theme={null} import { PrivyClient } from "@privy-io/node"; const privy = new PrivyClient({ appId: process.env.PRIVY_APP_ID!, appSecret: process.env.PRIVY_APP_SECRET!, }); ``` ### 3. Create a Starknet Wallet ```typescript theme={null} // Server-managed wallet (app-owned) — no end-user JWT required to sign. const wallet = await privy.wallets().create({ chain_type: "starknet" }); // Or tie the wallet to a Privy user (user-owned). Note: it's `owner: { user_id }` // with the user's DID — not a top-level `user_id`, and not `owner_id` (that field // is a cuid2 key-quorum id, not a user DID). Signing then requires the user's JWT. // const wallet = await privy.wallets().create({ // chain_type: "starknet", // owner: { user_id: "did:privy:..." }, // }); console.log("Wallet ID:", wallet.id); console.log("Wallet Address:", wallet.address); console.log("Public Key:", wallet.public_key); ``` ## Integration with Starkzap ### Server-Side Signing Endpoint Create an endpoint that signs transaction hashes using Privy: ```typescript theme={null} import express from "express"; app.post("/api/wallet/sign", async (req, res) => { const { walletId, hash } = req.body; if (!walletId || !hash) { return res.status(400).json({ error: "walletId and hash required" }); } try { const result = await privy.wallets().rawSign(walletId, { params: { hash }, }); res.json({ signature: result.signature }); } catch (error) { res.status(500).json({ error: error.message }); } }); ``` ### Client-Side Integration > The access token comes from a Privy **client** SDK — `@privy-io/js-sdk-core` > (vanilla JS/Svelte/Vue), `@privy-io/react-auth` (React), or `@privy-io/expo` > (React Native) — after the user logs in. The `@privy-io/node` `PrivyClient` > shown above is **server-only** and has no `getAccessToken()`. Below, `privy` > refers to the initialized client SDK instance. Use Privy with Starkzap: ```typescript theme={null} import { StarkZap, PrivySigner, OnboardStrategy, accountPresets } from "starkzap"; const sdk = new StarkZap({ network: "sepolia" }); // `privy` here is your Privy client SDK instance (see note above). const accessToken = await privy.getAccessToken(); // Option 1: Using onboard API (recommended) const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, accountPreset: accountPresets.argentXV050, privy: { resolve: async () => { // Get Privy signer context (walletId + publicKey) from your backend const walletRes = await fetch("https://your-api.example/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const walletData = await walletRes.json(); return { walletId: walletData.wallet.id, publicKey: walletData.wallet.publicKey, serverUrl: "https://your-api.example/api/wallet/sign", }; }, }, deploy: "if_needed", }); const wallet = onboard.wallet; // Option 2: Using PrivySigner directly (reuse accessToken) const walletRes = await fetch("https://your-api.example/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet: privyWallet } = await walletRes.json(); const signer = new PrivySigner({ walletId: privyWallet.id, publicKey: privyWallet.publicKey, serverUrl: "https://your-api.example/api/wallet/sign", }); const walletFromSigner = await sdk.connectWallet({ account: { signer, accountClass: accountPresets.argentXV050 }, }); ``` ## Complete Example ### Backend (Express.js) ```typescript theme={null} import express from "express"; import { PrivyClient } from "@privy-io/node"; import cors from "cors"; const privy = new PrivyClient({ appId: process.env.PRIVY_APP_ID!, appSecret: process.env.PRIVY_APP_SECRET!, }); const app = express(); app.use(cors()); app.use(express.json()); // Verify the Privy access token → the authenticated user's DID. async function auth(req, res, next) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) return res.status(401).json({ error: "Missing token" }); try { const claims = await privy.utils().auth().verifyAccessToken(token); req.userId = claims.user_id; // e.g. "did:privy:..." next(); } catch { res.status(401).json({ error: "Invalid token" }); } } // Create or get the user's Starknet wallet. // In production, cache the wallet per user (e.g. in a database) so you don't // create a new one on every call. app.post("/api/wallet/starknet", auth, async (req, res) => { try { const wallet = await privy.wallets().create({ chain_type: "starknet", owner: { user_id: req.userId }, // user's DID from the verified token }); res.json({ wallet: { id: wallet.id, address: wallet.address, publicKey: wallet.public_key, }, }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Sign transaction hash app.post("/api/wallet/sign", async (req, res) => { const { walletId, hash } = req.body; try { const result = await privy.wallets().rawSign(walletId, { params: { hash }, }); res.json({ signature: result.signature }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(3001); ``` ### Frontend ```typescript theme={null} import { StarkZap, OnboardStrategy, accountPresets } from "starkzap"; const sdk = new StarkZap({ network: "sepolia" }); // `privy` is your Privy client SDK instance (js-sdk-core / react-auth / expo), // not the server-only @privy-io/node PrivyClient. const accessToken = await privy.getAccessToken(); // Connect with SDK const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, accountPreset: accountPresets.argentXV050, privy: { resolve: async () => { const walletRes = await fetch("http://localhost:3001/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet } = await walletRes.json(); return { walletId: wallet.id, publicKey: wallet.publicKey, serverUrl: "http://localhost:3001/api/wallet/sign", }; }, }, deploy: "if_needed", }); const connectedWallet = onboard.wallet; // Use the wallet const balance = await connectedWallet.balanceOf(STRK); console.log(balance.toFormatted()); ``` ## React Native Integration For React Native applications, use `@privy-io/expo`: ```typescript theme={null} import { PrivyProvider } from "@privy-io/expo"; import { StarkZap, OnboardStrategy } from "starkzap"; // Wrap your app with PrivyProvider (expo needs both appId and clientId) // In your component const privyClient = usePrivy(); const accessToken = await privyClient.getAccessToken(); const walletRes = await fetch("https://your-api.example/api/wallet/starknet", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }); const { wallet: walletData } = await walletRes.json(); const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, deploy: "never", privy: { resolve: async () => ({ walletId: walletData.id, publicKey: walletData.publicKey, serverUrl: "https://your-api.example/api/wallet/sign", }), }, }); ``` ## Resources * [Privy Documentation](https://docs.privy.io) * [Starknet Tier 2 Support](https://docs.privy.io/recipes/use-tier-2#starknet) * [Privy Node SDK](https://docs.privy.io/reference/server-sdk-node) * [Privy Expo SDK](https://docs.privy.io/reference/react-native-sdk) ## Best Practices 1. **Never expose private keys** - Always use server-side signing 2. **Authenticate requests** - Verify user identity before signing 3. **Use HTTPS** - Always use secure connections for signing endpoints 4. **Handle errors gracefully** - Provide user-friendly error messages 5. **Monitor usage** - Track wallet creation and signing operations # Lending & Borrowing Source: https://docs.starknet.io/build/starkzap/lending Supply, borrow, withdraw, and repay with Vesu—discover markets, manage positions, and check collateral health ## Overview Starkzap supports lending and borrowing through [**Vesu**](https://vesu.xyz/lite/markets). Users can supply assets (deposit), borrow against collateral, repay debt, and withdraw supplied assets. **What users can do with lending:** * 💰 **Supply assets** - Deposit tokens into lending pools and earn interest (supply APY) * 💳 **Borrow** - Use supplied collateral to borrow other assets * 💵 **Repay** - Pay down debt; optionally withdraw collateral in the same flow * 📤 **Withdraw** - Withdraw supplied tokens (or max withdraw in one call) * 📊 **Monitor positions** - Query position size, debt, and collateralization health The default lending provider is [Vesu](https://vesu.xyz/lite/markets). It is registered automatically when the wallet is created. You can register additional providers or set a different default via `wallet.lending().registerProvider()` and `setDefaultProvider()`. ## Configuration No extra configuration is required for [Vesu](https://vesu.xyz/lite/markets) on mainnet or Sepolia. The wallet's `lending()` client uses the connected chain: ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer, accountClass } }); // Lending client uses wallet chain and address const lending = wallet.lending(); ``` Optional: register another provider or set default: ```typescript theme={null} lending.registerProvider(myLendingProvider, true); // true = make default lending.setDefaultProvider("vesu"); ``` ## Discovering markets Fetch available lending markets for the wallet's chain: ```typescript theme={null} const markets = await wallet.lending().getMarkets(); for (const market of markets) { console.log(market.asset.symbol, market.poolAddress, market.vTokenAddress); console.log("Can be borrowed:", market.canBeBorrowed); } ``` ### LendingMarket shape ```typescript theme={null} interface LendingMarket { protocol: string; poolAddress: Address; poolName?: string; asset: Token; vTokenAddress: Address; vTokenSymbol?: string; canBeBorrowed?: boolean; stats?: LendingMarketStats; } ``` Use these markets (and their `poolAddress` / `asset` / `vTokenAddress`) when calling deposit, withdraw, borrow, and repay with the same token/pool. ## Depositing Supply tokens into a lending pool. The provider includes the approval call when building the transaction. ```typescript theme={null} const tx = await wallet.lending().deposit({ token: USDC, amount: Amount.parse("1000", USDC), // receiver?: Address — defaults to wallet address }); await tx.wait(); ``` With execution options (e.g. sponsored fees): ```typescript theme={null} const tx = await wallet.lending().deposit( { token: USDC, amount: Amount.parse("1000", USDC) }, { feeMode: { type: "paymaster" } } ); await tx.wait(); ``` ## Withdrawing Withdraw a specific amount of supplied tokens: ```typescript theme={null} const tx = await wallet.lending().withdraw({ token: USDC, amount: Amount.parse("200", USDC), receiver: wallet.address, // optional }); await tx.wait(); ``` Withdraw the maximum allowed (e.g. full supplied balance) in one call: ```typescript theme={null} const tx = await wallet.lending().withdrawMax({ token: USDC, receiver: wallet.address, }); await tx.wait(); ``` Max withdraw may not be supported by all providers. [Vesu](https://vesu.xyz/lite/markets) supports `withdrawMax`. Check `provider.prepareWithdrawMax` or catch the error if you need to support multiple providers. ## Borrowing Borrow against collateral. You specify the collateral pair (collateral token + debt token) and the amount to borrow (or collateral amount, depending on denomination). ```typescript theme={null} const tx = await wallet.lending().borrow({ collateralToken: ETH, debtToken: USDC, amount: Amount.parse("500", USDC), // amount of debt to borrow }); await tx.wait(); ``` Optional: pass `poolAddress` if you have a specific pool, or `provider: "vesu"` to force the provider. `user` defaults to the wallet address. ## Repaying Repay borrowed debt. You can optionally withdraw collateral in the same action (provider-dependent). ```typescript theme={null} const tx = await wallet.lending().repay({ collateralToken: ETH, debtToken: USDC, amount: Amount.parse("100", USDC), // withdrawCollateral?: boolean }); await tx.wait(); ``` ## Position and health ### Get position Retrieve the user's lending position for a collateral/debt pair: ```typescript theme={null} const position = await wallet.lending().getPosition({ collateralToken: ETH, debtToken: USDC, }); console.log(position.collateralShares); console.log(position.nominalDebt); console.log(position.collateralAmount, position.debtAmount); console.log("Collateralized:", position.isCollateralized); console.log("Collateral value:", position.collateralValue, "Debt value:", position.debtValue); ``` ### LendingPosition shape ```typescript theme={null} interface LendingPosition { collateralShares: bigint; nominalDebt: bigint; collateralAmount?: bigint; debtAmount?: bigint; collateralValue: bigint; // USD value [SCALE] debtValue: bigint; // USD value [SCALE] isCollateralized: boolean; } ``` ### Get health Check if the position is collateralized (above liquidation threshold): ```typescript theme={null} const health = await wallet.lending().getHealth({ collateralToken: ETH, debtToken: USDC, }); console.log("Collateralized:", health.isCollateralized); console.log("Collateral value:", health.collateralValue, "Debt value:", health.debtValue); ``` ### Quote projected health Simulate an action (e.g. borrow or repay) and get the projected health after the action, plus the prepared calls and fee simulation: ```typescript theme={null} const quote = await wallet.lending().quoteHealth({ action: { action: "borrow", request: { collateralToken: ETH, debtToken: USDC, amount } }, health: { collateralToken: ETH, debtToken: USDC }, feeMode: { type: "paymaster" }, }); console.log("Current health:", quote.current); console.log("Projected health after action:", quote.projected); console.log("Prepared calls:", quote.prepared.calls); console.log("Simulation:", quote.simulation); ``` Use this to show users how a borrow or repay would affect their collateralization before they confirm. ## Using the transaction builder Batch lending actions with transfers, staking, or other operations using the tx builder. Methods are prefixed with `lend`: * `.lendDeposit(request)` — add a deposit * `.lendWithdraw(request)` — add a withdraw * `.lendWithdrawMax(request)` — add a max withdraw * `.lendBorrow(request)` — add a borrow * `.lendRepay(request)` — add a repay **Example: deposit and then transfer in one tx** ```typescript theme={null} const tx = await wallet .tx() .lendDeposit({ token: USDC, amount: Amount.parse("500", USDC) }) .transfer(USDC, [{ to: recipient, amount: Amount.parse("100", USDC) }]) .send(); await tx.wait(); ``` **Example: repay and withdraw collateral** ```typescript theme={null} const tx = await wallet .tx() .lendRepay({ collateralToken: ETH, debtToken: USDC, amount: Amount.parse("200", USDC), withdrawCollateral: true, }) .send(); await tx.wait(); ``` See [Tx Builder](/build/starkzap/tx-builder) for the full list of builder methods. ## Best practices 1. **Check health before and after** borrow/repay so users understand liquidation risk. 2. **`Use quoteHealth()`** to show projected health after an action before the user confirms. 3. **Discover markets once** (e.g. on load) and cache `getMarkets()` if you list markets in the UI. 4. **Use token presets** for `token`, `collateralToken`, and `debtToken` so addresses match the chain. ## Troubleshooting ### "Lending provider returned no calls" The provider could not build calls for the given request (e.g. wrong pool, unsupported pair, or invalid amount). Verify the market exists for the current chain and that you are passing the correct tokens and pool. ### Provider does not support chain [Vesu](https://vesu.xyz/lite/markets) supports `SN_MAIN` and `SN_SEPOLIA`. Ensure the wallet is on one of these chains. If you use a custom provider, ensure it supports the wallet's chain. ### "No default lending provider configured" The default is set when the wallet is created (Vesu). If you replaced it, call `setDefaultProvider("vesu")` or pass `provider: "vesu"` in each request. ## Next steps * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) — Token presets and amounts * [Transactions](/build/starkzap/transactions) — Execution options and fee modes * [Tx Builder](/build/starkzap/tx-builder) — Batching operations * [API Reference](/build/starkzap/api-reference) — Full method signatures # LST Staking Source: https://docs.starknet.io/build/starkzap/lst-staking Use Endur liquid staking through Starkzap with a staking-like API and asset-based vaults ## Overview Starkzap supports Endur liquid staking (LST) through `wallet.lstStaking(asset)`. LST staking is different from native delegation pools: * You stake into an asset vault (for example `STRK`, `WBTC`) instead of a validator pool. * Yield is reflected in LST share value over time. * There is no separate claim transaction for rewards. * Exits redeem shares; there is no native two-step cooldown flow. Looking for native delegation pools with `enterPool`, `addToPool`, and reward claims? See [Staking & Delegation](/build/starkzap/staking). ## Quick Start ```typescript theme={null} import { StarkZap, StarkSigner } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer: new StarkSigner(privateKey) }, }); const lst = wallet.lstStaking("STRK"); ``` ## Supported Assets Supported assets come from chain-aware presets. * **Mainnet:** `STRK`, `WBTC`, `tBTC`, `LBTC`, `solvBTC` * **Sepolia:** `STRK`, `TBTC1`, `TBTC2` Use `wallet.lstStaking(asset)` with one of the supported asset symbols on the connected chain. ## Core Actions ### Enter LST Position `enter`, `stake`, and `add` all perform the same LST deposit operation. ```typescript theme={null} import { Amount } from "starkzap"; const tx = await lst.enter(wallet, Amount.parse("100", 18)); await tx.wait(); ``` ### Enter with Referral ```typescript theme={null} const tx = await lst.enterWithReferral( wallet, Amount.parse("100", 18), "ABC123" ); await tx.wait(); ``` ### Enter to Validator ```typescript theme={null} const tx = await lst.enterToValidator( wallet, Amount.parse("100", 18), "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" ); await tx.wait(); ``` ### Exit LST Position Redeem a specific amount with `exitIntent`: ```typescript theme={null} const tx = await lst.exitIntent(wallet, Amount.parse("25", 18)); await tx.wait(); ``` Redeem full share balance with `exit`: ```typescript theme={null} const tx = await lst.exit(wallet); await tx.wait(); ``` ## Read Operations ### Position ```typescript theme={null} const position = await lst.getPosition(wallet); if (position) { console.log(`Shares: ${position.staked.toFormatted()}`); console.log(`Total: ${position.total.toFormatted()}`); } ``` ### APY and TVL ```typescript theme={null} const apy = await lst.getAPY(); const tvl = await lst.getTVL(); console.log(apy); console.log(tvl); ``` ### Commission and Rewards ```typescript theme={null} const commission = await lst.getCommission(); // 0 console.log(commission); ``` `claimRewards` is not applicable for LST staking because yield is embedded in share price. ## Configuration You can pass fetch options when creating the LST client: ```typescript theme={null} const lst = wallet.lstStaking("STRK", { timeoutMs: 15000, }); ``` Use this when you need custom HTTP timeout behavior for APY/TVL reads. ## Best Practices 1. Always use the correct `Amount` decimals for the selected asset. 2. Treat LST and native delegation as separate UX flows in your app. 3. Show users that yield is reflected in share value rather than claimed rewards. 4. Validate supported assets per connected chain before rendering actions. ## Next Steps * Learn about [Staking & Delegation](/build/starkzap/staking) for native pool staking * Explore [Transaction Builder](/build/starkzap/transactions#transaction-builder) for batching operations * Check the [API Reference](/build/starkzap/api-reference) for detailed method signatures # Overview Source: https://docs.starknet.io/build/starkzap/overview TypeScript SDK for building consumer applications with Starknet wallet integration, token operations, and DeFi features Starkzap SDK overview hero ## Introduction **Starkzap** enables any application to bring Bitcoin, stablecoins, and DeFi to their users. Built on Starknet's native account abstraction, it provides the tools to integrate wallets, swap, stake token, borrow/lend, make private transfers, and execute transactions—without requiring users to manage seed phrases or understand blockchain complexity. **Quick Start:** Get your first wallet integration working in under 5 minutes with our [Quick Start Guide](/build/starkzap/quick-start) ## Key Features Complete SDK with web, mobile, and server support, production-ready codebase, and minimal configuration Independent modules that allow you to use only what you need while maintaining clean code separation Ready-to-use integrations with major Starknet services including AVNU Paymaster, Privy, AVNU, EKUBO, Vesu, Tongo Cash and Cartridge Controller Works seamlessly in Node.js, browsers, and React Native with the same API Social login, email authentication, and biometrics—no seed phrases required Paymaster and Cartridge policy-based sponsorship where configured, plus staking, swapping, and borrowing for smooth user experiences ## Protocol Integrations The SDK includes ready-to-use integrations with major Starknet services: **Wallet Solutions & Gas Management** * **Privy** - Like OAuth for wallets: users sign in with email or social accounts, and Privy securely manages their wallet keys on their servers (no seed phrases needed) * **Cartridge** - For games: social / passkey login on **web** (Controller) or **React Native** (native session with `starkzap-native`). Cartridge can sponsor **policy-matching** calls via its paymaster/session stack; other calls may still cost the user * **Argent Account** - Pre-built wallet template you can use (like using a UI component library) * **Braavos Account** - Another pre-built wallet template option * **AVNU Paymaster** - Service that pays transaction fees for your users (like offering free shipping—you cover the costs so users don't have to) **DeFi & Financial Services** * **Native Staking** - Let users earn passive income by staking tokens (like earning interest on a savings account) * **Token Operations** - Send and receive tokens, check balances (like handling payments and account balances) * **Swaps** - Let users exchange one token for another via AVNU (aggregator) or Ekubo (concentrated liquidity) * **Lending** - Enable users to supply, borrow, and repay with Vesu * **Troves** - Access Troves strategies, inspect positions, and route deposits or withdrawals through the wallet * **Privacy** - Two independent protocols: the STRK20 privacy pool hides amounts, sender, and recipient; Tongo Cash hides amounts * **Cross-chain Bridge** - Bridge assets from Ethereum or Solana into Starknet ## Who Is This For? Jump into blockchain development without learning everything from scratch. Leverage familiar patterns (REST APIs, OAuth) and focus on user experience. Extend your reach to consumer applications using pre-built integrations without learning mobile/web frameworks from scratch. Rapidly prototype and launch blockchain-based applications, reducing development time from months to weeks. ## What Can You Build? Build applications where users can swap tokens, stake assets, and earn rewards—all with a simple API. **Examples:** * [**EKUBO**](https://ekubo.org) - Concentrated liquidity AMM * [**AVNU**](https://avnu.fi) - DEX aggregator and paymaster * [**VESU**](https://vesu.io) - DeFi protocol suite **Key Features:** * Token swapping and DEX integration * Staking and earning passive income * Portfolio management and analytics [Learn more about Staking](/build/starkzap/staking) Enable users to send and receive cryptocurrency, accept payments, and manage digital assets like Bitcoin and stablecoins. **Examples:** * [**Ready Wallet**](https://ready.gg) - Consumer payment and wallet solution **Key Features:** * Send/receive Bitcoin, stablecoins, and tokens * Payment processing and invoicing * Multi-currency wallet management [Learn more about Tokens](/build/starkzap/erc20) Create games with real digital assets, NFTs, and blockchain-based economies where players truly own their items. **Examples:** * [**LootSurvivor2**](https://lootsurvivor.io) - On-chain survival game * [**Ponziland**](https://ponziland.io) - Blockchain gaming platform **Key Features:** * In-game asset ownership * NFT integration * Play-to-earn mechanics * Gasless transactions for seamless gameplay [See Examples](/build/starkzap/examples) Build consumer-friendly wallets with social login, biometrics, and gasless transactions. **Examples:** * [**Ready**](https://ready.gg) - Consumer wallet * [**Cartridge**](https://cartridge.gg) - Gaming-focused wallet * [**Braavos**](https://braavos.app) - Multi-chain wallet * [**Privy**](https://privy.io) - Embedded wallet infrastructure **Key Features:** * Social login (Google, Twitter, email) * Biometric authentication (Face ID, Touch ID) * Multi-chain support * Gasless transaction experience [Learn about Wallets](/build/starkzap/connecting-wallets) *** ## Architecture Overview The SDK consists of modular components that work together seamlessly: ``` Starkzap | +-- Core SDK | +-- StarkZap | | `-- Network & configuration orchestration | `-- WalletInterface | `-- Unified wallet abstraction | +-- Signer Backends | +-- StarkSigner | | `-- Local private key signing | +-- PrivySigner | | `-- Server-side key management | `-- Cartridge | `-- Social login & passkey authentication | `-- Feature Modules +-- Token Operations | `-- ERC20 transfers, balances, approvals +-- Staking & Delegation | `-- Pool management, rewards, exits +-- Bridge | `-- Deposit, quotes +-- Swap & DCA | `-- Swapping, quotes, dca +-- Borrowing & Lending | `-- Borrow, lend, withdraw +-- Troves | `-- Strategies, positions, deposits and withdrawals +-- Privacy — STRK20 pool | `-- Shielded deposit, transfer, withdraw; relayer submission +-- Privacy — Tongo | `-- Fund, transfer, withdraw against an encrypted balance +-- Transaction Builder | `-- Batching, preflight, fee estimation `-- Type-Safe Primitives `-- Address, Amount, ChainId ``` **Core Components:** * **StarkZap** - Main orchestration layer for network and configuration management * **WalletInterface** - Unified wallet abstraction with interchangeable backends * **Signer Backends** - StarkSigner, PrivySigner, Cartridge - flexible authentication * **Feature Modules** - Token ops, staking, bridging, swap and DCA, lending, Troves, transactions, type-safe primitives * **Privacy** - Two independent protocols with no shared interface: the [STRK20 pool](/build/starkzap/privacy/strk20) spends notes and proves remotely, [Tongo](/build/starkzap/privacy/tongo) keeps an encrypted balance and proves locally ## What's Next? Ready to start building? Here's your roadmap: **Setup Guide** * [Installation](/build/starkzap/installation) - Install the SDK and dependencies * [Quick Start](/build/starkzap/quick-start) - Get your first wallet integration working in minutes **Core Concepts** * [Configuration](/build/starkzap/configuration) - Configure networks, RPC providers, and integrations * [Paymasters](/build/starkzap/paymasters) - Set up gasless transactions (AVNU or Cartridge) * [Connecting Wallets](/build/starkzap/connecting-wallets) - Connect wallets with social login, biometrics, or email * [Transactions](/build/starkzap/transactions) - Send transactions, batch operations, and handle fees **Features** * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) - Send and receive tokens, check balances * [Staking & DeFi](/build/starkzap/staking) - Enable staking and earning rewards * [Bridging](/build/starkzap/bridging) - Bridge assets from Ethereum and Solana into Starknet * [Swaps](/build/starkzap/swap) - Exchange tokens with AVNU or Ekubo * [DCA](/build/starkzap/dollar-cost-average) - Recurring buys (Dollar-Cost Averaging) with AVNU or Ekubo * [Lending](/build/starkzap/lending) - Supply, borrow, and repay with Vesu * [Troves](/build/starkzap/troves) - Discover strategies and deposit or withdraw through Troves * [Privacy](/build/starkzap/privacy/overview) - Choose between two independent protocols: the [STRK20 privacy pool](/build/starkzap/privacy/strk20) hides amounts, sender, and recipient; [Tongo](/build/starkzap/privacy/tongo) hides amounts * [Integrations](/build/starkzap/integrations/avnu-paymaster) - AVNU Paymaster, Privy, Cartridge **Examples** [Complete Examples](/build/starkzap/examples) - See practical examples for web, mobile, and server applications **Reference** * [Glossary](/build/starkzap/glossary) - Web3 terms explained in web2-friendly language * [API Reference](/build/starkzap/api-reference) - Complete API documentation New to blockchain? Don't worry! The SDK is designed to be beginner-friendly while remaining powerful for experienced developers. ## Community & Support Join our growing community: **Telegram Groups:** * **[Starknet Ecosystem Founders](https://t.me/+J6u7yj3DrghiZDA1)** - For founder exposure and ecosystem connections * **[Cairo Core Stars](https://t.me/sncorestars)** - For technical questions and Cairo development * **[Starknet Ecosystem](https://t.me/+9xp-85igaUtlYTY8)** - For ecosystem-wide visibility and discussions **Other Channels:** * **Discord** - [Starknet Community](https://discord.gg/starknet-community) - Get real-time help and connect with other developers * **Forum** - [Starknet Community Forum](https://community.starknet.io/) - Ask questions and share knowledge * **GitHub** - Report issues, contribute, and explore the codebase # Paymasters Source: https://docs.starknet.io/build/starkzap/paymasters Set up gasless transactions using AVNU Paymaster or Cartridge's built-in paymaster ## Overview Paymasters allow you to sponsor (pay for) transaction fees so users don't have to worry about gas costs. Starkzap supports two paymaster options depending on your wallet connection strategy. Use AVNU Paymaster for **Privy** and **Private Key** strategies. AVNU provides both gasfree (you sponsor) and gasless (user pays in tokens) modes. ## When to Use AVNU Paymaster * ✅ Using **Privy** strategy for wallet connection * ✅ Using **Private Key** strategy (server-side) * ✅ Want to sponsor all gas fees for users (gasfree mode) * ✅ Want users to pay gas in tokens instead of STRK (gasless mode) In gasfree mode, your dApp covers all gas costs. This is ideal for: * User onboarding flows * Premium UX experiences * Consumer applications ## Setup ### 1. Get an API Key Get an API key from the [AVNU Portal](https://portal.avnu.fi). This is required for gasfree mode. ### 2. Configure the SDK ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", paymaster: { nodeUrl: "https://starknet.paymaster.avnu.fi", headers: { "x-paymaster-api-key": "your-api-key-here" }, }, }); ``` ### 3. Use Sponsored Transactions ```typescript theme={null} // Execute with sponsored fees const tx = await wallet.execute([call], { feeMode: { type: "paymaster" }, }); // Or set as default when connecting const wallet = await sdk.connectWallet({ account: { signer }, feeMode: { type: "paymaster" }, }); ``` ## Propulsion Program The [Starknet Foundation Propulsion Program](https://docs.avnu.fi/docs/paymaster/propulsion-program) offers up to \$1M in gas subsidies for qualifying projects. This program helps reduce the cost of sponsoring transactions for your users. ## Server-Side Paymaster Proxy For production applications, you may want to proxy paymaster requests through your backend: ```typescript theme={null} // Backend endpoint app.post("/api/paymaster", async (req, res) => { const response = await fetch("https://starknet.paymaster.avnu.fi", { method: "POST", headers: { "Content-Type": "application/json", ...(AVNU_API_KEY && { "x-paymaster-api-key": AVNU_API_KEY }), }, body: JSON.stringify(req.body), }); const data = await response.json(); res.status(response.status).json(data); }); ``` Then configure the SDK to use your proxy: ```typescript theme={null} const sdk = new StarkZap({ network: "mainnet", paymaster: { nodeUrl: "https://your-api.example/paymaster", }, }); ``` In gasless mode, users pay gas fees in supported tokens instead of STRK. No API key required. ## Supported Tokens USDC, USDT, DAI, ETH, STRK, WBTC, solvBTC, LBTC, EKUBO, NSTR, LORDS, wstETH, and more. ## Setup ### 1. Configure the SDK ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", paymaster: { nodeUrl: "https://starknet.paymaster.avnu.fi", // No API key needed for gasless mode }, }); ``` ### 2. Use Transactions The SDK automatically handles gasless transactions when a paymaster is configured. Users can pay in any supported token: ```typescript theme={null} import { fromAddress } from "starkzap"; // Let users pay gas in a supported token (for example, USDC) const tx = await wallet.execute([call], { feeMode: { type: "paymaster", gasToken: fromAddress("0x053c91253bc96bfed3be381a97265e250ed0a2e2cbf1a54898ad0d2f7982f78f"), }, }); await tx.wait(); ``` Users will pay gas fees in their preferred token (USDC, USDT, etc.) instead of STRK. ## Best Practices 1. **Use gasfree mode** for onboarding and critical user flows 2. **Use gasless mode** to let users pay in their preferred tokens 3. **Proxy paymaster requests** through your backend in production to keep API keys secure 4. **Monitor usage** through the [AVNU Portal](https://portal.avnu.fi) dashboard 5. **Apply for Propulsion Program** if eligible for gas subsidies ## Resources * [AVNU Paymaster Documentation](https://docs.avnu.fi/docs/paymaster/index) * [AVNU Portal](https://portal.avnu.fi) - Get your API key and monitor usage * [Propulsion Program](https://docs.avnu.fi/docs/paymaster/propulsion-program) - Gas subsidies up to \$1M * [starknet.js Paymaster Guide](https://starknetjs.com/docs/guides/account/paymaster) Use Cartridge's paymaster when using the **Cartridge** wallet strategy (**web:** Controller; **React Native:** native session via `starkzap-native` when supported). Sponsorship is **policy-bound** — calls that match approved policies (and eligible session / SNIP-9 paths) can be paymastered; other calls may require user fees. ## When to Use Cartridge Paymaster * ✅ Using **Cartridge** for wallet connection (web or native) * ✅ Building gaming applications with explicit **policies** for contract calls * ✅ Want paymastered execution for **matching** session calls after policy approval * ✅ Want minimal extra paymaster configuration compared with AVNU for those paths ## How It Works Cartridge combines sessions with a paymaster service so **approved** execution paths can be sponsored. This is not a guarantee that every arbitrary `execute()` is gasless — scope your **policies** to the calls you intend to sponsor. ### Session-Based Paymastered Transactions According to the [Cartridge Controller architecture](https://docs.cartridge.gg/controller/architecture#how-paymastered-transactions-work), Cartridge uses session-based transactions with the paymaster: 1. **User approves policies once** - Defines what contracts/methods can be called 2. **Session is registered** - Can be registered without signature for eligible paymastered transactions 3. **Matching transactions** - When you call `account.execute()` for calls that fit the session / SNIP-9 flow: * SDK validates the session is active * Constructs an `OutsideExecution` message (meta-transaction via SNIP-9) where applicable * Signs the message with the session key * Sends the signed payload to Cartridge's paymaster service * The paymaster may submit the transaction on-chain and pay gas fees **React Native:** use [React Native Integration](/build/starkzap/react-native): Metro `withStarkzap` and **register** the native Cartridge adapter before connecting. Sponsored execution on native is often tied to **`feeMode: { type: "paymaster" }`** on the session wallet. Native Cartridge does not support `gasToken`. ### Policies for Paymaster Policies define what contracts and methods can be called in paymastered transactions. Users approve these policies once when connecting; **calls that match** those policies can be sponsored: ```typescript theme={null} const policies = [ { target: "0xTOKEN_CONTRACT", method: "transfer" }, { target: "0xGAME_CONTRACT", method: "play_card" }, { target: "0xGAME_CONTRACT", method: "claim_rewards" }, ]; ``` Calls outside those policies are not guaranteed to be paymastered and may need user-paid fees or additional approval. ### Register Session Without Signature For eligible paymastered transactions, sessions can sometimes be registered with reduced friction once policies are approved. Behavior can differ between **web** and **native** builds — follow [Cartridge Controller](/build/starkzap/integrations/cartridge-controller) and [React Native Integration](/build/starkzap/react-native). ```typescript theme={null} const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies: [ { target: "0xTOKEN_CONTRACT", method: "transfer" }, ], }, // Native flows may default to deploy: "never"; pass deploy: "if_needed" if you need deployment checks }); const wallet = onboard.wallet; ``` ## Integration Connect with Cartridge and define policies. **Matching** calls can be sponsored per Cartridge rules; on **native**, you may pass **`feeMode: { type: "paymaster" }`** where your wallet/session supports it. ```typescript theme={null} import { StarkZap, OnboardStrategy } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies: [ { target: "0xTOKEN_CONTRACT", method: "transfer" }, { target: "0xGAME_CONTRACT", method: "play_card" }, ], }, }); const wallet = onboard.wallet; const tx = await wallet.execute([call]); await tx.wait(); ``` ## Key Advantages * ✅ **Low friction for games** - Policy-first flows after user approval * ✅ **Session-based** - Approved paths can execute without a popup per tx * ✅ **No AVNU key** for Cartridge-native sponsorship paths * ✅ **Web and (when supported) React Native** - Same policy concepts; native needs adapter registration ## Comparison with AVNU Paymaster | Feature | Cartridge Paymaster | AVNU Paymaster | | - | - | - | | **Setup** | Policies + Cartridge session | SDK `paymaster` config (+ API key for gasfree) | | **API Key** | Not used for Cartridge path | Required for AVNU gasfree mode | | **Use Case** | Gaming / session UX | Privy, private key, general sponsorship | | **Strategy** | Cartridge (web or native) | Privy or Private Key (typical) | | **Sponsorship scope** | Policy-matching session calls | Configured paymaster modes | ## Resources * [Cartridge Controller Documentation](https://docs.cartridge.gg/controller/overview) * [Cartridge Architecture](https://docs.cartridge.gg/controller/architecture#how-paymastered-transactions-work) - How paymastered transactions work * [Cartridge Integration Guide](/build/starkzap/integrations/cartridge-controller) - Detailed integration instructions ## Choosing the Right Paymaster **Use AVNU Paymaster if:** * You're using Privy or Private Key strategies * You want control over gas sponsorship * You need gasless mode (user pays in tokens) **Use Cartridge Paymaster if:** * You're using **Cartridge** (web Controller or native session) * You're building gaming applications with **explicit policies** * You want paymastered execution for **matching** session calls after policy approval ## Next Steps * Learn about [AVNU Paymaster Integration](/build/starkzap/integrations/avnu-paymaster) for detailed setup * Learn about [Cartridge Controller Integration](/build/starkzap/integrations/cartridge-controller) for gaming applications * Configure your [SDK Configuration](/build/starkzap/configuration) with paymaster settings # Choosing a privacy protocol Source: https://docs.starknet.io/build/starkzap/privacy/overview Starkzap supports two independent privacy protocols—the STRK20 privacy pool and Tongo. Compare what each hides and pick the right one for your integration. ## Overview Starkzap supports two privacy protocols. They are **independent implementations, not two backends behind one interface** — they have different state models, different proving setups, and different submission paths. You pick one per integration and code against it directly. * **[STRK20 privacy pool](/build/starkzap/privacy/strk20)** — one pool for every token. Your balance is a set of private **notes**. A remote proving service builds a proof, and a relayer submits it, so your account never appears on-chain. * **[Tongo](/build/starkzap/privacy/tongo)** — one contract per token. Your balance is a single **encrypted number** on your own account. Proofs are generated locally, and you submit the transaction yourself. The names matter more than the words "privacy" and "confidential", which are near-synonyms in English but point at different protocols here. Say **STRK20** or **Tongo**. ## Comparison | | STRK20 privacy pool | Tongo | | - | - | - | | **Hides** | Amounts, sender, and recipient | Amounts | | **State model** | Private notes you spend | One encrypted balance per account | | **Contracts** | One pool, all tokens | One contract per token | | **Proving** | Remote proving service | Local, in your process | | **Submission** | The paymaster's relayer — your account stays off-chain | Your own account | | **Batching** | Never — the proof belongs to the transaction | Yes, through `wallet.tx()` | | **You get back** | `send()` executes and returns a transaction hash | `Call[]` that you batch and send | | **Keys** | Viewing key derived from your signing key | A separate Tongo private key you hold | | **Wallets** | Private-key login only | Any wallet | | **Install** | GitHub Packages, auth required, Node 24+ | npm, no auth | | **Status** | Prerelease. Deposits need StarkWare access; the rest is self-hostable | Available | ## Which one to use **Choose STRK20** when you need to hide *who* transacts, not just how much. Because a relayer submits the transaction, the sender's address, nonce, and gas payment stay off-chain. That is the property Tongo does not offer. **Choose Tongo** when hiding amounts is enough, and you want an integration you can ship today: it installs from npm with no access request, works with any wallet, proves locally with no external service, and batches with your other calls in a single transaction. Starkzap does not offer a shared abstraction over the two. Quoting the SDK itself: Tongo keeps an encrypted *balance* per account and proves locally, while the privacy pool spends *notes* and needs a remote prover whose output rides on the transaction rather than inside a call. There is no shared interface to code against. ## What neither protocol hides Both protocols privatize what happens *between* entering and leaving. Entering and leaving are public in both. Deposits and withdrawals are public. Withdrawing to the address you deposited from puts both ends of the pool on one address, which is enough to link them — so a fresh address is what preserves the gap. Neither protocol hides that you interacted with it at all, and neither hides the timing or the token. Treat them as tools for unlinking a source from a destination, not as cloaks over your whole on-chain history. ## Next steps Hide amounts, sender, and recipient. Notes, a remote prover, and relayer submission. Hide amounts. Encrypted balances, local proving, batches with your other calls. # STRK20 privacy pool Source: https://docs.starknet.io/build/starkzap/privacy/strk20 Deposit, transfer, and withdraw privately through the STRK20 privacy pool—private notes, a remote proving service, and relayer submission that keeps your account off-chain ## Overview The **STRK20 privacy pool** is a single contract that holds balances for every token. Your balance in it is not a number on your account but a set of private **notes**, and you spend them by proving you own them rather than by signing a call. **Deposits require access from StarkWare.** Every deposit is screened against sanctions lists through [Elliptic](https://www.elliptic.co/). That screening runs on credentials StarkWare administers, so the proving service behind it is not an open endpoint. Contact StarkWare to deposit. Everything else you can run yourself. Transfers, withdrawals and the discovery service can be self-hosted from the open-source components. See [Running your own services](#running-your-own-services). The SDK is a prerelease (`0.14.x-rc`) published to GitHub Packages and requires Node 24 or later. Three things make it different from every other Starkzap module: * **A remote proving service** builds a validity proof for each private transaction. The proof travels as transaction-level fields, not inside a call, so a privacy operation can **never** be batched with other calls and cannot go through the [transaction builder](/build/starkzap/tx-builder). * **A relayer submits it.** No user signature is involved. The pool authorises the transaction from the proof alone. Your address, nonce, and gas payment stay off-chain, unless you wrap public calls with `invoke`. * **A viewing key**, derived from your signing key, lets you find your own notes and lets others send to you. The client handles the fee, the proving block and the submission. So `send()` executes for you and returns a transaction hash. It does not return calls. For how this compares to Tongo, see [Choosing a privacy protocol](/build/starkzap/privacy/overview). ## Prerequisites and installation The privacy SDK is an optional peer dependency published to **GitHub Packages**, not npm. Point the `@starkware-libs` scope at that registry in your project's `.npmrc`: ```txt theme={null} @starkware-libs:registry=https://npm.pkg.github.com ``` Reads require authentication even though the package is public. Put a token with `read:packages` in your **`~/.npmrc`** — never in the committed project file: ```txt theme={null} //npm.pkg.github.com/:_authToken=YOUR_TOKEN ``` Then install: ```bash theme={null} npm install @starkware-libs/starknet-privacy-sdk ``` The OHTTP transport needs WebCrypto. On Node that means **Node 24 or later**. The installer does not enforce this, so an older Node fails at runtime. On **React Native**, Hermes provides no `crypto.subtle` at all, so OHTTP needs a library that supplies one; see [React Native](/build/starkzap/react-native). ### Supported wallets Privacy needs a **private-key login**. `connectPrivacy` takes a `Wallet` rather than a `WalletInterface`, because a Cartridge wallet cannot derive a viewing key at all. The viewing key is derived from the account key **inside the signer**, so the signer has to implement `deriveViewingKey`. `StarkSigner` does. The Privy and Cartridge signers can only sign, so they cannot produce the key. Starkzap refuses those signers **before any network call**. The user-facing reason, as the example app phrases it: > The privacy pool needs a private-key login: the viewing key is derived from the account key inside the signer, which the Privy and Cartridge signers cannot do. A custom signer opts in by implementing `deriveViewingKey`. `deriveAccountLeafViewingKey` implements the profile for you. It comes from the **root** entry, not `starkzap/privacy`, so a signer needs no privacy types: ```typescript theme={null} import { deriveAccountLeafViewingKey, type ViewingKeyContext } from "starkzap"; class MySigner implements SignerInterface { async deriveViewingKey(context: ViewingKeyContext): Promise { return deriveAccountLeafViewingKey(this.privateKey, context); } } ``` Failing that, supply your own `viewingKeyDerivation`. ## Configuration Privacy lives at its own entry point, **`starkzap/privacy`**. Its config is passed to `connectPrivacy`, not to `StarkZap`. `poolContractAddress`, `prover`, `discovery` and `paymaster` are required. `connectPrivacy` throws without a paymaster. Import from `starkzap/privacy`, not from the root. The privacy types name the privacy SDK, which is an optional peer. Exporting them from the root would make that peer mandatory for everyone. ```typescript theme={null} import { StarkZap } from "starkzap"; import { connectPrivacy } from "starkzap/privacy"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer } }); const privacy = await connectPrivacy(wallet, { poolContractAddress: POOL, prover: PROVER, discovery: DISCOVERY, paymaster: { url: "https://my-app.example.com/api/paymaster/mainnet", fee: { mode: "sponsored" }, // Ceiling on what one quote may withdraw for its fee, in base units of the // fee token, and the forwarder addresses allowed to receive it. Both come // from your paymaster operator. maxFee: 10n ** 19n, allowedFeeRecipients: [FORWARDER], }, }); ``` `connectPrivacy` caches one client per wallet, so repeated calls are cheap and the user is asked to sign once. A failure is not cached, so you can retry after fixing the cause. Later calls return the cached client and ignore the config you pass them. | Field | Required | Purpose | | - | - | - | | `poolContractAddress` | Yes | The pool contract. Folded into the viewing key as a felt, so `0x040…` and `0x40…` are the same pool. | | `prover` | Yes | Proving service base URL, or a `ProofProviderInterface` instance. A prover you host covers transfers and withdrawals. **Deposits** also need the screening step, which only StarkWare's prover performs. | | `discovery` | Yes | Discovery service base URL, or a `DiscoveryProviderInterface` instance. You can host this yourself. | | `paymaster` | In practice | Where and how private transactions are submitted. See the table below. | | `ohttp` | No | Envelope-encrypts requests to the two services. Defaults to on. | | `viewingKeyDerivation` | No | Replace the default derivation scheme. | The `paymaster` block: | Field | Required | Purpose | | - | - | - | | `url` | Yes | Endpoint that submits private transactions. Point it at a proxy that holds your API key, not at the paymaster directly. Use one proxy per network, because each AVNU deployment whitelists only its own pool. | | `fee` | Yes | How the pool fee is paid. There is no default. See [Fee modes](#fee-modes). | | `tip` | No | Transaction priority: `"slow"`, `"normal"`, or `"fast"`. AVNU fills in `"normal"` when omitted. | | `maxFee` | Yes | Refuse a quote whose fee exceeds this, in base units of the fee token. This is the only limit on what a bad endpoint can withdraw from your shielded balance. There is no default. Size it for your fee mode, see [Fee modes](#fee-modes). | | `allowedFeeRecipients` | Yes | Forwarder addresses allowed to receive the fee. A quote naming any other address is refused. An empty list refuses every quote. Get the address from your paymaster operator, per network, and update it when they rotate the forwarder. Compared by value, so padding does not matter. | | `fetch` | No | Transport override. Defaults to the global `fetch`. Wrap it to add auth headers, cookies, retries, a timeout or tracing. | There are **no built-in per-network presets** for the pool or the services. Supply them per chain yourself. ### Running your own services Both services are open source, so `prover` and `discovery` can point at infrastructure you operate. | Component | Where to get it | | - | - | | Discovery service | [`deploy/discovery-service/`](https://github.com/starkware-libs/starknet-privacy/tree/main/deploy/discovery-service) in the protocol repo — Dockerfile plus local and multi-platform build instructions. The service itself is [`crates/discovery-service/`](https://github.com/starkware-libs/starknet-privacy/tree/main/crates/discovery-service). | | Proving service | [`snip-36-prover-backend`](https://github.com/starknet-innovation/snip-36-prover-backend) — proves [SNIP-36](https://community.starknet.io/t/snip-36-in-protocol-proof-verification/116123) virtual-block execution with the stwo-cairo prover, which is the mechanism the pool's proofs use. | Deposits are the exception. The prover does not screen deposits itself. It calls a [`proof-interceptor`](https://github.com/starkware-libs/starknet-privacy/tree/main/proof-interceptor) sidecar, which sends each deposit address to an [`elliptic-proxy`](https://github.com/starkware-libs/starknet-privacy/tree/main/elliptic-proxy) that holds Elliptic credentials. The client does not see the screening. An allowed transaction returns a proof as usual. A blocked one returns JSON-RPC error `10000`. Running that path needs Elliptic access, which StarkWare provides when it onboards you. The prover backend lives under a different organisation and describes itself as developer tooling, so treat it as a reference implementation rather than a supported production deployment. Check that its API matches the `prover` contract your SDK version expects before relying on it. ### Keep OHTTP on With `ohttp` disabled, your viewing key reaches the proving and discovery services in plaintext — inside TLS, but readable by whoever operates them. Turn it off only if the server does not support OHTTP. Pass `true` for defaults, or an object to route through a relay that also hides your IP from the discovery service: ```typescript theme={null} ohttp: { relayUrl: OHTTP_RELAY } ``` ### Fee modes Starkzap refuses to pick a fee mode for you. All three modes go through the relayer and withdraw the fee from your private balance, so **the choice is about cost, not privacy**. | Mode | API key | Cost | | - | - | - | | `{ mode: "sponsored" }` | Required | Relayer pays gas. Pool fee in STRK. Use this unless you have a reason not to. | | `{ mode: "sponsored_private", poolFeeToken }` | Required | As sponsored, but you choose the pool-fee token. Private transactions only. | | `{ mode: "default", gasToken }` | None | No key needed. The withdrawal is sized at the paymaster's suggested *maximum* gas, not its estimate, so you pay for headroom you may not use. `quote()` reports both figures. | Sponsored modes need an API key, and an API key needs a proxy to hold it. Raise that proxy's request body limit: a proof runs to a few hundred kilobytes, above the 100 kb that `express.json()` allows by default. **Sizing `maxFee`.** The ceiling bounds the whole withdrawal. Under `sponsored` and `sponsored_private` the withdrawal is the flat pool fee, so a ceiling a little above that fee is safe. Under `default` the withdrawal is the pool fee **plus gas at the suggested maximum**, so leave room for that at the gas prices you expect. `quote()` reports both figures. Revisit the value when the deployment or the network changes. ### Two ways in: `connectPrivacy` and `createPrivacy` Both derive the same viewing key from the same signer. They differ in what you get back and who cleans up. | Aspect | `connectPrivacy` | `createPrivacy` | | - | - | - | | Returns | this wrapper: pool fee, proving block, relayed submission | the privacy SDK's own `PrivateTransfersInterface` | | Submission | through the paymaster's relayer | yours to arrange | | Cached per wallet | Yes | No. Each call derives the key again | | Revoked by `wallet.disconnect()` | Yes | No. Call `revokePrivacy(transfers)` yourself | Use `createPrivacy` only for a flow this wrapper does not model, such as a private swap. Use `connectPrivacy` for everything else. The revocation row matters most. A viewing key must not outlive its session. Suppose account A disconnects and account B logs in, while a client built from A is still reachable. That client can still read A's notes. Worse, deposits made by B can be proven as A's notes, which only A's key can spend. ## Registering and your viewing key By default the signer derives your viewing key from your account's private key. It follows the `account-leaf-v1` profile of [SNIP-44](https://github.com/starknet-io/SNIPs/pull/177): an HMAC-SHA256 keyed with the account key, over a fixed domain separator and a context of chain, account, pool and key slot. Two pools, two chains or two accounts never share a key. The key is **never stored**. It can be derived again from the account key, so your notes survive a wallet reinstall with nothing to back up. It lives in the client for as long as the session. `wallet.disconnect()` **revokes** the key. The client refuses to decrypt afterwards, so a stale client someone still holds cannot keep reading notes. `revokePrivacy(transfers)` does the same for a client built with `createPrivacy`. See [the comparison above](#two-ways-in-connectprivacy-and-createprivacy). Neither call scrubs memory, and neither undoes the decryption of notes already discovered. The pool stores the **first** key an account registers and it cannot be replaced. Changing `viewingKeyDerivation` for an account that already registered orphans its notes, and the discovery service rejects a mismatched key outright. ### Supplying your own derivation The pool only sees the key's public x-coordinate, so you can derive the key any way you like. Pass `viewingKeyDerivation` to `connectPrivacy` to replace the default. Usual reasons: a wallet with its own key-derivation function, a hardware device, a key held outside the app, or a signer that cannot run the default derivation. ```typescript theme={null} import type { ViewingKeyDerivation } from "starkzap/privacy"; const fromWalletKdf: ViewingKeyDerivation = async (context, signer) => { // context: { chainId, accountAddress, poolAddress, keyIndex? } // signer: the account's own signer, for schemes that reach key material // through it or delegate to a device return myWallet.deriveViewingKey(context); // 0x-prefixed hex }; ``` Two requirements, both enforced: * **Deterministic, forever, on every device.** The same context and signer must always produce the same key. The pool keeps the first key it is given. A derivation that changes its output orphans every existing note. * **Inside the pool's canonical range,** `[1, n/2)`. `assertCanonicalViewingKey` rejects anything outside it. Fold your output into the range. Only the scheme that produced a key can reproduce it. Notes shielded with your scheme need your scheme to recover. Keep the scheme with the account. SNIP-44 is still a draft, so its profile may change before it is final. `deriveAccountLeafViewingKey` implements it and is exported, which is what you want when writing a signer's `deriveViewingKey` rather than replacing the scheme wholesale. Registration is **per account, not per token**, so any token answers the question. `discoverRequirement` returns `0` (`SetupRequirement.Register`) when the account has not registered yet: ```typescript theme={null} const requirement = await privacy.discoverRequirement(wallet.address, token.address); const registered = requirement !== 0; // SetupRequirement.Register === 0 ``` You cannot register on its own. The pool fee comes from your private balance, which is empty before your first deposit. A standalone `register()` fails with `Insufficient balance`. Set `autoRegister: true` on the first deposit instead. Whether a **recipient** can receive is a different question, and needs a different call. A transfer to an account with no registered viewing key cannot be built. Check before offering the action, by asking for the recipient's channel: ```typescript theme={null} export async function recipientReady(recipient: string): Promise { const address = recipient.trim(); try { const { channels } = await privacy.discoverChannels([address]); return Boolean(channels?.get(address)?.publicKey); } catch { return false; } } ``` Do not use `discoverRequirement` for the recipient. With the indexer discovery service, its `Register` verdict reports whether the **sender** is registered. Once you have deposited, it answers "ready" for any address. The transfer then fails with `Missing channel context for recipient`. The channel's `publicKey` is what the build needs, so check for that. A recipient who is registered but has no channel with you yet passes, as it should: opening that channel is what `autoSetup` does. ## Deposit A deposit needs a public ERC20 `approve`, because the pool pulls the funds. That step names your account either way. You choose whether it is its own transaction or part of the paymaster's. ### In one transaction `send({ invoke })` hands the approve to the paymaster, which relays it through your account's `execute_from_outside` in the same transaction as the pool action. The wrapped call runs first, so the allowance is in place by the time the pool reads it. ```typescript theme={null} import { Amount, fromAddress } from "starkzap"; import { waitForFundedBalance } from "starkzap/privacy"; const amount = Amount.parse("100", token); const hash = await privacy.send( (b) => b .with(token.address, (t) => t.deposit({ amount: amount.toBase() })) .surplusTo(wallet.address), { // Relayed alongside the pool action. `calls()` resolves the builder without // sending, so every ERC20 and protocol helper is available here. invoke: await wallet.tx().approve(token, fromAddress(POOL), amount).calls(), autoRegister: true, autoSetup: true, autoDiscover: { notes: "refresh", channels: "refresh" }, provingBlockId: await waitForFundedBalance( wallet.getProvider(), token, fromAddress(wallet.address), amount.toBase() ), } ); ``` You sign the paymaster's SNIP-12 typed data rather than a transaction, and Starkzap asks for that signature *before* proving. The signature does not depend on the proof, so declining costs you nothing. Wrapped calls are **not** private. They are executed by your account and name it on-chain. Use this for work that is already public, which the approve is — not to hide anything. This needs an account that supports SNIP-9 outside execution. The OpenZeppelin, Argent and Braavos presets all do. When an account does not, the paymaster refuses at build time with code `156` and the reason `invalid version`. Starkzap replaces that with a message that names the account and points at the two-transaction form. ### In two transactions Send the approve yourself when the account cannot do outside execution, or when you would rather keep the steps apart. ```typescript theme={null} // 1. Public approve so the pool can pull the funds. const approve = await wallet .tx() .approve(token, fromAddress(POOL), amount) .send(); await approve.wait(); // 2. The private deposit — the same call as above, without `invoke`. const hash = await privacy.send( (b) => b .with(token.address, (t) => t.deposit({ amount: amount.toBase() })) .surplusTo(wallet.address), { autoRegister: true, autoSetup: true, autoDiscover: { notes: "refresh", channels: "refresh" }, provingBlockId: await waitForFundedBalance( wallet.getProvider(), token, fromAddress(wallet.address), amount.toBase() ), } ); ``` ### The proving block and registration, either way The approve does **not** have to age. It is checked when the deposit executes, not when it is proven, so the deposit can follow a separate approve at once. The **balance** is different. The proof reads your ERC20 balance at the proving block, and the client cannot know when funds arrived. So pass `provingBlockId` from `waitForFundedBalance`, which checks the balance directly. This also covers funds that arrived in a transaction you did not send. Bundling the approve does not change this. It saves a transaction, not the wait, so `provingBlockId` is needed either way. This is also where registration happens, via `autoRegister`. Tongo's `fund()` bundles its own approve, so there is no choice to make there. ## Transfer ```typescript theme={null} const hash = await privacy.send( (b) => b .with(token.address, (t) => t.transfer({ recipient: recipient.trim(), amount: amount.toBase() }) ) .surplusTo(wallet.address), { autoSetup: true, autoSelectNotes: "naive", autoDiscover: { notes: "refresh", channels: "refresh" }, } ); ``` `autoSelectNotes: "naive"` lets the SDK pick which of your notes to spend. `surplusTo(wallet.address)` sends the change back to you as a **private** note, so it stays in the pool. ## Withdraw ```typescript theme={null} const hash = await privacy.send( (b) => b .with(token.address, (t) => t.withdraw({ recipient: recipient.trim(), amount: amount.toBase() }) ) .surplusTo(wallet.address), { autoSelectNotes: "naive", autoDiscover: { notes: "refresh", channels: "refresh" }, } ); ``` Make the recipient an explicit choice in your UI rather than defaulting to the connected wallet. Deposits and withdrawals are public. Only what happens between them is private. Withdrawing to the address you deposited from links both ends. Use a fresh address. Withdrawing to yourself is legitimate. It just has to be a choice, not a default. ## Reading private balances There is no `balanceOf`. **One** `discoverNotes()` call covers every token, returning a map keyed by token address as a `bigint`; a balance is a grouping of it: ```typescript theme={null} const { notes } = await privacy.discoverNotes(); const balances = tokens.map((token) => { const owned = notes.get(BigInt(token.address)) ?? []; const total = owned.reduce((sum, note) => sum + note.amount, 0n); return { token, private: Amount.fromRaw(total, token), notes: owned.length, }; }); ``` Note counts are worth surfacing: a balance split across many small notes behaves differently from the same amount in one note. ## Previewing a transaction `simulate()` runs what `send()` would run, without proving it. It takes the same callback and options. It quotes the same fee, appends the same fee withdrawal and resolves the same proving block, then runs against a mock prover. ```typescript theme={null} const { warnings, feeAction } = await privacy.simulate((b) => b .with(token, (t) => t.withdraw({ recipient, amount })) .surplusTo(wallet.address) ); if (warnings.length > 0) { // Ask the user. `USER_LINKAGE` is the one to expect on a withdrawal. } ``` The difference is *when* you learn about the warnings. `send()`'s `onWarnings` callback reports the same list, but only after the proof is paid for. `simulate()` costs only a quote, a chain-head read and a view call. It waits for no block and writes no private state, so it is safe to run while a send is in flight. In that window it reads a slightly older block than the send will prove against. The proving block matters. Channels and notes are discovered **at** that block. A preview against the chain head would describe a different transaction, for example when the recipient registered in the last few blocks. No proof comes back. A mock proof cannot be submitted. ## Fees: pool fee and gas The pool charges a fee that is withdrawn **inside the proof**, separately from gas. `quote()` returns it: ```typescript theme={null} const fee = await privacy.quote(); console.log(fee.feeAction.amount); // bigint, base units of fee.feeAction.token // What the paymaster reckons the gas costs, when it reports it. if (fee.gas) { console.log(fee.gas.estimatedInGasToken, fee.gas.suggestedMaxInGasToken); } ``` `gas` is the paymaster's gas estimate, not your fee. The two are the same only in `default` mode, where the withdrawal equals `suggestedMaxInGasToken`. In the sponsored modes the relayer pays the gas, so do not show those numbers to the user as a cost. In `default` mode, show the estimate next to the suggested maximum. The difference is headroom the user pays for and may not use. `gas` is optional. A deployment may omit it, and a figure Starkzap cannot read is dropped, not treated as an error. Show users `quote()`, not simulated gas. Gas is not the cost that leaves their shielded balance. An amount of `0n` means the deployment charges nothing, and no withdrawal is added. A proof that omits a non-zero fee withdrawal is rejected by the paymaster. Re-quote after each operation — the amount can change. ## Waiting for a provable block The proving block is **10 blocks** behind the chain head (`PROOF_BASE_BLOCK_DEPTH`). Any on-chain state a proof reads must be in a block before the proving block. This includes your viewing key, your token balance and the spent-note set. So you must wait after a previous private transaction, after deploying the account before `register()`, and after funding the account before `deposit()`. Without the wait, the sequencer rejects the proof, or the proof reads a balance the chain does not have yet. Both fail late, with unclear errors. `send()` waits for you. Your job is to **show the wait**. It is silent, and it takes seconds on Sepolia but minutes on mainnet. A plain spinner looks like a hang: ```typescript theme={null} import type { ProvableAttempt } from "starkzap/privacy"; const hash = await privacy.send(compose, { wait: { onAttempt: ({ attempt, head, provingBlock, ready }: ProvableAttempt) => { if (ready) return; console.log(`#${attempt}: head ${head}, proving block ${provingBlock} — polling`); }, }, }); ``` For preconditions the client cannot know about, wait explicitly and pass the result as `provingBlockId`: | Helper | Waits until | | - | - | | `waitForFundedBalance(provider, token, owner, amount)` | The owner's ERC20 balance covers `amount` at the proving block | | `waitForDeployedAccount(provider, address)` | The account is deployed | | `waitForProvableState(provider, isVisible)` | Your own predicate holds at the proving block | Use these when you have no receipt, for example when another party funded the account. They read the chain state instead of counting blocks from a receipt. All three **throw on timeout** (default 300 seconds) rather than resolving — handle that. ## Best practices 1. **Leave OHTTP on.** Disabling it exposes your viewing key to the service operators. 2. **Withdraw to a fresh address.** Reusing the deposit address links both ends of the pool and undoes the privacy you paid for. 3. **Show `quote()` before the user commits**, and never present simulated gas as the total cost. 4. **Show `wait.onAttempt`.** A silent wait of minutes looks like a hang. `wait` also carries `depth`, `pollIntervalMs` and `timeoutMs` for chains the two-second default does not suit. 5. **Never persist the viewing key.** It is reproducible from the signing key; storing it only creates something to leak. `wallet.disconnect()` revokes it; call `revokePrivacy()` yourself if you built the client with `createPrivacy`. 6. **Raise the body limit on any proxy** in front of the paymaster. A proof is a few hundred kilobytes — past the default in most HTTP stacks. 7. **Keep prover and discovery URLs out of version control.** Configure them per environment. 8. **Check the recipient's channel before offering a transfer** so users get a clear reason instead of a build failure. `discoverChannels`, not `discoverRequirement` — see [above](#registering-and-your-viewing-key). 9. **Keep `invoke` for calls that are already public.** It is there to save a transaction, not to shield one: the calls are executed by the account and name it on-chain. ## Troubleshooting ### Missing optional peer dependency The error names the registry and the required permission, and mentions `read:packages` and `Node >= 24`. Confirm your project `.npmrc` maps the `@starkware-libs` scope to `https://npm.pkg.github.com`, that `~/.npmrc` holds a token with `read:packages`, and that you are on Node 24 or later. ### Signer refused before any network call The signer does not implement `deriveViewingKey`. Use a private-key login backed by `StarkSigner`; Privy and Cartridge signers cannot derive a viewing key. A custom signer can implement the method — `deriveAccountLeafViewingKey` is exported for exactly that — or you can pass your own `viewingKeyDerivation`. ### `Insufficient balance` on register Registration cannot stand alone. The pool fee comes from a private balance you do not have yet. Deposit with `autoRegister: true` instead. This also appears when the quoted pool fee exceeds the amount you are depositing. ### Screening rejected the deposit Deposits are screened server-side before a proof is produced. Classify the failure rather than showing a raw error: ```typescript theme={null} import { screeningVerdict } from "starkzap/privacy"; switch (screeningVerdict(err)) { case "rejected": // The source address is blocked. Retrying will not help. break; case "unavailable": // Screening is down and deposits fail closed. Retry later. break; default: // Not a screening failure — surface the underlying error. } ``` Do not treat every failure as a screening verdict. The same error code is also used for non-pool transactions and internal faults. ### Paymaster rejected the transaction Starkzap reports the paymaster's own message and the reason from its `data`. Read that first. A few codes are worth knowing: | Code | Meaning | | - | - | | `151` | The fee token is not supported. | | `156` | Execution failure, **including a pool address the paymaster has not whitelisted**. The reason is in `data.execution_error`. With `invoke`, the reason `invalid version` means the account does not support SNIP-9 outside execution. Starkzap rewrites that one into a clear message. | | `163` | An invalid API key, an unavailable service, or a blacklisted call. This is the generic catch-all, so `data` is what distinguishes them. | | `165` | The proof is missing the fee withdrawal. Append `feeAction` from `quote()` before proving. | | `167` | The withdrawn fee is below the quoted pool fee. Quote and prove again. | | `168` | On **build**, `sponsored_private` was used for a non-private transaction. On **execute**, the proof is missing. Branch on method and code together. | | `169` | The transaction exceeds the maximum L2 gas. | A retry means *re-proving*. The paymaster remembers the calls it has seen, so re-submitting the same proof answers `156 :: execution error Tx already sent`. ### `non-JSON response` or an HTTP status you did not expect Something in front of the paymaster failed, usually a proxy. Starkzap reports the real HTTP status as the error's `code` and keeps the response body in `data`. So a `413` from a proxy that caps request bodies is distinguishable from a paymaster rejection. A proof is around 300–320 kb for a simple transfer, and larger for transactions carrying more actions. That is above `express.json()`'s 100 kb default, so a proxy in front of the paymaster needs its limit raised; the example server uses `4mb`. ### Prover or discovery URL rejected Both must parse as URLs and use `https://`, or `http://` on a loopback host (`localhost`, `127.0.0.1`, `[::1]`). Plain `http://` to any other host is refused, because both services receive the viewing key. The same rule applies to `paymaster.url` and the OHTTP relay. For a trusted network such as a LAN devnet reached from a device or emulator, set `allowInsecureHttp: true` in the privacy config. A warning is still printed for any plain-http service URL. ### Web bundler reloads the page on first use The SDK reaches its optional peers through `await import(...)`, which Vite's scanner does not see. Add `@starkware-libs/starknet-privacy-sdk` to `optimizeDeps.include` so it lands in the first pre-bundle pass. ## Next steps * [Choosing a privacy protocol](/build/starkzap/privacy/overview) — STRK20 compared with Tongo * [Tongo confidential transfers](/build/starkzap/privacy/tongo) — the other privacy protocol * [Paymasters](/build/starkzap/paymasters) — how relayed submission works * [Transactions](/build/starkzap/transactions) — execution options * [API Reference](/build/starkzap/api-reference) — full method signatures # Tongo confidential transfers Source: https://docs.starknet.io/build/starkzap/privacy/tongo Fund, transfer, and withdraw using Tongo—privacy-preserving balances and transfers with the Tongo SDK ## Overview Starkzap supports **confidential** (privacy-preserving) transfers through the **[Tongo](https://www.tongo.cash/)** protocol. Using the Tongo SDK, users can perform confidential transfers (amount is obfuscated) on Starknet. Tongo hides **amounts**. It keeps one encrypted balance per account, proves locally, and returns plain calls that you batch and send from your own account. Tongo is one of two independent privacy protocols in Starkzap. It is not interchangeable with the [STRK20 privacy pool](/build/starkzap/privacy/strk20) — the two share no interface, so you pick one per integration. If you are still deciding, start with [Choosing a privacy protocol](/build/starkzap/privacy/overview). **What users can do with confidential:** * 🔐 **Transfer confidentially** - Send to another confidential account by recipient public key; amounts are hidden on-chain * 📋 **Compliance and auditing** - [Tongo](https://www.tongo.cash/) provides flexible auditing mechanisms that enable compliance without sacrificing user privacy. Through viewing keys and ex-post proving, regulators can verify transaction details while preserving confidentiality for all other parties. * ⚡ **Ragequit** -- Exit full balance and rollover (activate pending balance) The Tongo private key is separate from the Starknet wallet key. Your app must create and hold a `TongoConfidential` instance with the user's Tongo key and the correct Tongo contract address for the chain. ## Configuration Create a `TongoConfidential` instance with the Tongo contract for your chain, the user's Tongo private key, and an RPC provider (typically the wallet's provider). ```typescript theme={null} import { StarkZap, TongoConfidential } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer, accountClass } }); const confidential = await TongoConfidential.create({ privateKey: tongoPrivateKey, // BigNumberish or Uint8Array contractAddress: TONGO_CONTRACT, // Address for mainnet or Sepolia provider: wallet.getProvider(), }); ``` `create` is async because `@fatsolutions/tongo-sdk` is an optional peer dependency loaded on first use — if it is not installed, this rejects with an install hint instead of breaking the `starkzap` import. Use the Tongo protocol [documentation](https://docs.tongo.cash/protocol/contracts.html) or deployment list for the correct `contractAddress` per chain. There is no wallet-level "register confidential"; you pass the `TongoConfidential` into the tx builder methods. Tongo and the [STRK20 privacy pool](/build/starkzap/privacy/strk20) are separate integrations with no shared interface. Tongo keeps an encrypted *balance* per account and proves locally; the pool spends *notes* and needs a remote prover whose output rides on the transaction rather than inside a call. Pick the one you are integrating — there is nothing to code against generically. ## TongoConfidential: address and state **Tongo address (for display or sharing):** The `address` is a single base58-encoded public key — the recommended value to share with others so they can send you confidential transfers: ```typescript theme={null} const tongoAddress = confidential.address; // base58-encoded public key ``` **Recipient identity (for receiving confidential transfers):** `recipientId` is the same public key expressed as on-chain `{ x, y }` coordinates. Pass it (or a decoded shared address, see below) as `to` in a confidential transfer: ```typescript theme={null} const recipientId = confidential.recipientId; // { x, y } — pass as `to` in confidentialTransfer ``` **Decode a shared address into a recipient:** When a sender has someone's shareable `address` (the single base58 public key), `recipientFromAddress` converts it into the `{ x, y }` recipient expected by `confidentialTransfer` — so users can exchange one public key instead of raw coordinates: ```typescript theme={null} const recipient = confidential.recipientFromAddress(theirTongoAddress); // { x, y } ``` **Decrypted state (balance, pending, nonce):** ```typescript theme={null} const state = await confidential.getState(); console.log("Active balance:", state.balance); // spendable console.log("Pending balance:", state.pending); // needs rollover to become active console.log("Nonce:", state.nonce); ``` ### ConfidentialState shape ```typescript theme={null} interface ConfidentialState { balance: bigint; // active (spendable) pending: bigint; // pending (activate via rollover) nonce: bigint; } ``` ### Unit conversion (optional) Convert between public ERC20 amounts and confidential (Tongo) units when you need to display or validate amounts: ```typescript theme={null} const confidentialUnits = await confidential.toConfidentialUnits(Amount.parse("100", USDC)); const publicUnits = await confidential.toPublicUnits(confidentialUnits); ``` ## Funding a confidential account Convert public ERC20 into confidential balance. The tx builder calls the provider's `fund()` method, which returns the approve call (if needed) and the fund call. ```typescript theme={null} const tx = await wallet .tx() .confidentialFund(confidential, { amount: Amount.parse("100", USDC), sender: wallet.address, }) .send(); await tx.wait(); ``` Optional: pay a fee to the sender (e.g. for relayed transactions): ```typescript theme={null} .confidentialFund(confidential, { amount: Amount.parse("100", USDC), sender: wallet.address, feeTo: feeAmount, }) ``` ## Confidential transfer Send from this confidential account to another confidential account. The recipient is identified by their public key — either their `recipientId` (`{ x, y }`) or a shared `address` decoded with `recipientFromAddress`. ```typescript theme={null} // If you have the recipient's shared Tongo address (base58 public key): const to = confidential.recipientFromAddress(theirTongoAddress); // { x, y } const tx = await wallet .tx() .confidentialTransfer(confidential, { amount: Amount.parse("50", USDC), to, // or recipientConfidential.recipientId sender: wallet.address, }) .send(); await tx.wait(); ``` The transfer generates ZK proofs locally and submits the call on-chain; amounts are not visible on the public ledger. ## Withdrawing to a public address Convert confidential balance back to ERC20 and send to a Starknet address. ```typescript theme={null} const tx = await wallet .tx() .confidentialWithdraw(confidential, { amount: Amount.parse("25", USDC), to: wallet.address, // or any Starknet address sender: wallet.address, }) .send(); await tx.wait(); ``` ## Tongo-specific: ragequit and rollover **Ragequit** — Exit the entire confidential balance to a public address in one call. Use when the user wants to close the confidential account or move everything on-chain. ```typescript theme={null} const calls = await confidential.ragequit({ to: wallet.address, sender: wallet.address, }); const tx = await wallet.tx().add(...calls).send(); await tx.wait(); ``` **Rollover** — Move pending balance (from received confidential transfers) into the active balance so it can be spent. ```typescript theme={null} const calls = await confidential.rollover({ sender: wallet.address }); const tx = await wallet.tx().add(...calls).send(); await tx.wait(); ``` ## Batching with the transaction builder You can combine confidential operations with transfers, approvals, or other builder methods: ```typescript theme={null} const tx = await wallet .tx() .confidentialFund(confidential, { amount: Amount.parse("100", USDC), sender: wallet.address }) .transfer(USDC, [{ to: anotherAddress, amount: Amount.parse("10", USDC) }]) .send(); await tx.wait(); ``` Use at most **one** confidential operation per transaction for a given account. Each one is proved against the balance and nonce read when it was built. Once the first applies, the second is stale, and the whole transaction reverts. One confidential operation plus ordinary calls, as above, is fine. Send two confidential operations one after another. **Builder methods for confidential:** | Method | Description | | - | - | | `.confidentialFund(confidential, details)` | Fund confidential account (includes approve when needed) | | `.confidentialTransfer(confidential, details)` | Transfer to another confidential account | | `.confidentialWithdraw(confidential, details)` | Withdraw to a public Starknet address | Ragequit and rollover are not on the builder; use `confidential.ragequit(...)` / `confidential.rollover(...)` and add the returned calls with `.add(...calls)`. ## Reaching the rest of the Tongo SDK `TongoConfidential` wraps the operations that produce calls, plus balance reads and unit conversion. Tongo's own `Account` does more than that: transaction history and per-event reads, audit and ex-post proofs, raw encrypted state, and manual decryption. For any of those, construct the SDK's `Account` yourself with the same values you passed to `create`: ```typescript theme={null} import { Account } from "@fatsolutions/tongo-sdk"; const tongo = new Account(tongoPrivateKey, TONGO_CONTRACT, wallet.getProvider()); const history = await tongo.getTxHistory(fromBlock); const incoming = await tongo.getEventsTransferIn(fromBlock); ``` You already hold all three arguments, and the SDK is already installed. A second `Account` instance is safe. It holds no chain state and reads its nonce and balance fresh on every call, so it cannot disagree with the one inside `TongoConfidential`. ## Best practices 1. **Keep the Tongo key separate** from the Starknet wallet key; treat it as sensitive user data and protect it accordingly. 2. **Use the same Tongo contract address** as the rest of your app (same chain) so all operations are consistent. 3. **Check state before withdraw** — ensure `state.balance` (and pending if needed) is sufficient; use `toPublicUnits` / `toConfidentialUnits` if you need to show human-readable amounts. ## Troubleshooting ### Wrong contract or chain Ensure `contractAddress` matches the [Tongo](https://www.tongo.cash/) deployment for the wallet's chain. A mismatch can lead to failed transactions or wrong state. ### Fund fails or "approve" errors The provider includes the approve call in `fund()`; if the token or spender is non-standard, you may need to approve manually before calling the builder. Verify the token is the one supported by the Tongo contract. ### Recipient format for confidential transfer `to` must be a `ConfidentialRecipient` — the object `{ x, y }`. Get it from the recipient's `confidential.recipientId`, or decode their shared base58 Tongo address with `confidential.recipientFromAddress(address)`. Do not pass a Starknet address; use the recipient's Tongo public key. ## Next steps * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) — Token presets and amounts * [Transactions](/build/starkzap/transactions) — Execution options * [Tx Builder](/build/starkzap/tx-builder) — Batching and builder method list * [API Reference](/build/starkzap/api-reference) — Full method signatures # Quick Start Source: https://docs.starknet.io/build/starkzap/quick-start Get your first wallet integration working in minutes with code examples Quick Start wallets hero ## Quick Start ### Basic Example Here's a minimal example to get you started: **For testing, use Sepolia testnet** - Replace `"mainnet"` with `"sepolia"` and select tokens from `getPresets(wallet.getChainId())`. The [Starknet Sepolia Faucet](https://starknet-faucet.vercel.app/) is only available for Sepolia testnet (not mainnet). On mainnet, you'll need to purchase or transfer real tokens. ```typescript theme={null} import { StarkZap, StarkSigner, Amount, fromAddress, getPresets } from "starkzap"; // 1. Initialize the SDK (use "sepolia" for testing) const sdk = new StarkZap({ network: "sepolia" }); // 2. Connect a wallet with a private key const wallet = await sdk.connectWallet({ account: { signer: new StarkSigner("0xYOUR_PRIVATE_KEY") }, }); // 3. Get your account address to fund it const address = wallet.address; console.log("Your account address:", address.toString()); // Copy this address and use it to request tokens from the faucet: // https://starknet-faucet.vercel.app/ // 4. Fund the account first if this is a brand-new wallet (Sepolia testnet only): // - Visit https://starknet-faucet.vercel.app/ (only works for Sepolia testnet) // - Paste your account address // - Request 100 STRK (or 800 STRK if signed in with GitHub) // - Wait a few moments for the tokens to arrive // Note: On mainnet, you'll need to purchase or transfer real tokens // 5. Check a token balance const STRK = getPresets(wallet.getChainId()).STRK; const balance = await wallet.balanceOf(STRK); console.log(balance.toFormatted()); // "STRK 100" or "STRK 0" if empty // 6. Ensure the account is deployed before executing user-paid transactions // This will create the wallet on the blockchain if it doesn't exist yet // Note: Deployment requires tokens to pay for the transaction (unless using a paymaster) if (!balance.isZero()) { await wallet.ensureReady({ deploy: "if_needed" }); } // 7. Transfer tokens (only if balance is sufficient) if (!balance.isZero() && balance.gte(Amount.parse("10", STRK))) { const tx = await wallet.transfer(STRK, [ { to: fromAddress("0xRECIPIENT"), amount: Amount.parse("10", STRK) }, ]); console.log(tx.explorerUrl); // https://sepolia.voyager.online/tx/0x... await tx.wait(); } else { console.log("Insufficient balance. Get test tokens from: https://starknet-faucet.vercel.app/"); } ``` ### Onboarding API (Recommended) For most consumer applications, the **Onboarding API** is the recommended approach. Instead of manually setting up signers, creating wallets, and checking if accounts exist, the `onboard()` method does all of this in one call: Privy login/signup already happened before this snippet. `accessToken` identifies the current Privy user so your backend can return the correct signer context (`walletId`, `publicKey`) and authorize signatures. Your backend is a thin relay to Privy server APIs: 1. verify `accessToken` 2. return `{ walletId, publicKey, serverUrl }` 3. relay sign requests to Privy `rawSign` using `PRIVY_APP_SECRET` (kept server-side) ```typescript theme={null} import { StarkZap, OnboardStrategy, accountPresets, Amount, fromAddress, getPresets, } from "starkzap"; const sdk = new StarkZap({ network: "sepolia" }); // Example: user is already authenticated with Privy in your app const accessToken = await privy.getAccessToken(); // Onboard with Privy (recommended for consumer apps) const { wallet } = await sdk.onboard({ strategy: OnboardStrategy.Privy, privy: { resolve: async () => fetch("https://your-api.example/signer-context", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }).then((r) => r.json()), // { walletId, publicKey, serverUrl } }, accountPreset: accountPresets.argentXV050, feeMode: { type: "paymaster" }, deploy: "if_needed", }); // Use the wallet immediately const token = getPresets(wallet.getChainId()).STRK; const balance = await wallet.balanceOf(token); console.log(balance.toFormatted(true)); ``` ## Supported Onboarding Strategies The `sdk.onboard()` method lets you choose how users authenticate and connect their wallets. You can pick from these strategies: Best for consumer apps where you want Privy to manage wallet keys securely on their servers (like how OAuth providers manage login credentials). For sponsored transactions, you'll need to configure AVNU Paymaster separately: ```typescript theme={null} const onboard = await sdk.onboard({ strategy: OnboardStrategy.Privy, privy: { resolve: async () => fetch("https://your-api.example/signer-context", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, }).then((r) => r.json()), // { walletId, publicKey, serverUrl } }, accountPreset: accountPresets.argentXV050, deploy: "if_needed", }); ``` For apps where you manage private keys yourself (only use on secure servers, never in client-side code). For sponsored transactions, you'll need to configure AVNU Paymaster separately: ```typescript theme={null} import { StarkSigner, OnboardStrategy } from "starkzap"; const onboard = await sdk.onboard({ strategy: OnboardStrategy.Signer, account: { signer: new StarkSigner("0xPRIVATE_KEY") }, accountPreset: "openzeppelin", deploy: "if_needed", }); ``` Best for gaming applications where users sign in with social accounts (Google, Twitter) or biometrics (Face ID, Touch ID). **Web:** uses `@cartridge/controller`. **React Native / Expo:** use `starkzap-native`, Metro `withStarkzap`, and **register** the native Cartridge adapter before connecting — see [React Native Integration](/build/starkzap/react-native) and [Cartridge Controller](/build/starkzap/integrations/cartridge-controller). Cartridge can sponsor **policy-matching** calls via its paymaster/session stack; arbitrary transactions are not guaranteed to be gasless. ```typescript theme={null} import { OnboardStrategy } from "starkzap"; const onboard = await sdk.onboard({ strategy: OnboardStrategy.Cartridge, cartridge: { policies: [{ target: "0xCONTRACT", method: "transfer" }], }, // On native Cartridge, you may want deploy: "never" (default in some flows) or "if_needed" explicitly // deploy: "if_needed", }); ``` ## What the Onboarding API Handles The `sdk.onboard()` method automatically handles everything needed to get a wallet ready: * ✅ Chooses the right authentication method based on your strategy * ✅ Sets up the signer (the thing that proves ownership) * ✅ Connects and initializes the wallet * ✅ Creates the wallet on the blockchain if it doesn't exist yet * ✅ Configures network settings * ✅ Sets up fee payment options (user pays or paymaster) ## Next Steps Now that you have a basic example working: 1. Learn about [Configuration](/build/starkzap/configuration) options 2. Explore different [Wallet Connection](/build/starkzap/connecting-wallets) methods 3. See how to [Execute Transactions](/build/starkzap/transactions) 4. Explore [Bridging](/build/starkzap/bridging) for Ethereum/Solana deposits into Starknet # React Native Integration Source: https://docs.starknet.io/build/starkzap/react-native Set up StarkZap in React Native/Expo projects with starkzap-native and Metro configuration ## Overview Use `starkzap-native` when integrating StarkZap in React Native/Expo apps. It re-exports the SDK API and adds a Metro helper (`starkzap-native/metro`) that handles ESM/CJS resolver compatibility for `starknet` and its dependencies, and hoists your polyfills to run before the app entry. ## 1) Install Packages Install the React Native package and the one polyfill every StarkZap flow needs: ```bash theme={null} npm install starkzap-native npm install fast-text-encoding ``` Install the rest **only for the features you actually use** — each is reached by a specific code path, not by every flow: ```bash theme={null} # Cartridge sessions, paymaster / outside-execution (SNIP-9), random key generation npm install react-native-get-random-values # Ethereum bridge routes — pulls in ethers, which needs Buffer + crypto shims npm install ethers buffer @ethersproject/shims # Solana bridging — required for any Solana route npm install @solana/web3.js # Additionally, only if you use the Hyperlane Solana routes npm install @hyperlane-xyz/sdk @hyperlane-xyz/registry @hyperlane-xyz/utils # STRK20 privacy pool with OHTTP on. Supplies the `crypto.subtle` Hermes lacks npm install react-native-quick-crypto react-native-nitro-modules react-native-quick-base64 ``` ## 2) Configure Metro Set up Metro once and wrap your config with `withStarkzap`: ```javascript theme={null} // metro.config.js const { getDefaultConfig } = require("expo/metro-config"); const { withStarkzap } = require("starkzap-native/metro"); const config = getDefaultConfig(__dirname); // Optional: add your own resolver overrides before wrapping. module.exports = withStarkzap(config); ``` `withStarkzap` handles resolver compatibility for StarkZap dependencies and hoists your polyfills to run first. It does **not** import the polyfills for you — do that at your app entry (next step). ## 3) Load polyfills at your app entry `withStarkzap` can only run polyfills that are in Metro's module graph — it reorders them so they run first, but it cannot import them for you. Import the ones your app needs **once**, at your entry point (e.g. `index.js` or your root layout), before any StarkZap code runs. Each polyfill exists for a specific reason, so import only what your app actually uses: | Polyfill | Provides | Needed by | | - | - | - | | `fast-text-encoding` | `TextEncoder` / `TextDecoder` | **Every flow** — `starknet` hashes an entrypoint selector via `TextEncoder` on every contract read/write (`getSelectorFromName`), and decodes returned strings/byte-arrays | | `react-native-get-random-values` | `crypto.getRandomValues` | **Cartridge** sessions, **paymaster / outside-execution** (SNIP-9) nonces, and random key generation. Deterministic signing and read calls do **not** need it | | `buffer` (+ set the global) | `Buffer` | **Bridge only** — `ethers` and the Solana bridge code | | `@ethersproject/shims` | ethers crypto/random shims | **Bridge only** — `ethers` (v6) used by the Ethereum bridge. The shims package is itself v5-era, but it is what supplies the missing React Native crypto globals | | `react-native-quick-crypto` | `crypto.subtle` | **STRK20 privacy with OHTTP only.** The OHTTP transport needs WebCrypto. Hermes has none, and `react-native-get-random-values` supplies only `getRandomValues` | Required for every app: ```typescript theme={null} // polyfills.ts — imported first at your app entry import "fast-text-encoding"; ``` Add if you use Cartridge, the paymaster, or generate keys: ```typescript theme={null} import "react-native-get-random-values"; ``` Add if you use the bridge features: ```typescript theme={null} import { Buffer } from "buffer"; import "@ethersproject/shims"; if (!globalThis.Buffer) globalThis.Buffer = Buffer; ``` Add this if you use the [STRK20 privacy pool](/build/starkzap/privacy/strk20) with OHTTP on. Take only `subtle`. The package's own `install()` replaces the whole `crypto` global, including the `getRandomValues` installed above for `starknet`: ```typescript theme={null} if (!globalThis.crypto.subtle) { const { subtle } = require("react-native-quick-crypto"); Object.defineProperty(globalThis.crypto, "subtle", { value: subtle, configurable: true, }); } ``` Use `require` inside the guard, not a top-level `import`. The package loads native modules at import time, so an unconditional import crashes where those modules are absent, such as Expo Go. It needs a dev build (`npx expo run:android`). In an existing native project, run `npx expo prebuild` again so the C++ module is linked. Then import that file as the very first line of your entry: ```typescript theme={null} // app entry (e.g. app/_layout.tsx) import "./polyfills"; ``` If you skip `fast-text-encoding`, `starknet` crashes on the first contract call (`TextEncoder is not defined`) — `starkzap-native` warns in the console when it's absent. Skipping `react-native-get-random-values` surfaces a clear `crypto.getRandomValues must be defined` only when a Cartridge/paymaster/keygen path runs. ## 4) Initialize the SDK Import from `starkzap-native` and use the same onboarding APIs as web: ```typescript theme={null} import { StarkZap, OnboardStrategy, StarkSigner } from "starkzap-native"; const sdk = new StarkZap({ network: "sepolia" }); const { wallet } = await sdk.onboard({ strategy: OnboardStrategy.Signer, account: { signer: new StarkSigner("0xYOUR_PRIVATE_KEY") }, deploy: "if_needed", }); ``` For Privy-based onboarding, see [Privy Integration](/build/starkzap/integrations/privy). ## 5) Cartridge (native session) When your `starkzap-native` version supports it, you can onboard with **Cartridge** using an in-app browser / deep-link **session** flow (not the web `@cartridge/controller` popup). 1. **Register** the native Cartridge adapter **once** at app startup, before `connectCartridge()` or `onboard({ strategy: OnboardStrategy.Cartridge })`. Exact export names depend on your SDK version (for example `registerCartridgeTsAdapter` / `registerCartridgeNativeAdapter`). 2. Pass **policies** and/or a Cartridge **preset** that resolves policies for your chain (same concepts as [Cartridge Controller](/build/starkzap/integrations/cartridge-controller)). 3. Align `rpcUrl` / `chainId` (or `network`) on `new StarkZap({ ... })` with the session and paymaster you target. 4. **Deploy:** native Cartridge flows often default to **`deploy: "never"`** or recommend it when deployment semantics differ from the browser Controller. Pass **`deploy: "if_needed"`** explicitly if you need core-style deployment checks. 5. **Fees:** sponsored execution on native is tied to the session wallet (commonly **`feeMode: { type: "paymaster" }`** only for matching policy paths, without `gasToken`). See [Cartridge Controller](/build/starkzap/integrations/cartridge-controller) for policy and paymaster behavior, and the `examples/tic-tac-toe` app in the [Starkzap repository](https://github.com/keep-starknet-strange/starkzap) when available for a full Expo reference. ## 6) External Wallet Providers (Optional) If your app uses WalletConnect/Reown for external wallets, initialize its RN compatibility layer at app startup: ```typescript theme={null} import "@walletconnect/react-native-compat"; ``` Then pass the resulting providers into StarkZap external wallet adapters as described in [Bridging](/build/starkzap/bridging#connect-external-wallets). If `connectCartridge` or Cartridge onboarding throws **not implemented**, your installed `starkzap-native` build does not ship the native adapter yet — upgrade to a version that documents native Cartridge, or use **Signer** / **Privy** until then. ## Next Steps * [Quick Start](/build/starkzap/quick-start) * [Connecting Wallets](/build/starkzap/connecting-wallets) * [Bridging](/build/starkzap/bridging) * [Troubleshooting](/build/starkzap/troubleshooting) * [Examples](/build/starkzap/examples) # Staking & Delegation Source: https://docs.starknet.io/build/starkzap/staking Stake in Starknet delegation pools, claim rewards, and manage exits with the native two-step staking flow Staking and Delegation hero ## Overview This page covers Starkzap support for Starknet's native delegation staking protocol. Validators run pools where delegators can stake tokens and earn rewards. Looking for liquid staking (LST) via Endur? See [LST Staking](/build/starkzap/lst-staking). **What users can do with staking:** * 💰 **Earn passive income** - Stake tokens (like STRK) and earn rewards over time, similar to a savings account with interest * 🏦 **Support network security** - Stake tokens to help secure the Starknet network while earning rewards * 📈 **Grow their holdings** - Rewards compound over time, allowing users to increase their token holdings without additional purchases * ⏰ **Flexible participation** - Add more tokens anytime, claim rewards when ready, and exit when needed (with a cooldown period) **Real-world analogy:** Think of staking like putting money in a high-yield savings account or certificate of deposit (CD). You lock up your tokens for a period, and in return, you earn interest (rewards). The longer you stake, the more you can potentially earn. Staking contract is selected automatically from chain-aware presets. You only need `staking.contract` if you want to override the default. ## Configuration Staking works out of the box when using a supported chain preset: ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", }); ``` Optional override: ```typescript theme={null} import { StarkZap, fromAddress } from "starkzap"; const sdk = new StarkZap({ network: "mainnet", staking: { contract: fromAddress( "0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7" ), }, }); ``` ## Discovering Validators and Pools ### Using Validator Presets ```typescript theme={null} import { mainnetValidators, sepoliaValidators } from "starkzap"; // Use built-in validator presets for (const validator of Object.values(mainnetValidators)) { console.log(`${validator.name}: ${validator.stakerAddress}`); } // Also available: sepoliaValidators ``` ### Getting Stakeable Tokens ```typescript theme={null} const tokens = await sdk.stakingTokens(); console.log(tokens.map((t) => t.symbol)); // ["STRK", ...] ``` ### Getting Pools for a Validator ```typescript theme={null} const pools = await sdk.getStakerPools(validator.stakerAddress); for (const pool of pools) { console.log(`${pool.token.symbol}: ${pool.amount.toFormatted()} delegated`); console.log(`Pool contract: ${pool.poolContract}`); } ``` ## Entering a Pool Stake tokens for the first time in a validator's pool. The SDK handles the token approval automatically. ```typescript theme={null} const tx = await wallet.enterPool(poolAddress, Amount.parse("100", STRK)); await tx.wait(); ``` This will: 1. Approve the staking contract to spend your tokens (if needed) 2. Call `enter_delegation_pool` on the staking contract 3. Return a transaction you can track ## Smart Stake (Recommended) Use `wallet.stake()` when you don't want to branch on membership manually: ```typescript theme={null} const tx = await wallet.stake(poolAddress, Amount.parse("100", STRK)); await tx.wait(); ``` `wallet.stake()` automatically: * calls `enter_delegation_pool` for first-time stakers * calls `add_to_delegation_pool` for existing members ## Adding to an Existing Stake If you're already a pool member, add more tokens: ```typescript theme={null} const tx = await wallet.addToPool(poolAddress, Amount.parse("50", STRK)); await tx.wait(); ``` ## Claiming Rewards Users earn rewards continuously while their tokens are staked. Rewards accumulate over time and can be claimed at any time: ```typescript theme={null} const position = await wallet.getPoolPosition(poolAddress); if (position && !position.rewards.isZero()) { const tx = await wallet.claimPoolRewards(poolAddress); await tx.wait(); console.log(`Claimed ${position.rewards.toFormatted()}`); } ``` **What users get:** Rewards are paid in the same token they staked (e.g., if you stake STRK, you earn STRK rewards). Users can claim rewards and either: * Withdraw them to their wallet * Re-stake them to compound earnings (add them back to the pool) ## Exiting a Pool (Two-Step Process) Exiting a pool is a two-step process with an exit window: ### Step 1: Declare Exit Intent Tokens stop earning rewards immediately: ```typescript theme={null} const tx = await wallet.exitPoolIntent( poolAddress, Amount.parse("50", STRK) ); await tx.wait(); ``` ### Step 2: Complete Withdrawal Wait for the exit window, then complete the withdrawal: ```typescript theme={null} const position = await wallet.getPoolPosition(poolAddress); if (position?.unpoolTime && new Date() >= position.unpoolTime) { const tx = await wallet.exitPool(poolAddress); await tx.wait(); console.log("Tokens returned to wallet"); } ``` ## Querying Position Get detailed information about your staking position to show users their earnings: ```typescript theme={null} const position = await wallet.getPoolPosition(poolAddress); if (position) { console.log(`Staked: ${position.staked.toFormatted()}`); // Original amount staked console.log(`Rewards: ${position.rewards.toFormatted()}`); // Earned rewards (can claim) console.log(`Total: ${position.total.toFormatted()}`); // Staked + rewards combined console.log(`Commission: ${position.commissionPercent}%`); // Validator's fee console.log(`Unpooling: ${position.unpooling.toFormatted()}`); // Amount being withdrawn console.log(`Unpool at: ${position.unpoolTime}`); // When withdrawal completes } ``` **What this shows users:** * How much they've staked (their original investment) * How much they've earned in rewards (their passive income) * Their total position value (staked + rewards) * When they can withdraw if they've initiated an exit ### Position Interface ```typescript theme={null} interface PoolMember { staked: Amount; rewards: Amount; total: Amount; unpooling: Amount; unpoolTime: Date | null; commissionPercent: number; rewardAddress: Address; } ``` ### Checking Membership ```typescript theme={null} const isMember = await wallet.isPoolMember(poolAddress); ``` ### Getting Commission Rate ```typescript theme={null} const commission = await wallet.getPoolCommission(poolAddress); console.log(`Commission: ${commission}%`); ``` ## Using Transaction Builder The transaction builder makes it easy to combine staking operations with other actions: ```typescript theme={null} const tx = await wallet .tx() // Transfer some tokens .transfer(STRK, [{ to: recipient, amount: Amount.parse("10", STRK) }]) // Stake in a pool (auto-detects enter vs. add) .stake(poolAddress, Amount.parse("100", STRK)) // Claim rewards from another pool .claimPoolRewards(anotherPoolAddress) .send(); await tx.wait(); ``` The `.stake()` method automatically: * Calls `enter_delegation_pool` for new members * Calls `add_to_delegation_pool` for existing members ## Staking Flow Examples ### Complete Staking Flow Here's a complete example showing how users can stake, earn, and manage their position: ```typescript theme={null} // 1. Discover pools const pools = await sdk.getStakerPools(validator.stakerAddress); const strkPool = pools.find((p) => p.token.symbol === "STRK"); // 2. Enter pool - User stakes 100 STRK and starts earning immediately await wallet.enterPool(strkPool.poolContract, Amount.parse("100", STRK)); // User's tokens are now earning rewards in the background // 3. Check position - Show user their earnings const position = await wallet.getPoolPosition(strkPool.poolContract); console.log(`Staked: ${position.staked.toFormatted()}`); // "STRK 100" console.log(`Rewards earned: ${position.rewards.toFormatted()}`); // "STRK 2.5" (example) // 4. Add more later - User can increase their stake anytime await wallet.addToPool(strkPool.poolContract, Amount.parse("50", STRK)); // Now staking 150 STRK total, earning more rewards // 5. Claim rewards - User withdraws their earned rewards await wallet.claimPoolRewards(strkPool.poolContract); // Rewards are sent to user's wallet, can be spent or re-staked // 6. Exit (two-step) - User wants to withdraw some tokens await wallet.exitPoolIntent(strkPool.poolContract, Amount.parse("25", STRK)); // ... wait for exit window (cooldown period) ... await wallet.exitPool(strkPool.poolContract); // 25 STRK returned to user's wallet ``` ### Monitoring Rewards Show users their earnings and help them claim rewards: ```typescript theme={null} async function checkRewards(wallet, poolAddress) { const position = await wallet.getPoolPosition(poolAddress); if (!position) { console.log("Not a member of this pool"); return; } if (position.rewards.isZero()) { console.log("No rewards available yet"); return; } // Show user their earnings console.log(`Available rewards: ${position.rewards.toFormatted()}`); console.log(`Total position: ${position.total.toFormatted()}`); // Staked + rewards // Auto-claim if above threshold (optional) if (position.rewards.gt(Amount.parse("1", STRK))) { await wallet.claimPoolRewards(poolAddress); console.log("Rewards claimed! Tokens sent to your wallet."); } } ``` **User experience:** Users can see their earnings growing in real-time, claim rewards whenever they want, and watch their total position value increase over time. ## User Benefits Summary **What users can do with staking in your app:** * 💰 **Earn passive income** - Stake tokens and earn rewards automatically, like interest on a savings account * 📈 **Grow their holdings** - Rewards accumulate over time, increasing their total token balance * 🔄 **Flexible management** - Add more tokens anytime, claim rewards when ready, exit when needed * 🏦 **Support the network** - Help secure Starknet while earning rewards * ⏱️ **Set and forget** - Once staked, tokens earn rewards in the background without daily management **Real-world use cases:** * **Savings feature** - Let users "save" their tokens and earn interest * **Investment tool** - Enable users to grow their crypto holdings over time * **Rewards program** - Users can stake tokens to earn additional rewards * **Long-term holding** - Encourage users to hold tokens by offering staking rewards ## Best Practices 1. **Always check pool membership** before calling `addToPool` 2. **Monitor rewards regularly** and show users their earnings 3. **Help users understand exit windows** before initiating exit 4. **Use transaction builder** to batch staking with other operations 5. **Override staking contract only if needed** (custom deployments, forks, or private environments) 6. **Display earnings clearly** - Show users their staked amount, earned rewards, and total position value ## Troubleshooting ### "No staking contract configured for chain ..." The SDK has no staking contract for the current chain, or you set a custom `staking.contract` that doesn't match. Remove the override to use the built-in preset, or set the correct contract for your network. ### Pool Not Found Ensure you're using the correct pool contract address. Use `getStakerPools()` to discover available pools. ## Next Steps * Learn about [Transaction Builder](/build/starkzap/transactions#transaction-builder) for batching operations * Explore [LST Staking](/build/starkzap/lst-staking) for liquid staking flows with `wallet.lstStaking(asset)` * Explore [ERC20 Token Operations](/build/starkzap/erc20) * Check the [API Reference](/build/starkzap/api-reference) for detailed method signatures # Swaps Source: https://docs.starknet.io/build/starkzap/swap Exchange one token for another using AVNU or Ekubo swap providers—get quotes, set slippage, and execute swaps ## Overview Starkzap lets users swap one token for another through pluggable **swap providers**. The SDK supports **[AVNU](https://app.avnu.fi/en)** (DEX aggregator) and **[Ekubo](https://ekubo.org/starknet/swap)** (concentrated liquidity AMM). You get a quote, optionally set slippage tolerance, and execute the swap in one call. **What users can do with swaps:** * 📊 **Get quotes** - See how much they will receive before swapping (amount out, price impact) * 🔄 **Execute swaps** - Exchange tokens in a single transaction with optional slippage protection * 🏪 **Choose providers** - Use [AVNU](https://app.avnu.fi/en) for aggregated routes or [Ekubo](https://ekubo.org/starknet/swap) for concentrated liquidity * 📦 **Batch with other actions** - Run a swap then use the transaction builder for follow-up operations For **recurring buys (Dollar-Cost Averaging)**, see [DCA](/build/starkzap/dollar-cost-average). The wallet fills `chainId` and `takerAddress` from the connected wallet when you call `getQuote()` or `swap()`. You only need to pass `tokenIn`, `tokenOut`, and `amountIn` (and optionally `slippageBps` or `provider`). ## Configuration ### Registering swap providers Register one or more swap providers when connecting the wallet (or at runtime). If you register multiple providers, set a default so `getQuote()` and `swap()` work without specifying `provider` on each request. `AvnuSwapProvider` requires the optional peer dependency `@avnu/avnu-sdk` (`npm install @avnu/avnu-sdk`). It is loaded lazily on the first quote/swap, so the first call throws an install hint if the package is missing. `EkuboSwapProvider` needs no extra dependency. See [Installation](/build/starkzap/installation#peer-dependencies-by-feature). **At connect / onboard:** ```typescript theme={null} import { StarkZap, AvnuSwapProvider, EkuboSwapProvider, ArgentPreset } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer, accountClass: ArgentPreset }, swapProviders: [ new AvnuSwapProvider(), new EkuboSwapProvider(), ], defaultSwapProviderId: "avnu", }); ``` **At runtime (e.g. after Cartridge onboard):** ```typescript theme={null} wallet.registerSwapProvider(new AvnuSwapProvider()); wallet.registerSwapProvider(new EkuboSwapProvider()); wallet.setDefaultSwapProvider("avnu"); ``` ### Token presets Use the SDK token presets for `tokenIn` and `tokenOut` so addresses and decimals match the chain: ```typescript theme={null} import { getPresets, mainnetTokens } from "starkzap"; const tokens = getPresets(wallet.getChainId()); const STRK = tokens.STRK ?? mainnetTokens.STRK; const USDC = tokens.USDC ?? mainnetTokens.USDC; ``` See [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20#token-presets) for more on token presets. ## Getting a quote Call `wallet.getQuote()` with the swap input. The wallet uses the default swap provider unless you pass `provider`: ```typescript theme={null} import { Amount } from "starkzap"; const quote = await wallet.getQuote({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), }); console.log(quote.amountInBase); // input in base units console.log(quote.amountOutBase); // expected output in base units console.log(quote.priceImpactBps); // optional price impact (basis points) console.log(quote.provider); // e.g. "avnu" ``` **With slippage and a specific provider:** ```typescript theme={null} const quote = await wallet.getQuote({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), slippageBps: 50n, // 0.5% provider: "ekubo", }); ``` ### Quote shape ```typescript theme={null} interface SwapQuote { amountInBase: bigint; amountOutBase: bigint; routeCallCount?: number; priceImpactBps?: bigint | null; provider?: string; } ``` ## Executing a swap Use the same request shape as the quote. The wallet resolves the provider and builds the swap calls, then executes them. **Minimal swap:** ```typescript theme={null} const tx = await wallet.swap({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), }); await tx.wait(); ``` **With slippage:** ```typescript theme={null} const tx = await wallet.swap( { tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), slippageBps: 100n, // 1% }, { feeMode: { type: "paymaster" } } // optional execution options ); await tx.wait(); ``` ## Providers | Provider | Id | Description | | - | - | - | | **[AVNU](https://app.avnu.fi/en)** | `avnu` | DEX aggregator; finds best route across liquidity sources | | **[Ekubo](https://ekubo.org/starknet/swap)** | `ekubo` | Concentrated liquidity AMM; single-protocol routes | **List and switch providers:** ```typescript theme={null} const ids = wallet.listSwapProviders(); console.log(ids); // ["avnu", "ekubo"] // Use a specific provider for one request const quote = await wallet.getQuote({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), provider: "ekubo", }); ``` ## Batching with other operations Use the transaction builder's `.swap()` method to batch a swap with other operations in a single atomic transaction: ```typescript theme={null} const tx = await wallet .tx() .swap({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), }) .transfer(USDC, [{ to: recipient, amount: Amount.parse("50", USDC) }]) .send(); await tx.wait(); ``` You can also execute a swap standalone via `wallet.swap()`: ```typescript theme={null} const tx = await wallet.swap({ tokenIn: STRK, tokenOut: USDC, amountIn: Amount.parse("100", STRK), }); await tx.wait(); ``` For **Dollar-Cost Averaging (recurring buys)** with AVNU or Ekubo, see [DCA](/build/starkzap/dollar-cost-average). ## Best practices 1. **Always get a quote first** and show users the expected amount out and price impact before executing. 2. **Set slippage** (`slippageBps`) for volatile pairs or large sizes to avoid failed transactions. 3. **Use token presets** from `getPresets(chainId)` or `mainnetTokens` / `sepoliaTokens` so token addresses and decimals match the chain. 4. **Handle chain mismatch** — ensure the wallet's chain matches the swap provider's supported chains (AVNU and Ekubo support mainnet and Sepolia). ## Troubleshooting ### "No default swap provider configured" Register at least one swap provider and call `setDefaultSwapProvider(providerId)` (or pass `defaultSwapProviderId` when connecting). Alternatively, pass `provider: "avnu"` or `provider: "ekubo"` on every `getQuote()` / `swap()` call. ### "requires optional peer dependency "@avnu/avnu-sdk"" The AVNU provider is selected but `@avnu/avnu-sdk` is not installed. Run `npm install @avnu/avnu-sdk`, or use `EkuboSwapProvider` (no extra dependency) instead. ### Chain or provider mismatch Ensure the wallet is on a chain supported by the chosen provider (e.g. `SN_MAIN` or `SN_SEPOLIA`). If you see a chain mismatch error, align the SDK network config with the RPC/wallet chain. ### Swap returned no calls The provider could not build a route for the pair or amount. Try a different provider, a smaller amount, or another token pair. ## Next steps * [DCA](/build/starkzap/dollar-cost-average) — Recurring buys (Dollar-Cost Averaging) with AVNU or Ekubo * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) — Token presets and amounts * [Transactions](/build/starkzap/transactions) — Execution options and fee modes * [Tx Builder](/build/starkzap/tx-builder) — Batching transfers and other operations * [API Reference](/build/starkzap/api-reference) — Full method signatures # Executing Transactions Source: https://docs.starknet.io/build/starkzap/transactions Send transactions, batch operations, simulate transactions, and handle fees ## Overview Transaction execution is the process of sending operations to the Starknet blockchain. Starkzap provides multiple ways to execute transactions, from simple contract calls to complex batched operations. **What is a transaction?** A transaction is a request to execute a function on a smart contract deployed on Starknet. Think of it like calling an API endpoint—you're invoking a function on a deployed contract that performs some action (like transferring tokens, updating state, or executing business logic). **Requirements:** * A smart contract must be deployed on Starknet before you can execute transactions against it * Your wallet account must be deployed (the SDK handles this automatically with `deploy: "if_needed"`) * Sufficient balance to pay for gas fees (unless using a paymaster for sponsored transactions) **Want to deploy your own smart contracts?** This guide focuses on executing transactions against existing contracts. If you need to create and deploy your own smart contracts, check out the [Starknet Quickstart Guide](https://docs.starknet.io/build/quickstart/overview) for comprehensive tutorials on smart contract development with Cairo. This guide covers all transaction execution patterns for interacting with deployed smart contracts. ## Direct Execution Use `wallet.execute()` for arbitrary contract calls: ```typescript theme={null} import type { Call } from "starknet"; const call: Call = { contractAddress: "0xCONTRACT_ADDRESS", entrypoint: "transfer", calldata: ["0xRECIPIENT", "1000000", "0"], }; const tx = await wallet.execute([call]); await tx.wait(); ``` ### Multiple Calls (Atomic Batching) Multiple calls execute atomically in a single transaction: ```typescript theme={null} const tx = await wallet.execute([approveCall, swapCall, transferCall]); await tx.wait(); ``` All calls succeed or fail together — no partial execution. ## Transaction Tracking Every `execute()`, `deploy()`, or `transfer()` call returns a `Tx` object: ```typescript theme={null} const tx = await wallet.execute(calls); // Transaction hash console.log(tx.hash); // "0x..." // Block explorer link console.log(tx.explorerUrl); // "https://voyager.online/tx/0x..." // Wait for confirmation (L2 acceptance) await tx.wait(); // Wait for L1 finality await tx.wait({ successStates: [TransactionFinalityStatus.ACCEPTED_ON_L1], }); // Get full receipt const receipt = await tx.receipt(); console.log(receipt.actual_fee); // Watch status changes in real-time const unsubscribe = tx.watch(({ finality, execution }) => { console.log(`Status: ${finality} (${execution})`); // "RECEIVED" → "ACCEPTED_ON_L2" → "ACCEPTED_ON_L1" }); // Stop watching early if needed unsubscribe(); ``` ## Paymaster Transactions (Sponsored / Gasless) If the SDK is configured with a paymaster, use `feeMode: { type: "paymaster" }`: ```typescript theme={null} // Per-transaction sponsorship const tx = await wallet.execute([call], { feeMode: { type: "paymaster" } }); // Or set as default when connecting const wallet = await sdk.connectWallet({ account: { signer }, feeMode: { type: "paymaster" }, }); // Now all transactions are sponsored by default const tx = await wallet.execute([call]); // sponsored automatically ``` The SDK works seamlessly with [AVNU Paymaster](https://docs.avnu.fi/docs/paymaster/index) for sponsored transactions. See the [AVNU Paymaster Integration](/build/starkzap/integrations/avnu-paymaster) guide for setup instructions. **Cartridge:** On **web** Controller and **native** session flows, matching calls can be paymastered per Cartridge's session / SNIP-9 rules after users approve **policies**. You typically do **not** configure AVNU for those paths. Arbitrary calls outside approved policies are not guaranteed to be sponsored. On React Native, sponsored execution is commonly tied to **`feeMode: { type: "paymaster" }`** on the session wallet. See [Cartridge Controller](/build/starkzap/integrations/cartridge-controller) and [React Native Integration](/build/starkzap/react-native). `feeMode: "sponsored"` is still supported as a deprecated alias for backward compatibility. ## Preflight Simulation Simulate transactions before sending to check for errors: ```typescript theme={null} const result = await wallet.preflight({ calls }); if (!result.ok) { console.error(result.reason); } else { const tx = await wallet.execute(calls); await tx.wait(); } ``` Always preflight non-trivial batches before submitting to catch errors early. ## Transaction Builder For batching multiple operations into a single atomic transaction, use the `TxBuilder`. This is especially useful for complex operations that need to execute together. **For detailed TxBuilder documentation**, see the [Tx Builder](/build/starkzap/tx-builder) guide which covers all builder methods, preflight simulation, fee estimation, and best practices. Quick example: ```typescript theme={null} const tx = await wallet .tx() .transfer(USDC, { to: alice, amount }) .stake(poolAddress, stakeAmount) .send(); await tx.wait(); ``` ## Fee Modes ### User Pays (Default) ```typescript theme={null} const tx = await wallet.execute(calls, { feeMode: "user_pays" }); ``` The user's account pays for transaction fees. ### Paymaster (Sponsored / Gasless) ```typescript theme={null} const tx = await wallet.execute(calls, { feeMode: { type: "paymaster" } }); ``` The paymaster path covers transaction fees (sponsored) or lets users pay with a supported gas token. ```typescript theme={null} import { fromAddress } from "starkzap"; const tx = await wallet.execute(calls, { feeMode: { type: "paymaster", gasToken: fromAddress("0x053c91253bc96bfed3be381a97265e250ed0a2e2cbf1a54898ad0d2f7982f78f"), }, }); ``` **Cartridge:** Policy-matching calls can be paymastered per Cartridge session rules; arbitrary calls are not guaranteed sponsored. You typically do not configure AVNU for Cartridge paths. See [Cartridge Controller](/build/starkzap/integrations/cartridge-controller) and [React Native Integration](/build/starkzap/react-native). ## Transaction Status Transactions go through several states: 1. **RECEIVED** - Transaction received by the sequencer 2. **ACCEPTED\_ON\_L2** - Transaction accepted on L2 (most common success state) 3. **ACCEPTED\_ON\_L1** - Transaction finalized on L1 ```typescript theme={null} // Wait for L2 acceptance (default) await tx.wait(); // Wait for L1 finality await tx.wait({ successStates: [TransactionFinalityStatus.ACCEPTED_ON_L1], }); ``` ## Error Handling Always handle transaction errors: ```typescript theme={null} try { const tx = await wallet.execute(calls); await tx.wait(); console.log("Transaction successful!"); } catch (error) { console.error("Transaction failed:", error); } ``` ## Best Practices 1. **Always preflight** non-trivial batches before submitting 2. **Use the transaction builder** for complex operations to save gas 3. **Handle errors gracefully** and provide user feedback 4. **Standardize fee behavior** (`user_pays` vs `paymaster`) per flow 5. **Wait for appropriate confirmation** based on your use case ## Next Steps * Learn about [ERC20 Token Operations](/build/starkzap/erc20) * Explore [Staking & Delegation](/build/starkzap/staking) * Check the [API Reference](/build/starkzap/api-reference) for detailed method signatures # Troubleshooting Source: https://docs.starknet.io/build/starkzap/troubleshooting Common issues and solutions when using Starkzap ## Common Issues ### "No staking contract configured for chain ..." **Problem:** The SDK has no staking contract for the current chain, or you set a custom `staking.contract` that doesn't match. **Solution:** Remove the custom override to use chain-aware defaults, or set the correct contract for your network: ```typescript theme={null} import { StarkZap } from "starkzap"; // Recommended: use built-in chain-aware staking preset const sdk = new StarkZap({ network: "mainnet" }); ``` ### Transaction Submits but Explorer URL Points to Wrong Explorer **Problem:** Transactions execute successfully but `tx.explorerUrl` points to the default explorer (Voyager) instead of your preferred one. **Solution:** Configure the `explorer` in your SDK config to use your preferred provider: ```typescript theme={null} const sdk = new StarkZap({ network: "mainnet", explorer: { provider: "starkscan" }, // or "voyager" (default) }); ``` ### React Native Random Values/Text Encoding Issues **Problem:** Errors related to random value generation or text encoding in React Native. **Solution:** Use the React Native setup guide and Metro wrapper, which handles required polyfills and resolver compatibility. 1. Follow [React Native Integration](/build/starkzap/react-native) 2. Ensure your `metro.config.js` is wrapped with `withStarkzap(...)` 3. Clear Metro cache after dependency changes (`npx expo start -c`) If you still need manual installation checks, make sure these packages are present: ```bash theme={null} npm install react-native-get-random-values fast-text-encoding buffer @ethersproject/shims ``` If not using `withStarkzap`, import polyfills in your entrypoint (e.g. `index.js` or `App.tsx`): ```typescript theme={null} import "react-native-get-random-values"; import "fast-text-encoding"; import "@ethersproject/shims"; ``` ### Account Not Deployed Error **Problem:** Getting errors about account not being deployed when trying to execute transactions. **Solution:** Ensure the account is deployed before executing transactions: ```typescript theme={null} // Option 1: Use ensureReady await wallet.ensureReady({ deploy: "if_needed" }); // Option 2: Deploy explicitly if (!(await wallet.isDeployed())) { await wallet.deploy(); } // Option 3: Use onboard API which handles this automatically const onboard = await sdk.onboard({ strategy: OnboardStrategy.Signer, account: { signer }, deploy: "if_needed", }); ``` ### Privy Signer Backend Not Responding **Problem:** Privy signer fails with network errors or timeouts. **Solution:** 1. Verify your backend endpoint is accessible 2. Ensure the endpoint accepts POST requests with `{ walletId, hash }` 3. Check that the endpoint returns `{ signature }` in the correct format 4. Verify CORS settings if calling from a browser Example backend endpoint: ```typescript theme={null} // Express.js example app.post("/api/wallet/sign", async (req, res) => { const { walletId, hash } = req.body; const signature = await privyClient.wallets().rawSign(walletId, { params: { hash }, }); res.json({ signature }); }); ``` ### Cartridge Connection Fails **Problem:** Cartridge wallet connection doesn't work or popup doesn't appear. **Solution:** 1. Ensure `@cartridge/controller` is installed 2. Check that you're calling `connectCartridge` in a browser environment 3. Verify network configuration matches Cartridge's supported networks 4. Check browser console for Cartridge-specific errors ### Bridge Token Loading Fails **Problem:** `sdk.getBridgingTokens()` throws an error or returns no tokens. **Solution:** 1. Verify the SDK chain config is correct (`SN_MAIN` vs `SN_SEPOLIA`) and matches your intended environment 2. Confirm network access to the token API endpoint used by the SDK 3. Install required optional deps for the chains you use: * Ethereum routes: `ethers` * Solana routes: `@solana/web3.js` (and Hyperlane packages for Solana bridge operations) ```bash theme={null} npm install ethers @solana/web3.js @hyperlane-xyz/sdk @hyperlane-xyz/registry @hyperlane-xyz/utils ``` ### OFT Bridge Fails with LayerZero API Key Error **Problem:** You see an error like: `OFT bridging requires a LayerZero API key. Set "bridging.layerZeroApiKey"...` **Solution:** 1. Set `bridging.layerZeroApiKey` in `new StarkZap({ ... })` 2. Use a mainnet route for OFT/OFT-migrated bridge flows ```typescript theme={null} const sdk = new StarkZap({ network: "mainnet", bridging: { layerZeroApiKey: "YOUR_LAYERZERO_API_KEY", }, }); ``` ### Bridge Deposit Fails with Chain Mismatch **Problem:** Deposit fails with errors indicating token chain and external wallet chain do not match. **Solution:** 1. Ensure the selected `BridgeToken.chain` matches the connected external wallet type 2. Ensure external wallet network and Starknet network pairing is valid (mainnet with mainnet, testnet with testnet) 3. Recreate connected external wallets with `ConnectedEthereumWallet.from(...)` / `ConnectedSolanaWallet.from(...)` after network switch ### Amount Arithmetic Errors **Problem:** Getting errors when performing arithmetic on amounts. **Solution:** Ensure amounts have compatible decimals and symbols: ```typescript theme={null} // ❌ This will throw const a = Amount.parse("10", STRK); // 18 decimals const b = Amount.parse("5", USDC); // 6 decimals const c = a.add(b); // Error: incompatible amounts // ✅ Use amounts with same token const a = Amount.parse("10", STRK); const b = Amount.parse("5", STRK); const c = a.add(b); // Works! ``` ### Transaction Fails in Preflight but Not in Execution **Problem:** Preflight simulation shows errors, but transaction would succeed. **Solution:** Preflight uses the current state, which may differ from execution time. This is normal - preflight is a best-effort check. Always handle transaction failures gracefully: ```typescript theme={null} try { const tx = await wallet.execute(calls); await tx.wait(); } catch (error) { // Handle error appropriately console.error("Transaction failed:", error); } ``` ### TypeScript Type Errors **Problem:** TypeScript complains about type mismatches. **Solution:** Ensure you're using the correct types: ```typescript theme={null} import { Address, Amount, ChainId } from "starkzap"; // Use fromAddress for addresses const addr: Address = fromAddress("0x..."); // Use Amount.parse for amounts const amount: Amount = Amount.parse("10", STRK); // Use ChainId constants const chainId = ChainId.MAINNET; ``` ### Network Configuration Issues **Problem:** SDK can't connect to the network or wrong network is used. **Solution:** Verify your network configuration: ```typescript theme={null} // Check current chain ID const chainId = wallet.getChainId(); console.log(chainId.toLiteral()); // "SN_MAIN" or "SN_SEPOLIA" // Verify RPC URL const provider = sdk.getProvider(); const chainIdFromProvider = await provider.getChainId(); ``` ### Fee Estimation Fails **Problem:** `estimateFee()` throws errors. **Solution:** 1. Ensure the account is deployed 2. Verify the calls are valid 3. Check that you have sufficient balance for fees 4. Try using `preflight()` first to catch call errors ## Getting More Help If you're still experiencing issues: 1. Check the [API Reference](/build/starkzap/api-reference) for detailed method signatures 2. Review the [Examples](/build/starkzap/examples) for working code samples 3. Ask for help in the [Starknet Discord community](https://discord.gg/starknet-community) 4. Browse the [Starknet community forum](https://community.starknet.io/) ## Reporting Issues If you find a bug or have a feature request: 1. Check if the issue is already reported 2. Create a detailed issue report with: * SDK version * Runtime environment (Node.js, browser, React Native) * Steps to reproduce * Expected vs actual behavior * Error messages and stack traces # Troves Source: https://docs.starknet.io/build/starkzap/troves Discover Troves strategies, inspect positions, and deposit or withdraw through Starkzap ## Overview Starkzap supports [Troves](https://app.troves.fi) strategies through `wallet.troves()`. Troves combines off-chain strategy discovery with on-chain execution: * 📊 **Discover strategies** - Fetch the currently available Troves strategies and protocol-level stats * 🧾 **Inspect positions** - Read the wallet's share balance and the current underlying asset amounts for a strategy * 💸 **Deposit** - Build and execute the Troves deposit flow from typed `Amount` inputs * 📤 **Withdraw** - Build and execute the Troves withdraw flow with the same typed API * 📦 **Batch with other actions** - Compose Troves deposits or withdrawals inside `wallet.tx()` Troves uses the Troves HTTP API to discover strategies and build the required calls, then executes those calls through the connected Starknet wallet. ## Configuration Troves is available from the wallet: ```typescript theme={null} import { StarkZap } from "starkzap"; const sdk = new StarkZap({ network: "mainnet" }); const wallet = await sdk.connectWallet({ account: { signer, accountClass }, }); const troves = wallet.troves(); ``` By default, Troves is **mainnet-only**. If the wallet is not on Starknet Mainnet, `wallet.troves()` throws unless you explicitly override the API base: ```typescript theme={null} const troves = wallet.troves({ apiBase: "https://your-custom-troves-backend.example.com", timeoutMs: 15000, }); ``` Use `apiBase` only when you intentionally want to point the SDK at a custom Troves-compatible backend. ## Discovering strategies Fetch the current strategy catalog: ```typescript theme={null} const { strategies, lastUpdated } = await wallet.troves().getStrategies(); console.log("Updated at:", lastUpdated); for (const strategy of strategies) { console.log(strategy.id, strategy.name); console.log("APY:", strategy.apy); console.log("TVL (USD):", strategy.tvlUsd); console.log( "Deposit tokens:", strategy.depositTokens.map((token) => token.symbol).join(", ") ); } ``` You can force a fresh fetch instead of using the default cached endpoint: ```typescript theme={null} const data = await wallet.troves().getStrategies({ noCache: true }); ``` ### Strategy shape ```typescript theme={null} interface TrovesStrategyAPIResult { id: string; name: string; apy: number | string; apySplit: { baseApy: number; rewardsApy: number; }; depositTokens: TrovesDepositToken[]; leverage: number; contracts: TrovesContract[]; tvlUsd: number; status: { number: number; value: string }; liveStatus?: string; isAudited: boolean; isRetired: boolean; assets: string[]; protocols: string[]; } ``` ### Protocol-level stats ```typescript theme={null} const stats = await wallet.troves().getStats(); console.log(stats.tvl); console.log(stats.lastUpdated); ``` ## Querying a position Read the wallet's position in a strategy by id: ```typescript theme={null} const position = await wallet.troves().getPosition("ekubo_cl_strketh"); if (position) { console.log("Shares:", position.shares); position.amounts.forEach((amount) => { console.log(amount.toFormatted()); }); } ``` `getPosition()` returns `null` when the wallet holds no shares, or when the strategy does not expose a readable vault contract for this flow. ### Position shape ```typescript theme={null} interface TrovesPosition { strategyId: string; vaultAddress: Address; shares: bigint; amounts: Amount[]; } ``` For single-asset strategies, `amounts` contains one `Amount`. For dual-asset strategies, it contains two entries in the same order as the strategy's `depositTokens`. ## Depositing Deposit into a Troves strategy with typed `Amount` inputs: ```typescript theme={null} const tx = await wallet.troves().deposit({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("10", STRK), }); await tx.wait(); ``` Dual-asset strategies can provide `amount2`: ```typescript theme={null} const tx = await wallet.troves().deposit({ strategyId: "some_dual_asset_strategy", amount: Amount.parse("10", TOKEN_A), amount2: Amount.parse("0.5", TOKEN_B), }); await tx.wait(); ``` With execution options: ```typescript theme={null} const tx = await wallet.troves().deposit( { strategyId: "ekubo_cl_strketh", amount: Amount.parse("10", STRK), }, { feeMode: { type: "paymaster" } } ); await tx.wait(); ``` ## Withdrawing Withdraw from a Troves strategy using the same typed shape: ```typescript theme={null} const tx = await wallet.troves().withdraw({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("5", STRK), }); await tx.wait(); ``` For dual-asset strategies: ```typescript theme={null} const tx = await wallet.troves().withdraw({ strategyId: "some_dual_asset_strategy", amount: Amount.parse("5", TOKEN_A), amount2: Amount.parse("0.25", TOKEN_B), }); await tx.wait(); ``` ## Using the transaction builder Troves can be composed into a larger atomic transaction through the tx builder: ```typescript theme={null} const tx = await wallet .tx() .trovesDeposit({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("10", STRK), }) .transfer(STRK, [{ to: recipient, amount: Amount.parse("1", STRK) }]) .send(); await tx.wait(); ``` Withdraw through the builder: ```typescript theme={null} const tx = await wallet .tx() .trovesWithdraw({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("5", STRK), }) .send(); await tx.wait(); ``` ## Low-level call preparation If you need to inspect or batch Troves calls manually, use the populate helpers: ```typescript theme={null} const depositCalls = await wallet.troves().populateDeposit({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("10", STRK), }); const withdrawCalls = await wallet.troves().populateWithdraw({ strategyId: "ekubo_cl_strketh", amount: Amount.parse("5", STRK), }); ``` These return Starknet `Call[]` values without executing them. ## Best Practices 1. Use `getStrategies()` to drive the strategy picker instead of hard-coding ids. 2. Treat Troves as mainnet-only unless you are explicitly using a custom compatible backend. 3. Use typed `Amount` values from the strategy's deposit tokens to avoid decimals mismatches. 4. Prefer `wallet.tx().trovesDeposit(...)` and `wallet.tx().trovesWithdraw(...)` when Troves actions should be atomic with other wallet operations. 5. Expect some strategies to return `apy` as a string label rather than a numeric yield; use `apySplit` when you need structured numeric values. ## Next Steps * [Tx Builder](/build/starkzap/tx-builder) — Batch Troves actions with transfers, staking, swaps, and more * [Transactions](/build/starkzap/transactions) — Direct execution, fee modes, and preflight * [Bitcoin, Stablecoins, and Tokens](/build/starkzap/erc20) — Token presets and typed `Amount` handling * [API Reference](/build/starkzap/api-reference) — Full method and type signatures # Tx Builder Source: https://docs.starknet.io/build/starkzap/tx-builder Batch multiple operations into a single atomic transaction using the fluent TxBuilder API Tx Builder hero ## Overview The `TxBuilder` provides a fluent API for batching multiple operations into a single atomic transaction. This saves gas and guarantees all-or-nothing execution—either all operations succeed together, or none of them execute. ## Basic Usage ```typescript theme={null} const tx = await wallet .tx() .enterPool(poolAddress, Amount.parse("100", STRK)) .send(); await tx.wait(); ``` ## Mixing Operations Combine transfers, staking, approvals, and raw calls: ```typescript theme={null} const tx = await wallet .tx() // Transfer tokens to multiple recipients .transfer(USDC, [ { to: alice, amount: Amount.parse("50", USDC) }, { to: bob, amount: Amount.parse("25", USDC) }, ]) // Stake in a pool (auto-detects enter vs. add) .stake(poolAddress, Amount.parse("100", STRK)) // Claim rewards .claimPoolRewards(anotherPoolAddress) // Add raw contract calls .add({ contractAddress: "0xDEX_CONTRACT", entrypoint: "swap", calldata: [/* ... */], }) .send(); await tx.wait(); ``` The `.stake()` method is smart — it automatically calls `enter_delegation_pool` for new members or `add_to_delegation_pool` for existing members. ## Preflight with Builder ```typescript theme={null} const builder = wallet .tx() .stake(poolAddress, amount) .transfer(USDC, { to: alice, amount: usdcAmount }); const result = await builder.preflight(); if (!result.ok) { console.error("Transaction would fail:", result.reason); } else { const tx = await builder.send(); await tx.wait(); } ``` ## Fee Estimation ```typescript theme={null} const fee = await wallet .tx() .transfer(USDC, { to: alice, amount }) .stake(poolAddress, stakeAmount) .estimateFee(); console.log("Estimated fee:", fee.overall_fee); ``` ## Extracting Calls You can also extract the raw calls for inspection: ```typescript theme={null} const calls = await wallet .tx() .transfer(USDC, { to: alice, amount }) .enterPool(poolAddress, stakeAmount) .calls(); console.log(`${calls.length} calls in this transaction`); ``` ## Available Builder Methods | Method | Description | | - | - | | `.add(...calls)` | Add raw `Call` objects | | `.approve(token, spender, amount)` | ERC20 approval | | `.transfer(token, transfers)` | ERC20 transfer(s) | | `.stake(pool, amount)` | Smart stake (enter or add based on membership) | | `.enterPool(pool, amount)` | Enter pool as new member | | `.addToPool(pool, amount)` | Add to existing pool position | | `.claimPoolRewards(pool)` | Claim staking rewards | | `.exitPoolIntent(pool, amount)` | Start exit process | | `.exitPool(pool)` | Complete exit after window | | `.swap(request)` | Provider-driven token swap — see [Swaps](/build/starkzap/swap) | | `.lendDeposit(request)` | Lending deposit (supply) — see [Lending](/build/starkzap/lending) | | `.lendWithdraw(request)` | Lending withdraw | | `.lendWithdrawMax(request)` | Lending max withdraw | | `.lendBorrow(request)` | Lending borrow | | `.lendRepay(request)` | Lending repay | | `.trovesDeposit(params)` | Troves strategy deposit — see [Troves](/build/starkzap/troves) | | `.trovesWithdraw(params)` | Troves strategy withdraw — see [Troves](/build/starkzap/troves) | | `.dcaCreate(request)` | Create a DCA (recurring buy) order — see [DCA](/build/starkzap/dollar-cost-average) | | `.dcaCancel(request)` | Cancel a DCA order | | `.confidentialFund(confidential, details)` | Fund confidential account — see [Tongo](/build/starkzap/privacy/tongo) | | `.confidentialTransfer(confidential, details)` | Confidential transfer | | `.confidentialWithdraw(confidential, details)` | Withdraw from confidential to public address | | `.calls()` | Resolve all calls without sending | | `.estimateFee()` | Estimate gas cost | | `.preflight()` | Simulate the transaction | | `.send(options?)` | Execute all calls atomically | ## Best Practices 1. **Use the transaction builder** for complex operations to save gas 2. **Always preflight** non-trivial batches before submitting 3. **Combine related operations** into a single transaction when possible 4. **Use `.estimateFee()`** to show users the cost before executing ## Next Steps * Learn about [Transaction Execution](/build/starkzap/transactions) for direct execution methods * Explore [Staking and Delegation](/build/starkzap/staking) for staking operations * Check the [Bitcoin, Stablecoins, and Token](/build/starkzap/erc20) module for token operations * [Lending](/build/starkzap/lending) — Batch deposit, withdraw, borrow, and repay with Vesu * [Troves](/build/starkzap/troves) — Batch Troves deposits and withdrawals with other wallet operations * [Tongo confidential transfers](/build/starkzap/privacy/tongo) — Fund, transfer, and withdraw with Tongo Only Tongo batches. [STRK20 privacy pool](/build/starkzap/privacy/strk20) operations cannot go through the builder: the proof belongs to the transaction rather than to a call, so a privacy operation can never share a transaction with other calls. # AI-Powered Development Source: https://docs.starknet.io/build/starkzap/using-llms Configure Cursor IDE and MCP using Starkzap's internal standards for smarter coding. ## Overview Maximize development speed by providing Cursor and LLMs with the full context of the Starkzap codebase using our built-in AI configuration files. ## 1. Cursor IDE Setup ### Add Project Rules For the best experience, Cursor needs to understand our coding standards (ESM, Strict TS, and Path Aliases). 1. Copy the contents of `CLAUDE.md` from the root of the [repository](https://github.com/keep-starknet-strange/starkzap). 2. Paste them into a `.cursorrules` file in your project root. 3. This allows Cursor to automatically use the correct commands (like `npm run typecheck`) and follow our internal ``. ### Add External Documentation Add the Starkzap API reference to Cursor's indexed docs: 1. **Settings > Features > Docs** 2. **Add new doc** 3. URL: `https://docs.starkzap.com/llms-full.txt` (or your local `docs/export/` path) *** ## 2. Using the Agent Model (MCP) If you are using the **Model Context Protocol (MCP)** with Claude or Cursor: * **Context Loading:** Point your filesystem MCP to `CLAUDE.md` and `AGENTS.md`. * **Workflow Alignment:** The AI will now understand the `Coordinator -> Executor -> Reviewer` workflow defined in our repo, making it better at planning complex features. *** ## 3. Common AI Commands Inside Cursor Chat (Cmd+L or Ctrl+L), you can now use specific context: * **"Check my types"**: The AI will know to run `npm run typecheck` based on `CLAUDE.md`. * **"Generate a new token preset"**: The AI will point you to `npm run generate:tokens` instead of suggesting manual edits. * **"@docs Starkzap how to batch transactions"**: Uses the indexed documentation for accurate SDK syntax. ## Next Steps * Review the **Development Workflow** in [CLAUDE.md](https://github.com/keep-starknet-strange/starkzap/blob/main/CLAUDE.md) * Learn about **Multi-agent coordination** in [AGENTS.md](https://github.com/keep-starknet-strange/starkzap/blob/main/AGENTS.md) # Source: https://docs.starknet.io/index The ultimate guide to building on Starknet - Cairo smart contracts, ZK technology, and scaling Ethereum & Bitcoin.

WELCOME TO THE NEW
STARKNET DOCS

The Starknet Docs is the unified home for Starknet's technical documentation aimed to help you unlock the full potential of Ethereum and Bitcoin

## Explore the docs
## Join the community
# Additional Examples Source: https://docs.starknet.io/learn/S-two-book/air-development/additional-examples/index Here, we introduce some additional AIRs that may help in designing more complex AIRs. ## Selectors A selector is a column of 0s and 1s that selectively enables or disables a constraint. One example of a selector is the `IsFirst` column which has a value of 1 only on the first row. This can be used when constraints are defined over both the current and previous rows but we need to make an exception for the first row. For example, as seen in [Figure 1](#figure-1), when we want to track the cumulative sum of a column, i.e. $b_2 = a_1 + a_2$, the previous row of the first row points to the last row, creating an incorrect constraint $a_1 = b_4 + b_1$. Thus, we need to disable the constraint for the first row and enable a separate constraint $a_1 = b_1$. This can be achieved by using a selector column that has a value of 1 on the first row and 0 on the other rows and multiplying the constraint by the selector column: $$ (1 - \text{IsFirst(X)}) \cdot (A(\omega \cdot X) - B(X) - B(\omega \cdot X)) + \text{IsFirst(X)} \cdot (A(X) - B(X)) = 0 $$ where $X$ refers to the previous value of $\omega\cdot X$ in the multiplicative subgroup of the finite field.
*Figure 1: IsFirst selector*
## IsZero Checking that a certain field element is zero is a common use case when writing AIRs. To do this efficiently, we can use the property of finite fields that a non-zero field element always has a multiplicative inverse. For example, in [Figure 2](#figure-2), we want to check whether a field element in $\mathbb{F}_5$ is zero. We create a new column that contains the multiplicative inverse of each field element $a_i$. We then use the multiplication of the two columns and check whether the result is 0 or 1. Note that if the existing column has a zero element, we can insert any value in the new column since the multiplication will always be zero. This way, we can create a constraint that uses the `IsZero` condition as part of the constraint, e.g. $(1 - (A(X) \cdot Inv(X))) \cdot (\text{constraint\_1}) + (A(X) \cdot Inv(X)) \cdot (\text{constraint\_2}) = 0$, which checks $\text{constraint\_1}$ if $A(X)$ is 0 and $\text{constraint\_2}$ if $A(X)$ is not 0.
*Figure 2: IsZero over a prime field 5*
## Public Inputs When writing AIRs, we may want to expose some values in the trace to the verifier to check in the open. For example, when running an AIR for a Cairo program, we may want to check that the program that was executed is the correct one. In S-two, we can achieve this by adding the public input portion of the trace as a LogUp column as negative multiplicity. As shown in [Figure 3](#figure-3), the public inputs $a_1, a_2$ are added as LogUp values with negative multiplicity $\frac{-1}{X - a_1}$ and $\frac{-1}{X - a_2}$. The public inputs are given to the verifier as part of the proof and the verifier can directly compute the LogUp values with positive multiplicity $\frac{1}{X - a_1}$ and $\frac{1}{X - a_2}$ and add it to the LogUp sum and check that the total sum is 0.
*Figure 3: Public inputs*
One important thing to note is that the public inputs must be added to the Fiat-Shamir channel before drawing random elements for the interaction trace. We refer the reader to this [example implementation](https://github.com/starknet-io/starknet-docs/blob/main/learn/S-two-book/stwo-examples/examples/public_input.rs) for reference. ## XOR We can also handle XOR operations as part of the AIR. First, as we did in the [Components](../components/index) section, we create a computing component and a scheduling component. Then, we connect the two components using lookups: the computing component sets the LogUp value as a negative multiplicity and the scheduling component sets the same value as a positive multiplicity. For example, [Figure 4](#figure-4) shows the XOR operation for 4-bit integers. To accommodate the entire combination of inputs, the size of the trace for the computing component is $2^8=256$ rows. Note that the size of the trace for the scheduling component does not have to be the same as the computing component.
*Figure 4: XOR operations for 4-bit integers*
Note that for larger integers, we may need to decompose into smaller limbs to avoid creating large tables. Also note that the M31 field does not fully support XOR operations for 31-bit integers since we cannot use $2^{31} -1$, although this is not feasible as it would require a table of size of around $2^{31} \times 2^{31}$. # Components Source: https://docs.starknet.io/learn/S-two-book/air-development/components/index So now that we know how to create a self-contained AIR, the inevitable question arises: How do we make this modular? Fortunately, S-two provides an abstraction called **components** that allows us to create independent AIRs and compose them together. In other proving frontends, this is also commonly referred to as a *chip*, but the idea is the same. One of the most common use cases of components is to separate frequently used functions (e.g. a hash function) from the main component into a separate component and reuse it, avoiding trace column bloat. Even if the function is not frequently used, it can be useful to separate it into a component to avoid the degree of the constraints becoming too high. This second point is possible because when we create a new component and connect it to the old component, we do it by using lookups, which means that the constraints of the new component are not added to the degree of the old component. ## Hash Function Example To illustrate how to use components, we will create two components where the main component calls a hash function component. For simplicity, instead of an actual hash function, the second component will compute $x^5 + 1$ from an input $x$. This component will have, in total, three columns: \[input, intermediate, output], which will correspond to the values $[x, x^3, x^5 + 1]$. Our main component, on the other hand, will have two columns, \[input, output], which corresponds to the values $[x, x^5 + 1]$. We'll refer to the main component as the **scheduling component** and the hash function component as the **computing component**, since the main component is essentially *scheduling* the hash function component to run its function with a given input and the hash function component *computes* on the provided input. As can be seen in [Figure 1](#figure-1), the input and output of each component are connected by lookups.
*Figure 1: Scheduling and Computing components*
### Design
*Figure 2: Traces of each component*
When we implement this in S-two, the traces of each component will look like [Figure 2](#figure-2) above. Each component has its own original and LogUp traces, and the inputs and outputs of each component are connected by lookups. Since the scheduling component sets the LogUp value as a positive multiplicity and the computing component sets the same value as a negative multiplicity, the verifier can simply check that the sum of the two LogUp columns is zero. Note that we combine the input and output randomly as $$ \frac{1}{Z - x \cdot \alpha^0 - (x^5+1) \cdot \alpha^1} $$ to form a single lookup. This is because we want to ensure that each input is paired with the correct output. If we add the input and output as separate lookups as $$ \frac{1}{Z - x} + \frac{1}{Z - (x^5+1)} $$ A malicious prover can switch the output with a different row and still come up with a valid proof. For example, the following scheduling component | Input | Output | | - | - | | x | y^5 + 1 | | y | x^5 + 1 | And the following computing component | Input | Intermediate | Output | | - | - | - | | x | x^3 + 1 | x^5 + 1 | | y | y^3 + 1 | y^5 + 1 | would be valid. ## Implementation Let's move on to the implementation. ```rust theme={null} fn main() { // --snip-- // Create trace columns let scheduling_trace = gen_scheduling_trace(log_size); let computing_trace = gen_computing_trace(log_size, &scheduling_trace[0], &scheduling_trace[1]); // Statement 0 let statement0 = ComponentsStatement0 { log_size }; statement0.mix_into(channel); // Commit to the trace columns let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals([scheduling_trace.clone(), computing_trace.clone()].concat()); tree_builder.commit(channel); // Draw random elements to use when creating the random linear combination of lookup values in the LogUp columns let lookup_elements = ComputationLookupElements::draw(channel); // Create LogUp columns let (scheduling_logup_cols, scheduling_claimed_sum) = gen_scheduling_logup_trace( log_size, &scheduling_trace[0], &scheduling_trace[1], &lookup_elements, ); let (computing_logup_cols, computing_claimed_sum) = gen_computing_logup_trace( log_size, &computing_trace[0], &computing_trace[2], &lookup_elements, ); // Statement 1 let statement1 = ComponentsStatement1 { scheduling_claimed_sum, computing_claimed_sum, }; statement1.mix_into(channel); // Commit to the LogUp columns let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals([scheduling_logup_cols, computing_logup_cols].concat()); tree_builder.commit(channel); let components = Components::new(&statement0, &lookup_elements, &statement1); let stark_proof = prove(&components.component_provers(), channel, commitment_scheme).unwrap(); let proof = ComponentsProof { statement0, statement1, stark_proof, }; // --snip-- } ``` The code above for proving the components should look pretty familiar by now. Since we need to do everything twice as many times, we create structs like `ComponentsStatement0`, `ComponentsStatement1`, `Components`, and `ComponentsProof`, but the main logic is the same. Let's take a closer look at how the LogUp columns are generated. ```rust theme={null} fn gen_scheduling_logup_trace( log_size: u32, scheduling_col_1: &CircleEvaluation, scheduling_col_2: &CircleEvaluation, lookup_elements: &ComputationLookupElements, ) -> ( Vec>, SecureField, ) { // --snip-- let scheduling_input_output: PackedSecureField = lookup_elements.combine(&[scheduling_col_1.data[row], scheduling_col_2.data[row]]); col_gen.write_frac(row, PackedSecureField::one(), scheduling_input_output); // --snip-- fn gen_computing_logup_trace( log_size: u32, computing_col_1: &CircleEvaluation, computing_col_3: &CircleEvaluation, lookup_elements: &ComputationLookupElements, ) -> ( Vec>, SecureField, ) { // --snip-- let computing_input_output: PackedSecureField = lookup_elements.combine(&[computing_col_1.data[row], computing_col_3.data[row]]); col_gen.write_frac(row, -PackedSecureField::one(), computing_input_output); // --snip-- } ``` As you can see, the LogUp values of the input and output columns of both the scheduling and computing components are batched together, but in the scheduling component, the output LogUp value is subtracted from the input LogUp value, while in the computing component, the input LogUp value is subtracted from the output LogUp value. This means that when the LogUp sums from both components are added together, they should cancel out to zero. Next, let's check how the constraints are created. ```rust theme={null} impl FrameworkEval for SchedulingEval { // --snip-- fn evaluate(&self, mut eval: E) -> E { let input_col = eval.next_trace_mask(); let output_col = eval.next_trace_mask(); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, E::EF::one(), &[input_col, output_col], )); eval.finalize_logup(); eval } } impl FrameworkEval for ComputingEval { // --snip-- fn evaluate(&self, mut eval: E) -> E { let input_col = eval.next_trace_mask(); let intermediate_col = eval.next_trace_mask(); let output_col = eval.next_trace_mask(); eval.add_constraint( intermediate_col.clone() - input_col.clone() * input_col.clone() * input_col.clone(), ); eval.add_constraint( output_col.clone() - intermediate_col.clone() * input_col.clone() * input_col.clone() - E::F::one(), ); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, -E::EF::one(), &[input_col, output_col], )); eval.finalize_logup(); eval } } ``` As you can see, we define the LogUp constraints for each component, and we also add two constraints that make sure the computations $x^3$ and $x^5 + 1$ are correct. ```rust theme={null} fn main() { // --snip-- // Verify claimed sums assert_eq!( scheduling_claimed_sum + computing_claimed_sum, SecureField::zero() ); // Unpack proof let statement0 = proof.statement0; let statement1 = proof.statement1; let stark_proof = proof.stark_proof; // Create channel and commitment scheme let channel = &mut Blake2sChannel::default(); let commitment_scheme = &mut CommitmentSchemeVerifier::::new(config); let log_sizes = statement0.log_sizes(); // Preprocessed columns. commitment_scheme.commit(stark_proof.commitments[0], &log_sizes[0], channel); // Commit to statement 0 statement0.mix_into(channel); // Trace columns. commitment_scheme.commit(stark_proof.commitments[1], &log_sizes[1], channel); // Draw lookup element. let lookup_elements = ComputationLookupElements::draw(channel); // Commit to statement 1 statement1.mix_into(channel); // Interaction columns. commitment_scheme.commit(stark_proof.commitments[2], &log_sizes[2], channel); // Create components let components = Components::new(&statement0, &lookup_elements, &statement1); verify( &components.components(), channel, commitment_scheme, stark_proof, ) .unwrap(); ``` Finally, we verify the components! # Dynamic Lookups Source: https://docs.starknet.io/learn/S-two-book/air-development/dynamic-lookups/index In the last section, we implemented a static lookup. A dynamic lookup is the same as a static lookup except that the values that are being looked up are not known before the proving process (i.e. they are not preprocessed columns but trace columns). In this section, we will implement one of the simplest dynamic lookups: a permutation check. A permutation check simply checks that two sets of values have the same elements, but not necessarily in the same order. For example, the values $[1, 2, 3]$ and $[3, 1, 2]$ are a permutation of each other, but $[1, 2, 3]$ and $[1, 2]$ are not. If you went through the previous section, you should have a good intuition for how to implement this. First, create two original trace columns that each contain a random permutation of the same set of values. Then, create a LogUp column where the first original trace column is added as a fraction with multiplicity $1$ and the second original trace column is added as a fraction with multiplicity $-1$. Then, check that the `claimed_sum`, or the sum of the fractions in the two LogUp columns, is $0$.
*Figure 1: Permutation check*
Let's move on to the implementation. ```rust theme={null} fn gen_trace(log_size: u32) -> Vec> { let mut rng = rand::thread_rng(); let values = (0..(1 << log_size)).map(|i| i).collect::>(); // Create a random permutation of the values let mut random_values = values.clone(); random_values.shuffle(&mut rng); let random_col_1 = BaseColumn::from_iter(random_values.iter().map(|v| M31::from(*v))); // Create another random permutation of the values let mut random_values = random_values.clone(); random_values.shuffle(&mut rng); let random_col_2 = BaseColumn::from_iter(random_values.iter().map(|v| M31::from(*v))); // Convert table to trace polynomials let domain = CanonicCoset::new(log_size).circle_domain(); vec![random_col_1, random_col_2] .into_iter() .map(|col| CircleEvaluation::new(domain, col)) .collect() } fn gen_logup_trace( log_size: u32, random_col_1: &BaseColumn, random_col_2: &BaseColumn, lookup_elements: &LookupElements, ) -> ( Vec>, SecureField, ) { let mut logup_gen = LogupTraceGenerator::new(log_size); let mut col_gen = logup_gen.new_col(); for row in 0..(1 << (log_size - LOG_N_LANES)) { // 1 / random - 1 / ordered = (ordered - random) / (random * ordered) let random_val: PackedSecureField = lookup_elements.combine(&[random_col_1.data[row]]); let ordered_val: PackedSecureField = lookup_elements.combine(&[random_col_2.data[row]]); col_gen.write_frac(row, ordered_val - random_val, random_val * ordered_val); } col_gen.finalize_col(); logup_gen.finalize_last() } fn main() { ... // Create and commit to the trace columns let trace = gen_trace(log_size); let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(trace.clone()); tree_builder.commit(channel); // Draw random elements to use when creating the random linear combination of lookup values in the LogUp columns let lookup_elements = LookupElements::draw(channel); // Create and commit to the LogUp columns let (logup_cols, claimed_sum) = gen_logup_trace(log_size, &trace[0], &trace[1], &lookup_elements); let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(logup_cols); tree_builder.commit(channel); ... } ``` Looking at the code above, we can see that it looks very similar to the implementation in the previous section. Instead of creating a preprocessed column, we create two columns where the first column is a random permutation of values `[0, 1 << log_size)` and the second column contains the values in order. Note that this is equivalent to "looking up" all values in the first trace column once. And since all the values are looked up exactly once, we do not need a separate multiplicity column. Then, we create a LogUp column that contains the values $\frac{1}{original} - \frac{1}{permuted}$. ```rust theme={null} struct TestEval { log_size: u32, lookup_elements: LookupElements, } impl FrameworkEval for TestEval { fn log_size(&self) -> u32 { self.log_size } fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR } fn evaluate(&self, mut eval: E) -> E { let random_col = eval.next_trace_mask(); let ordered_col = eval.next_trace_mask(); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, E::EF::one(), &[random_col], )); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, -E::EF::one(), &[ordered_col], )); eval.finalize_logup_in_pairs(); eval } } ``` The `TestEval` struct is also very similar to the one in the previous section. The only difference is that we call `add_to_relation` twice and add them together by calling `finalize_logup_in_pairs()` on the `TestEval` instance. This is equivalent to calling the `finalize_logup_batched` function with `&vec![0, 0]`. # Introduction Source: https://docs.starknet.io/learn/S-two-book/air-development/index > This section is intended for developers who want to create custom proofs using S-two (proofs of custom VMs, ML inference, etc.). It assumes that the reader is familiar with Rust and has some background knowledge of cryptography (e.g. finite fields). It also assumes that the reader is familiar with the concept of proof systems and knows what they want to create proofs for, but it does not assume any prior experience with creating them. All the code that appears throughout this section is available [here](https://github.com/starknet-io/starknet-docs/tree/main/learn/S-two-book/stwo-examples/examples). # Local Row Constraints Source: https://docs.starknet.io/learn/S-two-book/air-development/local-row-constraints/index Until now, we have only considered constraints that apply over values in a single row. But what if we want to express constraints over multiple adjacent rows? For example, we may want to ensure that the difference between the values in two adjacent rows is always the same. Turns out we can implement this as an AIR constraint, as long as the same constraints are applied to all rows. We will build upon the example in the previous section, where we created two columns and proved that they are permutations of each other by asserting that the second column looks up all values in the first column exactly once. Here, we will create two columns and prove that not only are they permutations of each other, but also that the second column is a sorted version of the first column. Since the sorted column will contain in order the values $[0,num\_rows)$, this is equivalent to asserting that **the difference between every current row and the previous row is $1$**. We will implement this in three iterations, fixing a different issue in each iteration. ## First Try ```rust theme={null} fn evaluate(&self, mut eval: E) -> E { let unsorted_col = eval.next_trace_mask(); let [sorted_col_prev_row, sorted_col_curr_row] = eval.next_interaction_mask(ORIGINAL_TRACE_IDX, [-1, 0]); // New constraint eval.add_constraint( E::F::one() - (sorted_col_curr_row.clone() - sorted_col_prev_row.clone()), ); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, E::EF::one(), &[unsorted_col], )); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, -E::EF::one(), &[sorted_col_curr_row], )); eval.finalize_logup_in_pairs(); eval } ``` The logic for creating the trace and LogUp columns is basically the same as in the previous section (except that one of the columns is now sorted), so we omit them for brevity. Another change is in the `evaluate` function, where we call `eval.next_interaction_mask(ORIGINAL_TRACE_IDX, [-1, 0])` instead of `eval.next_trace_mask()`. The function `next_trace_mask()` is a wrapper for `next_interaction_mask(ORIGINAL_TRACE_IDX, [0])`, where the first parameter specifies which part of the trace to retrieve values from (see [this figure](../static-lookups/index#figure-1) for an example of the different parts of a trace). Since we want to retrieve values from the original trace, we set the value of the first parameter to `ORIGINAL_TRACE_IDX`. Next, the second parameter indicates the row offset of the value we want to retrieve. Since we want to retrieve both the previous and current row values for the sorted column, we set the value of the second parameter to `[-1, 0]`. Once we have these values, we can now assert that the difference between the current and previous row is always one with the constraint: `E::F::one() - (sorted_col_curr_row.clone() - sorted_col_prev_row.clone())`. But this will fail with a `ConstraintsNotSatisfied` error, can you see why? (You can try running it yourself [here](https://github.com/starknet-io/starknet-docs/blob/main/learn/S-two-book/stwo-examples/examples/local_row_constraints_fails_1.rs)) ## Second Try The issue was that when calling `evaluate` on the first row of our trace, the previous row value wraps around to the last row because there are no negative indices. This means that in our example, we are expecting the `0 - 15 = 1` constraint to hold, which is clearly not true. To fix this, we can use the `IsFirstColumn` preprocessed column that we created in the [Preprocessed Trace](../preprocessed-trace/index) section. So we will copy over the same code for creating the preprocessed column and modify our new constraint as follows: ```rust theme={null} let is_first_col = eval.get_preprocessed_column(self.is_first_id.clone()); eval.add_constraint( (E::F::one() - is_first_col.clone()) * (E::F::one() - (sorted_col_curr_row.clone() - sorted_col_prev_row.clone())), ); ``` Now, we have a constraint that is disabled for the first row, which is exactly what we want. Still, however, this will fail with the same `ConstraintsNotSatisfied` error. (You can run it [here](https://github.com/starknet-io/starknet-docs/blob/main/learn/S-two-book/stwo-examples/examples/local_row_constraints_fails_2.rs)) ## Third Try So when we were creating `CircleEvaluation` instances from our `BaseColumn` instances, the order of the elements that we were creating it with was actually not the order that S-two understands it to be. Instead, it assumes that the values are in the bit-reversed, circle domain order. It's not important to understand what this order is, specifically, but this does mean that when S-two tries to find the `-1` offset when calling `evaluate`, it will find the previous value assuming that it's in a different order. This means that when we create a `CircleEvaluation` instance, we need to convert it to a bit-reversed circle domain order. Thus, every time we create a `CircleEvaluation` instance, we need to convert the order of the values in the `BaseColumn` beforehand. ```rust theme={null} impl IsFirstColumn { ... impl IsFirstColumn { ... } fn gen_trace(log_size: u32) -> Vec> { // Create a table with random values let mut rng = rand::thread_rng(); let sorted_values = (0..(1 << log_size)).map(|i| i).collect::>(); let mut unsorted_values = sorted_values.clone(); unsorted_values.shuffle(&mut rng); let mut unsorted_col = BaseColumn::from_iter(unsorted_values.iter().map(|v| M31::from(*v))); let mut sorted_col = BaseColumn::from_iter(sorted_values.iter().map(|v| M31::from(*v))); // Convert table to trace polynomials let domain = CanonicCoset::new(log_size).circle_domain(); //////////////////////////////////////////////////////////////////// // Convert the columns to bit-reversed circle domain order bit_reverse_coset_to_circle_domain_order(unsorted_col.as_mut_slice()); bit_reverse_coset_to_circle_domain_order(sorted_col.as_mut_slice()); //////////////////////////////////////////////////////////////////// vec![unsorted_col, sorted_col] .into_iter() .map(|col| CircleEvaluation::new(domain, col)) .collect() } ``` Voilà, we have successfully implemented the constraint. You can run it [here](https://github.com/starknet-io/starknet-docs/blob/main/learn/S-two-book/stwo-examples/examples/local_row_constraints.rs). Things to consider when implementing constraints over multiple rows: 1. Change the order of elements in `BaseColumn` in-place via `bit_reverse_coset_to_circle_domain_order` before creating a `CircleEvaluation` instance. This is required because S-two assumes that the values are in the bit-reversed, circle domain order. 2. For the first row, the 'previous' row is the last row of the trace, so you may need to disable the constraint for the first row. This is typically done by using a preprocessed column. # Preprocessed Trace Source: https://docs.starknet.io/learn/S-two-book/air-development/preprocessed-trace/index > This section and the following sections are intended for developers who have completed the [First Breath of AIR](../writing-a-simple-air/index) section or are already familiar with the workflow of creating an AIR. If you have not gone through the previous section, we recommend doing so first as the following sections gloss over a lot of boilerplate code. For those of you who have completed the [First Breath of AIR](../writing-a-simple-air/index) tutorial, you should now be familiar with the concept of a trace as a table of integers that are filled in by the prover (we will now refer to this as the **original trace**). In addition to the original trace, S-two also has a concept of a **preprocessed trace**, which is a table whose values are fixed and therefore cannot be arbitrarily chosen by the prover. In other words, these are columns whose values are known in advance of creating a proof and essentially agreed upon by both the prover and the verifier. One of the use cases of the preprocessed trace is as *a selector for different constraints*. Remember that in an AIR, the same constraints are applied to every row of the trace? If we go back to the spreadsheet analogy, this means that we can't create a spreadsheet that runs different computations for different rows. To get around this, note that if we multiply a constraint with a "selector" that is zero, the constraint will be trivially satisfied. Building on this, we can create a selector column of 0s and 1s, and multiply the constraint with the selector column. For example, let's say we want to create a constraint that runs different computations for the first 2 rows and the next 2 rows. We can do this by creating a selector column that has value 0 for the first 2 rows and 1 for the next 2 rows and combining it with the constraints as follows: $$ (1 - \text{selector}) \cdot \text{constraint}_1 + \text{selector} \cdot \text{constraint}_2 = 0 $$
*Figure 1: Preprocessed trace as a selector*
Another use case is to use the preprocessed trace to *express constant values used in constraints*. For example, when creating a hash function in an AIR, we often need to use round constants, which the verifier needs to be able to verify or the resulting hash may be invalid. We can also "look up" the constant values as an optimization technique, which we will discuss in more detail in the next section. In this section, we will explore how to implement a preprocessed trace as a selector, and we will implement the simplest form: a single `IsFirst` column, where the value is 1 for the first row and 0 for all other rows. Boilerplate code is omitted for brevity. Please refer to the [full example code](https://github.com/starknet-io/starknet-docs/blob/main/learn/S-two-book/stwo-examples/examples/preprocessed_trace.rs) for the full implementation. ```rust theme={null} struct IsFirstColumn { pub log_size: u32, } #[allow(dead_code)] impl IsFirstColumn { pub fn new(log_size: u32) -> Self { Self { log_size } } pub fn gen_column(&self) -> CircleEvaluation { let mut col = BaseColumn::zeros(1 << self.log_size); col.set(0, M31::from(1)); CircleEvaluation::new(CanonicCoset::new(self.log_size).circle_domain(), col) } pub fn id(&self) -> PreProcessedColumnId { PreProcessedColumnId { id: format!("is_first_{}", self.log_size), } } } ``` First, we need to define an `IsFirstColumn` struct that will be used as a preprocessed trace. We will use the `gen_column()` function to generate a `CircleEvaluation` struct that is 1 for the first row and 0 for all other rows. The `id()` function is needed to identify this column when evaluating the constraints. ```rust theme={null} fn main() { ... // Create and commit to the preprocessed trace let is_first_column = IsFirstColumn::new(log_size); let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(vec![is_first_column.gen_column()]); tree_builder.commit(channel); // Commit to the size of the trace channel.mix_u64(log_size as u64); // Create and commit to the original trace let trace = gen_trace(log_size); let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(trace); tree_builder.commit(channel); ... } ``` Then, in our main function, we will create and commit to the preprocessed and original traces. For those of you who are curious about why we need to commit to the trace, please refer to the [Committing to the Trace Polynomials](../writing-a-simple-air/committing-to-the-trace-polynomials) section. ```rust theme={null} struct TestEval { is_first_id: PreProcessedColumnId, log_size: u32, } impl FrameworkEval for TestEval { fn log_size(&self) -> u32 { self.log_size } fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR } fn evaluate(&self, mut eval: E) -> E { let is_first = eval.get_preprocessed_column(self.is_first_id.clone()); let col_1 = eval.next_trace_mask(); let col_2 = eval.next_trace_mask(); let col_3 = eval.next_trace_mask(); // If is_first is 1, then the constraint is col_1 * col_2 - col_3 = 0 // If is_first is 0, then the constraint is col_1 * col_2 + col_1 - col_3 = 0 eval.add_constraint( is_first.clone() * (col_1.clone() * col_2.clone() - col_3.clone()) + (E::F::from(M31::from(1)) - is_first.clone()) * (col_1.clone() * col_2.clone() + col_1.clone() - col_3.clone()), ); eval } } ``` Now that we have the traces, we need to create a struct that contains the logic for evaluating the constraints. As mentioned before, we need to use the `is_first_id` field to retrieve the row value of the `IsFirstColumn` struct. Then, we compose two constraints using the `IsFirstColumn` row value as a selector and adding them together. If you're unfamiliar with how `max_constraint_log_degree_bound(&self)` should be implemented, please refer to [this note](../writing-a-simple-air/constraints-over-trace-polynomials.mdx#max_constraint_log_degree_bound). ```rust theme={null} fn main() { ... // Create a component let component = FrameworkComponent::::new( &mut TraceLocationAllocator::default(), TestEval { is_first_id: is_first_column.id(), log_size, }, QM31::zero(), ); // Prove let proof = prove(&[&component], channel, commitment_scheme).unwrap(); // Verify let channel = &mut Blake2sChannel::default(); let commitment_scheme = &mut CommitmentSchemeVerifier::::new(config); let sizes = component.trace_log_degree_bounds(); commitment_scheme.commit(proof.commitments[0], &sizes[0], channel); channel.mix_u64(log_size as u64); commitment_scheme.commit(proof.commitments[1], &sizes[1], channel); verify(&[&component], channel, commitment_scheme, proof).unwrap(); } ``` Finally, we can create a `FrameworkComponent` using the `TestEval` struct and then prove and verify the component. # Static Lookups Source: https://docs.starknet.io/learn/S-two-book/air-development/static-lookups/index In the previous section, we showed how to create a preprocessed trace. In this section, we will introduce the concept of an interaction trace, and use it with the preprocessed trace to implement **static lookups**. Let's start with a brief introduction to lookups. A lookup is a way to connect values from one part of the table to another part of the table. A simple example is when we want to copy values across parts of the table. At first glance, this seems feasible using a constraint. For example, we can copy $col_1$ values to $col_2$ by creating a constraint that $col_1 - col_2$ is equal to $0$. The limitation with this approach, however, is that the same constraint needs to be satisfied over every row in the columns. In other words, we can only check that $col_2$ is an exact copy of $col_1$: $$ col_1[i] = col_2[i] \quad \forall\, i $$ But what if we want to check that $col_2$ is a copy of $col_1$ regardless of the order of the values? This can be done by comparing that the grand product of the random linear combinations of all values in $col_1$ is equal to the grand product of the random linear combinations of all values in $col_2$: $$ \prod_{i=0}^{n-1} (X - col_1[i]) = \prod_{i=0}^{n-1} (X - col_2[i]) $$ where $X$ is a random value from the verifier. By taking the logarithmic derivative of each side of the equation, we can rewrite it: $$ \sum_{i=0}^{n-1} \frac{1}{X-col_1[i]} = \sum_{i=0}^{n-1} \frac{1}{X-col_2[i]} $$ We can go further and allow each of the original values to be copied a different number of times. This is supported by modifying the check to the following: $$ \sum_{i=0}^{n-1} \frac{1}{X-col_1[i]} = \sum_{i=0}^{n-1} \frac{m_i}{X-col_2[i]} $$ Where $m_i$ represents the multiplicity, or the number of times $col_1[i]$ appears in $col_2$. In S-two, these fractions (which we will hereafter refer to as *LogUp fractions*) are stored in a special type of trace called an *interaction trace*. An interaction trace is used to contain values that involve interaction between the prover and the verifier. As mentioned above, a LogUp fraction requires a random value $X$ from the verifier, which is why it is stored in an interaction trace. ## Range-check AIR We will now walk through the implementation of a static lookup, which is a lookup where the values that are being looked up are static, i.e. part of the preprocessed trace. Specifically, we will implement a **range-check AIR**, which checks that a certain value is within a given range. This is especially useful for frameworks like S-two that use finite fields because it allows checking for underflow and overflow. A range-check checks that all values in a column are within a certain range. For example, as in [Figure 1](#figure-1), we can check that all values in the range-checked columns are between 0 and 3. We do this by first creating a multiplicity column that counts the number of times each value in the preprocessed trace appears in the range-checked columns. Then, we create two LogUp columns as part of the interaction trace. The first column contains in each row a fraction with numerator equal to the multiplicity and denominator equal to the random linear combination of the value in the range column. For example, for row 1, the fraction should be $\frac{2}{X-0}$, where $X$ is a random value. The second column contains batches of fractions where the denominator of each fraction is the random linear combination of the value in the range-checked column. Note that the numerator of each fraction is always -1, i.e. we apply a negation, because we want the sum of the first column to be equal to the sum of the second column.
*Figure 1: Range-check lookup*
If we add all the fractions in the two columns together, we get 0. This means that the verifier will be convinced with high probability that the values in the range-checked columns are a subset of the values in the range column. ## Implementation Now let's move on to the implementation where we create a 4-bit range-check AIR. We do this by creating a preprocessed trace column with the integers $[0, 16)$, then using a lookup to force the values in the original trace columns to lie in the values of the preprocessed column. ```rust theme={null} struct RangeCheckColumn { pub log_size: u32, } #[allow(dead_code)] impl RangeCheckColumn { pub fn new(log_size: u32) -> Self { Self { log_size } } pub fn gen_column(&self) -> CircleEvaluation { let col = BaseColumn::from_iter((0..(1 << self.log_size)).map(|i| M31::from(i))); CircleEvaluation::new(CanonicCoset::new(self.log_size).circle_domain(), col) } pub fn id(&self) -> PreProcessedColumnId { PreProcessedColumnId { id: format!("range_check_{}_bits", self.log_size), } } } ``` First, we need to create the range-check column as a preprocessed column. This should look familiar to the code from the previous section. ```rust theme={null} fn gen_trace(log_size: u32) -> Vec> { // Create a table with random values let mut rng = rand::thread_rng(); let lookup_col_1 = BaseColumn::from_iter((0..(1 << log_size)).map(|_| M31::from(rng.gen_range(0..16)))); let lookup_col_2 = BaseColumn::from_iter((0..(1 << log_size)).map(|_| M31::from(rng.gen_range(0..16)))); let mut multiplicity_col = BaseColumn::zeros(1 << log_size); lookup_col_1 .as_slice() .iter() .chain(lookup_col_2.as_slice().iter()) .for_each(|value| { let index = value.0 as usize; multiplicity_col.set(index, multiplicity_col.at(index) + M31::from(1)); }); // Convert table to trace polynomials let domain = CanonicCoset::new(log_size).circle_domain(); vec![ lookup_col_1.clone(), lookup_col_2.clone(), multiplicity_col.clone(), ] .into_iter() .map(|col| CircleEvaluation::new(domain, col)) .collect() } ``` Next, we create the original trace columns. The first two columns are random values in the range $[0, 15]$, and the third column contains the counts of the values in the range-check column. ```rust theme={null} relation!(SmallerThan16Elements, 1); fn gen_logup_trace( range_log_size: u32, log_size: u32, range_check_col: &BaseColumn, lookup_col_1: &BaseColumn, lookup_col_2: &BaseColumn, multiplicity_col: &BaseColumn, lookup_elements: &SmallerThan16Elements, ) -> ( Vec>, SecureField, ) { let mut logup_gen = LogupTraceGenerator::new(range_log_size); let mut col_gen = logup_gen.new_col(); for simd_row in 0..(1 << (range_log_size - LOG_N_LANES)) { let numerator: PackedSecureField = PackedSecureField::from(multiplicity_col.data[simd_row]); let denom: PackedSecureField = lookup_elements.combine(&[range_check_col.data[simd_row]]); col_gen.write_frac(simd_row, -numerator, denom); } col_gen.finalize_col(); let mut col_gen = logup_gen.new_col(); for simd_row in 0..(1 << (log_size - LOG_N_LANES)) { let lookup_col_1_val: PackedSecureField = lookup_elements.combine(&[lookup_col_1.data[simd_row]]); let lookup_col_2_val: PackedSecureField = lookup_elements.combine(&[lookup_col_2.data[simd_row]]); // 1 / denom1 + 1 / denom2 = (denom1 + denom2) / (denom1 * denom2) let numerator = lookup_col_1_val + lookup_col_2_val; let denom = lookup_col_1_val * lookup_col_2_val; col_gen.write_frac(simd_row, numerator, denom); } col_gen.finalize_col(); logup_gen.finalize_last() } fn main() { ... // Draw random elements to use when creating the random linear combination of lookup values in the LogUp columns let lookup_elements = SmallerThan16Elements::draw(channel); // Create and commit to the LogUp columns let (logup_cols, claimed_sum) = gen_logup_trace( range_log_size, log_num_rows, &range_check_col, &trace[0], &trace[1], &trace[2], &lookup_elements, ); ... } ``` Now we need to create the LogUp columns. First, note that we are creating a `SmallerThan16Elements` instance using the macro `relation!`. This macro creates an API for performing random linear combinations. Under the hood, it creates two random values $z, \alpha$ that can create a random linear combination of an arbitrary number of elements. In our case, we only need to combine one value (value in $[0,15]$), which is why we pass in `1` to the macro. Inside `gen_logup_trace`, we create a `LogupTraceGenerator` instance. This is a helper class that allows us to create LogUp columns. Every time we create a new column, we need to call `new_col()` on the `LogupTraceGenerator` instance. You may notice that we are iterating over `BaseColumn` in chunks of 16, or `1 << LOG_N_LANES` values. This is because we are using the `SimdBackend`, which runs 16 lanes simultaneously, so we need to preserve this structure. The `Packed` in `PackedSecureField` means that it packs 16 values into a single value. You may also notice that we are using a `SecureField` instead of just the `Field`. This is because the random value we created by `SmallerThan16Elements` lies in the degree-4 extension field $\mathbb{F}_{p^4}$. This is necessary for the security of the protocol and interested readers can refer to the [Mersenne Primes](../../how-it-works/mersenne-prime) section for more details. Once we set the fractions for each `simd_row`, we need to call `finalize_col()` to finalize the column. This process modifies the LogUp columns from individual fractions to cumulative sums of the fractions as shown in [Figure 2](#figure-2).
*Figure 2: Finalizing each LogUp column*
Finally, we need to call `finalize_last()` on the `LogupTraceGenerator` instance to finalize the LogUp columns, which will return the LogUp columns as well as the sum of the fractions in the LogUp columns. ```rust theme={null} struct TestEval { range_check_id: PreProcessedColumnId, log_size: u32, lookup_elements: SmallerThan16Elements, } impl FrameworkEval for TestEval { fn log_size(&self) -> u32 { self.log_size } fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR } fn evaluate(&self, mut eval: E) -> E { let range_check_col = eval.get_preprocessed_column(self.range_check_id.clone()); let lookup_col_1 = eval.next_trace_mask(); let lookup_col_2 = eval.next_trace_mask(); let multiplicity_col = eval.next_trace_mask(); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, -E::EF::from(multiplicity_col), &[range_check_col], )); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, E::EF::one(), &[lookup_col_1], )); eval.add_to_relation(RelationEntry::new( &self.lookup_elements, E::EF::one(), &[lookup_col_2], )); eval.finalize_logup_batched(&vec![0, 1, 1]); eval } } ``` The last piece of the puzzle is to create the constraints. We use the same `TestEval` struct as in the previous sections, but the `evaluate` function will look slightly different. Instead of calling `add_constraint` on the `EvalAtRow` instance, we will call `add_to_relation`, which recreates the fractions that we added in the LogUp columns using values in the range-check, lookup, and multiplicity columns. Once we add the fractions as constraints, we call the `finalize_logup_batched` function, which indicates how we want to batch the fractions. In our case, we added 3 fractions but want to create batches where the last two fractions are batched together, so we pass in `&vec![0, 1, 1]`. ```rust theme={null} // Verify assert_eq!(claimed_sum, SecureField::zero()); let channel = &mut Blake2sChannel::default(); let commitment_scheme = &mut CommitmentSchemeVerifier::::new(config); let sizes = component.trace_log_degree_bounds(); commitment_scheme.commit(proof.commitments[0], &sizes[0], channel); channel.mix_u64((log_num_rows) as u64); commitment_scheme.commit(proof.commitments[1], &sizes[1], channel); commitment_scheme.commit(proof.commitments[2], &sizes[2], channel); verify(&[&component], channel, commitment_scheme, proof).unwrap(); ``` When we verify the proof, as promised, we check that the `claimed_sum`, which is the sum of the fractions in the LogUp columns, is 0. And that's it! We have successfully created a static lookup for a range-check. **How many fractions can we batch together?** This depends on how we set the `max_constraint_log_degree_bound` function, as discussed in this [note](../writing-a-simple-air/constraints-over-trace-polynomials.md#max_constraint_log_degree_bound). More specifically, we can batch up to exactly the blowup factor. e.g. * `self.log_size + 1` -> 2 fractions * `self.log_size + 2` -> 4 fractions * `self.log_size + 3` -> 8 fractions * `self.log_size + 4` -> 16 fractions * ... Note that unlike what [Figure 1](#figure-1) shows, the size of the range column and the range-checked columns do not have to be the same. As we will learn in the [Components](../components/index) section, we can create separate components for the range-check and the range-checked columns to support such cases. # Committing to the Trace Polynomials Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/committing-to-the-trace-polynomials
*Figure 1: Prover workflow: Commitment*
Now that we have created the trace polynomials, we need to commit to them. As we can see in [Figure 1](#figure-1), S-two commits to the trace polynomials by first expanding the trace polynomials (i.e. adding more evaluations) and then committing to the expanded evaluations using a Merkle tree. The rate of expansion (commonly referred to as the *blowup factor*) is a parameter of the FRI protocol and for the purposes of this tutorial, we will use the default value. ```rust theme={null} const LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR: u32 = 1; fn main() { // --snip-- // Config for FRI and PoW let config = PcsConfig::default(); // Precompute twiddles for evaluating and interpolating the trace let twiddles = SimdBackend::precompute_twiddles( CanonicCoset::new( log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR + config.fri_config.log_blowup_factor, ) .circle_domain() .half_coset, ); // Create the channel and commitment scheme let channel = &mut Blake2sChannel::default(); let mut commitment_scheme = CommitmentSchemeProver::::new(config, &twiddles); // Commit to the preprocessed trace let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(vec![]); tree_builder.commit(channel); // Commit to the size of the trace channel.mix_u64(log_num_rows as u64); // Commit to the original trace let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_evals(trace); tree_builder.commit(channel); } ``` We begin with some setup. First, we create a default `PcsConfig` instance, which sets the values for the FRI and PoW operations. Setting non-default values is related to the security of the proof, which is outside the scope of this tutorial. Next, we precompute twiddles, which are factors multiplied during FFT for a particular domain. Notice that the log size of the domain is set to `log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR + config.fri_config.log_blowup_factor`, which is the max log size of the domain that is needed throughout the proving process. For committing to the trace polynomial, we only need to add `config.fri_config.log_blowup_factor` but as we will see in the next section, we also need to commit to a polynomial of a higher degree, which is the reason we also add `LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR`. The final setup is creating a commitment scheme and a channel. The commitment scheme will be used to commit to the trace polynomials as Merkle trees, while the channel will be used to keep a running hash of all data in the proving process (i.e. transcript of the proof). This is part of the Fiat-Shamir transformation, which derives randomness safely in a non-interactive setting. Here, we use the `Blake2sChannel` and `Blake2sMerkleChannel` for the channel and commitment scheme, respectively, but we can also use the `Poseidon252Channel` and `Poseidon252MerkleChannel` pair. Now that we have our setup, we can commit to the trace polynomials. But before we do so, we need to first commit to an empty vector called a *preprocessed trace*, which doesn't do anything but is required by S-two. Then, we need to commit to the size of the trace, which is another vital part that the prover should not be able to cheat on. After doing so, we can finally commit to the original trace polynomials. Now that we have committed to the trace polynomials, we can move on to how we can create constraints over the trace polynomials! # Evaluating Constraints Over Trace Polynomials Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/constraints-over-trace-polynomials
*Figure 1: Prover workflow: Constraints*
## Proving Spreadsheet Functions When we want to perform computations over the cells in a spreadsheet, we don't want to manually fill in the computed values. Instead, we leverage spreadsheet functions to autofill cells based on a given computation. We can do the same thing with our table, except in addition to autofilling cells, we can also create a constraint that the result was computed correctly. Remember that the purpose of using a proof system is that the verifier can verify a computation was executed correctly without having to execute it themselves? Well, that's exactly why we need to create a constraint. Now let's say we want to add a new column `C` to our spreadsheet that computes the product of the previous columns plus the first column. We can set `C1` as `A1 * B1 + A1` as in [Figure 2](#figure-2). The corresponding constraint is expressed as `C1 = A1 * B1 + A1`. However, we use an alternate representation `A1 * B1 + A1 - C1 = 0` because we can only enforce constraints stating that an expression should equal zero. Generalizing this constraint to the whole column, we get `col1_row1 * col2_row1 + col1_row1 - col3_row1 = 0`.
*Figure 2: Proving spreadsheet functions as constraints*
## Identical Constraints Every Row Obviously, as can be seen in [Figure 2](#figure-2), our new constraint is satisfied for every row in the table. This means that we can substitute creating a constraint for each row with a single constraint over the columns, i.e. the trace polynomials. Thus, `col1_row1 * col2_row1 + col1_row1 - col3_row1 = 0` becomes: $$ f_1(x,y) \cdot f_2(x,y) + f_1(x,y) - f_3(x,y) = 0 $$ The idea that all rows must have the same constraint may seem restrictive, compared to say a spreadsheet where we can define different functions for different rows. However, we will show in later sections how to handle such use-cases. (Spoiler alert: it involves selectors and components) ## Composition Polynomial We will now give a name to the polynomial that expresses the constraint: a **composition polynomial**. $$ C(x,y) = f_1(x,y) \cdot f_2(x,y) + f_1(x,y) - f_3(x,y) $$ Basically, in order to prove that the constraints are satisfied, we need to show that the composition polynomial evaluates to 0 over the original domain (i.e. the domain of size the number of rows in the table). But first, as can be seen in the upper part of [Figure 1](#figure-1), we need to expand the evaluations of the trace polynomials by a factor of 2. This is because when you multiply two trace polynomials of degree `n-1` (where `n` is the number of rows) to compute the constraint polynomial, the degree of the constraint polynomial will be the sum of the degrees of the trace polynomials, which is `2n-2`. To adjust for this increase in degree, we double the number of evaluations. Once we have the expanded evaluations, we can evaluate the composition polynomial $C(x,y)$. Since we need to do a FRI operation on the composition polynomial as well, we expand the evaluations again by a factor of 2 and commit to them as a merkle tree. This part corresponds to the bottom part of [Figure 1](#figure-1). ## Implementation Let's see how this is implemented in the code. ```rust theme={null} struct TestEval { log_size: u32, } impl FrameworkEval for TestEval { fn log_size(&self) -> u32 { self.log_size } fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR } fn evaluate(&self, mut eval: E) -> E { let col_1 = eval.next_trace_mask(); let col_2 = eval.next_trace_mask(); let col_3 = eval.next_trace_mask(); eval.add_constraint(col_1.clone() * col_2.clone() + col_1.clone() - col_3.clone()); eval } } fn main() { // --snip-- let mut col_3 = BaseColumn::zeros(num_rows); col_3.set(0, col_1.at(0) * col_2.at(0) + col_1.at(0)); col_3.set(1, col_1.at(1) * col_2.at(1) + col_1.at(1)); // Convert table to trace polynomials let domain = CanonicCoset::new(log_num_rows).circle_domain(); let trace: ColumnVec> = vec![col_1, col_2, col_3] .into_iter() .map(|col| CircleEvaluation::new(domain, col)) .collect(); // --snip-- // Create a component let _component = FrameworkComponent::::new( &mut TraceLocationAllocator::default(), TestEval { log_size: log_num_rows, }, QM31::zero(), ); } ``` First, we add a new column `col_3` that contains the result of the computation: `col_1 * col_2 + col_1`. Note that all the columns are padded with 0 to a length of 16 via `BaseColumn::zeros(num_rows)` and we got lucky because this satisfies our constraint (i.e. `0 * 0 + 0 - 0 = 0`), so we don't need to modify the constraint. Then, to create a constraint over the trace polynomials, we first create a `TestEval` struct that implements the `FrameworkEval` trait. Then, we add our constraint logic in the `FrameworkEval::evaluate` function. Note that this function is called for every row in the table, so we only need to define the constraint once. Inside `FrameworkEval::evaluate`, we call `eval.next_trace_mask()` consecutively three times, retrieving the cell values of all three columns (see [Figure 3](#figure-3) below for a visual representation). Once we retrieve all three column values, we add a constraint of the form `col_1 * col_2 + col_1 - col_3`, which should equal 0. Note that `FrameworkEval::evaluate` will be called for every row in the table.
*Figure 3: Evaluate function*
We also need to implement `FrameworkEval::max_constraint_log_degree_bound(&self)` for `FrameworkEval`. As mentioned in the [Composition Polynomial section](#composition-polynomial), we need to expand the trace polynomial evaluations because the degree of our composition polynomial is higher than that of the trace polynomial. Expanding it by `LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR=1` is sufficient for our example as the total degree of the highest degree term $f_1(x,y) \cdot f_2(x,y)$ is 2, so we return `self.log_size + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR`. For those who are interested in how to set this value in general, we leave a detailed note below. **What value to set for `max_constraint_log_degree_bound(&self)`?** `self.log_size + max(1, ceil(log2(max_degree - 1)))`, where `max_degree` is the maximum total degree of all defined constraint polynomials. For example, the `max_degree` of constraint $f_1(x,y) \cdot f_2(x,y) = 0$ is 2, while that of $f_1(x,y) \cdot f_1(x,y) \cdot f_2(x,y) \cdot f_3(x,y) = 0$ is 4. e.g. * degree 1 - 3: `self.log_size + 1` * degree 4 - 5: `self.log_size + 2` * degree 6 - 9: `self.log_size + 3` * degree 10 - 17: `self.log_size + 4` * ... Now that we know the degree of the composition polynomial, we can now why we need to set the `log_size` of the domain to `log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR + config.fri_config.log_blowup_factor` when precomputing twiddles in the following code: ```rust theme={null} // Precompute twiddles for evaluating and interpolating the trace let twiddles = SimdBackend::precompute_twiddles( CanonicCoset::new( log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR + config.fri_config.log_blowup_factor, ) .circle_domain() .half_coset, ); ``` To prove that the composition polynomial evaluates to 0 over the trace domain (since the composition polynomial is composed of constraints that evaluates to 0 over the trace domain), we first divide the composition polynomial by the **vanishing polynomial**, which is a polynomial that evaluates to 0 over the trace domain. If the composition polynomial is created correctly, this will result in a polynomial instead of a rational function, and we can perform FRI over this polynomial to prove this. Thus, we need to commit to this new polynomial, which is called the **quotient polynomial**. We can calculate its degree by subtracting the degree of the vanishing polynomial from the degree of the composition polynomial. Since the trace is of size `1 << log_num_rows`, the degree of the vanishing polynomial will be `1 << log_num_rows - 1`, so the resulting degree will be `1 << (log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR) - (1 << log_num_rows - 1)`. However, since we can only commit to a power of two degree, we can just use the `1 << (log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR)` value. If we apply the FRI blowup as well, we finally end up with the following log domain size: `log_num_rows + LOG_CONSTRAINT_EVAL_BLOWUP_FACTOR + config.fri_config.log_blowup_factor`. Using the new `TestEval` struct, we can create a new `FrameworkComponent::` component, which the prover will use to evaluate the constraint. For now, we can ignore the other parameters of the `FrameworkComponent::` constructor. We now move on to the final section where we finally create and verify a proof. Finally, we can break down what an Algebraic Intermediate Representation (AIR) means. *Algebraic* means that we are using polynomials to represent the constraints. *Intermediate Representation* means that this is a modified representation of our statement so that it can be proven. So AIR is just another way of saying that we are representing statements to be proven as constraints over polynomials. # From Spreadsheet to Trace Polynomials Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/from-spreadsheet-to-trace-polynomials
*Figure 1: Prover workflow: Trace polynomials*
In the previous section, we created a table (aka spreadsheet). In this section, we will convert the table into something called trace polynomials.
*Figure 2: From spreadsheet to trace polynomials*
In STARKs, the computation trace is represented as evaluations of a polynomial over some domain. Typically this domain is a coset of a multiplicative subgroup. But since the multiplicative subgroup of M31 is not smooth, S-two works over the circle group which is the subgroup of degree-2 extension of M31 (as explained in the [Mersenne Primes](../../how-it-works/mersenne-prime) and [Circle Group](../../how-it-works/circle-group) sections). Thus the domain in S-two is formed of points $(x_i, y_i)$ on the circle curve. Note that when we interpolate a polynomial over the points on the circle curve, we get a bivariate trace polynomial $f_j(x, y)$. We will explain why using a polynomial representation is useful in the next section, but for now, let's see how we can create trace polynomials for our code. Note that we are building upon the code from the previous section, so there's not much new code here. ```rust theme={null} fn main() { // --snip-- // Convert table to trace polynomials let domain = CanonicCoset::new(log_num_rows).circle_domain(); let _trace: ColumnVec> = vec![col_1, col_2] .into_iter() .map(|col| CircleEvaluation::new(domain, col)) .collect(); } ``` Here, `domain` refers to the $(x_i, y_i)$ values used to interpolate the trace polynomials. For example, $(x_1, y_1), (x_2, y_2)$ in [Figure 2](#figure-2) are the domain values for our example. Note that when creating the domain, we set the `log_num_rows` to the log of the actual number of rows that are used in the table. In our example, we set it to 4 since S-two requires that we use at least 16 rows. For a background on what `CanonicCoset` and `.circle_domain()` mean, you can refer to the [Circle Group](../../how-it-works/circle-group) section. Now that we have created 2 trace polynomials for our 2 columns, let's move on to the next section where we commit to those polynomials! # Hello World Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/hello-world Let's first set up a Rust project with S-two. ```bash theme={null} $ cargo new stwo-example ``` We need to specify the nightly Rust version to use S-two. ```bash theme={null} $ echo -e "[toolchain]\nchannel = \"nightly-2025-01-02\"" > rust-toolchain.toml ``` Now let's edit the `Cargo.toml` file as follows: ```rust,ignore theme={null} [package] name = "stwo-examples" version = "0.1.0" edition = "2021" license = "MIT" [dependencies] stwo = { git = "https://github.com/starkware-libs/stwo.git", rev = "75a6b0ac9bcc7101d8658445dded51923ab2586f", features = ["prover"]} stwo-constraint-framework = { git = "https://github.com/starkware-libs/stwo.git", rev = "75a6b0ac9bcc7101d8658445dded51923ab2586f", package = "stwo-constraint-framework", features = ["prover"] } num-traits = "0.2.17" itertools = "0.12.0" rand = "0.8.5" ``` We are all set! # Introduction Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/index
*Figure 1: Proving lifecycle in S-two*
Welcome to the guide for writing AIRs in S-two! In this section, we will go through the process of writing a simple AIR from scratch. This requires some understanding of the proving lifecycle in S-two, so we added a diagram showing a high-level overview of the whole process. As we go through each step, please note that the diagram may contain more steps than the code. This is because there are steps that are abstracted away by the S-two implementation, but are necessary to understand the code that we write when creating an AIR. # Proving and Verifying an AIR Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/proving-an-air
*Figure 1: Prover workflow: perform FRI and PoW*
We're finally ready for the last step: prove and verify an AIR! Since the code is relatively short, let us present it first and then go over the details. ```rust theme={null} fn main() { // --snip-- // Prove let proof = prove(&[&component], channel, commitment_scheme).unwrap(); // Verify let channel = &mut Blake2sChannel::default(); let commitment_scheme = &mut CommitmentSchemeVerifier::::new(config); let sizes = component.trace_log_degree_bounds(); commitment_scheme.commit(proof.commitments[0], &sizes[0], channel); channel.mix_u64(log_num_rows as u64); commitment_scheme.commit(proof.commitments[1], &sizes[1], channel); verify(&[&component], channel, commitment_scheme, proof).unwrap(); } ``` ## Prove As you can see, there is only a single line of code added to create the proof. The `prove` function performs the FRI and PoW operations under the hood, although, technically, the constraint-related steps in [Figure 1](#figure-1) were not performed in the previous section and are only performed once `prove` is called. ## Verify In order to verify our proof, we need to check that the constraints are satisfied using the commitments from the proof. In order to do that, we need to set up a `Blake2sChannel` and `CommitmentSchemeVerifier`, along with the same `PcsConfig` that we used when creating the proof. Then, we need to recreate the Fiat-Shamir channel by passing the Merkle tree commitments and the `log_num_rows` to the `CommitmentSchemeVerifier` instance by calling `commit` (remember: the order is important!). Then, we can verify the proof using the `verify` function. Try setting the dummy values in the table to 1 instead of 0. Does it fail? If so, can you see why? Congratulations! We have come full circle. We now know how to create a table, convert it to trace polynomials, commit to them, create constraints over the trace polynomials, and prove and verify the constraints (i.e. an AIR). In the following sections, we will go over some more complicated AIRs to explain S-two's other features. # Writing a Spreadsheet Source: https://docs.starknet.io/learn/S-two-book/air-development/writing-a-simple-air/writing-a-spreadsheet
*Figure 1: Prover workflow: Create a table*
In order to write a proof, we first need to create a table of rows and columns. This is no different from writing integers to an Excel spreadsheet as we can see in [Figure 2](#figure-2).
*Figure 2: From spreadsheet to table*
But there is a slight caveat to consider when creating the table. S-two implements [SIMD operations](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) to speed up the prover in the CPU, but this requires providing the table cells in chunks of 16 rows. Simply put, this is because S-two supports 16 lanes of 32-bit integers, which means that the same instruction can be run simultaneously for 16 different data. Alas, for our table, we will need to create 14 dummy rows to make the total number of rows equal to 16, as shown in [Figure 3](#figure-3). For the sake of simplicity, however, we will omit the dummy rows in the diagrams of the following sections.
*Figure 3: Creating a table with 16 rows*
Given all that, let's create this table using S-two. ```rust theme={null} use stwo::prover::{ backend::{ simd::{column::BaseColumn, m31::N_LANES}, Column, }, }; use stwo::core::fields::m31::M31; fn main() { let num_rows = N_LANES; let mut col_1 = BaseColumn::zeros(num_rows as usize); col_1.set(0, M31::from(1)); col_1.set(1, M31::from(7)); let mut col_2 = BaseColumn::zeros(num_rows as usize); col_2.set(0, M31::from(5)); col_2.set(1, M31::from(11)); } ``` As mentioned above, we instantiate the `num_rows` of our table as `N_LANES=16` to accommodate SIMD operations. Then we create a `BaseColumn` of `N_LANES=16` rows for each column and populate the first two rows with our values and the rest with dummy values. Note that the values in the `BaseColumn` need to be of type `M31`, which refers to the Mersenne-31 prime field that S-two uses. This means that the integers in the table must lie in the range $[0, 2^{31}-1)$. Now that we have our table, let's move on! # Awesome S-two Source: https://docs.starknet.io/learn/S-two-book/awesome-stwo/index Explore the community-curated [Awesome S-two](https://github.com/keep-starknet-strange/awesome-stwo) list for more resources and projects building on S-two. # Benchmarks Report Source: https://docs.starknet.io/learn/S-two-book/benchmarks/index * Overview of zkVM and Proof Systems | zkVM / Proof System | Architecture | Frontend | Backend | Security Bits | | :- | :- | :- | :- | :- | | [RISC Zero](https://github.com/risc0/risc0) | RISC-V | Rust | STARK-based | 96 bits | | [SP1](https://github.com/succinctlabs/sp1) | RISC-V | Rust | STARK-based | 100 bits | | [OpenVM](https://github.com/openvm-org/openvm) | RISC-V | Rust | STARK-based | 100 bits | | [Jolt](https://github.com/a16z/jolt) | RISC-V | Rust | Lookup-based | - | | [Stone](https://github.com/starkware-libs/stone-prover) | Cairo VM | Cairo | STARK-based | 100 bits | | [S-two](https://github.com/starkware-libs/stwo) | Cairo VM | Cairo | STARK-based | 96 bits | * All benchmarks presented here are **CPU-only** and do not utilize any GPU acceleration. * Stone benchmarks were generated using the `dynamic` layout with these [configurations](https://github.com/zksecurity/zkvm-benchmarks/tree/master/stone/configs). * The benchmarks for SP1, R0 and OpenVM use compressed or succinct prover type, which aggregates all the STARK proofs into a single STARK proof. * Benchmarks which run out of memory have been indicated by `💾` and benchmarks which generate errors in proof generation have been indicated by `❌` in the tables. ## Time and Commit Hash * **Commit Hash**: f27856ec17fbd9e85ec31cba9c2cb9e96d8dd08f * **Timestamp**: Thursday, July 03, 2025 20:52:50 UTC ## System Information ### OS Version Ubuntu 24.04.2 LTS ### CPU Info * **Architecture**: x86\_64 * **CPU(s)**: 48 * **Model name**: AMD EPYC-Rome Processor * **Thread(s) per core**: 2 * **Core(s) per socket**: 24 * **Socket(s)**: 1 * **L3 cache**: 16 MiB (1 instance) ### Memory Info * **MemTotal**: 184.25 GB * **MemFree**: 166.19 GB * **MemAvailable**: 178.69 GB ## Fibonacci Benchmark `n` Fibonacci iterations. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 32768 | 3.208 | 15.003 | 34.291 | 17.128 | 56.325 | 13.198 | | 65536 | 5.183 | 18.252 | 42.403 | 29.241 | 95.468 | 11.372 | | 131072 | 9.17 | 24.982 | 57.978 | 52.298 | 💾 | 12.886 | | 262144 | 15.771 | 39.327 | 92.229 | 86.743 | 💾 | 12.587 | | 524288 | 28.16 | 54.812 | 158.128 | 167.607 | 💾 | 16.166 | | 1048576 | 54.42 | 72.028 | 320.082 | 306.386 | 💾 | 18.612 | | 2097152 | 107.89 | 128.215 | 515.567 | 605.92 | 💾 | 31.047 | | 4194304 | ❌ | 222.332 | 1042.58 | 1206.76 | 💾 | 60.939 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 32768 | 53.0 | 108 | 142 | 23 | 92.0 | 41 | | 65536 | 77.0 | 107 | 139 | 10 | 113.0 | 16 | | 131072 | 58.0 | 101 | 140 | 23 | 💾 | 18 | | 262144 | 103.0 | 100 | 141 | 22 | 💾 | 89 | | 524288 | 56.0 | 83 | 140 | 24 | 💾 | 14 | | 1048576 | 104.0 | 83 | 57 | 23 | 💾 | 414 | | 2097152 | 98.0 | 84 | 57 | 10 | 💾 | 367 | | 4194304 | ❌ | 83 | 57 | 23 | 💾 | 10 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 32768 | 187.615 | 1315.53 | 1708.31 | 223.234 | 125.608 | 789.578 | | 65536 | 197.799 | 1315.53 | 1708.31 | 223.234 | 129.8 | 783.63 | | 131072 | 208.399 | 1315.53 | 1708.31 | 223.234 | 💾 | 783.834 | | 262144 | 219.415 | 1315.53 | 1708.31 | 223.234 | 💾 | 802.678 | | 524288 | 230.847 | 1315.53 | 1708.31 | 223.234 | 💾 | 801.194 | | 1048576 | 242.695 | 1315.53 | 852.937 | 223.234 | 💾 | 808.038 | | 2097152 | 254.959 | 1315.53 | 852.937 | 223.234 | 💾 | 819.122 | | 4194304 | ❌ | 1315.53 | 816.649 | 223.234 | 💾 | 866.882 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | | 32768 | 196974 | 168808 | 166215 | 229390 | 262143 | | 65536 | 393582 | 332648 | 330055 | 458766 | 524287 | | 131072 | 786798 | 660328 | 657735 | 💾 | 1048575 | | 262144 | 1573230 | 1315688 | 1313095 | 💾 | 2097151 | | 524288 | 3146041 | 2626408 | 2623815 | 💾 | 4194303 | | 1048576 | 6291822 | 5247848 | 5245255 | 💾 | 8388607 | | 2097152 | 12583293 | 10490728 | 10488135 | 💾 | 16777215 | | 4194304 | ❌ | 20976488 | 20973895 | 💾 | 33554431 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 32768 | 6.44 | 5.12 | 7 | 2.3 | 59.44 | 11.08 | | 65536 | 5.83 | 6.42 | 5.43 | 4.58 | 118.54 | 11.46 | | 131072 | 8.79 | 9.18 | 5.43 | 9.15 | 💾 | 12.2 | | 262144 | 14.73 | 14.3 | 6.57 | 9.17 | 💾 | 14.01 | | 524288 | 26.95 | 15.12 | 12.22 | 9.17 | 💾 | 17.57 | | 1048576 | 50.85 | 25.94 | 12.22 | 9.17 | 💾 | 24.57 | | 2097152 | 98.93 | 35.61 | 12.42 | 9.18 | 💾 | 38.62 | | 4194304 | ❌ | 51.25 | 12.43 | 9.18 | 💾 | 72.26 | ## Sha2 Benchmark Sha256 hash of `n` bytes. For Stone, the [cairo implementation of sha256](https://github.com/cartridge-gg/cairo-sha256) by cartridge was used for benchmarking and for other zkvms [sha2 Rust crate](https://crates.io/crates/sha2) was used for benchmarking. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 1.657 | 12.71 | 28.943 | 11.196 | 10.835 | 17.978 | 34.019 | 8.316 | 44.301 | | 512 | 1.711 | 13.184 | 29.561 | 11.306 | 19.88 | 15.051 | 33.991 | 11.33 | 43.728 | | 1024 | 2.173 | 13.863 | 30.609 | 17.136 | 31.021 | 16.833 | 33.83 | 11.201 | 44.897 | | 2048 | 3.324 | 15.021 | 34.184 | 28.934 | 31.294 | 17.726 | 34.531 | 17.174 | 44.683 | | 4096 | ❌ | 16.88 | 39.38 | 52.487 | 66.539 | 14.084 | 34.418 | 28.982 | 47.701 | | 8192 | ❌ | 24.037 | 50.539 | 86.688 | 116.467 | 14.979 | 35.564 | 52.48 | 48.995 | | 16384 | ❌ | 37.958 | 78.057 | 167.856 | 💾 | 21.402 | 38.267 | 110.887 | 52.865 | | 32768 | ❌ | 52.566 | 129.842 | 300.572 | 💾 | 16.555 | 46.209 | 184.873 | 60.148 | | 65536 | ❌ | 72.136 | 249.403 | 589.84 | 💾 | 18.384 | 60.138 | 357.716 | 79.957 | | 131072 | ❌ | 136.592 | 532.796 | 1172.21 | 💾 | 27.871 | 87.29 | 709.726 | 124.996 | | 262144 | ❌ | 257.02 | 916.603 | 2338.03 | 💾 | 27.362 | 121.022 | 1415.34 | 219.654 | | 524288 | ❌ | 513.726 | 1790.29 | 4673.32 | 💾 | 43.242 | 233.959 | 2825.46 | 393.678 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 50.0 | 108 | 141 | 10 | 428.0 | 10 | 83 | 22 | 142 | | 512 | 49.0 | 113 | 144 | 20 | 418.0 | 11 | 106 | 23 | 141 | | 1024 | 49.0 | 102 | 140 | 23 | 426.0 | 11 | 90 | 15 | 141 | | 2048 | 75.0 | 97 | 140 | 22 | 440.0 | 11 | 109 | 22 | 142 | | 4096 | ❌ | 106 | 139 | 22 | 454.0 | 11 | 99 | 23 | 141 | | 8192 | ❌ | 109 | 141 | 22 | 525.0 | 11 | 87 | 22 | 142 | | 16384 | ❌ | 82 | 144 | 22 | 💾 | 42 | 92 | 23 | 142 | | 32768 | ❌ | 94 | 140 | 23 | 💾 | 49 | 102 | 15 | 142 | | 65536 | ❌ | 83 | 141 | 23 | 💾 | 143 | 95 | 23 | 141 | | 131072 | ❌ | 83 | 57 | 24 | 💾 | 441 | 83 | 24 | 141 | | 262144 | ❌ | 83 | 56 | 24 | 💾 | 236 | 83 | 23 | 142 | | 524288 | ❌ | 83 | 56 | 23 | 💾 | 12 | 82 | 24 | 141 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 172.143 | 1315.56 | 1708.31 | 223.482 | 104.104 | 986.394 | 1315.56 | 223.482 | 1784.35 | | 512 | 172.143 | 1315.56 | 1708.31 | 223.482 | 114.152 | 1000.61 | 1315.56 | 223.482 | 1784.35 | | 1024 | 181.495 | 1315.56 | 1708.31 | 223.482 | 117.224 | 1011.16 | 1315.56 | 223.482 | 1784.35 | | 2048 | 191.263 | 1315.56 | 1708.31 | 223.482 | 120.04 | 1011.71 | 1315.56 | 223.482 | 1784.35 | | 4096 | ❌ | 1315.56 | 1708.31 | 223.482 | 126.632 | 1007.77 | 1315.56 | 223.482 | 1784.35 | | 8192 | ❌ | 1315.56 | 1708.31 | 223.482 | 130.824 | 1013.42 | 1315.56 | 223.482 | 1784.35 | | 16384 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1005.31 | 1315.56 | 223.482 | 1784.35 | | 32768 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1018.49 | 1315.56 | 223.482 | 1784.35 | | 65536 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1017.72 | 1315.56 | 223.482 | 1784.35 | | 131072 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1037.15 | 1315.56 | 223.482 | 1784.35 | | 262144 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1045.93 | 1315.56 | 223.482 | 1784.35 | | 524288 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 1092.14 | 1315.56 | 223.482 | 1784.35 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | sp1-precompile | r0-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 35325.0 | 33911 | 51280 | 14791.0 | 131071 | 11492 | 31643 | | 512 | 60893.0 | 52743 | 90848 | 29233.0 | 131071 | 15820 | 55467 | | 1024 | 112029 | 90407 | 169984 | 46295.0 | 131071 | 24476 | 103133 | | 2048 | 214301 | 165735 | 328256 | 80419.0 | 131071 | 41788 | 198463 | | 4096 | ❌ | 316391 | 644800 | 160489 | 262143 | 76412 | 389106 | | 8192 | ❌ | 617703 | 1277888 | 308807 | 524287 | 145660 | 770392 | | 16384 | ❌ | 1220327 | 2544064 | 💾 | 1048575 | 284156 | 1532981 | | 32768 | ❌ | 2425575 | 5076416 | 💾 | 2097151 | 561148 | 3058159 | | 65536 | ❌ | 4836071 | 10141120 | 💾 | 4194303 | 1115132 | 6108532 | | 131072 | ❌ | 9657063 | 20270528 | 💾 | 8388607 | 2223100 | 12209241 | | 262144 | ❌ | 19299047 | 40529344 | 💾 | 16777215 | 4439036 | 24410676 | | 524288 | ❌ | 38583015 | 81046976 | 💾 | 33554431 | 8870908 | 48813546 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 5.09 | 4.29 | 5.45 | 1.43 | 9.63 | 10.79 | 7.84 | 1.43 | 5.44 | | 512 | 4.9 | 4.42 | 5.44 | 1.42 | 18.9 | 10.82 | 8.18 | 1.42 | 5.45 | | 1024 | 4.9 | 4.54 | 5.44 | 2.3 | 35.74 | 10.86 | 8.01 | 1.42 | 5.44 | | 2048 | 4.91 | 5.02 | 5.44 | 4.58 | 35.73 | 10.87 | 8.1 | 2.3 | 5.44 | | 4096 | ❌ | 6.28 | 5.44 | 9.15 | 71.46 | 11.03 | 8.29 | 4.59 | 5.44 | | 8192 | ❌ | 8.6 | 5.44 | 9.17 | 142.68 | 11.34 | 8.53 | 9.16 | 5.44 | | 16384 | ❌ | 13.32 | 5.44 | 9.18 | 💾 | 11.94 | 8.79 | 9.17 | 5.44 | | 32768 | ❌ | 15.31 | 8.78 | 9.18 | 💾 | 13.48 | 8.63 | 9.18 | 5.44 | | 65536 | ❌ | 29.19 | 16.64 | 9.18 | 💾 | 16.52 | 12.9 | 9.18 | 5.44 | | 131072 | ❌ | 35.4 | 21.6 | 9.2 | 💾 | 22.5 | 21.26 | 9.19 | 6.89 | | 262144 | ❌ | 45.34 | 22.77 | 9.21 | 💾 | 34.52 | 34.15 | 9.21 | 12.86 | | 524288 | ❌ | 54.88 | 25.59 | 9.25 | 💾 | 63.89 | 49.57 | 9.23 | 24.78 | ## Sha2-Chain Benchmark Sha256 hash of 32 bytes for `n` iteration. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 2.164 | 12.798 | 29.444 | 11.303 | 11.083 | 11.488 | 34.606 | 6.862 | 43.511 | | 16 | 2.973 | 13.875 | 30.349 | 11.252 | 11.115 | 18.583 | 34.339 | 8.352 | 44.295 | | 32 | 4.912 | 14.62 | 33.814 | 17.156 | 11.991 | 11.938 | 34.203 | 8.305 | 43.893 | | 64 | 8.285 | 17.617 | 38.294 | 28.948 | 10.631 | 14.158 | 33.862 | 11.289 | 44.197 | | 128 | 14.469 | 24.728 | 49.582 | 52.457 | 19.206 | 10.335 | 35.134 | 11.315 | 45.006 | | 256 | 25.909 | 37.471 | 73.574 | 74.818 | 26.184 | 14.057 | 36.234 | 17.422 | 42.793 | | 512 | 50.064 | 52.895 | 126.086 | 144.234 | 56.529 | 22.012 | 40.643 | 29.057 | 46.573 | | 1024 | 96.901 | 71.752 | 231.888 | 285.012 | 95.878 | 20.846 | 50.02 | 52.522 | 52.862 | | 2048 | ❌ | 132.275 | 516.789 | 574.819 | 💾 | 14.061 | 77.808 | 110.071 | 61.761 | | 4096 | ❌ | 227.325 | 905.706 | 1110.77 | 💾 | 17.799 | 103.011 | 191.208 | 81.617 | | 8192 | ❌ | 444.028 | 1814.44 | 2210.89 | 💾 | 18.607 | 193.572 | 365.446 | 122.31 | | 16384 | ❌ | 872.638 | 3596.93 | 4404.24 | 💾 | 20.569 | 319.33 | 723.963 | 212.16 | | 32768 | ❌ | 1698.85 | 7067.26 | 8791.74 | 💾 | 25.041 | 628.712 | 1443.04 | 388.56 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 49.0 | 82 | 140 | 22 | 432.0 | 10 | 83 | 23 | 141 | | 16 | 55.0 | 101 | 140 | 23 | 398.0 | 10 | 93 | 20 | 142 | | 32 | 86.0 | 108 | 140 | 22 | 402.0 | 10 | 94 | 21 | 141 | | 64 | 102.0 | 88 | 140 | 23 | 375.0 | 10 | 87 | 22 | 141 | | 128 | 78.0 | 108 | 141 | 23 | 413.0 | 10 | 84 | 21 | 141 | | 256 | 60.0 | 83 | 139 | 22 | 422.0 | 11 | 83 | 23 | 141 | | 512 | 60.0 | 84 | 140 | 22 | 420.0 | 37 | 83 | 22 | 142 | | 1024 | 103.0 | 83 | 139 | 23 | 456.0 | 41 | 83 | 22 | 141 | | 2048 | ❌ | 83 | 57 | 23 | 💾 | 40 | 83 | 10 | 141 | | 4096 | ❌ | 86 | 56 | 22 | 💾 | 119 | 83 | 23 | 141 | | 8192 | ❌ | 83 | 56 | 22 | 💾 | 116 | 83 | 10 | 142 | | 16384 | ❌ | 84 | 56 | 24 | 💾 | 182 | 86 | 23 | 141 | | 32768 | ❌ | 83 | 56 | 24 | 💾 | 262 | 83 | 23 | 141 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 181.495 | 1315.56 | 1708.31 | 223.482 | 109.544 | 952.094 | 1315.56 | 223.482 | 1784.35 | | 16 | 191.263 | 1315.56 | 1708.31 | 223.482 | 106.728 | 971.154 | 1315.56 | 223.482 | 1784.35 | | 32 | 201.447 | 1315.56 | 1708.31 | 223.482 | 108.776 | 955.554 | 1315.56 | 223.482 | 1784.35 | | 64 | 212.047 | 1315.56 | 1708.31 | 223.482 | 106.568 | 962.798 | 1315.56 | 223.482 | 1784.35 | | 128 | 223.063 | 1315.56 | 1708.31 | 223.482 | 112.36 | 946.158 | 1315.56 | 223.482 | 1784.35 | | 256 | 234.495 | 1315.56 | 1708.31 | 223.482 | 117.992 | 969.698 | 1315.56 | 223.482 | 1784.35 | | 512 | 246.343 | 1315.56 | 1708.31 | 223.482 | 124.168 | 947.738 | 1315.56 | 223.482 | 1784.35 | | 1024 | 258.607 | 1315.56 | 1708.31 | 223.482 | 129.512 | 948.874 | 1315.56 | 223.482 | 1784.35 | | 2048 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 959.414 | 1315.56 | 223.482 | 1784.35 | | 4096 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 966.074 | 1315.56 | 223.482 | 1784.35 | | 8192 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 976.142 | 1315.56 | 223.482 | 1784.35 | | 16384 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 986.186 | 1315.56 | 223.482 | 1784.35 | | 32768 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1001.51 | 1315.56 | 223.482 | 1784.35 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | sp1-precompile | r0-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 69290.0 | 48118 | 45952 | 3008.0 | 65535 | 13495 | 14624 | | 16 | 136282 | 85686 | 83432 | 5872.0 | 65535 | 20287 | 20776 | | 32 | 270266 | 160822 | 158392 | 11600.0 | 65535 | 33871 | 33080 | | 64 | 538234 | 311094 | 308312 | 23056.0 | 65535 | 61039 | 57688 | | 128 | 1074187 | 611638 | 608152 | 45968.0 | 65535 | 115375 | 106904 | | 256 | 2146059 | 1212726 | 1207832 | 91792.0 | 131071 | 224047 | 205336 | | 512 | 4289803 | 2414902 | 2407192 | 183440 | 262143 | 441391 | 402200 | | 1024 | 8577291 | 4819254 | 4805912 | 366736 | 524287 | 876079 | 795928 | | 2048 | ❌ | 9627958 | 9603352 | 💾 | 1048575 | 1745455 | 1583384 | | 4096 | ❌ | 19245366 | 19198232 | 💾 | 2097151 | 3484207 | 3158296 | | 8192 | ❌ | 38480182 | 38387992 | 💾 | 4194303 | 6961711 | 6308120 | | 16384 | ❌ | 76949814 | 76767512 | 💾 | 8388607 | 13916719 | 12607768 | | 32768 | ❌ | 153889078 | 153526552 | 💾 | 16777215 | 27826735 | 25207064 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 5.1 | 4.31 | 5.44 | 1.42 | 9.1 | 10.74 | 7.81 | 1.44 | 5.44 | | 16 | 4.91 | 4.54 | 5.44 | 1.42 | 9.04 | 10.74 | 8.04 | 1.43 | 5.44 | | 32 | 5.92 | 4.98 | 5.44 | 2.3 | 9.04 | 10.75 | 8.5 | 1.43 | 5.44 | | 64 | 8.69 | 6.19 | 5.44 | 4.58 | 9.05 | 10.77 | 8.05 | 1.42 | 5.44 | | 128 | 14.39 | 8.81 | 5.44 | 9.14 | 17.87 | 10.78 | 8.29 | 1.42 | 5.44 | | 256 | 26.36 | 13.27 | 5.44 | 9.17 | 29.66 | 10.83 | 8.52 | 2.3 | 5.44 | | 512 | 48.32 | 14.7 | 8.57 | 9.17 | 59.33 | 10.95 | 8.63 | 4.59 | 5.44 | | 1024 | 93.03 | 27.2 | 16.24 | 9.17 | 118.62 | 11.18 | 11.08 | 9.15 | 5.44 | | 2048 | ❌ | 40.4 | 20.59 | 9.18 | 💾 | 11.64 | 18.61 | 9.17 | 5.44 | | 4096 | ❌ | 44.04 | 20.6 | 9.19 | 💾 | 12.88 | 35.18 | 9.17 | 5.44 | | 8192 | ❌ | 55.14 | 20.62 | 9.2 | 💾 | 15.31 | 53.3 | 9.17 | 6.64 | | 16384 | ❌ | 58.47 | 20.66 | 9.2 | 💾 | 20.08 | 52.23 | 9.18 | 12.34 | | 32768 | ❌ | 60.22 | 20.8 | 9.27 | 💾 | 29.71 | 63.03 | 9.18 | 23.74 | ## Sha3 Benchmark Keccak256 hash of `n` bytes. For Stone, the implementation of Keccak256 from stdlib as well as builtin was benchmarked. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 1.719 | 13.328 | 31.471 | 11.348 | 20.266 | 19.743 | 60.343 | 33.9 | 57.925 | 18.361 | | 512 | 2.109 | 13.627 | 32.773 | 17.205 | 30.972 | 10.897 | 60.387 | 36.853 | 59.354 | 17.21 | | 1024 | 3.15 | 15.275 | 34.777 | 17.179 | 65.925 | 23.258 | 60.193 | 42.789 | 58.748 | 18.037 | | 2048 | 4.924 | 17.564 | 39.929 | 29.094 | 115.746 | 16.661 | 60.696 | 43.047 | 59.419 | 16.58 | | 4096 | ❌ | 24.096 | 45.142 | 52.54 | 💾 | 11.693 | 60.073 | 54.792 | 61.503 | 27.567 | | 8192 | ❌ | 37.851 | 60.927 | 110.08 | 💾 | 16.392 | 61.477 | 78.203 | 63.798 | 43.043 | | 16384 | ❌ | 50.158 | 94.74 | 225.876 | 💾 | 15.422 | 63.416 | 136.103 | 70.003 | 86.217 | | 32768 | ❌ | 66.943 | 165.212 | 422.471 | 💾 | 16.528 | 70.234 | 251.263 | 79.62 | 151.873 | | 65536 | ❌ | 109.098 | 308.693 | 841.045 | 💾 | 21.269 | 84.047 | 437.697 | 102.599 | 💾 | | 131072 | ❌ | 207.231 | 753.494 | 1671.26 | 💾 | 32.631 | 96.357 | 856.136 | 151.591 | 💾 | | 262144 | ❌ | 367.867 | 1526.34 | 3310.6 | 💾 | 59.078 | 133.31 | 1691.49 | 252.934 | 💾 | | 524288 | ❌ | 701.99 | 3048.73 | 6615.82 | 💾 | 106.153 | 191.352 | 3376.32 | 454.067 | 💾 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 77.0 | 107 | 139 | 23 | 505.0 | 11 | 116 | 24 | 141 | 269.0 | | 512 | 66.0 | 107 | 139 | 23 | 494.0 | 11 | 108 | 23 | 141 | 213.0 | | 1024 | 58.0 | 84 | 139 | 22 | 495.0 | 11 | 88 | 23 | 141 | 244.0 | | 2048 | 61.0 | 109 | 140 | 24 | 544.0 | 11 | 104 | 22 | 141 | 217.0 | | 4096 | ❌ | 110 | 140 | 22 | 💾 | 11 | 114 | 22 | 142 | 229.0 | | 8192 | ❌ | 108 | 139 | 23 | 💾 | 11 | 89 | 22 | 142 | 254.0 | | 16384 | ❌ | 83 | 140 | 24 | 💾 | 38 | 91 | 23 | 142 | 262.0 | | 32768 | ❌ | 83 | 140 | 24 | 💾 | 139 | 88 | 22 | 141 | 273.0 | | 65536 | ❌ | 82 | 140 | 24 | 💾 | 198 | 111 | 23 | 141 | 💾 | | 131072 | ❌ | 83 | 57 | 24 | 💾 | 201 | 88 | 22 | 141 | 💾 | | 262144 | ❌ | 83 | 56 | 23 | 💾 | 631 | 88 | 23 | 141 | 💾 | | 524288 | ❌ | 83 | 56 | 22 | 💾 | 191 | 88 | 23 | 141 | 💾 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 170.823 | 1315.56 | 1708.31 | 223.482 | 113.64 | 1002.79 | 1477.23 | 223.482 | 1787.55 | 108.008 | | 512 | 180.175 | 1315.56 | 1708.31 | 223.482 | 119.272 | 991.494 | 1477.23 | 223.482 | 1787.55 | 107.752 | | 1024 | 189.943 | 1315.56 | 1708.31 | 223.482 | 126.92 | 1001.15 | 1477.23 | 223.482 | 1787.55 | 110.312 | | 2048 | 200.127 | 1315.56 | 1708.31 | 223.482 | 130.536 | 1001.4 | 1477.23 | 223.482 | 1787.55 | 110.056 | | 4096 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1016.95 | 1477.23 | 223.482 | 1787.55 | 114.92 | | 8192 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1012.45 | 1477.23 | 223.482 | 1787.55 | 119.848 | | 16384 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1027.4 | 1477.23 | 223.482 | 1787.55 | 126.088 | | 32768 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1014.61 | 1477.23 | 223.482 | 1787.55 | 132.872 | | 65536 | ❌ | 1315.56 | 1708.31 | 223.482 | 💾 | 1037.78 | 1477.23 | 223.482 | 1787.55 | 💾 | | 131072 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1073.61 | 1477.23 | 223.482 | 1787.55 | 💾 | | 262144 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 1154.34 | 1477.23 | 223.482 | 1787.55 | 💾 | | 524288 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1179.28 | 1477.23 | 223.482 | 1787.55 | 💾 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | sp1-precompile | r0-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 46732.0 | 47653 | 63651 | 19431.0 | 131071 | 15337 | 36390 | 998.0 | | 512 | 90274.0 | 84714 | 121925 | 38611.0 | 131071 | 20082 | 63910 | 1762.0 | | 1024 | 177358 | 158823 | 238473 | 59058.0 | 262143 | 29559 | 118950 | 3286.0 | | 2048 | 351574 | 307036 | 471569 | 117859 | 524287 | 48508 | 229030 | 6334.0 | | 4096 | ❌ | 586913 | 921296 | 💾 | 1048575 | 86015 | 448102 | 12241.0 | | 8192 | ❌ | 1146654 | 1820750 | 💾 | 2097151 | 161016 | 886246 | 24055.0 | | 16384 | ❌ | 2266147 | 3619658 | 💾 | 2097151 | 311029 | 1762534 | 47683.0 | | 32768 | ❌ | 4505110 | 7217474 | 💾 | 4194303 | 611032 | 3515110 | 94930.0 | | 65536 | ❌ | 8999653 | 14429571 | 💾 | 8388607 | 1211497 | 7021350 | 💾 | | 131072 | ❌ | 17988714 | 28853765 | 💾 | 16777215 | 2412402 | 14038604 | 💾 | | 262144 | ❌ | 35966823 | 57702153 | 💾 | 33554431 | 4814199 | 28068169 | 💾 | | 524288 | ❌ | 71923036 | 115398929 | 💾 | 67108863 | 9617788 | 56132428 | 💾 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 256 | 5.43 | 4.5 | 5.44 | 1.42 | 19.02 | 10.82 | 11.56 | 3.46 | 5.49 | 11.13 | | 512 | 4.9 | 4.54 | 5.44 | 2.3 | 35.74 | 10.85 | 11.59 | 3.45 | 5.49 | 11.07 | | 1024 | 4.9 | 5.01 | 5.44 | 2.3 | 71.41 | 10.97 | 11.55 | 3.45 | 5.49 | 11.07 | | 2048 | 5.8 | 6.16 | 5.44 | 4.59 | 142.54 | 11.21 | 11.62 | 3.45 | 5.49 | 11.07 | | 4096 | ❌ | 8.7 | 5.44 | 9.17 | 💾 | 11.9 | 11.79 | 4.59 | 5.49 | 21.97 | | 8192 | ❌ | 13.65 | 5.44 | 9.17 | 💾 | 13.19 | 11.96 | 9.16 | 5.49 | 43.91 | | 16384 | ❌ | 12.24 | 5.85 | 9.18 | 💾 | 13.73 | 11.87 | 9.17 | 5.49 | 87.17 | | 32768 | ❌ | 24.03 | 10.77 | 9.18 | 💾 | 17 | 12.23 | 9.17 | 5.48 | 174.24 | | 65536 | ❌ | 39 | 20.53 | 9.19 | 💾 | 23.54 | 12.22 | 9.18 | 5.49 | 💾 | | 131072 | ❌ | 52.23 | 21.23 | 9.2 | 💾 | 42.08 | 17.09 | 9.18 | 8.19 | 💾 | | 262144 | ❌ | 52.28 | 22.77 | 9.2 | 💾 | 78.97 | 27.25 | 9.21 | 15.42 | 💾 | | 524288 | ❌ | 57.67 | 22.93 | 9.27 | 💾 | 151.16 | 55.68 | 9.22 | 29.87 | 💾 | ## Sha3-Chain Benchmark Keccak256 hash of 32 bytes for `n` iteration. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 3.151 | 14.691 | 31.427 | 17.2 | 66.291 | 13.552 | 59.266 | 34.114 | 58.806 | 16.827 | | 16 | 5.142 | 17.431 | 34.818 | 28.941 | 115.28 | 18.47 | 60.353 | 37.322 | 59.329 | 18.279 | | 32 | 8.57 | 23.603 | 43.135 | 52.437 | 💾 | 13.475 | 60.409 | 36.973 | 58.913 | 27.208 | | 64 | 15.082 | 37.422 | 57.017 | 74.883 | 💾 | 14.55 | 60.377 | 42.978 | 59.971 | 43.739 | | 128 | 29.25 | 50.151 | 90.378 | 132.931 | 💾 | 18.699 | 59.433 | 54.814 | 61.487 | 85.956 | | 256 | 55.496 | 66.364 | 156.377 | 261.039 | 💾 | 22.329 | 61.843 | 78.503 | 65.217 | 151.067 | | 512 | 105.689 | 104.036 | 297.659 | 515.447 | 💾 | 29.078 | 65.634 | 92.151 | 72.255 | 💾 | | 1024 | ❌ | 192.377 | 715.326 | 1037.74 | 💾 | 52.14 | 73.4 | 176.441 | 88.201 | 💾 | | 2048 | ❌ | 357.885 | 1474.13 | 2034.16 | 💾 | 94.358 | 91.595 | 348.058 | 115.792 | 💾 | | 4096 | ❌ | 681.138 | 2909.76 | 4083.82 | 💾 | ❌ | 109.247 | 665.897 | 171.808 | 💾 | | 8192 | ❌ | 1316.41 | 5795.09 | 8122.75 | 💾 | ❌ | 183.78 | 1286.1 | 510.264 | 💾 | | 16384 | ❌ | 2597.86 | 11684.6 | 16241.2 | 💾 | ❌ | 353.935 | 2565.25 | 870.635 | 💾 | | 32768 | ❌ | 5192.04 | 23068.5 | 32449.9 | 💾 | ❌ | 657.383 | 5090.45 | 1712.06 | 💾 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 83.0 | 107 | 139 | 23 | 526.0 | 11.0 | 122 | 20 | 142 | 252.0 | | 16 | 114.0 | 86 | 139 | 23 | 555.0 | 11.0 | 116 | 23 | 142 | 254.0 | | 32 | 75.0 | 111 | 139 | 23 | 💾 | 11.0 | 88 | 23 | 142 | 256.0 | | 64 | 79.0 | 102 | 139 | 23 | 💾 | 11.0 | 116 | 23 | 142 | 265.0 | | 128 | 57.0 | 99 | 139 | 23 | 💾 | 11.0 | 99 | 22 | 141 | 270.0 | | 256 | 64.0 | 83 | 139 | 22 | 💾 | 42.0 | 90 | 20 | 141 | 288.0 | | 512 | 118.0 | 83 | 139 | 23 | 💾 | 44.0 | 103 | 23 | 141 | 💾 | | 1024 | ❌ | 83 | 56 | 23 | 💾 | 49.0 | 106 | 23 | 144 | 💾 | | 2048 | ❌ | 84 | 56 | 23 | 💾 | 572.0 | 88 | 24 | 142 | 💾 | | 4096 | ❌ | 83 | 56 | 23 | 💾 | ❌ | 88 | 23 | 141 | 💾 | | 8192 | ❌ | 84 | 56 | 23 | 💾 | ❌ | 87 | 22 | 56 | 💾 | | 16384 | ❌ | 83 | 55 | 20 | 💾 | ❌ | 88 | 24 | 57 | 💾 | | 32768 | ❌ | 84 | 57 | 23 | 💾 | ❌ | 88 | 22 | 55 | 💾 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 191.263 | 1315.56 | 1708.31 | 223.482 | 124.616 | 995.554 | 1477.23 | 223.482 | 1787.55 | 109.8 | | 16 | 201.447 | 1315.56 | 1708.31 | 223.482 | 129.96 | 1006.302 | 1477.23 | 223.482 | 1787.55 | 108.264 | | 32 | 212.047 | 1315.56 | 1708.31 | 223.482 | 💾 | 1028.558 | 1477.23 | 223.482 | 1787.55 | 115.944 | | 64 | 223.063 | 1315.56 | 1708.31 | 223.482 | 💾 | 1023.458 | 1477.23 | 223.482 | 1787.55 | 122.856 | | 128 | 234.495 | 1315.56 | 1708.31 | 223.482 | 💾 | 1032.058 | 1477.23 | 223.482 | 1787.55 | 123.304 | | 256 | 246.343 | 1315.56 | 1708.31 | 223.482 | 💾 | 1031.81 | 1477.23 | 223.482 | 1787.55 | 131.944 | | 512 | 258.607 | 1315.56 | 1708.31 | 223.482 | 💾 | 1084.746 | 1477.23 | 223.482 | 1787.55 | 💾 | | 1024 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | 1155.054 | 1477.23 | 223.482 | 1787.55 | 💾 | | 2048 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 1182.062 | 1477.23 | 223.482 | 1787.55 | 💾 | | 4096 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | ❌ | 1477.23 | 223.482 | 1787.55 | 💾 | | 8192 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | ❌ | 1477.23 | 223.482 | 852.937 | 💾 | | 16384 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | ❌ | 1477.23 | 223.482 | 852.937 | 💾 | | 32768 | ❌ | 1315.56 | 852.937 | 223.482 | 💾 | ❌ | 1477.23 | 223.482 | 816.649 | 💾 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | sp1-precompile | r0-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 203331 | 147841 | 147328 | 57648.0 | 262143 | 18577 | 27805 | 3480.0 | | 16 | 404363 | 284353 | 286232 | 115185 | 524287 | 25825 | 43693 | 6904.0 | | 32 | 806427 | 557377 | 564040 | 💾 | 1048575 | 40321 | 75469 | 13752.0 | | 64 | 1610555 | 1103425 | 1119656 | 💾 | 2097151 | 69313 | 139021 | 27448.0 | | 128 | 3218828 | 2195521 | 2230888 | 💾 | 4194303 | 127297 | 266125 | 54840.0 | | 256 | 6435340 | 4379713 | 4453352 | 💾 | 8388607 | 243265 | 520333 | 109624 | | 512 | 12868364 | 8748097 | 8898280 | 💾 | 16777215 | 475201 | 1028749 | 💾 | | 1024 | ❌ | 17484865 | 17788136 | 💾 | 33554431 | 939073 | 2050355 | 💾 | | 2048 | ❌ | 34958401 | 35567848 | 💾 | 67108863 | 1866817 | 4093575 | 💾 | | 4096 | ❌ | 69905473 | 71127272 | 💾 | ❌ | 3722305 | 8174812 | 💾 | | 8192 | ❌ | 139799617 | 142246120 | 💾 | ❌ | 7433281 | 16338156 | 💾 | | 16384 | ❌ | 279587905 | 284483816 | 💾 | ❌ | 14855233 | 32669621 | 💾 | | 32768 | ❌ | 559164481 | 568959208 | 💾 | ❌ | 29699137 | 65327886 | 💾 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | sp1-precompile | r0-precompile | openvm-precompile | stone-precompile | | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | -: | | 8 | 5.09 | 5.14 | 5.44 | 2.3 | 71.49 | 10.95 | 11.61 | 3.45 | 5.49 | 11.07 | | 16 | 5.85 | 6.23 | 5.44 | 4.58 | 142.53 | 11.14 | 11.43 | 3.45 | 5.49 | 11.07 | | 32 | 8.88 | 9.03 | 5.44 | 9.14 | 💾 | 11.83 | 11.95 | 3.45 | 5.49 | 21.98 | | 64 | 14.89 | 13.13 | 5.44 | 9.17 | 💾 | 13.2 | 11.6 | 3.45 | 5.49 | 43.91 | | 128 | 27.13 | 12.02 | 5.7 | 9.18 | 💾 | 15.6 | 11.49 | 4.57 | 5.49 | 87.26 | | 256 | 51.24 | 21.98 | 10.48 | 9.18 | 💾 | 20.66 | 11.14 | 9.13 | 5.49 | 174.37 | | 512 | 100.0 | 39.84 | 19.95 | 9.18 | 💾 | 36.36 | 11.92 | 9.16 | 5.49 | 💾 | | 1024 | ❌ | 41.18 | 19.95 | 9.19 | 💾 | 67.57 | 11.24 | 9.16 | 5.49 | 💾 | | 2048 | ❌ | 47.4 | 19.96 | 9.2 | 💾 | 128.39 | 14.17 | 9.16 | 6.28 | 💾 | | 4096 | ❌ | 59.51 | 20.31 | 9.22 | 💾 | ❌ | 30.3 | 9.17 | 11.39 | 💾 | | 8192 | ❌ | 61.16 | 20.5 | 9.26 | 💾 | ❌ | 53.85 | 9.18 | 20.72 | 💾 | | 16384 | ❌ | 61.43 | 20.75 | 9.34 | 💾 | ❌ | 57.23 | 9.2 | 20.8 | 💾 | | 32768 | ❌ | 63.58 | 20.93 | 9.49 | 💾 | ❌ | 63.67 | 9.22 | 20.87 | 💾 | ## BLAKE Benchmark Blake2s256 hash of `n` bytes. For the **S-two** benchmarks, a dedicated opcode for BLAKE was used, as illustrated in [this Cairo source file](https://github.com/starkware-libs/cairo-lang/blob/ab8be40403a7634ba296c467b26b8bd945ba5cfa/src/starkware/cairo/common/cairo_blake2s/blake2s.cairo#L671). **SP1** and **R0** do not support precompiles for BLAKE. ### Prover Time (s) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 2048 | 14.272 | 28.817 | 35.206 | | 4096 | 14.773 | 52.33 | 34.752 | | 8192 | 17.678 | 65.67 | 30.911 | | 16384 | 24.308 | 126.219 | 32.214 | | 32768 | 37.485 | 240.843 | 30.324 | | 65536 | 55.119 | 476.273 | 38.614 | | 131072 | 101.786 | 947.306 | 35.269 | | 262144 | 162.863 | 1888.15 | 33.434 | ### Verifier Time (ms) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 2048 | 102 | 10 | 11 | | 4096 | 105 | 23 | 11 | | 8192 | 111 | 22 | 11 | | 16384 | 99 | 24 | 11 | | 32768 | 104 | 23 | 12 | | 65536 | 81 | 22 | 12 | | 131072 | 81 | 23 | 37 | | 262144 | 81 | 22 | 48 | ### Proof Size (KB) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 2048 | 1315.56 | 223.482 | 1117.14 | | 4096 | 1315.56 | 223.482 | 1138.83 | | 8192 | 1315.56 | 223.482 | 1134.37 | | 16384 | 1315.56 | 223.482 | 1121.81 | | 32768 | 1315.56 | 223.482 | 1144.87 | | 65536 | 1315.56 | 223.482 | 1158.19 | | 131072 | 1315.56 | 223.482 | 1141.67 | | 262144 | 1315.56 | 223.482 | 1148.66 | ### Cycle Count | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 2048 | 100997 | 264515 | 65535 | | 4096 | 193061 | 521763 | 65535 | | 8192 | 377189 | 1036259 | 65535 | | 16384 | 745445 | 2065251 | 65535 | | 32768 | 1481957 | 4123235 | 131071 | | 65536 | 2954981 | 8239203 | 262143 | | 131072 | 5901029 | 16471139 | 524287 | | 262144 | 11793125 | 32935011 | 1048575 | ### Peak Memory (GB) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 2048 | 4.55 | 4.58 | 19.98 | | 4096 | 5 | 9.14 | 19.94 | | 8192 | 6.09 | 9.17 | 19.98 | | 16384 | 8.83 | 9.18 | 20.02 | | 32768 | 13.83 | 9.18 | 20.17 | | 65536 | 16.59 | 9.18 | 20.5 | | 131072 | 38 | 9.19 | 20.88 | | 262144 | 45.31 | 9.19 | 21.97 | ## BLAKE-Chain Benchmark Blake2s256 hash of 32 bytes for `n` iteration. For the **S-two** benchmarks, a dedicated opcode for BLAKE was used, as illustrated in [this Cairo source file](https://github.com/starkware-libs/cairo-lang/blob/ab8be40403a7634ba296c467b26b8bd945ba5cfa/src/starkware/cairo/common/cairo_blake2s/blake2s.cairo#L671). **SP1** and **R0** do not support precompiles for BLAKE. ### Prover Time (s) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 128 | 17.319 | 28.76 | 30.885 | | 256 | 24.279 | 52.074 | 32.974 | | 512 | 38.5 | 86.181 | 33.26 | | 1024 | 53.129 | 166.706 | 30.723 | | 2048 | 89.423 | 316.029 | 38.153 | | 4096 | 145.299 | 628.118 | 31.717 | | 8192 | 259.23 | 1260.14 | 37.422 | | 16384 | 495.878 | 2521.17 | 35.82 | ### Verifier Time (ms) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 128 | 105 | 22 | 11 | | 256 | 104 | 22 | 11 | | 512 | 108 | 23 | 11 | | 1024 | 84 | 23 | 11 | | 2048 | 81 | 23 | 41 | | 4096 | 85 | 23 | 40 | | 8192 | 81 | 23 | 49 | | 16384 | 81 | 22 | 39 | ### Proof Size (KB) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 128 | 1315.56 | 223.482 | 1119.07 | | 256 | 1315.56 | 223.482 | 1121.09 | | 512 | 1315.56 | 223.482 | 1128.54 | | 1024 | 1315.56 | 223.482 | 1119.77 | | 2048 | 1315.56 | 223.482 | 1127.81 | | 4096 | 1315.56 | 223.482 | 1127.26 | | 8192 | 1315.56 | 223.482 | 1134 | | 16384 | 1315.56 | 223.482 | 1143.83 | ### Cycle Count | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 128 | 376249 | 356507 | 65535 | | 256 | 742841 | 702491 | 65535 | | 512 | 1476025 | 1394459 | 131071 | | 1024 | 2942393 | 2778395 | 262143 | | 2048 | 5875129 | 5546267 | 524287 | | 4096 | 11740601 | 11082011 | 1048575 | | 8192 | 23471545 | 22153499 | 2097151 | | 16384 | 46933433 | 44296475 | 4194303 | ### Peak Memory (GB) | n | sp1 | r0 | stwo-precompile | | -: | -: | -: | -: | | 128 | 6.16 | 4.58 | 19.99 | | 256 | 9.12 | 9.15 | 20.01 | | 512 | 13.17 | 9.17 | 20.13 | | 1024 | 15.96 | 9.17 | 20.39 | | 2048 | 32.88 | 9.17 | 20.72 | | 4096 | 44.6 | 9.19 | 21.54 | | 8192 | 50.57 | 9.19 | 23.31 | | 16384 | 53.25 | 9.21 | 27.19 | ## Matrix Multiplication Benchmark multiplication of two matrices of size n x n. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 4 | 1.196 | 13.179 | 29.508 | 6.763 | 10.922 | 12.72 | | 8 | 1.648 | 12.862 | 30.119 | 8.21 | 9.879 | 19.566 | | 16 | 4.979 | 15.252 | 33.467 | 17.135 | 26.384 | 16.531 | | 32 | 27.702 | 26.491 | 58.102 | 65.806 | 💾 | 14.655 | | 64 | ❌ | 135.352 | 292.125 | 480.724 | 💾 | 21.199 | | 128 | ❌ | 881.29 | 2671.68 | 3809.76 | 💾 | 76.061 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 4 | 44.0 | 110 | 140 | 23 | 111.0 | 10 | | 8 | 51.0 | 110 | 140 | 22 | 120.0 | 10 | | 16 | 86.0 | 102 | 139 | 23 | 139.0 | 10 | | 32 | 84.0 | 109 | 140 | 25 | 💾 | 18 | | 64 | ❌ | 85 | 140 | 22 | 💾 | 41 | | 128 | ❌ | 84 | 56 | 23 | 💾 | 45 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 4 | 144.647 | 1315.53 | 1708.31 | 223.234 | 109.032 | 815.674 | | 8 | 169.607 | 1315.53 | 1708.31 | 223.234 | 108.52 | 833.086 | | 16 | 198.911 | 1315.53 | 1708.31 | 223.234 | 116.456 | 844.238 | | 32 | 233.591 | 1315.53 | 1708.31 | 223.234 | 💾 | 844.914 | | 64 | ❌ | 1315.53 | 1708.31 | 223.234 | 💾 | 876.354 | | 128 | ❌ | 1315.53 | 852.937 | 223.234 | 💾 | 981.178 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | | 4 | 7181.0 | 8430 | 5820 | 2181.0 | 32767 | | 8 | 42753.0 | 23951 | 21352 | 13521.0 | 32767 | | 16 | 314782 | 139844 | 137280 | 94569.0 | 131071 | | 32 | 2457710 | 1044394 | 1041904 | 💾 | 1048575 | | 64 | ❌ | 8211062 | 8208720 | 💾 | 8388607 | | 128 | ❌ | 65306638 | 65304592 | 💾 | 67108863 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 4 | 5.43 | 4.45 | 5.43 | 1.42 | 9.09 | 10.73 | | 8 | 4.91 | 4.29 | 5.43 | 1.43 | 9.03 | 10.73 | | 16 | 5.8 | 5.12 | 5.43 | 2.29 | 29.68 | 10.82 | | 32 | 26.45 | 10.34 | 5.43 | 9.17 | 💾 | 11.61 | | 64 | ❌ | 39.28 | 19.69 | 9.17 | 💾 | 19.92 | | 128 | ❌ | 60.01 | 20.09 | 9.19 | 💾 | 101.6 | ## Elliptic Curve Addition Benchmark n point doubling for secp256k1. ### Prover Time (s) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 256 | 50.41 | 70.731 | 158.631 | 283.941 | 18.869 | 11.993 | | 512 | 96.814 | 116.11 | 292.691 | 531.309 | 30.529 | 19.268 | | 1024 | ❌ | 211.962 | 582.356 | 1037.16 | 65.693 | 15.356 | | 2048 | ❌ | 394.022 | 1048.01 | 2072.757 | 114.312 | 14.318 | | 4096 | ❌ | 741.169 | 2029.72 | 4079.796 | 💾 | 14.407 | | 8192 | ❌ | 1426.64 | 4046.32 | 8181.789 | 💾 | 16.635 | | 16384 | ❌ | 2844.32 | 7953.88 | ❌ | 💾 | 24.382 | | 32768 | ❌ | 5628.39 | 15793.7 | ❌ | 💾 | 28.304 | ### Verifier Time (ms) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 256 | 67.0 | 83 | 140 | 22.0 | 188.0 | 10 | | 512 | 60.0 | 83 | 140 | 23.0 | 181.0 | 10 | | 1024 | ❌ | 83 | 56 | 23.0 | 187.0 | 34 | | 2048 | ❌ | 83 | 56 | 25.0 | 238.0 | 39 | | 4096 | ❌ | 83 | 57 | 23.0 | 💾 | 41 | | 8192 | ❌ | 83 | 56 | 11.0 | 💾 | 36 | | 16384 | ❌ | 83 | 56 | ❌ | 💾 | 130 | | 32768 | ❌ | 83 | 55 | ❌ | 💾 | 15 | ### Proof Size (KB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 256 | 249.399 | 1315.56 | 1708.31 | 223.482 | 111.592 | 918.41 | | 512 | 261.663 | 1315.56 | 1708.31 | 223.482 | 120.04 | 897.182 | | 1024 | ❌ | 1315.56 | 852.937 | 223.482 | 127.784 | 889.49 | | 2048 | ❌ | 1315.56 | 852.937 | 223.482 | 131.976 | 896.15 | | 4096 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 901.758 | | 8192 | ❌ | 1315.56 | 816.649 | 223.482 | 💾 | 910.154 | | 16384 | ❌ | 1315.56 | 852.937 | ❌ | 💾 | 929.282 | | 32768 | ❌ | 1315.56 | 816.649 | ❌ | 💾 | 931.042 | ### Cycle Count | n | jolt | sp1 | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | | 256 | 4760760 | 4715656 | 4711913 | 59936.0 | 131071 | | 512 | 9290168 | 9169544 | 9165801 | 119840 | 262143 | | 1024 | ❌ | 18077320 | 18073577 | 239648 | 262143 | | 2048 | ❌ | 35892872 | 35889129 | 479264 | 524287 | | 4096 | ❌ | 71523976 | 71520233 | 💾 | 1048575 | | 8192 | ❌ | 142786184 | 142782441 | 💾 | 2097151 | | 16384 | ❌ | 285310600 | ❌ | 💾 | 4194303 | | 32768 | ❌ | 570359432 | ❌ | 💾 | 8388607 | ### Peak Memory (GB) | n | jolt | sp1 | openvm | r0 | stone | stwo | | -: | -: | -: | -: | -: | -: | -: | | 256 | 48.62 | 23.6 | 10.37 | 9.18 | 17.93 | 10.9 | | 512 | 93.9 | 47.38 | 19.8 | 9.19 | 35.7 | 11.08 | | 1024 | ❌ | 44.31 | 19.83 | 9.19 | 71.47 | 11.27 | | 2048 | ❌ | 55.82 | 19.84 | 9.19 | 142.68 | 11.81 | | 4096 | ❌ | 62.49 | 19.86 | 9.22 | 💾 | 13.22 | | 8192 | ❌ | 63.14 | 19.94 | 9.26 | 💾 | 16.01 | | 16384 | ❌ | 66.48 | 19.95 | ❌ | 💾 | 21.62 | | 32768 | ❌ | 66.97 | 19.96 | ❌ | 💾 | 32.64 | # ADD Opcode Walkthrough Source: https://docs.starknet.io/learn/S-two-book/cairo-air/add-opcode/index To better understand how `Opcode` components work, let's walk through the implementation of the `AddOpcode` component.
*Figure 1: The `AddOpcode` columns*
Above is the list of all the columns used in the `AddOpcode` component. Note that the `dst_val`, `op0_val`, and `op1_val` columns are actually 28 columns each (to support 28 9-bit limbs), but we show them as single columns for brevity. To reiterate what an `Opcode` component does from the [Main Components](../main-components/index#opcode-component) section, it verifies the following: 1. The offsets and flag values are correct using the `VerifyInstruction` component. 2. The instruction is correctly mapped to the current `Opcode` component using the flags. 3. The operand values `op0`, `op1`, and `dst` computed with the registers and the offsets are correct using the `Memory` component. 4. The operation for the current `Opcode` component is done correctly. 5. The state transition of the three registers (`pc`, `ap`, `fp`) is done correctly using the flags. An instruction should contain 15 bits of flags but only 5 bits are represented in the `AddOpcode` columns. Can you see why? Check out how a Cairo instruction is pattern-matched to an `ADD` opcode [here](https://github.com/starkware-libs/stwo-cairo/blob/04f142e0376e2a0e797cdb64973449aa3e58a18f/stwo_cairo_prover/crates/adapter/src/opcodes.rs#L349). Check out how the decomposition of the flags is verified [here](https://github.com/starkware-libs/stwo-cairo/blob/04f142e0376e2a0e797cdb64973449aa3e58a18f/stwo_cairo_prover/crates/cairo-air/src/components/subroutines/decode_instruction_bc3cd.rs#L59). Items 1 and 3 should be familiar, as we already covered them in the [Main Components](../main-components/index#opcode-component) section. For item 2, you can check the specs for the `ADD` opcode [here](https://github.com/starkware-libs/stwo-cairo/blob/04f142e0376e2a0e797cdb64973449aa3e58a18f/stwo_cairo_prover/crates/adapter/src/opcodes.rs#L349). For item 5, the specs for a valid state transition can be found in Section 4.5 of the [Cairo paper](https://eprint.iacr.org/2021/1063.pdf). In this section, we will focus on how item 4 is implemented. ## Adding two 252-bit integers Assuming that the operands `op0`, `op1`, and `dst` are correctly accessed from the `Memory` table, we now check that the addition of two 252-bit integers is done correctly, i.e. `op0 + op1 = dst`. As noted in the [Felt252 to M31](../basic-building-blocks/index#felt252-to-m31) section, a 252-bit integer is stored as 28 9-bit limbs, so we need to check addition for each of the 28 limbs. We will incrementally build up to the final constraint. ### Limb-wise Addition and Carry To verify that the two sets of limbs are correctly added, we check limb-wise addition. Since each limb can create a carry, we also add the carry from the previous limb (except for the first limb). Thus, the constraints look like this: ```python theme={null} carry_limb_1 = (op0[0] + op1[0] - dst[0]) / 2^9 carry_limb_1 * (carry_limb_1 - 1) = 0 carry_limb_2 = (op0[1] + op1[1] + carry_limb_1 - dst[1]) / 2^9 carry_limb_2 * (carry_limb_2 - 1) = 0 ... op0[27] + op1[27] + carry_limb_27 - dst[27] = 0 ``` We divide `op0[0] + op1[0] - dst[0]` by `2^9` since this quantity is either `2^9` (if a carry exists) or `0` (if no carry exists). Dividing by `2^9` yields one or zero, respectively. To check that the carry is either `0` or `1`, we add the constraint `carry_limb_0 * (carry_limb_0 - 1) = 0`. For the final limb, we simply check that the addition is correct. ### Handling overflow beyond the 252-bit prime field We also need to account for addition overflowing the 252-bit prime field `P` (i.e., `op0 + op1 = dst + P`). To check this, we introduce a witness variable `sub_p_bit`, a 1-bit value set to 1 if there is an overflow. Note that since `P = 2^251 + 17 * 2^192 + 1`, we only subtract in the three limbs where `P` has a non-zero limb: `0`, `22`, `27`. Now let's revisit the constraints: ```python theme={null} sub_p_bit * (sub_p_bit - 1) = 0 carry_limb_1 = (op0[0] + op1[0] - dst[0] - sub_p_bit) / 2^9 carry_limb_1 * (carry_limb_1 - 1) = 0 ... carry_limb_23 = (op0[22] + op1[22] + carry_limb_22 - dst[22] - sub_p_bit * 136) / 2^9 carry_limb_23 * (carry_limb_23 - 1) = 0 ... op0[27] + op1[27] + carry_limb_27 - dst[27] - sub_p_bit * 256 = 0 ``` First, we verify that `sub_p_bit` is a bit. Then, we subtract `sub_p_bit` from the first limb, `sub_p_bit * 136` from the 22nd limb, and `sub_p_bit * 256` from the 27th limb. (Note that `136` and `256` are the values of `P` in the 22nd and 27th limbs, respectively.) A caveat of this approach is that subtracting `sub_p_bit` can introduce an underflow, i.e., `(op0[0] + op1[0] - dst[0] - sub_p_bit) / 2^9 = -1`. This means that `carry_limb_0` can be `-1` as well as `0` or `1`. Thus, we update the constraint for all carries to the following: ```python theme={null} ... carry_limb_1 * (carry_limb_1 - 1) * (carry_limb_1 + 1) = 0 ... ``` ### Optimization To optimize the number of constraints, we can combine all the constraints for each limb into a single constraint. Naively checking that `carry_limb_1 = (op0[0] + op1[0] - dst[0] - sub_p_bit) / 2^9` would require a dedicated column for `carry_limb_1`. Instead, we keep `carry_limb_1` as an intermediate value and inline the equation when computing the next carry. For example, the second-limb carry is computed as follows: ```python theme={null} carry_limb_2 = (op0[1] + op1[1] + ((op0[0] + op1[0] - dst[0] - sub_p_bit) / 2^9) - dst[1]) / 2^9 ``` This way, we avoid allocating extra columns for the carries and proceed until the last limb, where we check that the giant equation is correct. This is possible because the computation does not involve any multiplication of witness values, so the constraint degree does not blow up. ### Final constraint One last note: in the implementation, we replace division by `2^9` with multiplication by `2^22`, which is equivalent in the M31 field since `2^9` and `2^22` are multiplicative inverses—i.e., `2^9 * 2^22 = 1 mod 2^31 - 1`. So the final constraint looks like this: ```python theme={null} sub_p_bit * (sub_p_bit - 1) = 0 carry_limb_1 = (op0[0] + op1[0] - dst[0] - sub_p_bit) * 2^22 // intermediate representation carry_limb_1 * (carry_limb_1 - 1) * (carry_limb_1 + 1) = 0 ... carry_limb_27 = (op0[26] + op1[26] + carry_limb_26 - dst[26]) * 2^22 // intermediate representation carry_limb_27 * (carry_limb_27 - 1) * (carry_limb_27 + 1) = 0 op0[27] + op1[27] + carry_limb_27 - dst[27] - sub_p_bit * 256 = 0 ``` ## Enabler column Finally, we introduce the last column in the `AddOpcode` component: the `enabler` column. As the name suggests, this column enables or disables the constraint for the current row. It is used to support the dummy rows added to the end of the table to make the number of rows a power of two. In other words, it is set to `1` for all valid `ADD` opcode calls and `0` for all dummy rows. # Basic Building Blocks Source: https://docs.starknet.io/learn/S-two-book/cairo-air/basic-building-blocks/index This section covers the basic building blocks used to build the Cairo AIR. ## Felt252 to M31 Cairo works over the prime field $P = 2^{251} + 17 \cdot 2^{192} + 1$, while S-two works over the prime field $M31 = 2^{31} - 1$. Thus, in order to represent the execution of Cairo with S-two, we need to decompose the 252-bit integers into 31-bit integers. The Cairo AIR chooses to use the 9-bit decomposition, so a single 252-bit integer will result in 28 9-bit limbs. ## Range checks Range-checks are very commonly used in the Cairo AIR. They are used to ensure that the witness values are within a certain range, most commonly within a certain bit length. For example, in the previous section, we saw that a 252-bit integer is decomposed into 28 9-bit limbs, so we need to verify that each limb is in the range $0 \leq \text{limb} < 2^{9}$. This is done by using a preprocessed column that contains the entire range of possible values for the bit length. For example, for a 9-bit range check, the column will contain the values from 0 to $2^9 - 1$. We also have another column that contains the number of times the range-check was invoked for each valid value and we use lookups to check that each range-check is valid. For a more practical example, please refer to the [Static Lookups](../../air-development/static-lookups/index) section. # Overview of Cairo Source: https://docs.starknet.io/learn/S-two-book/cairo-air/cairo/index This is an informal overview of Cairo. For a more formal explanation, please refer to the original [Cairo paper](https://eprint.iacr.org/2021/1063.pdf). Let's start by understanding how Cairo works. Essentially, Cairo is a Turing-complete CPU architecture specifically designed to enable efficient proofs of execution using STARKs. In particular, Cairo uses a read-only memory model instead of the more common read-write memory model and does not use any general-purpose registers. ## Non-Deterministic Read-Only Memory A read-only memory model is one where each address in memory can have only a single value throughout the program's execution. This contrasts with the more common read-write memory model, where an address can have multiple values at different points during execution. The memory is also non-deterministic: the prover provides the values of the memory cells as witness values, and they do not need further constraints beyond ensuring that each address has a single value throughout the program's execution. ## Registers In physical CPUs, accessing memory is expensive compared to accessing registers due to physical proximity. This is why instructions typically operate over registers rather than directly over memory cells. In Cairo, accessing memory and registers incur the same cost, so Cairo instructions operate directly over memory cells. Thus, the three registers used by Cairo do not store instructions or operand values like in physical CPUs, but rather pointers to the memory cells where the instructions and operands are stored: * `pc` is the **program counter**, which points to the current Cairo instruction * `ap` is the **allocation pointer**, which points to the current available memory address * `fp` is the **frame pointer**, which points to the current frame in the "call stack" ## Cairo Instructions Let's now see what a Cairo instruction looks like.
*Figure 1: Cairo instruction (little-endian)*
As the figure above from the Cairo paper shows, an instruction is 64 bits, where the first three 16-bit integers are signed offsets to the operands `dst`, `op0`, and `op1`. The next 15 bits are flags. The `dst_reg` and `op0_reg` 1-bit flags indicate whether to use the `ap` or the `fp` register as the base for the `dst` and `op0` operands. The `op1_src` flag supports a wider range of base values for the `op1` operand: `op0`, `pc`, `fp`, and `ap`. The `res_logic` flag indicates how to compute the `res` operand: `op1`, `op0 + op1`, or `op0 * op1`. The `pc_update` and `ap_update` flags show how to update the `pc` and `ap` registers after computing the operands. The `opcode` flag indicates whether this instruction belongs to a predefined opcode (e.g., `CALL`, `RET`, `ASSERT_EQ`) and also defines how the `ap` and `fp` registers should be updated. For a more detailed explanation of the flags, please refer to Section 4.5 of the Cairo paper. Finally, the last bit is fixed to 0, but as we will see in the next section, this design has been modified in the current version of Cairo to support opcode extensions. ### Opcodes and Opcode Extensions In Cairo, an **opcode** refers to what the instruction should do. Cairo defines a set of common CPU operations as specific opcodes (e.g., `ADD`, `MUL`, `JUMP`, `CALL`), and the current version of Cairo also defines a new set of opcodes used to improve the performance of heavy computations such as Blake2s hashing and QM31 addition and multiplication. Since the 64-bit instruction structure is not flexible enough to support this extended set of opcodes, Cairo extends the instruction size to 72 bits and uses the last 9 bits as the **opcode extension** value.
*Figure 2: New instruction format with opcode extension*
As of [this commit](https://github.com/starkware-libs/stwo-cairo/blob/b712c77887f8f8ce0d39a8a9741221c89846836e/stwo_cairo_prover/crates/adapter/src/decode.rs#L4), the following opcode extension values are supported: * `0`: Stone (original opcodes) * `1`: Blake * `2`: BlakeFinalize * `3`: QM31Operation Even if an instruction does not belong to any predefined set of opcodes, it is considered a valid opcode as long as it adheres to the state-transition function defined in Section 4.5 of the Cairo paper. In S-two Cairo, this is referred to as a *generic opcode*. # Introduction Source: https://docs.starknet.io/learn/S-two-book/cairo-air/index The following sections cover how Cairo is expressed as an AIR and proved using S-two. The explanation is based on [this commit](https://github.com/starkware-libs/stwo-cairo/tree/b712c77887f8f8ce0d39a8a9741221c89846836e) of the S-two-Cairo repository. # Main Components Source: https://docs.starknet.io/learn/S-two-book/cairo-air/main-components/index Now that we have a basic understanding of Cairo and the building blocks that are used to build the Cairo AIR, let's take a look at the main components of the Cairo AIR. For readers who are unfamiliar with the concepts of components and lookups, we suggest going over the [Components](../../air-development/components/index) section of the book. ## Fetch, Decode, Execute Cairo follows the common CPU architecture of fetching, decoding, and executing an instruction in a single CPU step. Below is a high-level diagram of what a single CPU step looks like in Cairo.
*Figure 1: A single CPU step in Cairo*
Since we need to prove the correctness of all CPU steps, the Cairo AIR writes the results of fetching, decoding, and executing an instruction at every CPU step into a trace and proves that the constraints over the trace are satisfied—i.e., consistent with the semantics of Cairo. Let's keep this in mind while we go over the main components of the Cairo AIR. ## 1. Memory Component The first component we need is a `Memory` component, which implements the non-deterministic read-only memory model of Cairo. In Cairo AIR, instead of mapping the memory address to a value directly, we first map the `address` to an `id` and then map the `id` to a `value`. This is done to classify the memory values into two groups: `Small` and `Big`, where `Small` values are 72-bit integers and `Big` values are 252-bit integers. As many memory values do not exceed the `Small` size, this allows us to save cost on unnecessary padding. As a result, the `Memory` component is actually 2 components: `MemoryAddressToId` and `MemoryIdToValue`. The constraints for the `MemoryAddressToId` and `MemoryIdToValue` components are as follows: 1. An `address` must appear once and only once in the `MemoryAddressToId` component. 2. An `id` must appear once and only once in the `MemoryIdToValue` component. 3. Each `(address, id, value)` tuple must be unique. The first constraint is implemented by using a preprocessed column that contains the sequence of numbers `[0, MAX_ADDRESS)` and using this as the address values (in the actual code, the memory address starts at 1, so we need to add 1 to the sequence column). The second constraint is guaranteed because the `address` value is always unique. A short explainer on how the `id` value is computed: The `id` value is a 31-bit integer that is incremented by 1 from 0 whenever there is a unique memory access. For example, if the addresses `[5, 1523, 142]` were accessed in that order, the ids for those addresses will be `(5, 0)`, `(1523, 1)`, and `(142, 2)`. Since an `id` value needs to include information as to whether the corresponding `value` is `Small` or `Big`, we use the MSB as a flag (0 for `Small` and 1 for `Big`). Thus, an `id` value that corresponds to a `Small` value can occupy the space `[0, 2^30)`, while an `id` value that corresponds to a `Big` value can occupy the space `[2^30, 2^31)`. In reality, the size of each table will be `[0, NUM_SMALL_VALUES_ACCESSED)` and `[2^30, 2^30 + NUM_BIG_VALUES_ACCESSED)`, where `NUM_SMALL_VALUES_ACCESSED` and `NUM_BIG_VALUES_ACCESSED` are values that are provided by the prover. To make sure that the `id` values are created correctly, we can use the preprocessed column as the `id` values. ## 2. VerifyInstruction Component The `VerifyInstruction` component is responsible for accessing the instruction from the `Memory` component and decomposing the retrieved value. As mentioned in the [Felt252 to M31](../basic-building-blocks/index#felt252-to-m31) section, a 252-bit integer is stored as 28 9-bit limbs, so we need to decompose the limbs and concatenate them to get the values we need. For example, as in [Figure 2](#figure-2) in order to get the 3 16-bit offset values, we need to decompose the first 6 limbs into `[9, [7, 2], [9], [5, 4], [9], [3, 6]]` and concatenate them as the following: `[[9, 7], [2, 9, 5], [4, 9, 3]]`. Then, the remaining 6-bit value and the next limb will correspond to the 15-bit flags, and the next (8th) limb will be the opcode extension value. The other 20 limbs should all be zeros. At the end, we will have decomposed the instruction value into 3 16-bit offsets, 2 chunks of flags, and a 9-bit opcode extension.
*Figure 2: Limb decomposition*
Note that the decomposition will be constrained by range checking that each integer is within its corresponding range. ## 3. Opcode Component Since every Cairo instruction can be mapped to a specific `Opcode`, we can check that a Cairo instruction is executed by checking that the corresponding `Opcode` component was executed correctly. You can think of the `Opcode` component as the main component that uses the `VerifyInstruction` and `Memory` components. We define a single `Opcode` component for each predefined opcode and a `GenericOpcode` component, which is used for all instructions that do not map to any of the predefined opcodes. The following is a list of constraints that the `Opcode` component needs to verify: 1. The offsets and flag values are correct using the `VerifyInstruction` component. 2. The instruction is correctly mapped to the current `Opcode` component using the flags. 3. The operand values `op0`, `op1`, and `dst` computed with the registers and the offsets are correct using the `Memory` component. 4. The operation for the current `Opcode` component is done correctly. 5. The state transition of the three registers (`pc`, `ap`, `fp`) is done correctly using the flags. Of these constraints, items 2 and 4 are self-contained—meaning they do not require any other components to be verified. We will explore how the remaining three are verified using lookups between the different components. ## Bringing It All Together The following figure shows how each of the main components are connected to each other using lookups. If we look at the right-hand side first, we can see the main components of the Cairo AIR. The boxes in each component correspond to lookups within that component, and boxes with the same fill color correspond to lookups of the same values. Some lookups are yielded (i.e., subtracted), while others are used (i.e., added). Yielded lookups have red edges; used lookups have blue edges. On the left-hand side, we can see lookups that are computed directly by the verifier from data provided by the prover as part of the proof. Each component has a **claimed sum** value that corresponds to the sum of all the lookups in the component. These claimed sums are provided by the prover as part of the proof, and the verifier adds them together along with the lookups on the left-hand side. If the total sum is zero, the verifier is convinced that the proof is valid.
*Figure 3: Lookups between the main components*
### Memory Lookups The memory lookups correspond to looking up the `(address, id)` and `(id, value)` pairs. In the `Memory` component, each of the lookups is multiplied by a witness value `mult`, which indicates the number of times each memory address was accessed. Since the memory accesses are added to the total sum and the same amount is subtracted from the total sum in the `Memory` component, the total sum for memory lookups should equal zero. Note that the verifier also adds lookups for the Cairo program, which is necessary to ensure that the correct program is actually stored in memory and is properly executed. ### Instruction Lookups Once the `VerifyInstruction` component retrieves the instruction value using the `(pc_addr, pc_id)` and `(pc_id, pc_val)` lookups, it subtracts the lookup of the tuple `(pc, dst_off, op0_off, op1_off, flags1, flags2, opcode_extension)`, which are the decomposed values of the instruction. This lookup also has a `mult` witness because the `VerifyInstruction` component has a single row for each unique `pc` value (i.e., the Cairo instruction stored at the `pc` address). Thus, the same `pc` value can be invoked multiple times throughout the program, and the `mult` value represents the number of times the same `pc` value is invoked. Since the same tuple lookup is added to the total sum whenever the `Opcode` component uses the same instruction, the total sum for instruction lookups should equal zero. ### Register Lookups After computing over the operands, a Cairo instruction updates the register values based on the values in the flags. In an `Opcode` component, this update logic is verified using the columns `pc`, `ap`, `fp`, `new_pc`, `new_ap`, `new_fp`, and by constraining the values with the flags. In addition to checking that each state transition is done correctly, we also need to make sure that the initial register values (i.e., before running the program) and the final register values (i.e., after running the program) satisfy the Cairo semantics. For example, the final `pc` must point to an instruction that runs the `JUMPREL 0` opcode, which is an infinite loop (you can check the rest of the semantics enforced [here](https://github.com/starkware-libs/stwo-cairo/blob/95019d029fdd164091f9d17a12cb9288215fb9c3/stwo_cairo_verifier/crates/cairo_air/src/lib.cairo#L264)). Once we have verified that the initial and final register values are correct and each state transition is done correctly, we do a final check that the register values are all connected—that is, the register values used by the second instruction are the same as the new register values of the first instruction, and so on. We check this by adding the lookup of the `(pc, ap, fp)` tuple and subtracting the lookup of the `(new_pc, new_ap, new_fp)` tuple for each `Opcode` row. When we add up all the lookups, the total sum for register lookups should equal `(init_pc, init_ap, init_fp) - (final_pc, final_ap, final_fp)`. Thus, the verifier can compute the lookups of the initial and final register values, subtract the first, add the second, and check that the total sum is zero. # Components Source: https://docs.starknet.io/learn/S-two-book/how-it-works/air/components This section examines how the components are implemented and covers some important functions defined on them, without diving into AIR-specific details since they are already covered in the [earlier sections](/learn/S-two-book/air-development/index). The `Components` struct is a collection of components, implemented as follows: ```rust theme={null} pub struct Components<'a> { pub components: Vec<&'a dyn Component>, pub n_preprocessed_columns: usize, } ``` Here, `components` is a collection of objects that implement the `Component` trait, and `n_preprocessed_columns` is the total number of preprocessed columns used across all components (refer to [Preprocessed Trace](/learn/S-two-book/air-development/preprocessed-trace/index)). The `Component` trait represents a trace table along with a set of constraints. It implements the following functions: ```rust theme={null} pub trait Component { fn n_constraints(&self) -> usize; fn max_constraint_log_degree_bound(&self) -> u32; /// Returns the degree bounds of each trace column. The returned TreeVec should be of size /// `n_interaction_phases`. fn trace_log_degree_bounds(&self) -> TreeVec>; /// Returns the mask points for each trace column. The returned TreeVec should be of size /// `n_interaction_phases`. fn mask_points( &self, point: CirclePoint, ) -> TreeVec>>>; fn preproccessed_column_indices(&self) -> ColumnVec; /// Evaluates the constraint quotients combination of the component at a point. fn evaluate_constraint_quotients_at_point( &self, point: CirclePoint, mask: &TreeVec>>, evaluation_accumulator: &mut PointEvaluationAccumulator, ); } ``` This section will not examine each of these functions in detail but will explain them wherever they are used to implement various functions for the `Components` struct. The following are some important functions implemented for the `Components` struct. ## Composition Polynomial Degree Bound The `composition_log_degree_bound` function determines the log of the degree of the composition polynomial. ```rust theme={null} pub fn composition_log_degree_bound(&self) -> u32 { self.components .iter() .map(|component| component.max_constraint_log_degree_bound()) .max() .unwrap() } ``` For each component, this calls the `max_constraint_log_degree_bound()` function, which returns the log of the highest polynomial degree among all constraints in that component. It then takes the maximum value across all components. For the [example AIR containing two components](/learn/S-two-book/how-it-works/air/overview#air-to-composition-polynomial), this function returns $\max({\log{(\deg{(p_0)})}, \log{(\deg{(p_1)})}})$. ## Mask Points The `mask_points` function determines all evaluation points needed to verify constraints at a given point ```rust theme={null} pub fn mask_points( &self, point: CirclePoint, ) -> TreeVec>>> { let mut mask_points = TreeVec::concat_cols( self.components .iter() .map(|component| component.mask_points(point)), ); let preprocessed_mask_points = &mut mask_points[PREPROCESSED_TRACE_IDX]; *preprocessed_mask_points = vec![vec![]; self.n_preprocessed_columns]; for component in &self.components { for idx in component.preproccessed_column_indices() { preprocessed_mask_points[idx] = vec![point]; } } mask_points } ``` From the perspective of the prover (and verifier), when they need to open the composition polynomial at a specific point sent by the verifier (for example, an out-of-domain point), they require additional polynomial evaluations to verify the constraints. The composition polynomial combines many component-level constraint quotients. Each constraint involves relationships between multiple cells in the execution trace, potentially at different rows/offsets. The function `mask_points` performs the following: * Given a single point as input, it determines all related points where polynomial evaluations are needed to verify constraints. * Handles offsets for each component, since different components may have different constraint structures requiring different offset patterns. * Ensures preprocessed columns (shared lookup tables, etc.) are properly included. ## Evaluate Composition Polynomial The `eval_composition_polynomial_at_point` function evaluates the combined constraint polynomial. ```rust theme={null} pub fn eval_composition_polynomial_at_point( &self, point: CirclePoint, mask_values: &TreeVec>>, random_coeff: SecureField, ) -> SecureField { let mut evaluation_accumulator = PointEvaluationAccumulator::new(random_coeff); for component in &self.components { component.evaluate_constraint_quotients_at_point( point, mask_values, &mut evaluation_accumulator, ) } evaluation_accumulator.finalize() } ``` The inputs to this function are as follows: * `&self`: The `Components` on which the function is called. * `point`: The circle point at which the composition polynomial is to be evaluated. * `mask_values`: The evaluations of the polynomials at the mask points that were previously determined by the `mask_points` function. These provide the constraint polynomial values needed to compute the composition polynomial at the input `point`. * `random_coeff`: An element from the `SecureField` (i.e. $\mathsf{QM31}$). In the example, this is represented as $\gamma$, which is used to compose all constraints into a single composition polynomial. The function body operates as follows. First, an `evaluation_accumulator` is instantiated. Then, for each component, the evaluation of the component-level quotient is added to the `evaluation_accumulator`. For the [example AIR containing two components](/learn/S-two-book/how-it-works/air/overview#air-to-composition-polynomial), this adds the evaluations of component-level quotients $q_0$ and $q_1$ at the input `point` to the `evaluation_accumulator`. Finally, the `finalize()` function is called on the `evaluation_accumulator`, which outputs the random linear combination evaluated at the input `point`. $$ q = q_0 + \gamma^{c_0} \cdot q_1 $$ # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/air/index > This section explores the conversion of an AIR to a composition polynomial, covering both the mathematical framework and implementation details. We examine how multiple components with their respective constraints are combined into a single polynomial. This section is organized as follows: * **[Technical Overview](/learn/S-two-book/how-it-works/air/overview)**: Provides a high-level explanation of how multiple components are combined into a composition polynomial. * **[Components](/learn/S-two-book/how-it-works/air/components)**: Examines the implementation of the `Components` struct and key functions like `mask_points` and `eval_composition_polynomial_at_point`. * **[Prover Components](/learn/S-two-book/how-it-works/air/prover_components)**: Details the `ComponentProvers` struct and its specialized functions for computing composition polynomials during the proving process, including the main `compute_composition_polynomial` function. # Technical Overview Source: https://docs.starknet.io/learn/S-two-book/how-it-works/air/overview As shown in an earlier section (refer to [Components](/learn/S-two-book/air-development/components/index)), S-two represents the trace using multiple tables where each table is referred to as a *component*. An AIR in S-two is a collection of multiple components. Components can interact with one another, and the consistency of these interactions is verified using [*logUp*](https://eprint.iacr.org/2022/1530.pdf). For example, in the [hash function example](/learn/S-two-book/air-development/components/index#hash-function-example), the scheduling component and computing component interact with each other, where both the components lookup the input and output pair. The consistency of this interaction is then verified by adding logUp constraints to each component. Each component consists of a trace table of a specific height along with a set of constraints. These constraints include computation constraints as well as lookup constraints (refer to [Lookups](/learn/S-two-book/how-it-works/lookups)). This section provides an overview of how these components are converted into a composition polynomial. This composition polynomial is then used by a polynomial commitment scheme to commit and generate evaluation proofs. ## AIR to Composition Polynomial This section explains how a composition polynomial is computed using an example. Consider an AIR composed of two components with trace tables: $\mathscr{T}_0$ and $\mathscr{T}_1$. The component table $\mathscr{T}_0$ is defined over the trace domain $D_{n_0}$, which is a [canonic coset](/learn/S-two-book/how-it-works/circle-group#canonic-coset) of size $N_0 = 2^{n_0}$. Similarly, component table $\mathscr{T}_1$ is defined over the trace domain $D_{n_1}$ of size $N_1 = 2^{n_1}$. The prover interpolates the trace polynomials for each component and then evaluates the trace polynomials over an evaluation domain that is a blowup factor $B = 2^\beta$ times larger than the trace domain. Thus, the evaluation domain for component table $\mathscr{T}_0$ is $D_{n_0 + \beta}$ of size $2^{n_0 + \beta}$. Similarly, the evaluation domain for component table $\mathscr{T}_1$ is $D_{n_1 + \beta}$ of size $2^{n_1 + \beta}$. Both interpolation and evaluation use [circle FFT](/learn/S-two-book/how-it-works/circle-fft/index). The prover then commits to the evaluations of trace polynomials from all components using a single [Merkle tree](/learn/S-two-book/how-it-works/vcs/index). Both component tables define computation constraints and lookup constraints for their specific component. The component constraints are proven table-wise, each with its separate domain quotient, which are then combined into a single cross-domain composition polynomial. Suppose the component tables $\mathscr{T}_0$ and $\mathscr{T}_1$ have a total of $c_0$ and $c_1$ constraints, respectively. The verifier sends $\gamma \in \mathsf{QM31}$ to the prover. We use the same $\gamma$ to combine all constraints across different component tables. First, $\gamma$ is used to compute the random linear combination of all constraints on component $\mathscr{T}_0$ to obtain the component-level composition polynomial $p_0$. Then, the prover uses the same $\gamma$ to compute the component-level composition polynomial $p_1$ for component $\mathscr{T}_1$. The prover then computes the quotient for component table $\mathscr{T}_0$ as $q_0 = p_0 / v_{n_0}$, where $v_{n_0}$ is the vanishing polynomial for trace domain $D_{n_0}$. Similarly, the prover computes the quotient for component table $\mathscr{T}_1$ as $q_1 = p_1 / v_{n_1}$. Finally, the prover computes the cross-domain composition polynomial as $$ q = q_0 + \gamma^{c_0} \cdot q_1 $$ where $c_0$ is the total number of constraints of the component table $\mathscr{T}_0$. The next section examines how these concepts are implemented. # Prover Components Source: https://docs.starknet.io/learn/S-two-book/how-it-works/air/prover_components The `ComponentProvers` struct is similar to the `Components` struct but implements additional functions required by the prover, such as computing the composition polynomial. It is a collection of prover components as follows: ```rust theme={null} pub struct ComponentProvers<'a, B: Backend> { pub components: Vec<&'a dyn ComponentProver>, pub n_preprocessed_columns: usize, } ``` Here, `components` is a collection of objects that implement the `ComponentProver` trait. The `ComponentProver` trait is a wrapper around the `Component` trait with an additional function shown as follows: ```rust theme={null} pub trait ComponentProver: Component { /// Evaluates the constraint quotients of the component on the evaluation domain. /// Accumulates quotients in `evaluation_accumulator`. fn evaluate_constraint_quotients_on_domain( &self, trace: &Trace<'_, B>, evaluation_accumulator: &mut DomainEvaluationAccumulator, ); } ``` We can convert the `ComponentProvers` into a `Components` struct as follows: ```rust theme={null} pub fn components(&self) -> Components<'_> { Components { components: self .components .iter() .map(|c| *c as &dyn Component) .collect_vec(), n_preprocessed_columns: self.n_preprocessed_columns, } } ``` The main function defined on the `ComponentProvers` struct to compute the composition polynomial is implemented as follows: ```rust theme={null} pub fn compute_composition_polynomial( &self, random_coeff: SecureField, trace: &Trace<'_, B>, ) -> SecureCirclePoly { let total_constraints: usize = self.components.iter().map(|c| c.n_constraints()).sum(); let mut accumulator = DomainEvaluationAccumulator::new( random_coeff, self.components().composition_log_degree_bound(), total_constraints, ); for component in &self.components { component.evaluate_constraint_quotients_on_domain(trace, &mut accumulator) } accumulator.finalize() } ``` Let us examine the above function for our [example AIR containing two components](/learn/S-two-book/how-it-works/air/overview#air-to-composition-polynomial). It takes the following three inputs: * `&self`: This is the `ComponentProvers` on which the function is called. * `random_coeff`: This is an element from the `SecureField` (i.e. $\mathsf{QM31}$). In our example, this is represented as $\gamma$. * `trace`: The `Trace` struct which contains all the polynomials that make up the entire trace including all the components. For efficiency, it stores each polynomial in both coefficients and evaluations form. ```rust theme={null} pub struct Trace<'a, B: Backend> { /// Polynomials for each column. pub polys: TreeVec>>, /// Evaluations for each column (evaluated on their commitment domains). pub evals: TreeVec>>, } ``` Now let us examine the body of the function. First, we compute `total_constraints` and initialize an `accumulator`. The `total_constraints` determine the number of powers of $\gamma$ (`random_coeff`) required for the random linear combination. For each component, we call `evaluate_constraint_quotients_on_domain`, which computes and accumulates the evaluations of that component's quotients on their respective evaluation domains within the accumulator. For the $0$th component, we add the evaluations of the quotient $q_0$ over its evaluation domain $D_{n_0 + \beta}$ to the `accumulator`. Similarly, for the $1$st component, we add the evaluations of the quotient $q_1$ over its evaluation domain $D_{n_1 + \beta}$ to the `accumulator`. After adding all component quotient evaluations to the accumulator, we call the `finalize()` function, which: 1. Combines the accumulated evaluations at different domain sizes to compute the evaluations of the quotient composition polynomial $q$ over the domain $D_{n + \beta}$ where $n = \max{(n_1, n_2)}$. 2. Interpolates $q$ over $D_{n + \beta}$ using [circle FFT](/learn/S-two-book/how-it-works/circle-fft/index) to convert it into coefficient representation. Note that the output is a [`SecureCirclePoly`](/learn/S-two-book/how-it-works/circle-polynomials/secure-evals-and-poly#secure-circle-polynomials) since the evaluations of $q$ are in the secure field $\mathsf{QM31}$ (as $\gamma$ is randomly sampled from $\mathsf{QM31}$). # Algorithm Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fft/algorithm In this section, we will go through the Circle FFT algorithm specifically, to interpolate a bivariate polynomial given the evaluations over a circle domain. We will also go over a concrete example which will help us understand the algorithm. Circle FFT follows a divide-and-conquer strategy, as in the classical Cooley–Tukey FFT. We recursively reduce the task of interpolating a polynomial over some domain to interpolating a lower degree polynomial over a smaller domain. Thus at each recursive layer, we have "smaller" polynomials and their evaluations over "smaller" domains. Let us first go over this sequence of domains for the Circle FFT algorithm. ## Sequence of Domains for Circle FFT In Circle FFT, we use a 2-to-1 map to halve the domain size at each recursive layer. The domain used here is the [circle domain](/learn/S-two-book/how-it-works/circle-group#circle-domain) $D_n$ of size $|D_n| = 2^n$. $D_n = q + \langle g_{n-1} \rangle \cup -q + \langle g_{n-1} \rangle$ This section describes two specific 2-to-1 maps that are central to the Circle FFT construction: 1. **Projection map $\pi_x$**: The projection map $\pi_x$ projects the points $(x,y)$ and $(x,-y)$ to the same $x$-coordinate i.e. $\pi_x: D_n \rightarrow S_n, \quad \pi_x((x, y)) = x$ where $S_n$ is the set containing all the $x$-coordinates from $D_n$. This can be interpreted as saying that two points are considered equivalent if they differ only by sign. Since $\pi_x$ maps two points from $D_n$ to a single element in $S_n$, the size of $S_n$ will be half the size of $D_n$. 2. **Squaring map $\pi$**: The squaring map $\pi$ is a 2-to-1 map defined by: $\pi: S_n \rightarrow S_{n-1}, \quad \pi(x) = 2x^2 - 1$ This is obtained using the doubling map and the equality $y^2 = 1 - x^2$ to compute the $x$-coordinate: $\pi(x, y) = (x, y) + (x, y) = (x^2 - y^2, 2xy) = (2x^2-1, 2xy)$ In Circle FFT, we use both the projection map $\pi_x$ and the squaring map $\pi$ to construct the sequence of domains. The sequence of domains for Circle FFT is shown as follows: $D_n \xrightarrow{\pi_x} S_n \xrightarrow{\pi} S_{n-1} \xrightarrow{\pi} \cdots \xrightarrow{\pi} S_1$ Now that we know the sequence of domains, let us dive into the Circle FFT algorithm. ## Circle FFT The Circle FFT interpolates a bivariate polynomial $p(x,y)$ from the [polynomial space](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly#polynomials-over-the-circle) $L_N(F)$, given the evaluations over a circle domain $D_n$ of size $N=2^n$. The algorithm is a three step process described as follows. #### Step 1: Decompose $p(x, y)$ into sub-polynomials In the first step, we decompose the bivariate polynomial $p(x, y)$ over $D_n$ using the projection map $\pi_x$ into two univariate polynomials $p_0(x)$ and $p_1(x)$ as follows: $p(x, y) = p_0(\pi_x(x, y)) + y \cdot p_1(\pi_x(x, y)) = p_0(x) + y \cdot p_1(x)$ Now to continue with the FFT algorithm, we want to compute the evaluations of $p_0(x)$ and $p_1(x)$ over the smaller domain $S_n = \pi_x(D_n)$, given the evaluations of $p(x, y)$ over $D_n$. This is done using the following equations: $$ p_0(x) = \frac{p(x, y) + p(x, -y)}{2}, \quad p_1(x) = \frac{p(x, y) - p(x, -y)}{2 \cdot y} $$ Substituting all evaluations of $p(x, y)$ over $D_n$ in the above equations, gives the evaluations of $p_0(x)$ and $p_1(x)$ over the domain $S_n$. To compute the evaluations of $p_1(x)$ over the domain $S_n$, we subtract the evaluations i.e. compute $p(x, y) - p(x, -y)$ and then divide by $2 \cdot y$. These values $y$ are the $y$-coordinates of the points in the circle domain $D_n$. They are also referred to as *circle twiddles* and they only depend on the circle domain $D_n$. Therefore they can be precomputed before the start of the FFT algorithm. We will look into these in detail in the next section. > **Example**: To make all the calculations easier we will work on a concrete example over the Mersenne prime $p = 2^5 - 1 = 31$. Thus all calculations are over $\mathbb{F}_{31}$, i.e, modulo $31$. We are given the evaluations $\vec{v} = [13, 16, 9, 30, 29, 27, 13, 21]$ of $p(x, y)$ over the circle domain > >
> > $D_3 = [(7, 18), (13, 7), (24, 13), (18, 24), (7, 13), (13, 24), (24, 18), (18, 7)]$ > >
> > and we want to compute the coefficients of $p(x, y)$ i.e. interpolate $p(x, y)$ given its evaluations $\vec{v}$ over $D_3$. > >
> >
> > **Step 1**: First, decompose the polynomial $p(x, y)$ using the map $\pi_x(x, y) = x$: > >
> > $p(x, y) = p_0(\pi_x(x, y)) + y \cdot p_1(\pi_x(x, y)) = p_0(x) + y \cdot p_1(x)$ > >
> > Given the evaluations $\vec{v}$ of $p(x, y)$ over the circle domain $D_3$ we aim to compute the evaluations of $p_0$ and $p_1$ over the smaller domain $S_3$: > >
> > $S_3 = \pi_x(D_3) = [7, 13, 24, 18]$ > >
> > For $(x, y)=(7, 18)$ and $(x, -y) = (7, 13)$ in $D_3$, substituting them into the following equations give: > >
> > $p_0(x) = \frac{p(x, y) + p(x, -y)}{2} = \frac{p(7, 18) + p(7, 13)}{2} = \frac{13 + 29}{2} = 21$ > >
> > $p_1(x) = \frac{p(x, y) - p(x, -y)}{2 \cdot y} = \frac{p(7, 18) - p(7, 13)}{2 \cdot 18} = \frac{13 - 29}{2 \cdot 18} = 3$ > >
> > Repeating this process for other pairs $(x, y)$ and $(x, -y)$ in $D_3$, we obtain: > >
> > $\vec{v_0} = [21, 6, 11, 10], \quad \vec{v_1} = [3, 28, 7, 6]$ #### Step 2: Recursively apply FFT to sub-polynomials Given the evaluations of $p_0(x)$ and $p_1(x)$ over $S_n$, we now compute their coefficients. This step mirrors the recursive structure of the Cooley–Tukey FFT. Each polynomial is recursively split using the squaring map $\pi(x) = 2x^2 - 1$, and the process continues over successively smaller domains. We begin by decomposing $p_0(x)$ using the squaring map $\pi$ as follows: $p_0(x) = p_{00}(\pi(x)) + x \cdot p_{01}(\pi(x))$ We compute the evaluations of $p_{00}(x)$ and $p_{01}(x)$ over $S_{n-1} = \pi(S_n)$ using the following equations. $$ p_{00}(\pi(x)) = \frac{p_0(x) + p_0(-x)}{2}, \quad p_{01}(\pi(x)) = \frac{p_0(x) - p_0(-x)}{2 \cdot x} $$ This recursive process continues until we reach the base case: all evaluations of the polynomial over the domain are same. At that level, the coefficient is simply the evaluation of the constant polynomial. Finally, we reconstruct the coefficients of $p_0(x)$ by working backward through the recursive calls, using the decomposition equation at each level. The same process applies to compute the coefficients of $p_1(x)$. Similar to *circle twiddles*, to compute the evaluations of $p_{01}(x)$ over $S_{n-1}$ we divide by the values $x$ which are the values from the domain $S_n$. These values $x$ are referred to as *line twiddles*. For the next recursive layer, we divide by values from $S_{n-1}$ i.e. $\pi(x)$ and for the next recursive layer we divide by $\pi^2(x)$ and so on. Thus the line twiddles is a vector of values $x, \pi(x), \pi^2(x), \ldots$ and so on. These only depend on the initial domain $D_n$ and thus can be precomputed before the start of the algorithm. > **Step 2**: Given the evaluations of $p_0$ and $p_1$ over $S_3 = [7, 13, 24, 18]$: > >
> > $\vec{v_0} = [21, 6, 11, 10], \quad \vec{v_1} = [3, 28, 7, 6]$ > >
> > we recursively apply the FFT algorithm to compute the coefficients of $p_0$ and $p_1$. > >
> > At each layer, we decompose the polynomial using the polynomial map $\pi(x)$. For example, the decomposition of $p_0(x)$ is: > >
> > $p_0(x) = p_{00}(\pi(x)) + x \cdot p_{01}(\pi(x))$ > >
> > Omitting the intermediate steps of the recursive calls, we eventually obtain the coefficients of $p_0(x)$ and $p_1(x)$ as follows: > >
> > $p_0(x) = 12 + 26 \cdot x + \pi(x) + 28 \cdot x \cdot \pi(x)$ > >
> > $p_1(x) = 11 + 26 \cdot x + 14 \cdot \pi(x) + 20 \cdot x \cdot \pi(x)$ #### Step 3: Combine the coefficients Finally, we combine the coefficients of $p_0(x)$ and $p_1(x)$ to compute the coefficients of the original bivariate polynomial $p(x, y)$, using the decomposition: $p(x, y) = p_0(x) + y \cdot p_1(x)$ > **Step 3**: Given the coefficients of $p_0(x)$ and $p_1(x)$: > >
> > $p_0(x) = 12 + 26 \cdot x + \pi(x) + 28 \cdot x \cdot \pi(x)$ > >
> > $p_1(x) = 11 + 26 \cdot x + 14 \cdot \pi(x) + 20 \cdot x \cdot \pi(x)$ > >
> > we reconstruct the original polynomial $p(x, y)$ using the decomposition: > >
> > $p(x, y) = p_0(x) + y \cdot p_1(x)$ > >
> > Substituting the expressions for $p_0$ and $p_1$, we get: > >
> > $p(x, y) = 12 + 26 \cdot x + \pi(x) + 28 \cdot x \cdot \pi(x) + y \cdot (11 + 26 \cdot x + 14 \cdot \pi(x) + 20 \cdot x \cdot \pi(x))$ > >
> > $p(x, y) = 12 + 26 \cdot x + \pi(x) + 28 \cdot x \cdot \pi(x) + \quad \quad \quad \quad \quad \quad$ > >
> > $\quad \quad \quad \quad \quad \quad 11 \cdot y + 26 \cdot x \cdot y + 14 \cdot y \cdot \pi(x) + 20 \cdot x \cdot y \cdot \pi(x)$ This completes an overview of the interpolation algorithm using Circle FFT. In the next section, we will see how the twiddle values are computed and stored for Circle FFT. # Basis for Circle FFT Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fft/basis The circle FFT algorithm outputs coefficients $c_j$ with respect to some basis $b_j^{(n)}(x, y)$ such that: $p(x, y) = \sum_{j = 0}^{2^n - 1} c_j \cdot b_j^{(n)}(x, y)$ In the concrete example (covered in the [Algorithm section](/learn/S-two-book/how-it-works/circle-fft/algorithm)) of Circle FFT over twin-coset $D_3$, we saw that the algorithm computed the coefficient with respect to the following basis: $b^{(3)}_j(x, y) = [1, x, \pi(x), x \cdot \pi(x), y, y \cdot x, y \cdot \pi(x), y \cdot x \cdot \pi(x)]$ Using induction on $n$, we can show that the Circle FFT algorithm outputs coefficients with respect to the following basis \[[Theorem 2, Circle STARKs](https://eprint.iacr.org/2024/278.pdf)]: $b^{(n)}_j(x, y) := y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdot \pi^2(x)^{j_3} \cdots \pi^{n-2}(x)^{j_{n-1}}$ where $0 \leq j \leq 2^n - 1$ and $(j_0, \ldots, j_{n-1}) \in \{0, 1\}^n$ is the binary representation of $j$, i.e., $j = j_0 + j_1 \cdot 2 + \cdots + j_{n-1} \cdot 2^{n-1}$ # Dimension Gap We will now explore the space of polynomials interpolated by the Circle FFT algorithm. Let the space spanned by the basis polynomials in $b^{(n)}(x, y)$ be $L'_N(F)$. The basis $b^{(n)}(x, y)$ has a total of $N=2^n$ elements and thus the dimension of the space $L'_N(F)$ is $N$. However, the space of bivariate polynomials over the circle curve is $L_N(F)$, which has dimension $N+1$ (as covered in the [section on polynomials over the circle](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly#polynomials-over-the-circle)). We can identify the missing highest total degree element in $L'_N(F)$ by examining the basis. The highest total degree element in basis $b^{(n)}(x, y)$ is: $y \cdot x \cdot \pi(x) \cdot \pi^2(x) \cdot \pi^3(x) \cdots \pi^{n-2}(x)$ Using $deg(\pi^j(x) = 2^j)$, the highest degree of $X$ in the above term is: $1 + 2 + 2^2 + 2^3 + \cdots + 2^{n-2} = 2^{n-1} - 1 = N/2 - 1$ Since the highest degree of $X$ in $L'_N(F)$ is $N/2 - 1$, we can represent the space $L'_N(F)$ as follows: $L'_N(F) = F[x]^{ \leq N/2 - 1} + Y \cdot F[x]^{ \leq N/2 - 1}$ where $F[x]^{ \leq N/2 - 1}$ represents polynomials of degree at most $N/2 - 1$ with coefficients in $F$. Similarly, we can represent the space $L_N(F)$ as: $L_N(F) = F[x]^{ \leq N/2} + Y \cdot F[x]^{ \leq N/2 - 1}$ Thus the space $L'_N(F)$ does not include the monomial $X^{N/2}$, which lies in the space $L_N(F)$. Therefore, $L_N(F) = L'_N(F) + \langle X^{N/2} \rangle$ Since the space spanned by $X^{N/2}$ is same as the space spanned by the vanishing polynomial $v_n(x)$ which has degree $deg(v_n) = 2^{n-1} =N/2$, we can also write: $L_N(F) = L'_N(F) + \langle v_n \rangle$ A consequence of this dimension gap is that we cannot interpolate some polynomials over the circle i.e. those with $X^{N/2}$. We will address how this dimension gap is handled within the FRI protocol in the upcoming sections. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fft/index > This section introduces the Circle FFT algorithm, used to interpolate bivariate polynomials over the circle domain using a divide-and-conquer approach. This section is organized as follows: * **[Algorithm](/learn/S-two-book/how-it-works/circle-fft/algorithm)**: Overview of the Circle FFT algorithm with concrete examples showing the three-step interpolation process. * **[Twiddles](/learn/S-two-book/how-it-works/circle-fft/twiddles)**: Precomputation and storage of twiddle values required for efficient FFT operations. * **[Interpolate](/learn/S-two-book/how-it-works/circle-fft/interpolation)**: Detailed implementation walkthrough of the interpolation function with code breakdown. * **[Basis and Dimension Gap](/learn/S-two-book/how-it-works/circle-fft/basis)**: FFT basis for Circle FFT and analysis of the dimension gap in circle polynomial spaces. # Interpolation Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fft/interpolation This section covers the implementation of the `interpolate` function step by step. The function takes as input a `CircleEvaluation` and a `TwiddleTree` and outputs a `CirclePoly`. The circle FFT algorithm changes the order of the input. Since the `CircleEvaluation` is in `BitReversedOrder`, the output coefficients of the `CirclePoly` will be in `NaturalOrder`. The complete implementation of the `interpolate` function is shown below: ```rust theme={null} fn interpolate( eval: CircleEvaluation, twiddles: &TwiddleTree, ) -> CirclePoly { assert!(eval.domain.half_coset.is_doubling_of(twiddles.root_coset)); let mut values = eval.values; if eval.domain.log_size() == 1 { let y = eval.domain.half_coset.initial.y; let n = BaseField::from(2); let yn_inv = (y * n).inverse(); let y_inv = yn_inv * n; let n_inv = yn_inv * y; let (mut v0, mut v1) = (values[0], values[1]); ibutterfly(&mut v0, &mut v1, y_inv); return CirclePoly::new(vec![v0 * n_inv, v1 * n_inv]); } if eval.domain.log_size() == 2 { let CirclePoint { x, y } = eval.domain.half_coset.initial; let n = BaseField::from(4); let xyn_inv = (x * y * n).inverse(); let x_inv = xyn_inv * y * n; let y_inv = xyn_inv * x * n; let n_inv = xyn_inv * x * y; let (mut v0, mut v1, mut v2, mut v3) = (values[0], values[1], values[2], values[3]); ibutterfly(&mut v0, &mut v1, y_inv); ibutterfly(&mut v2, &mut v3, -y_inv); ibutterfly(&mut v0, &mut v2, x_inv); ibutterfly(&mut v1, &mut v3, x_inv); return CirclePoly::new(vec![v0 * n_inv, v1 * n_inv, v2 * n_inv, v3 * n_inv]); } let line_twiddles = domain_line_twiddles_from_tree(eval.domain, &twiddles.itwiddles); let circle_twiddles = circle_twiddles_from_line_twiddles(line_twiddles[0]); for (h, t) in circle_twiddles.enumerate() { fft_layer_loop(&mut values, 0, h, t, ibutterfly); } for (layer, layer_twiddles) in line_twiddles.into_iter().enumerate() { for (h, &t) in layer_twiddles.iter().enumerate() { fft_layer_loop(&mut values, layer + 1, h, t, ibutterfly); } } // Divide all values by 2^log_size. let inv = BaseField::from_u32_unchecked(eval.domain.size() as u32).inverse(); for val in &mut values { *val *= inv; } CirclePoly::new(values) } ``` The function includes hardcoded implementations for circle domains of small sizes for efficiency. Lets breakdown the function step by step. 1. **Compute Twiddles:** Given the `TwiddleTree`, the function computes the line twiddles and circle twiddles. * The `circle_twiddles` are twiddles required at the first layer where all points are projected to the $x$-axis. These correspond to the $y$-coordinate points in the half coset. * The `line_twiddles` are twiddles required to compute FFT at the subsequent recursive layers where the squaring map $\pi$ is used as the 2-to-1 map. > For the example of the [half coset](/learn/S-two-book/how-it-works/circle-fft/twiddles#fig-half-coset), there are three FFT layers: one layer with the projection map $\pi_x$ and two recursive layers with the squaring map $\pi$. > > The `line_twiddles` for the two recursive layers are computed using the inverse twiddles $[a^{-1}, b^{-1}, \pi(a)^{-1}, 1]$. For the first recursive layer, the twiddles are the inverse of the $x$-coordinates: $[a^{-1}, b^{-1}]$. For the second recursive layer, the twiddles are the inverse of the square of the $x$-coordinates: $[\pi(a)^{-1}]$. Thus for this example, the `line_twiddles` are $[[a^{-1}, b^{-1}], [\pi(a)^{-1}]]$. The `circle_twiddles` are computed using the first recursive layer `line_twiddles`. They are equal to the inverse of the $y$-coordinates of the half coset. > For this example, they take the values $[b^{-1}, -b^{-1}, -a^{-1}, a^{-1}]$. 2. **Apply FFT Layers:** With the twiddles for each layer computed, the FFT algorithm is applied first using the projection map $\pi_x$ and `circle_twiddles`, then using the squaring map $\pi$ and the `line_twiddles`. This process uses two key functions: * **`fft_layer_loop`**: Applies butterfly operations across a specific layer of the FFT. The key inputs are the values array, layer parameters, twiddle factor, and the butterfly function to apply. It iterates through pairs of elements in the values array at the appropriate indices and applies the butterfly operation with the twiddle factor. * **`ibutterfly`**: Performs the inverse butterfly operation on a pair of elements from the values array. This is the fundamental operation that transforms the values during interpolation. 3. **Scale and Output:** Finally, the `values` are scaled by dividing by the domain size, and the `CirclePoly` is output. This completes the description of the `interpolate` function. The `evaluate` function follows a similar approach, applying the same components in reverse order to convert from coefficient representation back to point-value representation. In the next section, we will explore the FFT basis underlying the Circle FFT algorithm. # Twiddles Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fft/twiddles This section provides a detailed look at how twiddles are computed and stored in S-two. As discussed earlier, S-two has two main backend implementations: `CpuBackend` and `SimdBackend`. Each backend implements the `PolyOps` trait, which provides core polynomial operations such as interpolation, evaluation and twiddle precomputation. Here and in the following sections on Circle FFT, we will explore how these functions are implemented for the `CpuBackend`. ## Twiddle Tree The twiddles are stored using the `TwiddleTree` struct implemented as follows: ```rust theme={null} pub struct TwiddleTree { pub root_coset: Coset, // TODO(shahars): Represent a slice, and grabbing, in a generic way pub twiddles: B::Twiddles, pub itwiddles: B::Twiddles, } ``` For `CpuBackend`, `B::Twiddles` is a vector of `BaseField` elements. Here, `root_coset` is the half coset $q + \langle g_{n-1} \rangle$ of our circle domain $D_n$. As we have seen in the earlier section, for interpolation we divide by twiddles or multiply by inverse twiddles. In the evaluation algorithm, we multiply by twiddles. Thus we store both `twiddles` and their inverses `itwiddles`. Now we will understand how the twiddle tree is computed using an example. Consider the following half coset $q + \langle g_2 \rangle$ of a circle domain $D_3$.
*Figure 1: Half Coset of size 4*
The `TwiddleTree` is constructed by the `precompute_twiddles` function, which takes the half coset as input and produces the twiddles needed to perform FFT over the corresponding circle domain. It is implemented as follows: ```rust theme={null} fn precompute_twiddles(coset: Coset) -> TwiddleTree { const CHUNK_LOG_SIZE: usize = 12; const CHUNK_SIZE: usize = 1 << CHUNK_LOG_SIZE; let root_coset = coset; let twiddles = slow_precompute_twiddles(coset); // Inverse twiddles. // Fallback to the non-chunked version if the domain is not big enough. if CHUNK_SIZE > root_coset.size() { let itwiddles = twiddles.iter().map(|&t| t.inverse()).collect(); return TwiddleTree { root_coset, twiddles, itwiddles, }; } let mut itwiddles = vec![BaseField::zero(); twiddles.len()]; twiddles .array_chunks::() .zip(itwiddles.array_chunks_mut::()) .for_each(|(src, dst)| { batch_inverse_in_place(src, dst); }); TwiddleTree { root_coset, twiddles, itwiddles, } } ``` As shown above, it first computes `twiddles` using the function `slow_precompute_twiddles` then computes their inverses `itwiddles` using batch inversion and finally stores them in the `TwiddleTree` along with the `root_coset` which is the input half coset. Now let us look into the function `slow_precompute_twiddles`. ```rust theme={null} pub fn slow_precompute_twiddles(mut coset: Coset) -> Vec { let mut twiddles = Vec::with_capacity(coset.size()); for _ in 0..coset.log_size() { let i0 = twiddles.len(); twiddles.extend( coset .iter() .take(coset.size() / 2) .map(|p| p.x) .collect::>(), ); bit_reverse(&mut twiddles[i0..]); coset = coset.double(); } // Pad with an arbitrary value to make the length a power of 2. twiddles.push(1.into()); twiddles } ``` The above function computes the twiddles required to compute FFT over the line (i.e. the recursive layer FFT, after projecting to the $x$-axis). For the example in the figure with the half coset points $[(a, b), (b, -a), (-a, -b), (-b, a)]$, this function will output $[a, b, \pi(a), 1]$. Thus, for the half coset in the figure, the `precompute_twiddles` will output `TwiddleTree` with `twiddles` as $[a, b, \pi(a), 1]$ and `itwiddles` as $[a^{-1}, b^{-1}, \pi(a)^{-1}, 1]$. The `twiddles` will be used for the evaluation algorithm and `itwiddles` will be used for the interpolation algorithm. In the next section, we will bring together everything we've covered so far on Circle FFT to examine the implementation of the interpolation algorithm. # FRI Prover Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fri/fri_prover In this section, we examine the FRI prover implementation, beginning with the FRI protocol configuration. ## FRI Protocol Configuration We configure the FRI protocol using the following parameters: * Log of blowup factor $\beta$ * Log of last layer degree bound (determines the number of rounds $r$ in the FRI protocol) * Number of queries $s$ made by the verifier in the query phase It is implemented as follows: ```rust theme={null} pub struct FriConfig { pub log_blowup_factor: u32, pub log_last_layer_degree_bound: u32, pub n_queries: usize, // TODO(andrew): fold_steps. } ``` We calculate the security bits of our protocol as follows: ```rust theme={null} pub const fn security_bits(&self) -> u32 { self.log_blowup_factor * self.n_queries as u32 } ``` This is as we discussed in the [Security Analysis section](/learn/S-two-book/how-it-works/circle-fri/overview#security-analysis). ## Proving Let us look into how the FRI prover struct is implemented. ```rust theme={null} pub struct FriProver<'a, B: FriOps + MerkleOps, MC: MerkleChannel> { config: FriConfig, first_layer: FriFirstLayerProver<'a, B, MC::H>, inner_layers: Vec>, last_layer_poly: LinePoly, } ``` Here, `FriOps` is a trait which implements functionality for the commit phase of FRI, such as folding the evaluations, and `MerkleOps` is the trait used in the [Merkle commitment scheme](/learn/S-two-book/how-it-works/vcs/hash_functions#merkleops-trait). The generic `B` refers to a specific backend, for example either `CpuBackend` or `SimdBackend`, which implements the `FriOps` and `MerkleOps` traits. We described [FRI](/learn/S-two-book/how-it-works/circle-fri/overview#protocol) as an interactive protocol between the prover and the verifier. To make the protocol non-interactive, we use the [Fiat-Shamir transform](https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic), where both the prover and verifier use a channel to hash the transcript and generate random challenges. These functionalities are defined by the `MerkleChannel` trait. In the non-interactive protocol, oracles to functions are replaced by Merkle commitments to their evaluations, and queries to the oracle by the verifier are replaced by Merkle decommitments, which the prover appends to the channel. The `FRIProver` struct is composed of several layers. Each layer contains a Merkle tree that commits to the evaluations of a polynomial for that layer. The main components are: **• `config`**: The `FriConfig` discussed in the previous section, which holds protocol parameters. **• `first_layer`**: The first layer of the FRI protocol, containing the commitment to the initial set of columns. ```rust theme={null} struct FriFirstLayerProver<'a, B: FriOps + MerkleOps, H: MerkleHasher> { columns: &'a [SecureEvaluation], merkle_tree: MerkleProver, } ``` For example, the `columns` are the array of evaluations $[h_{0}, h_{1}, h_{2}]$, and `merkle_tree` commits to $h_{0} \in F^{H_0}$, $h_{1} \in F^{H_1}$, and $h_{2} \in F^{H_2}$ using a single Merkle tree. **• `inner_layers`**: The inner layers of FRI, each representing a folding round and its corresponding Merkle commitment. ```rust theme={null} struct FriInnerLayerProver, H: MerkleHasher> { evaluation: LineEvaluation, merkle_tree: MerkleProver, } ``` In our example, there are two FRI inner layers: the first contains evaluations of $g_0$ over the "line" domain $F^{I_0}$ with a Merkle commitment to $g_0$, and the second contains evaluations of $g_1$ over $F^{I_1}$ with its Merkle commitment. **• `last_layer_poly`**: The last layer polynomial, which the prover sends in clear to the verifier. ```rust theme={null} pub struct LinePoly { /// Coefficients of the polynomial in [`line_ifft`] algorithm's basis. /// /// The coefficients are stored in bit-reversed order. #[allow(rustdoc::private_intra_doc_links)] coeffs: Vec, /// The number of coefficients stored as `log2(len(coeffs))`. log_size: u32, } ``` For our example, this is the polynomial $g_2$ in coefficient representation. ### Commitment The `commit` function corresponds to the commitment phase of our protocol and outputs the `FriProver` struct. This function handles multiple mixed-degree polynomials, each evaluated over domains of different sizes. We will now give a high-level overview of the function as it is implemented in Stwo. ```rust theme={null} pub fn commit( channel: &mut MC::C, config: FriConfig, columns: &'a [SecureEvaluation], twiddles: &TwiddleTree, ) -> Self { assert!(!columns.is_empty(), "no columns"); assert!(columns.iter().all(|e| e.domain.is_canonic()), "not canonic"); assert!( columns .iter() .tuple_windows() .all(|(a, b)| a.len() > b.len()), "column sizes not decreasing" ); let first_layer = Self::commit_first_layer(channel, columns); let (inner_layers, last_layer_evaluation) = Self::commit_inner_layers(channel, config, columns, twiddles); let last_layer_poly = Self::commit_last_layer(channel, config, last_layer_evaluation); Self { config, first_layer, inner_layers, last_layer_poly, } } ``` It takes the following inputs: * `channel`: The Merkle channel used for the Fiat-Shamir transform to generate random challenges and maintain the transcript. * `config`: The `FriConfig` containing protocol parameters. * `columns`: The array of evaluations of the functions. For our example, this will contain $[h_0, h_1, h_2]$ over their respective domains $[H_0, H_1, H_2]$. * `twiddles`: The [precomputed twiddle](/learn/S-two-book/how-it-works/circle-fft/twiddles#twiddle-tree) values needed for folding. The commitment phase consists of the following steps, corresponding to the protocol rounds described in the [overview](/learn/S-two-book/how-it-works/circle-fri/overview#protocol): 1. **First Layer Commitment** (`commit_first_layer`): * Takes the input functions $[h_0, h_1, h_2]$ and creates a Merkle commitment to all of them using a single Merkle tree. * Commits to the root of the Merkle tree by appending it to the channel as part of the transcript. * Returns the `FriFirstLayerProver` containing the columns and their Merkle commitment. 2. **Inner Layers Commitment** (`commit_inner_layers`): * Performs the folding rounds as described in the protocol. * In each round $i$: * Decomposes the previous round "line" polynomial $g_{i-1}$ into $g_{i-1, 0}$ and $g_{i-1, 1}$. * Decomposes the current round "circle" polynomial $h_i$ into $h_{i, 0}$ and $h_{i, 1}$. * Receives random challenge $\lambda_i$ from the channel. * Folds the decomposed functions to compute $g_i$ over domain $I_i$. * Creates Merkle commitment to $g_i$ and adds the root of the Merkle tree to the channel. * For our example with $r=2$, this creates two inner layers containing $g_0$ and $g_1$. * Returns the following two objects: * Two `FriInnerLayerProver` corresponding to $g_0$ and $g_1$. * Final `last_layer_evaluation`, i.e., evaluations of $g_2$ over the domain $I_2$. 3. **Last Layer Commitment** (`commit_last_layer`): * Takes the final evaluation $g_r$ (which will be sent to the verifier in clear). * Interpolates it to coefficient form and appends the coefficients into the channel as protocol transcript. * For our example, this converts $g_2 \in F^{I_2}$ to polynomial coefficient representation. * Returns the `last_layer_poly`. The function then constructs and returns the complete `FriProver` struct containing all layers, which will be used later for decommitment during the query phase. ### Decommitment Now we will look at the decommit function. It is implemented as follows: ```rust theme={null} pub fn decommit(self, channel: &mut MC::C) -> (FriProof, BTreeMap>) { let max_column_log_size = self.first_layer.max_column_log_size(); let queries = Queries::generate(channel, max_column_log_size, self.config.n_queries); let column_log_sizes = self.first_layer.column_log_sizes(); let query_positions_by_log_size = get_query_positions_by_log_size(&queries, column_log_sizes); let proof = self.decommit_on_queries(&queries); (proof, query_positions_by_log_size) } ``` It takes the following input: 1. `self`: The `FriProver` containing the Merkle tree commitments to all the FRI layers. 2. `channel`: The Fiat-Shamir channel used to hash the transcript and generate the random query points. Let us walk through the function step by step. 1. **Setup Query Generation**: Use the Fiat-Shamir channel to generate `n_queries` random positions on the maximum domain. 2. **Map Query Positions by Domain Size**: The function `get_query_positions_by_log_size` takes `queries` and `column_log_sizes` as input and maps each domain size to its respective query position in the column. 3. **Generate Proof**: The function `decommit_on_queries` generates the proof `FriProof` using the queries. The struct `FriProof` contains the Merkle decommitments for each layer with respect to the query positions. ```rust theme={null} pub struct FriProof { pub first_layer: FriLayerProof, pub inner_layers: Vec>, pub last_layer_poly: LinePoly, } ``` For our example, the components of `FriProof` will be as follows: * `first_layer`: The decommitments to query positions for $h_0$, $h_1$, and $h_2$. * `inner_layers`: There will be two inner layer proofs, i.e., one for the decommitments of $g_0$ and another for decommitments of $g_1$. * `last_layer_poly`: This will be the $g_2$ polynomial represented in coefficient form. 4. Return the following objects: * `proof`: The `FriProof` struct with all layer decommitments. * `query_positions_by_log_size`: The query mapping from domain log sizes to their respective query positions. Now let us look at the key function `decommit_on_queries` in detail. ```rust theme={null} pub fn decommit_on_queries(self, queries: &Queries) -> FriProof { let Self { config: _, first_layer, inner_layers, last_layer_poly, } = self; let first_layer_proof = first_layer.decommit(queries); let inner_layer_proofs = inner_layers .into_iter() .scan( queries.fold(CIRCLE_TO_LINE_FOLD_STEP), |layer_queries, layer| { let layer_proof = layer.decommit(layer_queries); *layer_queries = layer_queries.fold(FOLD_STEP); Some(layer_proof) }, ) .collect(); FriProof { first_layer: first_layer_proof, inner_layers: inner_layer_proofs, last_layer_poly, } } ``` The function `decommit_on_queries` generates the `FriProof` struct by decommitting all layers. Suppose there is a single query corresponding to point $P_0 = (x_0, y_0) \in H_0$ and let $P_i = \pi^i(x_i, y_i) \in H_i$. 1. **Decommit First Layer**: This provides Merkle tree decommitments for queried positions with respect to the first layer. This provides evaluations $h_0(P_0), h_0(-P_0)$, $h_1(P_1), h_1(-P_1)$, and $h_2(P_2), h_2(-P_2)$ along with their Merkle decommitments in the Merkle tree containing the first layer. 2. **Process Inner Layers with Folding**: We process the decommitment layer by layer. For our example, this proceeds as follows: * For the first inner layer: Provide the evaluation $g_0(x_0), g_0(-x_0)$ along with their Merkle decommitments. * For the second inner layer: Provide the evaluation $g_1(x_1), g_1(-x_1)$ along with their Merkle decommitments. 3. **Assemble Final Proof**: Combines all layer decommitments with the last layer polynomial $g_2$ into `FriProof`. # FRI Verifier Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fri/fri_verifier In this section, we describe the implementation of the FRI verifier in S-two, continuing the example from the technical overview and prover documentation. The verifier's role is to check that all folding steps have been performed correctly by the prover. The `FriVerifier` struct verifies the FRI proof generated by the prover. ```rust theme={null} pub struct FriVerifier { config: FriConfig, // TODO(andrew): The first layer currently commits to all input polynomials. Consider allowing // flexibility to only commit to input polynomials on a per-log-size basis. This allows // flexibility for cases where committing to the first layer, for a specific log size, isn't // necessary. FRI would simply return more query positions for the "uncommitted" log sizes. first_layer: FriFirstLayerVerifier, inner_layers: Vec>, last_layer_domain: LineDomain, last_layer_poly: LinePoly, /// The queries used for decommitment. Initialized when calling /// [`FriVerifier::sample_query_positions()`]. queries: Option, } ``` The main components are: * **config**: The `FriConfig` containing protocol parameters. * **first\_layer**: The first layer verifier, which checks Merkle decommitments for all initial circle polynomials (i.e., $h_0$, $h_1$, $h_2$). * **inner\_layers**: A vector of inner layer verifiers, each corresponding to a folding round and its Merkle decommitment (i.e., two layers, corresponding to $g_0$ and $g_1$). * **last\_layer\_domain**: The "line" domain for the final layer polynomial (i.e., $I_2$). * **last\_layer\_poly**: The final "line" polynomial $g_2$ sent in clear by the prover. * **queries**: The set of queries used for spot-checking the folding identities. ## Initialization The `commit` function initializes the `FriVerifier` using the FRI proof, the Fiat-Shamir channel, and protocol parameters. It sets up the verifier for the query phase. ```rust theme={null} pub fn commit( channel: &mut MC::C, config: FriConfig, proof: FriProof, column_bounds: Vec, ) -> Result { assert!(column_bounds.is_sorted_by_key(|b| Reverse(*b))); MC::mix_root(channel, proof.first_layer.commitment); let max_column_bound = column_bounds[0]; let column_commitment_domains = column_bounds .iter() .map(|bound| { let commitment_domain_log_size = bound.log_degree_bound + config.log_blowup_factor; CanonicCoset::new(commitment_domain_log_size).circle_domain() }) .collect(); let first_layer = FriFirstLayerVerifier { column_bounds, column_commitment_domains, proof: proof.first_layer, folding_alpha: channel.draw_secure_felt(), }; let mut inner_layers = Vec::new(); let mut layer_bound = max_column_bound.fold_to_line(); let mut layer_domain = LineDomain::new(Coset::half_odds( layer_bound.log_degree_bound + config.log_blowup_factor, )); for (layer_index, proof) in proof.inner_layers.into_iter().enumerate() { MC::mix_root(channel, proof.commitment); inner_layers.push(FriInnerLayerVerifier { degree_bound: layer_bound, domain: layer_domain, folding_alpha: channel.draw_secure_felt(), layer_index, proof, }); layer_bound = layer_bound .fold(FOLD_STEP) .ok_or(FriVerificationError::InvalidNumFriLayers)?; layer_domain = layer_domain.double(); } if layer_bound.log_degree_bound != config.log_last_layer_degree_bound { return Err(FriVerificationError::InvalidNumFriLayers); } let last_layer_domain = layer_domain; let last_layer_poly = proof.last_layer_poly; if last_layer_poly.len() > (1 << config.log_last_layer_degree_bound) { return Err(FriVerificationError::LastLayerDegreeInvalid); } channel.mix_felts(&last_layer_poly); Ok(Self { config, first_layer, inner_layers, last_layer_domain, last_layer_poly, queries: None, }) } ``` The inputs are as follows: * `channel`: The Fiat-Shamir channel for randomness and transcript hashing. * `config`: The `FriConfig` with protocol parameters. * `proof`: The `FriProof` struct containing Merkle decommitments and the last layer polynomial. * `column_bounds`: The degree bounds for each committed circle polynomial, in descending order. At a high level, it proceeds as follows: 1. Initializes the first layer verifier with the Merkle decommitments for circle polynomials $h_0$, $h_1$, $h_2$, their respective domains $H_0$, $H_1$, $H_2$, their degree bounds, and folding randomness $\lambda_0$. 2. Initializes each inner layer verifier with its Merkle decommitment, domain, degree bounds, and folding randomness. For our example: * The first inner FRI verifier layer will store decommitments for $g_0$, its domain $I_0$, degree bound, and folding randomness $\lambda_1$. * Similarly, the second inner FRI verifier layer will store decommitments for $g_1$, its domain $I_1$, degree bound, and folding randomness $\lambda_2$. 3. Stores the last layer polynomial $g_2$ and its domain $I_2$. 4. Initializes the `queries` as `None` and prepares the verifier for the query phase. 5. Outputs the `FriVerifier` struct. ## Query generation The function `sample_query_positions` uses the Fiat-Shamir channel to generate random query positions for checking the folding equations. It maps each domain size to its respective query positions, ensuring that queries are adapted to the domain of each polynomial. ```rust theme={null} pub fn sample_query_positions(&mut self, channel: &mut MC::C) -> BTreeMap> { let column_log_sizes = self .first_layer .column_commitment_domains .iter() .map(|domain| domain.log_size()) .collect::>(); let max_column_log_size = *column_log_sizes.iter().max().unwrap(); let queries = Queries::generate(channel, max_column_log_size, self.config.n_queries); let query_positions_by_log_size = get_query_positions_by_log_size(&queries, column_log_sizes); self.queries = Some(queries); query_positions_by_log_size } } ``` It takes the following input: * `&mut self`: This will be used to update the `queries` in `FriVerifier`, which was initialized to `None`. * `channel`: The Fiat-Shamir channel. At a high level, it proceeds as follows: 1. Samples `n_queries` random positions on the largest domain using the `channel`. 2. Returns a mapping from domain log sizes to their respective query positions using the function `query_positions_by_log_size`. Suppose $s=1$ query is sampled at $P_0 = (x_0, y_0) \in H_0$. The function computes the corresponding positions $P_1 = \pi(P_0)$ in $H_1$ and $P_2 = \pi(P_1)$ in $H_2$, mapping each to the correct domain size. ## Verification The function `decommit` verifies the `FriVerifier` after it has been initialized with the `FriProof` by verifying the Merkle decommitments and folding equations for all layers. It ensures that the prover's folding steps were performed correctly and that the final polynomial is close to the expected polynomial space. ```rust theme={null} pub fn decommit( mut self, first_layer_query_evals: ColumnVec>, ) -> Result<(), FriVerificationError> { let queries = self.queries.take().expect("queries not sampled"); self.decommit_on_queries(&queries, first_layer_query_evals) } fn decommit_on_queries( self, queries: &Queries, first_layer_query_evals: ColumnVec>, ) -> Result<(), FriVerificationError> { let first_layer_sparse_evals = self.decommit_first_layer(queries, first_layer_query_evals)?; let inner_layer_queries = queries.fold(CIRCLE_TO_LINE_FOLD_STEP); let (last_layer_queries, last_layer_query_evals) = self.decommit_inner_layers(&inner_layer_queries, first_layer_sparse_evals)?; self.decommit_last_layer(last_layer_queries, last_layer_query_evals) } ``` It takes the following as input: * `mut self`: The `FriVerifier` struct after initialization using the `commit` function. * `first_layer_query_evals`: The evaluations of the circle polynomials at the sampled query points. It proceeds as follows: 1. **First Layer Verification:** * Verifies Merkle decommitments for $h_0$, $h_1$, $h_2$ at the queried points $P_0$, $P_1$, and $P_2$, respectively. * Extracts the necessary evaluations for folding into the next layer. 2. **Inner Layer Verification:** * For each inner layer, verifies Merkle decommitments for $g_0$, $g_1$ at the folded query points $x_0$ and $x_1$, respectively. * Checks the following two folding equations using the folding randomness and the evaluations from previous layers. $$ \begin{aligned} g_0(x_0) &= \lambda_0^2 \cdot \left( \frac{h_0(x_0,y_0) + h_0(x_0,-y_0)}{2} + \lambda_0 \cdot \frac{h_0(x_0,y_0) - h_0(x_0,-y_0)}{2 \cdot y_0} \right) \end{aligned} $$ $$ \begin{aligned} g_1(x_1) &= \frac{g_{0}(x_{0}) + g_{0}(-x_{0})}{2} + \lambda_1 \cdot \frac{g_{0}(x_{0}) - g_{0}(-x_{0})}{2 \cdot x_{0}} \\ &\quad + \lambda_1^2 \cdot \left( \frac{h_1(x_1,y_1) + h_1(x_1,-y_1)}{2} + \lambda_1 \cdot \frac{h_1(x_1,y_1) - h_1(x_1,-y_1)}{2 \cdot y_1} \right) \end{aligned} $$ where $P_0 = (x_0, y_0)$ and $P_1 = \pi((x_0, y_0)) = (x_1, y_1)$. 3. **Last Layer Verification:** * Checks that the final polynomial $g_2$ matches the evaluations at the last layer's query positions. 4. Returns success if all checks pass; otherwise, returns an error indicating which layer failed. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fri/index > This section introduces the Circle FRI protocol, which is used to check proximity to low-degree polynomials. We provide a technical overview, walk through a concrete example, and detail the implementation of both the prover and verifier. This section is organized as follows: * **[Technical Overview](/learn/S-two-book/how-it-works/circle-fri/overview)**: Explains the mathematical foundations and protocol steps of Circle FRI, including a multi-table example and security analysis. * **[FRI Prover](/learn/S-two-book/how-it-works/circle-fri/fri_prover)**: Describes the implementation of the prover, including protocol configuration, commitment, and decommitment phases. * **[FRI Verifier](/learn/S-two-book/how-it-works/circle-fri/fri_verifier)**: Details the verifier's implementation, covering query generation and verification logic. # Technical Overview Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-fri/overview In this section, we describe a multi-table variant of the *circle FRI* low degree test. This variant supports domains of different sizes, thereby avoiding excessive padding overhead. This section does not provide a detailed analysis of how FRI works, but instead focuses on the protocol as implemented in S-two. Analyzing soundness and explaining why FRI works is out of scope. Interested readers are advised to consult ["A summary on the FRI low degree test"](https://eprint.iacr.org/2022/1216.pdf). ## Introduction Circle FRI tests the proximity of a function $f \in F^D$ over a [canonical coset](/learn/S-two-book/how-it-works/circle-group#canonic-coset) $D$ of size $|D|=2^{n + \beta}$ to some polynomial $p(x, y)$ from the [polynomial space](/learn/S-two-book/how-it-works/circle-fft/basis#dimension-gap) $L'_n(F)$. The function $f \in F^D$ is represented as a vector of evaluations over the domain $D$. Here: * $F$ is a field extension of $\mathsf{M31}$ * $\beta$ is the log of blowup $B = 2^\beta$ * $L'_n(F)$ is the space of polynomials interpolated by circle FFT, defined as follows: $$ L'_n(F) = F[x]^{< 2^{n-1}} + y \cdot F[x]^{< 2^{n-1}} $$ Let $\mathcal{L}_{n-1}(F) = F[x]^{< 2^{n-1}}$, then we can represent $L'_n(F)$ as: $$ L'_n(F) = \mathcal{L}_{n-1}(F) + y \cdot \mathcal{L}_{n-1}(F) $$ > **Why check proximity to a polynomial?** > > The prover sends evaluations of a function to the verifier. However, the verifier does not know if this function is a polynomial, or if it satisfies a pre-specified degree bound. FRI allows the verifier to check that the evaluations sent by the prover are indeed "close" to some polynomial of bounded degree. Here, the verifier only checks for proximity; that is, the verifier accepts even if the evaluations sent by the prover are "close enough" to those of a polynomial of bounded degree. Thus, the evaluations sent by the prover need not be exactly equal to evaluations of some polynomial of bounded degree. In S-two, we can represent the trace over multiple tables, with each trace table defined for a domain of different size. Thus, we are interested in circle FRI for the multi-table setting. We will now see an adaptation of circle FRI for the multi-table setting using an example. Consider three functions: $h_{0}$ defined over the domain $H_0$, $h_{1}$ defined over the domain $H_1$, and $h_{2}$ defined over the domain $H_2$: $$ h_{0} \in F^{H_0} \quad, \quad h_{1} \in F^{H_1} \quad, \quad h_{2} \in F^{H_2} $$ The domains $H_0, H_1$, and $H_2$ are canonical cosets which form a chain using the squaring map $\pi$: $$ H_0 \xrightarrow{\pi} H_1 \xrightarrow{\pi} H_2 $$ Suppose for the sake of our example, for $i$ in $\{0,1,2\}$ the domain $H_i = D_{3-i + \beta}$, where $D_{3-i + \beta}$ is a canonical coset of size $2^{3-i + \beta}$. For each $i$ in $\{0,1,2\}$, we need to check that the functions $h_{i} \in F^{H_i}$ are "close" to the polynomial space $L'_{3-i}(F)$. For example, for $i=0$, we need to check that the function $h_{0}$ defined over $H_0 = D_{3 + \beta}$ is close to the polynomial space $L'_{3}(F)$. Circle FRI follows a similar divide-and-conquer strategy as the [Circle FFT](/learn/S-two-book/how-it-works/circle-fft/algorithm) algorithm. The prover recursively decomposes the functions and folds the odd and even parts using randomness sent by the verifier. This reduction continues until the proximity test of the folded function to the polynomial space is small enough for the verifier to check directly; in this case, the prover sends the folded function directly to the verifier. We reduce the functions up to folded functions evaluated over a domain $H_r$. For the sake of our example, consider $r=2$. Then the sequence of domains for circle FRI in the multi-domain setting is as follows:
Sequence of domains for Circle FRI Figure 1: Sequence of domains for Circle FRI
As shown in the above diagram, we use two different maps, as in the circle FFT algorithm: the projection map $\pi_x$ and the squaring map $\pi$. Here, $I_i$ is the "line" domain; for every $i$ in $\{0,1,2\}$, $I_i = S_{3-i}$, where $S_{3-i}$ is the "line" domain of size $|S_{3-i}| = |D_{3-i}| / 2 = 2^{2-i+\beta}$. Using circle FRI, the prover will prove proximity of functions $h_{0}$ to polynomial space $L'_3(F)$, $h_{1}$ to space $L'_2(F)$, and $h_{2}$ to space $L'_1(F)$ by reducing these three checks into a single check i.e. checking proximity of function $g_2$ to the polynomial space $\mathcal{L}_0(F)$. The verifier will have oracle access to the evaluations $h_{0} \in F^{H_0}$, $h_{1} \in F^{H_1}$, $h_{2} \in F^{H_2}$. The protocol proceeds as follows. ## Protocol 1. *Commit phase*: Consists of $r+1 = 3$ rounds. * **Round 0**: The prover will decompose the function $h_0$ over the domain $H_0$ into two functions $h_{0,0}\;,\; h_{0,1}$ over the domain $I_0$ as follows $$ h_0(x, y) = h_{0,0}(x) + y \cdot h_{0,1}(x) $$ The prover will find the evaluations of functions $h_{0,0}\;,\; h_{0,1}$ over the domain $I_0$ using the evaluations of $h_0$ over domain $H_0$ as follows: $$ h_{0,0}(x) = \frac{h_0(x,y)+h_0(x,-y)}{2} \quad, \quad h_{0,1}(x) = \frac{h_0(x,y)-h_0(x,-y)}{2y}. $$ Note that this decomposition and the process of finding evaluations of the decomposed functions is very similar to the circle FFT algorithm. Now the prover will fold the evaluations of $h_{0,0}$ and $h_{0,1}$ over the domain $I_0$ using the randomness $\lambda_0 \in F$ from the verifier as follows: $$ g_0(x) = {\lambda_0}^2 \cdot (h_{0,0}(x) + \lambda_0 \cdot h_{0, 1}(x)) $$ The prover commits and gives the verifier oracle access to the evaluations $g_0 \in F^{I_0}$. * **Round 1**: The prover will decompose the function $h_1$ over the domain $H_1$ into two functions $h_{1,0}\;,\; h_{1,1}$ over the domain $I_1$, same as the decomposition of $h_0$ in **Round 0**. The prover will also decompose the function $g_0$ over domain $I_0$ into two functions $g_{0,0}$ and $g_{0,1}$ over the domain $I_1$ as follows: $$ g_0(x) = g_{0,0}(2x^2 - 1) + x \cdot g_{0,1}(2x^2 - 1) $$ The prover will find the evaluations of functions $g_{0,0}$ and $g_{0,1}$ over the domain $I_1$ using the evaluations of $g_0$ over domain $I_0$ as follows: $$ g_{0,0}(2x^2 - 1) = \frac{g_0(x)+g_0(-x)}{2} \quad, \quad g_{0,1}(2x^2 - 1) = \frac{g_0(x)-g_0(-x)}{2x}. $$ Now the prover will fold the evaluations of $h_{1,0}$, $h_{1,1}$, $g_{0,0}$, and $g_{0,1}$ over the domain $I_1$ using the randomness $\lambda_1 \in F$ from the verifier as follows: $$ g_1(x) = g_{0,0}(x) + \lambda_1 \cdot g_{0, 1}(x) + \lambda_1^2 \cdot (h_{1,0}(x) + \lambda_1 \cdot h_{1,1}(x)) $$ The prover commits and gives the verifier oracle access to the evaluations $g_1 \in F^{I_1}$. * **Round 2**: This round is very similar to **Round 1**. The prover will decompose $h_2$ into $h_{2,0}$ and $h_{2,1}$, then decompose the function $g_1$ into $g_{1,0}$ and $g_{1,1}$. Then fold all their evaluations as follows: $$ g_2(x) = g_{1,0}(x) + \lambda_2 \cdot g_{1, 1}(x) + \lambda_2^2 \cdot (h_{2,0}(x) + \lambda_2 \cdot h_{2,1}(x)) $$ This is the final round, so the prover will send $g_2(x) \in F^{I_2}$ to the verifier in plain. Let us describe the protocol for the general case. In each round $i = \{0, \dots, r\}$, the prover has previous evaluations $g_{i-1} \in F^{I_{i-1}}$ (for round $i = 0$ we take $g_{-1} = 0$). The verifier sends $\lambda_i \in F$ and the prover commits to $g_i \in F^{I_i}$, defined as follows: $$ g_i = g_{i-1,0} + \lambda_i \cdot g_{i-1,1} + \lambda_i^2 \cdot (h_{i,0} + \lambda_i \cdot h_{i,1}) $$ where $g_{i-1,0}, g_{i-1,1} \in F^{I_i}$ are the decomposition of $g_{i-1} \in F^{I_{i-1}}$ and $h_{i,0}, h_{i,1} \in F^{I_i}$ are the decompositions of $h_i \in F^{H_i}$. The prover gives the verifier oracle access to $g_i$, and in the final round $g_r$ is sent in plain. 2. *Query Phase*: In the query phase, the verifier checks that the prover computed the folding correctly using the oracles sent by the prover. It consists of $s \geq 1$ rounds. In each round, the verifier samples $P_0 = (x_0,y_0) \in H_0$, and queries the oracles for their values to spot-check the folding identities along the projection trace $P_i = (x_i,y_i) = \pi^i(x_0,y_0)$. That is, for all $i = \{0, 1, 2\}$ the verifier checks that: $$ \begin{aligned} g_i(x_i) &= \frac{g_{i-1}(x_{i-1}) + g_{i-1}(-x_{i-1})}{2} + \lambda_i \cdot \frac{g_{i-1}(x_{i-1}) - g_{i-1}(-x_{i-1})}{2 \cdot x_{i-1}} \\ &\quad + \lambda_i^2 \cdot \left( \frac{h_i(x_i,y_i) + h_i(x_i,-y_i)}{2} + \lambda_i \cdot \frac{h_i(x_i,y_i) - h_i(x_i,-y_i)}{2 \cdot y_i} \right) \end{aligned} $$ If in each of the query rounds all spot-checks hold, and if $g_2 \in F^{I_2}$ is "close" to the polynomial space $\mathcal{L}_0$, then the verifier accepts. Otherwise, it rejects. ## Security Analysis In this section, we analyze the security of the FRI protocol at a high level. There are roughly two ways a prover can cheat: * The functions $h_{i} \in F^{H_i}$ are "far" from the polynomial space $L'_{3-i}(F)$ but somehow the prover gets lucky and ends up with $g_2 \in \mathcal{L}_0$. The [proximity gaps paper](https://eprint.iacr.org/2020/654.pdf) analyzes that this happens with negligible probability. That is, if even one of the functions $h_{i} \in F^{H_i}$ is "far" from the polynomial space $L'_{3-i}(F)$, then the function $g_2$ will be "far" from $\mathcal{L}_0$ with high probability. * Now suppose $h_{i} \in F^{H_i}$ are "far" from the polynomial space $L'_{3-i}(F)$, then the function $g_2$ will be "far" from $\mathcal{L}_0$, but to cheat the verifier, the prover cheats in the folding rounds and sends some valid $g_2 \in \mathcal{L}_0$. Now the verifier must ensure that the prover has performed the folding correctly in each round using the oracles sent by the prover. This is exactly what the verifier checks in the query phase. From the "Toy Problem Conjecture" ([ethSTARK, Section 5.9](https://eprint.iacr.org/2021/582.pdf)), each query done by the verifier gives $\beta$ (i.e. log of blowup $B$) bits of security. Thus, for $s$ queries, the security is $s \cdot \beta$ bits. # Circle Group Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-group As discussed in the previous section, Mersenne prime field $\textsf{M31}$ lacks a smooth subgroup whose order is a large power of two. This property makes such fields unsuitable for instantiating STARK protocols. To address this, we consider extensions of $\textsf{M31}$ that have smooth subgroups, which are suitable for performing FFTs and implementing the FRI protocol. For a field extension $F$ of $\textsf{M31}$, we define the *circle curve* $C(F)$ as the set of points $(x, y) \in F^2$ satisfying the relation: $x^2 + y^2 = 1$ In S-two implementation, a point on the circle is defined as follows: ```rust theme={null} pub struct CirclePoint { pub x: F, pub y: F, } ``` The set $C(F)$ forms a cyclic group under the operation defined by: $(x,y) + (x', y') = (xx' - yy', xy' + x'y)$ Here, the group is defined additively, which differs from the multiplicative notation used in the Circle STARKs paper. In this documentation, we adopt the additive notation for consistency with the implementation. The above group operation is implemented as: ```rust theme={null} fn add(self, rhs: Self) -> Self::Output { let x = self.x.clone() * rhs.x.clone() - self.y.clone() * rhs.y.clone(); let y = self.x * rhs.y + self.y * rhs.x; Self { x, y } } ``` The identity element in this group is $(1, 0)$, implemented as: ```rust theme={null} pub fn zero() -> Self { Self { x: F::one(), y: F::zero(), } } ``` Negation in the circle group corresponds to the conjugation map $J$, defined by: $J(x, y) = (x, -y)$ This is same as complex conjugation in complex numbers. In S-two, the conjugate of a `CirclePoint` is computed as: ```rust theme={null} pub fn conjugate(&self) -> CirclePoint { Self { x: self.x.clone(), y: -self.y.clone(), } } ``` The total number of points in the circle group $C(F)$ is given by $|F| + 1$. Specifically, for $C(\textsf{M31})$, the number of points is $P + 1$, which, as discussed earlier, is a large power of two and can thus be used in STARK protocol instantiations. This result is proven in *Lemma 1* of the Circle STARKs paper. In S-two implementation, the generator $g$ of the group $C(\textsf{M31})$ is defined as: $g = (2, 1268011823)$ Subgroups of $C(\textsf{M31})$ of size $2^n$ can be generated using the following function: ```rust theme={null} pub fn subgroup_gen(n: u32) -> CirclePoint { assert!(n <= M31_CIRCLE_LOG_ORDER); // M31_CIRCLE_LOG_ORDER = 31 let s = 1 << (M31_CIRCLE_LOG_ORDER - n); M31_CIRCLE_GEN.mul(s) // M31_CIRCLE_GEN = g = (2, 1268011823) } ``` To generate a subgroup $\langle g_n \rangle$ of size $2^n$, the function computes $2^{31 - n} \cdot g$, i.e. it applies the group law to the generator $g$ with itself $2^{31 - n}$ times, as shown below: ```rust theme={null} pub fn mul(&self, mut scalar: u128) -> CirclePoint { let mut res = Self::zero(); let mut cur = self.clone(); while scalar > 0 { if scalar & 1 == 1 { res = res + cur.clone(); } cur = cur.double(); scalar >>= 1; } res } ``` Hence, the point $2^{31-n} \cdot g$ serves as a generator of a subgroup $\langle g_n \rangle$ of order $2^n$. ## Circle Domain In a STARK protocol, the computation trace is interpolated as a low-degree polynomial over a domain using FFT. For Circle STARKs, this domain consists of points on the circle curve and is referred to as the *circle domain*. The circle domain $D_n$ of size $2^n$ is constructed as the union of two disjoint cosets: $D_n = q + \langle g_{n-1} \rangle \cup -q + \langle g_{n-1} \rangle$ Here, $\langle g_{n-1} \rangle$ is a subgroup of size $2^{n-1}$, and $q$ is the coset offset such that $q \neq -q$. This union is also called the *twin-coset*. The second coset in the union can be viewed as the negation (or conjugation) of the first: $J(q + \langle g_{n-1} \rangle) = -q + \langle g_{n-1} \rangle$ Therefore, it suffices to store only the half coset $q + \langle g_{n-1} \rangle$, and generate the full domain via its conjugates. The circle domain is defined in S-two as: ```rust theme={null} pub struct CircleDomain { pub half_coset: Coset, } ``` The following figure shows a circle domain of size 8. It is constructed from the half coset $q + \langle g_2 \rangle$ of size 4 (shown as red points) and its negation $-q + \langle g_2 \rangle$ (shown as blue points).
*Figure 1: Circle Domain of size 8*
To iterate over all points in the circle domain, we can iterate over the half coset and its conjugates: ```rust theme={null} pub fn iter(&self) -> CircleDomainIterator { self.half_coset .iter() .chain(self.half_coset.conjugate().iter()) } ``` ## Canonic Coset For a specific choice of offset $q$, the twin-coset $D_n$ becomes a coset of a larger subgroup. In particular, if $q$ is a generator of a subgroup of order $2^{n+1}$, then: $D_n = q + \langle g_n \rangle = q + \langle g_{n-1} \rangle \cup -q + \langle g_{n-1} \rangle$ This result is proven in *Proposition 1* of the Circle STARKs paper. Such domains are called *standard position coset*, or are referred to as *canonic cosets*. They are implemented as follows: ```rust theme={null} pub struct CanonicCoset { pub coset: Coset, } ``` Here, `CanonicCoset` represents the full coset $q + \langle g_n \rangle$, while `CircleDomain` is represented with its half coset $q + \langle g_{n-1} \rangle$. Thus to compute the `CircleDomain` from the `CanonicCoset`, first calculate the half coset $q + \langle g_{n-1} \rangle$, which will be used to initialize the `CircleDomain` as shown below: ```rust theme={null} pub fn circle_domain(&self) -> CircleDomain { CircleDomain::new(self.half_coset()) } ``` The following figure shows a canonic coset of size 8. It is constructed from the coset $\langle g_3 \rangle$ of size 8 followed by an offset by $q$, where $q$ is the generator of subgroup $\langle g_4 \rangle$.
*Figure 2: Canonic Coset of size 8*
We can verify whether a given `CircleDomain` is *canonic* by checking the step size of the half coset against the initial coset offset. In the `CircleDomain` implementation, only the *half coset* $q + \langle g_{n-1} \rangle$ is explicitly stored. If `CircleDomain` is canonic, $q$ must be a generator of the subgroup $\langle g_{n+1} \rangle$, which has order $2^{n+1}$ i.e. $q = 2^{31 - (n+1)} \cdot g$. Recall that the generator of the subgroup $\langle g_{n-1} \rangle$ is $2^{31 - (n-1)} \cdot g$. Thus, the step size between consecutive elements in the half coset is $2^{31 - (n-1)} \cdot g$, and the initial point is $q = 2^{31 - (n+1)} \cdot g$. Therefore, the ratio between the step size and the initial coset offset is: $\frac{2^{31 - (n-1)}}{2^{31 - (n+1)}} = 2^2 = 4$ This means that in a canonic coset, the step size is exactly four times the initial coset offset. This condition is used to check whether a `CircleDomain` is canonic, as shown below: ```rust theme={null} pub fn is_canonic(&self) -> bool { self.half_coset.initial_index * 4 == self.half_coset.step_size } ``` In the next section, we will dive into polynomials defined over the circle. # Columns Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-polynomials/columns In S-two, the computation trace is represented using multiple columns, each containing elements from the Mersenne prime field $\mathsf{M31}$. The columns are defined via the `Column` trait, where `T` is typically `BaseField` (an alias for `M31`). ```rust theme={null} pub trait Column: Clone + Debug + FromIterator { /// Creates a new column of zeros with the given length. fn zeros(len: usize) -> Self; /// Creates a new column of uninitialized values with the given length. /// # Safety /// The caller must ensure that the column is populated before being used. unsafe fn uninitialized(len: usize) -> Self; /// Returns a cpu vector of the column. fn to_cpu(&self) -> Vec; /// Returns the length of the column. fn len(&self) -> usize; /// Returns true if the column is empty. fn is_empty(&self) -> bool { self.len() == 0 } /// Retrieves the element at the given index. fn at(&self, index: usize) -> T; /// Sets the element at the given index. fn set(&mut self, index: usize, value: T); } ``` The operations over a column such as bit reversal of elements is provided using the `ColumnOps` trait, which also implements the type alias `Col` to conveniently represent a column. ```rust theme={null} pub trait ColumnOps { type Column: Column; fn bit_reverse_column(column: &mut Self::Column); } pub type Col = >::Column; ``` S-two defines a `Backend` trait, with two main implementations: `CpuBackend` and `SimdBackend`. The `SimdBackend` offers optimized routines for hardware supporting SIMD instructions, while `CpuBackend` provides a straightforward reference implementation. Each backend implements the `ColumnOps` trait. Here and in the following sections, we will describe the trait implementations for the `CpuBackend`. The `ColumnOps` trait is implemented for the `CpuBackend` as follows: ```rust theme={null} impl ColumnOps for CpuBackend { type Column = Vec; fn bit_reverse_column(column: &mut Self::Column) { bit_reverse(column) } } ``` Here, `bit_reverse` performs a naive bit-reversal permutation on the `column`. ## Secure Field Columns An element of the secure field (`SecureField` = `QM31`) cannot be stored in a single `BaseField` column because it is a quartic extension of `M31`. Instead, each secure field element is represented by four base field coordinates and stored in four consecutive columns. ```rust theme={null} pub struct SecureColumnByCoords> { pub columns: [Col; SECURE_EXTENSION_DEGREE], } ``` Here, `SECURE_EXTENSION_DEGREE` is the extension degree of `QM31` i.e. 4. You can think of each row of the 4 columns containing a single element of the `SecureField`. Thus accessing an element by index reconstructs it from its base field coordinates, implemented as follows: ```rust theme={null} pub fn at(&self, index: usize) -> SecureField { SecureField::from_m31_array(std::array::from_fn(|i| self.columns[i].at(index))) } ``` Now that we know how columns are represented, we can explore their use in storing evaluations over the circle domain and in interpolating polynomials. # Circle Evaluations and Polynomials Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly A polynomial can be represented in two main ways: 1. **Point-value representation**: as evaluations over a domain 2. **Coefficient representation**: as coefficients with respect to some basis The conversion from point-value representation to coefficient representation is called *interpolation* and the conversion from coefficient representation to point-value representation is called *evaluation*. Both these conversions are based on fast Fourier transform (FFT), which will be covered in the upcoming sections. In this subsection, we will look at the implementations of both these representations in S-two and the respective functions defined on them to interpolate and evaluate polynomials. ## Point-value representation In S-two, the evaluations of a polynomial over a `CircleDomain` are stored using the struct `CircleEvaluation`, implemented as follows: ```rust theme={null} pub struct CircleEvaluation, F: ExtensionOf, EvalOrder = NaturalOrder> { pub domain: CircleDomain, pub values: Col, _eval_order: PhantomData, } ``` Here, the domain (i.e. `CircleDomain`) and the evaluations (i.e. `values`) have the same ordering (i.e. `EvalOrder`) which can either be `NaturalOrder` or `BitReversedOrder`. Now given the evaluations over a domain as `CircleEvaluation` we can interpolate a circle polynomial using the following functions: ```rust theme={null} impl CircleEvaluation { /// Computes a minimal [CirclePoly] that evaluates to the same values as this evaluation. pub fn interpolate(self) -> CirclePoly { let coset = self.domain.half_coset; B::interpolate(self, &B::precompute_twiddles(coset)) } /// Computes a minimal [CirclePoly] that evaluates to the same values as this evaluation, using /// precomputed twiddles. pub fn interpolate_with_twiddles(self, twiddles: &TwiddleTree) -> CirclePoly { B::interpolate(self, twiddles) } } ``` Here, `PolyOps` is a trait that defines operations on `BaseField` polynomials, such as interpolation and evaluation. Now before looking into the representation of the circle polynomial (i.e. `CirclePoly`), let us first look into some theory on polynomials over the circle. ## Polynomials over the circle Let $F$ be an extension of the Mersenne prime field $\mathsf{M31}$. Then $F[x, y]$ represents the ring of bivariate polynomials with coefficients in $F$. The *circle polynomials* are bivariate polynomials over $F$ quotient by the ideal $\left(x^2 + y^2 - 1\right)$. The space of these bivariate polynomials over the circle curve of total degree $\leq N/2$ is denoted by $L_N(F)$. $L_N(F) = F\left[x, y\right] / \left(x^2 + y^2 - 1\right)$ For any polynomial $p(x, y) \in L_N(F)$, we can substitute all the higher degree terms of $y$ with the equation of circle $y^2 = 1 - x^2$ and reduce $p(x, y)$ to the form: $p(x, y) = p_0(x) + y \cdot p_1(x),$ The above form is called the *canonical representation* of any polynomial $p(x, y) \in L_N(F)$. Since $p(x, y)$ is of total degree $\leq N/2$, $p_0$ has degree $\leq N/2$ and $p_1$ has degree $\leq N/2 - 1$. Thus an alternate representation of the space $L_N(F)$ is as follows: $L_N(F) = F[x]^{ \leq N/2} + y \cdot F[x]^{ \leq N/2 - 1}$ From the above representation we can see that the space $L_N(F)$ is spanned by the following monomial basis: $1, x, \ldots, x^{N/2}, y, y \cdot x, \ldots, y \cdot x^{N/2 - 1}$ The dimension of $L_N(F)$ is the number of monomial basis elements, which is $N + 1$. As a developer, you can usually ignore most of these details, since concepts like polynomial space and basis do not explicitly appear in the S-two implementation. However, they do arise implicitly in the Circle FFT algorithm, as we will see in the next section. Now let us describe the coefficient representation and how the circle polynomials are implemented in S-two. ## Coefficient representation Given the evaluations over a domain (i.e. `CircleEvaluation`) we can compute the coefficients of a polynomial (i.e. `CirclePoly`) with respect to some basis using the `interpolate` function. The coefficients are stored as follows: ```rust theme={null} pub struct CirclePoly> { /// Coefficients of the polynomial in the FFT basis. /// Note: These are not the coefficients of the polynomial in the standard /// monomial basis. The FFT basis is a tensor product of the twiddles: /// y, x, pi(x), pi^2(x), ..., pi^{log_size-2}(x). /// pi(x) := 2x^2 - 1. pub coeffs: Col, /// The number of coefficients stored as `log2(len(coeffs))`. log_size: u32, } ``` The `interpolate` function computes the coefficients with respect to the following basis: $b^{(n)}_j(x, y) := y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdot \pi^2(x)^{j_3} \cdots \pi^{n-2}(x)^{j_{n-1}}$ where * $\pi$ is the squaring map on the $x$-coordinate i.e. $\pi(x) = 2x^2 - 1$ and $\pi^i$ is applying the squaring map $i$ times, for example $\pi^2(x) = \pi(\pi(x))$ * $n$ is log of the size of the `CircleDomain` * $0 \leq j \leq 2^n - 1$ and $(j_0, \ldots, j_{n-1}) \in \{0, 1\}^n$ is the binary representation of $j$, i.e., $j = j_0 + j_1 \cdot 2 + \cdots + j_{n-1} \cdot 2^{n-1}$ Thus the `interpolate` function computes coefficients $c_j$ for polynomials $p(x, y)$ of the form: $p(x, y) = \sum_{j=0}^{2^n-1} c_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdot \pi^2(x)^{j_3} \cdots \pi^{n-2}(x)^{j_{n-1}}$ This polynomial form and its underlying basis are implicit in the Circle FFT construction, as we will see in the next section. However, as discussed earlier, you can largely ignore these details and simply view a polynomial as a set of evaluations over a domain, with its coefficients computed via the FFT algorithm. Now given the coefficient representation as `CirclePoly`, we can evaluate the polynomial over a given domain (i.e. compute the `CircleEvaluation`) using the following functions: ```rust theme={null} /// Evaluates the polynomial at all points in the domain. pub fn evaluate( &self, domain: CircleDomain, ) -> CircleEvaluation { B::evaluate(self, domain, &B::precompute_twiddles(domain.half_coset)) } /// Evaluates the polynomial at all points in the domain, using precomputed twiddles. pub fn evaluate_with_twiddles( &self, domain: CircleDomain, twiddles: &TwiddleTree, ) -> CircleEvaluation { B::evaluate(self, domain, twiddles) } ``` In the next section, we will see how the evaluations and polynomials are represented over the `SecureField`. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-polynomials/index > In STARK protocols, the computation trace is organized into columns and represented as polynomials. This section explains how S-two represents and manipulates polynomials over the circle, covering both point-value (evaluations) and coefficient representations. This section is organized as follows: * **[Columns](/learn/S-two-book/how-it-works/circle-polynomials/columns)**: How computation trace data is stored in columns, including both base field and secure field representations. * **[Circle Evaluations and Polynomials](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly)**: Point-value and coefficient representations of polynomials, their mathematical foundations, and conversion between forms. * **[Secure Evaluations and Polynomials](/learn/S-two-book/how-it-works/circle-polynomials/secure-evals-and-poly)**: Extension to secure field polynomials using the quartic extension QM31, with implementation details. # Secure Evaluations and Polynomials Source: https://docs.starknet.io/learn/S-two-book/how-it-works/circle-polynomials/secure-evals-and-poly # Secure Evaluations Similar to `CircleEvaluation`, `SecureEvaluation` is a [point-value representation](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly#point-value-representation) of a polynomial whose evaluations over the `CircleDomain` are from the `SecureField` (an alias for `QM31`). This is implemented as follows: ```rust theme={null} pub struct SecureEvaluation, EvalOrder> { pub domain: CircleDomain, pub values: SecureColumnByCoords, _eval_order: PhantomData, } ``` As discussed in the [previous subsection](/learn/S-two-book/how-it-works/circle-polynomials/columns#secure-field-columns), each `SecureField` element is represented by four base field elements and stored in four consecutive columns. Thus the evaluations are represented as `SecureColumnByCoords`, as shown above. Similar to `CircleEvaluation`, we can interpolate a `SecureCirclePoly` with coefficients from the `SecureField` as shown below: ```rust theme={null} impl SecureEvaluation { /// Computes a minimal [`SecureCirclePoly`] that evaluates to the same values as this /// evaluation, using precomputed twiddles. pub fn interpolate_with_twiddles(self, twiddles: &TwiddleTree) -> SecureCirclePoly { let domain = self.domain; let cols = self.values.columns; SecureCirclePoly(cols.map(|c| { CircleEvaluation::::new(domain, c) .interpolate_with_twiddles(twiddles) })) } } ``` # Secure Circle Polynomials Similar to `CirclePoly`, `SecureCirclePoly` is a [coefficient representation](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly#coefficient-representation) of a polynomial whose coefficients are from the `SecureField`. As discussed in the [earlier section](/learn/S-two-book/how-it-works/circle-polynomials/evals-and-poly#eq-circle-poly), we can define a circle polynomial as follows: $p(x, y) = \sum_{j=0}^{2^n -1} v_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdot \pi^2(x)^{j_3} \cdots \pi^{n-2}(x)^{j_{n-1}}$ Here, $v_j$ are the coefficients from `SecureField` (i.e. $\mathsf{QM31}$). We can represent the coefficient $v_j$ using four elements from the `BaseField` (i.e. $\mathsf{M31}$) as follows: $v_j = (a_j + i \cdot b_j) + (c_j + i \cdot d_j) \cdot u$ where $a_j, b_j, c_j, d_j \in \mathsf{M31}$. Substituting the above representation in the equation of $p(x, y)$ we get: $p(x, y) = \sum_{j=0}^{2^n -1} \Big(a_j + i \cdot b_j + u \cdot c_j + i \cdot u \cdot d_j \Big) \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdot \pi^2(x)^{j_3} \cdots \pi^{n-2}(x)^{j_{n-1}}$ $p(x, y) = \sum_{j=0}^{2^n -1} a_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdots \pi^{n-2}(x)^{j_{n-1}} \\ + \\ i \cdot \sum_{j=0}^{2^n -1} b_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdots \pi^{n-2}(x)^{j_{n-1}} \\ + \\$ $\\ \\ u \cdot \sum_{j=0}^{2^n -1} c_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdots \pi^{n-2}(x)^{j_{n-1}} \\ + \\ iu \cdot \sum_{j=0}^{2^n -1} d_j \cdot y^{j_0} \cdot x^{j_1} \cdot \pi(x)^{j_2} \cdots \pi^{n-2}(x)^{j_{n-1}}$ Thus we can represent a `SecureCirclePoly` using four `CirclePoly`: $p_a(x, y), p_b(x, y), p_c(x, y)$ and $p_d(x, y)$ as follows: $p(x, y) = p_a(x, y) + i \cdot p_b(x, y) + u \cdot p_c(x, y) + iu \cdot p_d(x, y)$ where $p_a(x,y)$ is a `CirclePoly` with coefficients $a_j \in \mathsf{M31}$, similarly for $p_b(x, y), p_c(x, y)$ and $p_d(x, y)$. This is implemented as follows: ```rust theme={null} pub struct SecureCirclePoly>(pub [CirclePoly; SECURE_EXTENSION_DEGREE]); ``` Here, `SECURE_EXTENSION_DEGREE` is the degree of extension of the `SecureField`, which is 4. Similar to `CirclePoly`, we can evaluate the `SecureCirclePoly` at points on the given `CircleDomain` which outputs the `SecureEvaluation`. ```rust theme={null} pub fn evaluate_with_twiddles( &self, domain: CircleDomain, twiddles: &TwiddleTree, ) -> SecureEvaluation { let polys = self.0.each_ref(); let columns = polys.map(|poly| poly.evaluate_with_twiddles(domain, twiddles).values); SecureEvaluation::new(domain, SecureColumnByCoords { columns }) } ``` In the next section, we will see how the `interpolate` and `evaluate` functions convert between the two polynomial representations using Circle FFT. As you may have noticed, the twiddles are precomputed for efficiency, and we will explore this in the next section on Circle FFT. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/index > This section provides an overview of the core components and protocols that make up S-two. It is intended for readers who want to understand the technical details and inner workings of the system implementation. Throughout this section, we describe the implementation as available in this [commit](https://github.com/starkware-libs/stwo/tree/0790eba46b8af5697083d84fb75bd34b08a0b31f). The following topics are covered: * [**Mersenne Primes**](/learn/S-two-book/how-it-works/mersenne-prime): Introduction to the Mersenne31 prime field used in S-two for efficient arithmetic. * [**Circle Group**](/learn/S-two-book/how-it-works/circle-group): Explains the algebraic structure underlying FFT and polynomial operations. * [**Circle Polynomials**](/learn/S-two-book/how-it-works/circle-polynomials/index): Details the representation and evaluation of polynomials in the circle group. * [**Circle FFT**](/learn/S-two-book/how-it-works/circle-fft/index): Describes the fast Fourier transform algorithm adapted for the circle group. * [**Vector Commitment Scheme**](/learn/S-two-book/how-it-works/vcs/index): Covers the use of Merkle trees for committing to vectors and enabling efficient proofs of inclusion. * [**AIR to Composition Polynomial**](/learn/S-two-book/how-it-works/air/index): Shows how algebraic constraints are encoded as polynomials for proof generation. * [**Circle FRI**](/learn/S-two-book/how-it-works/circle-fri/index): Explains the FRI protocol for low-degree testing of polynomials over the circle group. * [**Polynomial Commitment Scheme**](/learn/S-two-book/how-it-works/pcs/index): Describes the protocol for committing to and opening polynomials with soundness guarantees. * [**Proof Generation and Verification**](/learn/S-two-book/how-it-works/stark_proof/index): Walks through the process of generating and verifying a STARK proof in S-two. * [**Lookups**](/learn/S-two-book/how-it-works/lookups): Discusses lookup arguments and their implementation in S-two for efficient constraint checking. # Lookups Source: https://docs.starknet.io/learn/S-two-book/how-it-works/lookups Lookups are simply a way to connect one part of the table to another. When we "look up" a value, we are doing nothing more than creating a constraint that allows us to use that value in another part of the table without breaking soundness. ## Design We will walk through four steps to incrementally build up the design of lookups. ### Step 1: Suppose we want to have two columns with the same values. We can do this by creating two columns with the exact same values and adding a constraint over them: `col_1 - col_2 = 0`. ### Step 2: We want to check that the two columns have the same values but in a different order. We can use the idea that two sets of values will have the same cumulative product if they are indeed permutations of each other. So we add new columns, `col_1_cumprod` for `col_1` and `col_2_cumprod` for `col_2`, which contain the running cumulative product of `col_1` and `col_2`, respectively. The new constraints will check that each of these new columns do indeed contain the cumulative product values and that their last values are the same. We can optimize this by creating just one new column that keeps a running cumulative product of the fraction `col_1 / col_2`. ### Step 3: We want to check that all values in `col_2` are in `col_1`, but each value appears an arbitrary number of times. *(Note that this is a generalization of the second step in that for the second step, all values in `col_2` appear exactly once in `col_1`)* Supporting this third step is actually pretty simple: when creating the running cumulative product, we need to raise each value in `col_1` to its multiplicity, or the number of times it appears in `col_2`. The rest of the constraints do not need to be changed. ### Step 4: We want to check that all values in `[col_2, col_3, ...]` are in `col_1` with arbitrary multiplicities Finally, we want to create many more columns that contain values from `col_1`. Fortunately, To support this, we can use the same idea as the third step: when creating the running cumulative product, we need to raise each value in `col_1` to the power of the number of times it appears in `[col_2, col_3, ...]`. **In summary, lookups support the following use-cases:** 1. Prove equality: we want to prove that the values of the first column are equal to the values of the second column. 2. Prove permutation: we want to prove that the values of the first column are a permutation of the values of the second column. 3. Prove permutation with multiplicities: we want to prove that each value of the first column appears a certain number of times over multiple columns. ## Technique: LogUp LogUp is a technique used to constrain lookups. It's a successor to [Plookup](https://eprint.iacr.org/2020/315), and is especially useful for proving permutation with multiplicities. Here, we'll briefly explain why this is the case. Plookup and its variants use a technique called the Grand Product Check to prove permutation. $\prod_{i=0}^{n-1} (X - a_i) = \prod_{i=0}^{n-1} (X - b_i)$ In the equation above, we can check that the set $\{a_0,...,a_{n-1}\}$ is a permutation of the set $\{b_0,...,b_{n-1}\}$ by setting $X$ to a random value provided by the verifier. However, this becomes inefficient when we have multiplicities since we need to encode the multiplicities as powers of each lookup polynomial, and thus the degree of the polynomial increases linearly with the number of multiplicities. $\prod_{i=0}^{n-1} (X - a_i) = \prod_{i=0}^{n-1} (X - b_i)^{m_i}$ On the other hand, LogUp uses the derivative of the Grand Product Check: $\sum_{i=0}^{n-1} \frac{1}{X - a_i} = \sum_{i=0}^{n-1} \frac{m_i}{X - b_i}$ In this approach, each lookup polynomial is represented as a rational function with the multiplicity as the numerator. This transformation is significant because the degree of the polynomial remains constant regardless of the number of multiplicities, making LogUp more efficient for handling multiple lookups of the same value. ## Implementation Let's walk through how LogUp is implemented in S-two using a simple example where we look up values from a preprocessed trace. First, we create columns in the original trace, where all values are from the preprocessed trace $A$.
*Figure 1: Create original trace columns that look up values from a preprocessed trace*
Then, we add a multiplicity column to the original trace indicating the number of times each value in $A$ appears in the original trace.
*Figure 2: Add a multiplicity column*
Next, we create LogUp columns as part of the interaction trace, one for the preprocessed trace and the multiplicity column, and another for the batch of all lookups.
*Figure 3: Create LogUp columns*
To create a constraint over the LogUp columns, S-two modifies the LogUp columns to contain the cumulative sum of the fractions in each row. This results in columns that look like the following:
*Figure 4: Cumulative sum columns*
A constraint is created using the values of two rows of the cumulative sum LogUp column. For example, as in **Figure 5**, we can create a constraint by subtracting $row_1$ from $row_2$ and checking that it equals the LogUp fraction created using values $a_2$ and $m_2$: $$ \frac{m_2}{X - a_2} = row\_2 - row\_1 $$
*Figure 5: Constraint over two rows*
However, this constraint actually does not hold for the first row since $\frac{m_1}{X - a_1} \neq row_1 - row_n$. A typical solution to this problem would be to disable this constraint for the first row and create a separate constraint that is enabled only on the first row. But S-two solves this problem differently. First, before we accumulate the original LogUp column rows, we subtract each row by the average of the total sum of the rows. Only then do we accumulate each row. This way, the last row of the column will always equal zero, so we do not need to make an exception for the first row. The final constraint for the second row looks as follows: $$ \frac{m_2}{X - a_2} = row\_2 - row\_1 + \text{avg} $$ Where $\text{avg}$ is a witness value provided by the prover.
*Figure 6: Trick to not create a separate constraint for the first row*
The right column in **Figure 6** is the final form of the LogUp column that S-two commits to as part of the interaction trace. ## Batching In general, batching multiple lookups together helps reduce the size of the proof as it reduces the number of LogUp columns, which means we can commit to fewer columns. However, we cannot batch an arbitrary number of fractions together because it increases the degree of the constraint polynomial. More specifically, the degree of the constraint polynomial increases linearly with the number of fractions in the batch. Let's say we want to batch $k$ fractions together. This will create a constraint $\frac{1}{X - a_1} + \frac{1}{X - a_2} + ... + \frac{1}{X - a_k} = \text{sum} - \text{prev\_sum}$. Once we multiply both sides by the common denominator, we get a constraint of degree $k+1$ from the product $(X - a_1)\cdot \dots \cdot (X - a_k) \cdot \text{sum}$. To illustrate the accounting for how many fractions we can batch together, we first need to understand how the degree of the composition polynomial is calculated in S-two. Here, the composition polynomial is the constraint polynomial divided by the quotient polynomial, where the quotient polynomial is the vanishing polynomial of the trace domain (i.e. evaluates to 0 over the trace domain). So let's say we have a degree-$m$ constraint on two columns: $a^m + b^m = 0$. We can define the composition polynomial as $\frac{a^m + b^m}{x^N - 1}$ where $N$ is the trace height. The degree of $a$ and $b$ is $N-1$ so, if the constraint holds, the degree of the composition polynomial is $m \cdot (N-1) - N = (m-1) \cdot N - m$. Thus, given that we already has $N$ evaluations, we can expand it by $m-1$ to convince the verifier that the constraint holds since $(m-1) \cdot N > (m-1) \cdot N - m$. Now, coming back to the question of how many fractions we can batch together, we can see that we can batch up to exactly $k$ fractions if the rate of expansion is $k$. # Mersenne Primes Source: https://docs.starknet.io/learn/S-two-book/how-it-works/mersenne-prime Proof systems typically rely on finite field operations, where efficient field arithmetic is crucial for optimizing proof generation. In STARK protocols, there is no direct dependency between the security level of the proof system and the field size. This allows the use of small fields with highly efficient arithmetic. S-two uses a particular family of primes called *Mersenne primes*. A Mersenne prime is defined as a prime number that is one less than a power of two, expressed as $P = 2^k -1$. S-two uses the Mersenne prime $\textsf{M31}$ of size $P = 2^{31} - 1$, which is implemented as follows: ```rust theme={null} pub struct M31(pub u32); ``` ## Why $\textsf{M31}$? The key advantage is extremely cheap modular reduction after a 31-bit multiplication. Consider computing $a \cdot b$, where $a, b \in \textsf{M31}$. This operation involves a 31-bit integer multiplication, producing a 62-bit intermediate result, which is then reduced modulo $P$. Suppose $x = a \cdot b$ then we can decompose $x$ into two 31-bit values $b$ and $s$, such that $x = 2^{31} \cdot b + s$, as shown in the following figure.
Product decomposition Figure 1: Product decomposition
To perform modular reduction, we start with: $x \equiv (2^{31} \cdot b + s) \quad mod \quad (2^{31} - 1)$ Substituting $2^{31} \equiv 1 \mod (2^{31} - 1)$ gives: $x \equiv (b + s) \quad mod \quad (2^{31} - 1)$ Since $b$ and $s$ are both 31-bit values, they can be directly represented as field elements. Consequently, modular reduction is performed with a single field addition. This makes arithmetic over Mersenne primes exceptionally fast, making them an ideal choice for our STARK protocol. The above field multiplication is implemented as: ```rust theme={null} fn mul(self, rhs: Self) -> Self::Output { Self::reduce((self.0 as u64) * (rhs.0 as u64)) } ``` where `reduce` is a function which performs efficient reduction of the resulting number modulo $P$. ## Why We Need Extensions ? We cannot instantiate our STARK protocols using $\textsf{M31}$ since it is not FFT-friendly field, meaning it does not contain a multiplicative subgroup of order that is a large power of two (commonly referred to as a *smooth* subgroup). The multiplicative group of $\textsf{M31}$ has the following order: $P-1 = 2^{31}-2$ As shown above, the multiplicative group of $\textsf{M31}$ lacks a smooth subgroup of size that is a large power of two because there is no large power of two that divides $P-1$. In other words, there does not exist a sufficiently large $n$ such that $2^n|P - 1$. To make $\textsf{M31}$ compatible with STARKs, we will work over extensions of it. ## Field Operations S-two avoids code duplication by providing two Rust macros, `impl_field!` and `impl_extension_field!`, for implementing field and extension field operations. For example, field operations for $\textsf{M31}$ are implemented using the Rust macro `impl_field!`, which takes as argument the field $\textsf{M31}$ and the size of the field $P = 2^{31} - 1$, as follows: ```rust theme={null} impl_field!(M31, P); ``` Since we work over extensions of $\textsf{M31}$, it has the type alias `BaseField`, as follows: ```rust theme={null} pub type BaseField = M31; ``` # Extensions of Mersenne Prime Field This section describes two extensions of $\textsf{M31}$: *complex extension* and *quartic extension*. ## Complex Extension We construct the degree-2 extension of $\textsf{M31}$ denoted by $\textsf{CM31}$ using the polynomial $X^2 + 1$ which is irreducible over $\textsf{M31}$. $\textsf{CM31} = \textsf{M31}[X] / (X^2 + 1)$ This extension forms a field of size $P^2$, where elements can be represented as $(a, b)$ or $a + i \cdot b$ where $a, b \in \textsf{M31}$ and $i$ is the root of the polynomial $X^2 + 1$ i.e. $i^2 + 1 = 0$. This is implemented as follows: ```rust theme={null} pub struct CM31(pub M31, pub M31); ``` The order of the multiplicative group of $\textsf{CM31}$ is calculated as follows: $P^2 - 1 = (P-1) \cdot (P+1) = (2^{31}-2) \cdot (2^{31})$ As shown above, $2^{31} | P^2 - 1$ i.e. the multiplicative group of $\textsf{CM31}$ contains a subgroup of size that is a large power of two. This makes it suitable for instantiating STARKs. This subgroup is what we refer to as the *Circle group* (explored further in the next section). Similar to $\textsf{M31}$, the operations of $\textsf{CM31}$ are defined using the macros `impl_field!` and `impl_extension_field!` (which takes as argument the field $\textsf{CM31}$ and the field to be extended i.e. $\textsf{M31}$). These are implemented as follows: ```rust theme={null} impl_field!(CM31, P2); impl_extension_field!(CM31, M31); ``` where `P2` is the size of $\textsf{CM31}$ i.e. $P^2$. ## Quartic Extension For the soundness of the protocol, it is crucial that the verifier samples random challenges from a sufficiently large field to ensure that an adversary cannot guess or brute-force the challenges and generate a proof that passes verification without knowledge of the witness. If we use $P = 2^{31} -1$, then 31-bit random challenges are not sufficient to maintain the security of the protocol. To address this, the verifier draws random challenges from a degree-4 extension of $\textsf{M31}$ denoted by $\textsf{QM31}$, which is equivalent to degree-2 extension of $\textsf{CM31}$, denoted as $\textsf{QM31} = \textsf{CM31}[X]/(X^2 - 2 - i)$ Here the polynomial $(X^2 - 2 - i)$ is irreducible over $\textsf{CM31}$. The elements of $\textsf{QM31}$ can be represented as $(r, s)$ or $r + u \cdot s$ where $r, s \in \textsf{CM31}$ and $u$ is the root of the polynomial $X^2 - 2 - i$ i.e. $u^2 - 2 - i = 0$. This is implemented as follows: ```rust theme={null} pub struct QM31(pub CM31, pub CM31); pub type SecureField = QM31; ``` Since the verifier uses the field $\textsf{QM31}$ to sample random challenges it is given the type alias `SecureField`. Alternatively, the elements of $\textsf{QM31}$ can also be represented as four elements of $\textsf{M31}$ i.e. $((a, b), (c, d))$ or $(a + i \cdot b) + (c + i \cdot d) \cdot u$ where $a, b, c, d \in \textsf{M31}$. With four elements from $\textsf{M31}$, the challenge space consists of 124-bit values, offering a sufficiently large $2^{124}$ possibilities to sample a random challenge. Similar to $\textsf{CM31}$, the operations of $\textsf{QM31}$ are defined using the macros `impl_field!` and `impl_extension_field!`, implemented as follows: ```rust theme={null} impl_field!(QM31, P4); impl_extension_field!(QM31, CM31); ``` where `P4` is the size of $\textsf{QM31}$ i.e. $P^4$. In the next section, we will explore the circle group, which is used to instantiate the STARK protocol. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/pcs/index > This section presents the implementation of the polynomial commitment scheme in S-two, which is built on top of the FRI protocol described previously. Polynomial commitments are a core cryptographic primitive that enable a prover to commit to a polynomial and later reveal evaluations at specific points. This section is organized as follows: * [**Overview**](/learn/S-two-book/how-it-works/pcs/overview): Describes the polynomial commitment scheme of S-two. * [**PCS Prover**](/learn/S-two-book/how-it-works/pcs/prover): Details the implementation of the prover for the polynomial commitment scheme, including commitment and opening protocol. * [**PCS Verifier**](/learn/S-two-book/how-it-works/pcs/verifier): Describes the verifier implementation for checking commitments and evaluation proofs. # Technical Overview Source: https://docs.starknet.io/learn/S-two-book/how-it-works/pcs/overview In this section, we describe a polynomial commitment scheme using the FRI protocol covered in the previous section. ## Polynomial Commitment Scheme A polynomial commitment scheme (PCS) allows a prover to commit to a polynomial and later prove its evaluations at points chosen by the verifier. The verifier can then check that the evaluations are consistent with the committed polynomial. It consists of the following three algorithms: * $setup(d) \rightarrow pp$: Given an upper bound $d$ on the degree, it outputs public parameters $pp$ used to commit to polynomials of degree less than $d$. For STARKs, the public parameters include the hash functions used for the Merkle commitment scheme and the FRI protocol parameters. * $commit(pp, f) \rightarrow com_f$: Takes public parameters $pp$ and a polynomial $f$ of degree $< d$, and outputs a commitment $com_f$ to the polynomial. For STARKs, $com_f$ is the root of the Merkle tree which commits to the evaluations of the polynomial $f$ on the evaluation domain. * $open(\mathcal{P}, \mathcal{V}) \rightarrow 0/1$: An interactive protocol where the prover $\mathcal{P}$ convinces the verifier $\mathcal{V}$ that $f(u) = v$. The verifier outputs 1 (accept) or 0 (reject). For STARKs, this protocol is based on FRI. The verifier will ask the prover to open the polynomial $f$ (committed using the Merkle tree) at point $u$. The prover will send the opening $v$ and define the quotient: $$ q(x) = \frac{f(x) - v}{x - u} $$ If the prover sends the correct opening, then $q$ will be a polynomial of bounded degree. The prover then uses the FRI protocol to convince the verifier that $q$ is "close" to some polynomial with a pre-specified degree bound. For S-two, we have already described the $commit$ protocol, which evaluates the circle polynomial over a canonical coset and commits to those evaluations using a Merkle tree. The $open$ protocol follows the same idea as the univariate case discussed above, but it is slightly different, as described next. In S-two, instead of using the single-point opening as described above, we have two-point openings for the values at point $Q$ and its conjugate $\overline{Q}$. The protocol proceeds as follows: 1. The verifier first receives a Merkle commitment to the evaluations of the original polynomial $f$. 2. The verifier samples a circle point $Q \in C(\mathsf{QM31}) \setminus C(\mathsf{M31})$ and requests the evaluation $f(Q)$ from the prover. 3. The prover sends the purported value $V$, and then both prover and verifier engage in the FRI protocol on the quotient: $$ q(x, y) = \frac{f(x, y) - l_{Q,V}(x, y)}{v_Q(x, y)} $$ Here, $l_{Q,V}(x, y)$ is the linear polynomial interpolating $(Q, V)$ and $(\overline{Q}, \overline{V})$, while $v_Q(x, y)$ vanishes at $Q$ and its conjugate $\overline{Q}$. As in the univariate case, if $q(x, y)$ is "close" to a polynomial, then the verifier is convinced that the evaluation claim is correct, i.e., that $f(Q) = V$. In the above protocol, we are opening the polynomial at a single point $Q$. To open the polynomial at multiple points, we batch the quotients of each point using a random linear combination and then apply the FRI protocol to a single batched quotient. One key property of a polynomial commitment scheme is binding. Informally, the binding property states that once the prover commits to a polynomial, they cannot open some other polynomial which outputs a different evaluation. For STARKs, the binding property is closely related to out-of-domain sampling, which we will describe next. ## Out of Domain Sampling Out-of-domain sampling relates to the notion of "closeness" described in the [FRI section](/learn/S-two-book/how-it-works/circle-fri/overview#introduction). Informally, the FRI protocol tests whether a function provided by the prover is "close" to some bounded degree polynomial. There are two notions of "closeness": 1. **Unique Decoding Regime**: We operate in this regime if there is at most a single polynomial which is "close" to the function provided by the prover. If the function is "close" to a single polynomial, then we can infer that the function represents that *unique* polynomial. 2. **List Decoding Regime**: We operate in this regime if there is a list of polynomials which are "close" to the function provided by the prover. In this case, since the function can be "close" to a *list* of polynomials, we cannot be sure that it represents a unique polynomial. In practice, we are usually operate in the list decoding regime. So there can be multiple polynomials which are "close" to the function provided by the prover. This affects the binding property of the polynomial commitment scheme, since the function sent by the prover represents a list of polynomials rather than some unique polynomial. To bind the prover to a unique polynomial from the list, we ask the prover to open the polynomial at an out-of-domain point. This is also referred to as *Domain Extension for Eliminating Pretenders* (or the *DEEP method*). This is the informal motivation for out-of-domain sampling. For more details, please refer to ["A summary on the FRI low degree test"](https://eprint.iacr.org/2022/1216.pdf). As we have seen in the [Security Analysis section](/learn/S-two-book/how-it-works/circle-fri/overview#security-analysis), we can improve security by increasing the number of verifier queries. But this will lead to more prover work, because the prover will have to send a Merkle decommitment for each verifier query and also increase the proof size. We will now see a method to increase the security of our protocol without significantly increasing the prover's work. ## Proof of Work The key idea is that rather than increasing the number of verifier queries, we can increase the cost of generating a false proof by a malicious prover by using *proof of work* or *grinding*. We add an additional requirement to the FRI protocol: following all the commitments made by the prover, the prover must find a 64-bit nonce that, when hashed together with the state of the hash chain, results in a required number of leading zeros. The number of leading zeros defines a certain amount of work that the prover must perform before generating the randomness representing the queries. As a result, a malicious prover that attempts to generate favorable queries will need to repeat the grinding process every time a commitment is changed. On the other hand, an honest prover only needs to perform the grinding process once. This is similar to the grinding performed on many blockchains. The nonce found by the prover is sent to the verifier as part of the proof, and in turn the verifier checks its consistency with the state of the hash chain by running the hash function once. The required number of leading zeros is configured by the `pow_bits` parameter. This effectively reduces the computational power of the cheating prover while only slightly increasing the running time of the honest prover. This is because the honest prover needs to solve the proof-of-work once, while a cheating prover, during the long process of trying to find a false proof, would need to solve many different instances of the proof-of-work. # Polynomial Commitment Scheme Prover Source: https://docs.starknet.io/learn/S-two-book/how-it-works/pcs/prover In this section, we will see the implementation of the commitment scheme prover. We will start by looking at the building blocks. ## Commitment Tree Prover The `CommitmentTreeProver` struct represents the data for a single Merkle tree commitment. As we have seen in the [Merkle tree section](/learn/S-two-book/how-it-works/vcs/merkle_prover#merkle-prover), we can commit to multiple polynomials of different degrees in the same Merkle tree. It is implemented as follows: ```rust theme={null} pub struct CommitmentTreeProver, MC: MerkleChannel> { pub polynomials: ColumnVec>, pub evaluations: ColumnVec>, pub commitment: MerkleProver, } ``` Here, `pub type ColumnVec = Vec`. It contains the following fields: * `polynomials`: The set of polynomials committed in a single Merkle tree. * `evaluations`: The evaluations of these polynomials over their respective domains. * `commitment`: The `MerkleProver` struct as described in the [Merkle tree section](/learn/S-two-book/how-it-works/vcs/merkle_prover#merkleprover-structure). It is initialized as follows: ```rust theme={null} pub fn new( polynomials: ColumnVec>, log_blowup_factor: u32, channel: &mut MC::C, twiddles: &TwiddleTree, ) -> Self { let span = span!(Level::INFO, "Extension").entered(); let evaluations = B::evaluate_polynomials(&polynomials, log_blowup_factor, twiddles); span.exit(); let _span = span!(Level::INFO, "Merkle").entered(); let tree = MerkleProver::commit(evaluations.iter().map(|eval| &eval.values).collect()); MC::mix_root(channel, tree.root()); CommitmentTreeProver { polynomials, evaluations, commitment: tree, } } ``` It proceeds as follows. First, given the `polynomials`, we evaluate them on the evaluation domain using circle FFT to compute `evaluations`. Then we commit to those evaluations using the `MerkleProver` struct. Finally, we create and output the `CommitmentTreeProver` struct. ## Commitment Scheme Prover The `CommitmentSchemeProver` struct is the key struct which maintains a vector of commitment trees. It implements functionalities to open the committed polynomials, compute quotients, and then apply the FRI protocol. It contains the following fields: ```rust theme={null} pub struct CommitmentSchemeProver<'a, B: BackendForChannel, MC: MerkleChannel> { pub trees: TreeVec>, pub config: PcsConfig, twiddles: &'a TwiddleTree, } ``` It contains the following fields: * `tree`: This contains a vector of commitment trees. Here, `pub struct TreeVec(pub Vec)`. * `config`: This is the `PcsConfig`, which contains the `fri_config` and `pow_bits`. ```rust theme={null} pub struct PcsConfig { pub pow_bits: u32, pub fri_config: FriConfig, } ``` The security of the polynomial commitment scheme is computed as: ```rust theme={null} pub const fn security_bits(&self) -> u32 { self.pow_bits + self.fri_config.security_bits() } ``` * `twiddles`: This contains [precomputed twiddle factors](/learn/S-two-book/how-it-works/circle-fft/twiddles#twiddle-tree). Now we will see some key functions defined on the `CommitmentSchemeProver` struct. ### Commit The `commit` function, given a batch of polynomials, computes the `CommitmentTreeProver` struct which commits to the input polynomials and then appends the `tree` struct to the vector of stored `trees`. ```rust theme={null} fn commit(&mut self, polynomials: ColumnVec>, channel: &mut MC::C) { let _span = span!(Level::INFO, "Commitment").entered(); let tree = CommitmentTreeProver::new( polynomials, self.config.fri_config.log_blowup_factor, channel, self.twiddles, ); self.trees.push(tree); } ``` ### Trace The `trace` function returns a `Trace` struct containing all polynomials and their evaluations corresponding to all the commitment trees. It is implemented as follows: ```rust theme={null} pub fn trace(&self) -> Trace<'_, B> { let polys = self.polynomials(); let evals = self.evaluations(); Trace { polys, evals } } ``` ### Commitment Tree Builder The `tree_builder` function outputs the `TreeBuilder` struct. ```rust theme={null} pub fn tree_builder(&mut self) -> TreeBuilder<'_, 'a, B, MC> { TreeBuilder { tree_index: self.trees.len(), commitment_scheme: self, polys: Vec::default(), } } ``` The `TreeBuilder` struct is a helper for aggregating polynomials and evaluations before committing them in a Merkle tree. It allows the prover to collect columns (polynomials) and then commit them together as a batch. It is implemented as follows: ```rust theme={null} pub struct TreeBuilder<'a, 'b, B: BackendForChannel, MC: MerkleChannel> { tree_index: usize, commitment_scheme: &'a mut CommitmentSchemeProver<'b, B, MC>, polys: ColumnVec>, } ``` ### Prove The `prove_values` function is central to the protocol, handling the opening of committed polynomials at specified sample points and integrating with the FRI protocol for low-degree testing. It is implemented as follows: ```rust theme={null} pub fn prove_values( self, sampled_points: TreeVec>>>, channel: &mut MC::C, ) -> CommitmentSchemeProof { // Evaluate polynomials on open points. let span = span!( Level::INFO, "Evaluate columns out of domain", class = "EvaluateOutOfDomain" ) .entered(); let samples = self .polynomials() .zip_cols(&sampled_points) .map_cols(|(poly, points)| { points .iter() .map(|&point| PointSample { point, value: poly.eval_at_point(point), }) .collect_vec() }); span.exit(); let sampled_values = samples .as_cols_ref() .map_cols(|x| x.iter().map(|o| o.value).collect()); channel.mix_felts(&sampled_values.clone().flatten_cols()); // Compute oods quotients for boundary constraints on the sampled points. let columns = self.evaluations().flatten(); let quotients = compute_fri_quotients( &columns, &samples.flatten(), channel.draw_secure_felt(), self.config.fri_config.log_blowup_factor, ); // Run FRI commitment phase on the oods quotients. let fri_prover = FriProver::::commit(channel, self.config.fri_config, "ients, self.twiddles); // Proof of work. let span1 = span!(Level::INFO, "Grind", class = "Queries POW").entered(); let proof_of_work = B::grind(channel, self.config.pow_bits); span1.exit(); channel.mix_u64(proof_of_work); // FRI decommitment phase. let (fri_proof, query_positions_per_log_size) = fri_prover.decommit(channel); // Decommit the FRI queries on the merkle trees. let decommitment_results = self .trees .as_ref() .map(|tree| tree.decommit(&query_positions_per_log_size)); let queried_values = decommitment_results.as_ref().map(|(v, _)| v.clone()); let decommitments = decommitment_results.map(|(_, d)| d); CommitmentSchemeProof { commitments: self.roots(), sampled_values, decommitments, queried_values, proof_of_work, fri_proof, config: self.config, } } } ``` Here is a detailed breakdown: 1. **Evaluate Polynomials at Sample Points**: * For each committed polynomial and each sample point (including out-of-domain points and mask points which contain constraint offsets), the function evaluates the polynomials and collects the results in `samples`. * The `sampled_values` are mixed into the channel, ensuring they are bound to the proof and used for subsequent randomness generation. 2. **Compute FRI Quotients**: * The function computes FRI quotient polynomials using `compute_fri_quotients` to open the committed polynomials at sampled points in `samples`. This follows the same quotienting process as described in the [overview section](/learn/S-two-book/how-it-works/pcs/overview#polynomial-commitment-scheme). 3. **FRI Commitment Phase**: * The FRI protocol is run on the quotient polynomials, committing to their evaluations in Merkle trees and initializing the `fri_prover`. For more details, refer to the [FRI prover section](/learn/S-two-book/how-it-works/circle-fri/fri_prover). 4. **Proof of Work**: * A proof-of-work step is performed, with the result mixed into the channel. 5. **FRI Decommitment Phase**: * The function generates random query positions using the channel and decommits the FRI layers at those positions, providing Merkle decommitments for all queried values. For more details, refer to the [FRI prover section](/learn/S-two-book/how-it-works/circle-fri/fri_prover). 6. **Decommitment of Committed Trees**: * For each commitment tree, the function decommits the Merkle tree at the FRI query positions, providing the queried values and authentication paths. 7. **Return Proof Object**: * The function returns a `CommitmentSchemeProof` object containing: * Merkle roots of all commitments * Sampled values at all sample points * Merkle decommitments for all queries * Queried values * Proof-of-work result * FRI proof * Protocol configuration We will now look into the proof verifier implementation. # Polynomial Commitment Scheme Verifier Source: https://docs.starknet.io/learn/S-two-book/how-it-works/pcs/verifier In this section, we describe the implementation of the polynomial commitment scheme verifier. ## Commitment Scheme Verifier The `CommitmentSchemeVerifier` struct manages the verification process for the polynomial commitment scheme. It maintains a collection of [Merkle verifiers](/learn/S-two-book/how-it-works/vcs/merkle_verifier) (one for each commitment tree) and the protocol configuration. ```rust theme={null} pub struct CommitmentSchemeVerifier { pub trees: TreeVec>, pub config: PcsConfig, } ``` We will now see some key functions defined for the `CommitmentSchemeVerifier` struct. ### Read Commitments The `commit` function reads a Merkle root from the prover and initializes a `MerkleVerifier` for the committed columns. It is implemented as follows: ```rust theme={null} pub fn commit( &mut self, commitment: ::Hash, log_sizes: &[u32], channel: &mut MC::C, ) { MC::mix_root(channel, commitment); let extended_log_sizes = log_sizes .iter() .map(|&log_size| log_size + self.config.fri_config.log_blowup_factor) .collect(); let verifier = MerkleVerifier::new(commitment, extended_log_sizes); self.trees.push(verifier); } ``` ### Verify The `verify_values` function is the core of the verification protocol. It checks that the prover's openings at the sampled points are consistent with the commitments and that the committed polynomials are of low degree via the FRI protocol. It is implemented as follows: ```rust theme={null} pub fn verify_values( &self, sampled_points: TreeVec>>>, proof: CommitmentSchemeProof, channel: &mut MC::C, ) -> Result<(), VerificationError> { channel.mix_felts(&proof.sampled_values.clone().flatten_cols()); let random_coeff = channel.draw_secure_felt(); let bounds = self .column_log_sizes() .flatten() .into_iter() .sorted() .rev() .dedup() .map(|log_size| { CirclePolyDegreeBound::new(log_size - self.config.fri_config.log_blowup_factor) }) .collect_vec(); // FRI commitment phase on OODS quotients. let mut fri_verifier = FriVerifier::::commit(channel, self.config.fri_config, proof.fri_proof, bounds)?; // Verify proof of work. channel.mix_u64(proof.proof_of_work); if channel.trailing_zeros() < self.config.pow_bits { return Err(VerificationError::ProofOfWork); } // Get FRI query positions. let query_positions_per_log_size = fri_verifier.sample_query_positions(channel); // Verify merkle decommitments. self.trees .as_ref() .zip_eq(proof.decommitments) .zip_eq(proof.queried_values.clone()) .map(|((tree, decommitment), queried_values)| { tree.verify(&query_positions_per_log_size, queried_values, decommitment) }) .0 .into_iter() .collect::>()?; // Answer FRI queries. let samples = sampled_points.zip_cols(proof.sampled_values).map_cols( |(sampled_points, sampled_values)| { zip(sampled_points, sampled_values) .map(|(point, value)| PointSample { point, value }) .collect_vec() }, ); let n_columns_per_log_size = self.trees.as_ref().map(|tree| &tree.n_columns_per_log_size); let fri_answers = fri_answers( self.column_log_sizes(), samples, random_coeff, &query_positions_per_log_size, proof.queried_values, n_columns_per_log_size, )?; fri_verifier.decommit(fri_answers)?; Ok(()) } ``` Here is a detailed breakdown: 1. **Mix Sampled Values into the Fiat-Shamir Channel**: * The verifier mixes the `sampled_values` (openings at the queried points) into the Fiat-Shamir channel, ensuring that all subsequent randomness is bound to these values. 2. **Draw Random Coefficient**: * The verifier draws a `random_coeff` from the channel, which is used to combine the quotient polynomials in the FRI protocol. 3. **Determine Degree Bounds**: * The verifier computes the degree `bounds` for each column, based on the log sizes and the protocol's blowup factor. These bounds are used to configure the FRI verifier. 4. **FRI Commitment Phase**: * The verifier initializes the `fri_verifier` with the FRI protocol configuration, the FRI proof from the prover, and the degree bounds. 5. **Verify Proof of Work**: * The verifier checks the `proof_of_work` value provided by the prover using the `pow_bits` in the PCS config. 6. **Sample FRI Query Positions**: * The verifier uses the channel to generate random `query_positions_per_log_size` for the FRI protocol. 7. **Verify Merkle Decommitments**: * For each commitment tree, the verifier checks that the Merkle decommitments at the queried positions are valid and that the opened values match the commitments. 8. **Prepare FRI Query Answers**: * The verifier assembles the answers to the FRI queries by matching the sampled points and values, and prepares them for the FRI verifier. 9. **FRI Decommitment Phase**: * The verifier provides the FRI query answers to the FRI verifier, which checks that the quotient polynomials are of low degree. 10. **Return Verification Result**: * If all checks pass, the function returns `Ok(())`. If any check fails (e.g., Merkle decommitment, proof of work, or FRI check), it returns an appropriate error. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/stark_proof/index > In this final section, we cover the implementation of the STARK prover and verifier. This section brings together all the components from previous sections and describes the complete STARK proof generation and verification algorithms. We explain the `prove` and `verify` functions used to generate and verify proofs, as introduced in the [Writing a Simple AIR section](/learn/S-two-book/air-development/writing-a-simple-air/proving-an-air). This section is organized as follows: * [**STARK Prover**](/learn/S-two-book/how-it-works/stark_proof/prove): Details the implementation of the proof generation algorithm. * [**STARK Verifier**](/learn/S-two-book/how-it-works/stark_proof/verify): Describes the verification algorithm. # STARK Prover Source: https://docs.starknet.io/learn/S-two-book/how-it-works/stark_proof/prove This section provides an overview of the `prove` function, the key function in the STARK proof generation process. It is implemented as follows: ```rust theme={null} pub fn prove, MC: MerkleChannel>( components: &[&dyn ComponentProver], channel: &mut MC::C, mut commitment_scheme: CommitmentSchemeProver<'_, B, MC>, ) -> Result, ProvingError> { let n_preprocessed_columns = commitment_scheme.trees[PREPROCESSED_TRACE_IDX] .polynomials .len(); let component_provers = ComponentProvers { components: components.to_vec(), n_preprocessed_columns, }; let trace = commitment_scheme.trace(); // Evaluate and commit on composition polynomial. let random_coeff = channel.draw_secure_felt(); let span = span!(Level::INFO, "Composition", class = "Composition").entered(); let span1 = span!( Level::INFO, "Generation", class = "CompositionPolynomialGeneration" ) .entered(); let composition_poly = component_provers.compute_composition_polynomial(random_coeff, &trace); span1.exit(); let mut tree_builder = commitment_scheme.tree_builder(); tree_builder.extend_polys(composition_poly.into_coordinate_polys()); tree_builder.commit(channel); span.exit(); // Draw OODS point. let oods_point = CirclePoint::::get_random_point(channel); // Get mask sample points relative to oods point. let mut sample_points = component_provers.components().mask_points(oods_point); // Add the composition polynomial mask points. sample_points.push(vec![vec![oods_point]; SECURE_EXTENSION_DEGREE]); // Prove the trace and composition OODS values, and retrieve them. let commitment_scheme_proof = commitment_scheme.prove_values(sample_points, channel); let proof = StarkProof(commitment_scheme_proof); info!(proof_size_estimate = proof.size_estimate()); // Evaluate composition polynomial at OODS point and check that it matches the trace OODS // values. This is a sanity check. if proof.extract_composition_oods_eval().unwrap() != component_provers .components() .eval_composition_polynomial_at_point(oods_point, &proof.sampled_values, random_coeff) { return Err(ProvingError::ConstraintsNotSatisfied); } Ok(proof) } ``` Let us go through the function in detail. ## Input and Output It takes the following as input: * `components`: A list of AIR components. For more details, refer to the [Components](/learn/S-two-book/how-it-works/air/components) and [Prover Components](/learn/S-two-book/how-it-works/air/prover_components) sections. * `channel`: A Fiat-Shamir channel for non-interactive randomness. * `commitment_scheme`: A `CommitmentSchemeProver` for committing to trace and composition polynomials. For more details, refer to the [PCS Prover section](/learn/S-two-book/how-it-works/pcs/prover). It outputs a `StarkProof` object if successful, or a `ProvingError` if any constraint is not satisfied. The `StarkProof` object is a wrapper around `CommitmentSchemeProof`. ```rust theme={null} pub struct StarkProof(pub CommitmentSchemeProof); ``` ## Step-by-Step Breakdown 1. **Determine Preprocessed Columns** * The function determines the number of preprocessed columns, `n_preprocessed_columns`, from the `commitment_scheme`, which is used to initialize the `ComponentProvers` structure. 2. **Collect Trace Data** * The `trace`, containing all columns (execution, interaction, preprocessed), is retrieved from the `commitment_scheme`. This includes both coefficient and evaluation forms for each column. 3. **Composition Polynomial Construction** * A `random_coeff` is drawn from the channel. * The `composition_poly` is computed as a random linear combination of all constraint quotient polynomials, using powers of the random coefficient. For more details, refer to the [Prover Components](/learn/S-two-book/how-it-works/air/prover_components) section. 4. **Commit to the Composition Polynomial** * The `composition_poly` is split into coordinate polynomials and committed to using a Merkle tree. 5. **Out-of-Domain Sampling (OODS)** * An `oods_point` is drawn randomly from the channel. This point is used to bind the prover to a unique low-degree polynomial, preventing ambiguity in the list decoding regime. For more details, refer to the [Out-of-Domain Sampling](/learn/S-two-book/how-it-works/pcs/overview#out-of-domain-sampling) section. 6. **Determine Sample Points** * The function computes all `sample_points` required to verify constraints at the OODS point, using the `mask_points` function. This includes all necessary offsets for each constraint and the OODS points for the composition polynomial. 7. **Openings and Proof Generation** * The `commitment_scheme` is asked to open all committed polynomials at the sampled points, producing the required evaluations and Merkle authentication paths. This is handled by the `prove_values` function, which also integrates the FRI protocol for low-degree testing. For more details, refer to the [PCS Prover](/learn/S-two-book/how-it-works/pcs/prover#prove) section. 8. **Sanity Check** * The function checks that the composition polynomial evaluated at the OODS point matches the value reconstructed from the sampled trace values. If not, it returns a `ConstraintsNotSatisfied` error. 9. **Return Proof** * If all checks pass, the function returns a `StarkProof` object containing the full proof transcript, including all commitments, openings, and FRI proof. # STARK Verifier Source: https://docs.starknet.io/learn/S-two-book/how-it-works/stark_proof/verify This section provides an overview of the `verify` function, the key function that verifies the STARK proof. It is implemented as follows: ```rust theme={null} pub fn verify( components: &[&dyn Component], channel: &mut MC::C, commitment_scheme: &mut CommitmentSchemeVerifier, proof: StarkProof, ) -> Result<(), VerificationError> { let n_preprocessed_columns = commitment_scheme.trees[PREPROCESSED_TRACE_IDX] .column_log_sizes .len(); let components = Components { components: components.to_vec(), n_preprocessed_columns, }; tracing::info!( "Composition polynomial log degree bound: {}", components.composition_log_degree_bound() ); let random_coeff = channel.draw_secure_felt(); // Read composition polynomial commitment. commitment_scheme.commit( *proof.commitments.last().unwrap(), &[components.composition_log_degree_bound(); SECURE_EXTENSION_DEGREE], channel, ); // Draw OODS point. let oods_point = CirclePoint::::get_random_point(channel); // Get mask sample points relative to oods point. let mut sample_points = components.mask_points(oods_point); // Add the composition polynomial mask points. sample_points.push(vec![vec![oods_point]; SECURE_EXTENSION_DEGREE]); let sample_points_by_column = sample_points.as_cols_ref().flatten(); tracing::info!("Sampling {} columns.", sample_points_by_column.len()); tracing::info!( "Total sample points: {}.", sample_points_by_column.into_iter().flatten().count() ); let composition_oods_eval = proof .extract_composition_oods_eval() .ok_or(VerificationError::InvalidStructure( std_shims::ToString::to_string(&"Unexpected sampled_values structure"), ))?; if composition_oods_eval != components.eval_composition_polynomial_at_point( oods_point, &proof.sampled_values, random_coeff, ) { return Err(VerificationError::OodsNotMatching); } commitment_scheme.verify_values(sample_points, proof.0, channel) } ``` Let us go through the function in detail. ## Input and Output The `verify` function is the entry point for verifying a STARK proof. It takes as input: * `components`: A list of AIR components. For more details, refer to the [Components](/learn/S-two-book/how-it-works/air/components) section. * `channel`: A Fiat-Shamir channel for non-interactive randomness. * `commitment_scheme`: A `CommitmentSchemeVerifier` for verifying Merkle commitments and FRI proofs. For more details, refer to the [PCS Verifier section](/learn/S-two-book/how-it-works/pcs/verifier). * `proof`: The `StarkProof` object to be verified. It returns `Ok(())` if the proof is valid, or a `VerificationError` if any check fails. ## Step-by-Step Breakdown 1. **Determine Preprocessed Columns** * The function determines the number of preprocessed columns, `n_preprocessed_columns`, from the `commitment_scheme`, which is used to initialize the `Components` struct. 2. **Initialize Components** * The `Components` structure is created, encapsulating all AIR components and the number of preprocessed columns. 3. **Read Composition Polynomial Commitment** * The verifier reads the Merkle root of the composition polynomial from the `proof` and registers it with the commitment scheme verifier, along with the degree bounds for each coordinate polynomial. 4. **Out-of-Domain Sampling (OODS)** * An `oods_point` is drawn randomly from the channel. This point is used to bind the prover to a unique low-degree polynomial and prevent ambiguity in the list decoding regime. 5. **Determine Sample Points** * The function computes all `sample_points` required to verify constraints at the OODS point, using the `mask_points` function. This includes all necessary offsets for each constraint and the OODS points for the composition polynomial. 6. **Sanity Check: Composition Polynomial Evaluation** * The function checks that the composition polynomial evaluated at the OODS point (as provided in the proof) matches the value reconstructed from the sampled trace values. If not, it returns an `OodsNotMatching` error. 7. **Invoke Commitment Scheme Verifier** * The function calls `verify_values` on the commitment scheme verifier, passing the `sample_points`, the `proof`, and the `channel`. This step checks all Merkle decommitments, FRI low-degree proofs, and protocol soundness. 8. **Return Verification Result** * If all checks pass, the function returns `Ok(())`. If any check fails (e.g., Merkle decommitment, FRI check, or OODS mismatch), it returns an appropriate `VerificationError`. # Hash Functions Source: https://docs.starknet.io/learn/S-two-book/how-it-works/vcs/hash_functions This section describes the traits and implementations of hash functions used in the Merkle commitment scheme. S-two supports two hash functions: **BLAKE2s-256** and **Poseidon252**. Here, we focus on the implementation for BLAKE2s-256; Poseidon252 is implemented similarly (see [Poseidon reference](https://docs.starknet.io/learn/protocol/cryptography#poseidon-hash)). ## MerkleHasher Trait The `MerkleHasher` trait defines the interface for hash functions used in Merkle trees. Its main function, `hash_node`, computes the hash of a node from its children and (optionally) column values: ```rust theme={null} pub trait MerkleHasher: Debug + Default + Clone { type Hash: Hash; /// Hashes a single Merkle node. See [MerkleHasher] for more details. fn hash_node( children_hashes: Option<(Self::Hash, Self::Hash)>, column_values: &[BaseField], ) -> Self::Hash; } ``` ### Implementation for BLAKE2s-256 The `MerkleHasher` implementation for BLAKE2s-256 uses a wrapper struct `Blake2sHash`: ```rust theme={null} pub struct Blake2sHash(pub [u8; 32]); ``` The trait implementation is provided by `Blake2sMerkleHasher`: ```rust theme={null} pub struct Blake2sMerkleHasher; impl MerkleHasher for Blake2sMerkleHasher { type Hash = Blake2sHash; fn hash_node( children_hashes: Option<(Self::Hash, Self::Hash)>, column_values: &[BaseField], ) -> Self::Hash { let mut hasher = Blake2s256::new(); if let Some((left_child, right_child)) = children_hashes { hasher.update(left_child); hasher.update(right_child); } for value in column_values { hasher.update(value.0.to_le_bytes()); } Blake2sHash(hasher.finalize().into()) } } ``` In this Merkle tree implementation, node hashes are computed using both the children hashes and the column values. This differs from standard Merkle trees, where node hashes typically depend only on the children. More details are discussed in the next sections. ## MerkleOps Trait The `MerkleOps` trait defines Merkle tree operations for a commitment scheme, parameterized by a `MerkleHasher`. Its main function, `commit_on_layer`, takes the previous layer's hashes and the current layer's column values to generate the hashes for the next layer: ```rust theme={null} pub trait MerkleOps: ColumnOps + ColumnOps + for<'de> Deserialize<'de> + Serialize { /// Commits on an entire layer of the Merkle tree. /// See [MerkleHasher] for more details. /// /// The layer has 2^`log_size` nodes that need to be hashed. The topmost layer has 1 node, /// which is a hash of 2 children and some columns. /// /// `prev_layer` is the previous layer of the Merkle tree, if this is not the leaf layer. /// That layer is assumed to have 2^(`log_size`+1) nodes. /// /// `columns` are the extra columns that need to be hashed in each node. /// They are assumed to be of size 2^`log_size`. /// /// Returns the next Merkle layer hashes. fn commit_on_layer( log_size: u32, prev_layer: Option<&Col>, columns: &[&Col], ) -> Col; } ``` ### Implementation for BLAKE2s-256 The `MerkleOps` trait implementation for the `CpuBackend` is as follows: ```rust theme={null} impl MerkleOps for CpuBackend { fn commit_on_layer( log_size: u32, prev_layer: Option<&Vec>, columns: &[&Vec], ) -> Vec { (0..(1 << log_size)) .map(|i| { Blake2sMerkleHasher::hash_node( prev_layer.map(|prev_layer| (prev_layer[2 * i], prev_layer[2 * i + 1])), &columns.iter().map(|column| column[i]).collect_vec(), ) }) .collect() } } ``` In the next section, we will use these hash function implementations to describe the prover of the Merkle commitment scheme. # Introduction Source: https://docs.starknet.io/learn/S-two-book/how-it-works/vcs/index > In STARK protocols, the prover first interpolates the computation trace as polynomials over some domain, then evaluates those interpolated polynomials over a larger domain. Finally, the prover commits to those evaluations using a vector commitment scheme. > > S-two uses a Merkle tree-based vector commitment scheme that enables efficient commitment to multiple columns of data and generation of Merkle proofs for queried elements. This section covers the complete workflow: from hash function implementations to the commitment and decommitment processes. This section is organized as follows: * **[Hash Functions](/learn/S-two-book/how-it-works/vcs/hash_functions)**: Overview of the hash function traits and implementations used in Merkle trees. * **[Merkle Prover](/learn/S-two-book/how-it-works/vcs/merkle_prover)**: The commitment process that builds Merkle trees from column data and the decommitment process that generates proofs for queried elements. * **[Merkle Verifier](/learn/S-two-book/how-it-works/vcs/merkle_verifier)**: The verification process that checks the validity of Merkle proofs against a committed root. # Merkle Prover Source: https://docs.starknet.io/learn/S-two-book/how-it-works/vcs/merkle_prover This section explains the prover of the Merkle commitment scheme, focusing on how columns are committed to compute a Merkle root and how the Merkle tree layers are constructed, as well as how to generate Merkle proofs (decommitments). S-two represents the computation trace using multiple columns of different sizes. Rather than committing to each column in separate Merkle trees, S-two uses a modified Merkle tree structure where all columns of different sizes are committed to using a single Merkle tree. We will describe the structure of this Merkle tree in this section. ## MerkleProver Structure The `MerkleProver` struct represents a prover for a Merkle commitment scheme. It stores all layers of the Merkle tree, where each layer contains the hash values at that level. ```rust theme={null} pub struct MerkleProver, H: MerkleHasher> { /// Layers of the Merkle tree. /// The first layer is the root layer. /// The last layer is the largest layer. /// See [MerkleOps::commit_on_layer] for more details. pub layers: Vec>, } ``` ## The Commit Process The core of the Merkle prover is the `commit` function, which takes as input a set of columns and outputs a `MerkleProver` containing all layers of the Merkle tree. The columns must be of length that is a power of 2. Below is the complete implementation of the `commit` function: ```rust theme={null} pub fn commit(columns: Vec<&Col>) -> Self { let _span = span!(Level::TRACE, "Merkle", class = "MerkleCommitment").entered(); if columns.is_empty() { return Self { layers: vec![B::commit_on_layer(0, None, &[])], }; } let columns = &mut columns .into_iter() .sorted_by_key(|c| Reverse(c.len())) .peekable(); let mut layers: Vec> = Vec::new(); let max_log_size = columns.peek().unwrap().len().ilog2(); for log_size in (0..=max_log_size).rev() { // Take columns of the current log_size. let layer_columns = columns .peek_take_while(|column| column.len().ilog2() == log_size) .collect_vec(); layers.push(B::commit_on_layer(log_size, layers.last(), &layer_columns)); } layers.reverse(); Self { layers } } ``` Let's walk through the function step by step: 1. **Sort Columns by Length:** Columns are sorted in descending order of length (columns with the most elements appear first). This ensures that the largest columns are committed first. 2. **Layer-by-Layer Commitment:** * For each layer (from largest to smallest), the function collects all columns of the current size and computes the hashes for that layer using the `commit_on_layer` function. * For the largest layer, the previous layer's hashes are empty, so the hash is computed directly from the column values. * For subsequent layers, the hash is computed from the previous layer's hashes and the current layer's column values. 3. **Reverse Layers:** After all layers are computed, the list of layers is reversed so that the root layer is at the beginning. ### Example: Commit Process Suppose the input column data is as shown below:
Example column data to commit using a Merkle tree Figure 1: Example column data to commit using a Merkle tree
For this example, the columns are already in the sorted order: $\textcolor{red}{\text{\textit{Column} 0}}$, $\textcolor{green}{\text{\textit{Column} 1}}$, $\textcolor{blue}{\text{\textit{Column} 2}}$ (from longest to shortest). We will now compute the hashes stored at each layer. * **First Layer (Leaves):** The hashes are computed directly from the column values: $[h_{00}, h_{01}, h_{10}, h_{11}] = [H(\textcolor{red}{a}, \textcolor{green}{p}), H(\textcolor{red}{b}, \textcolor{green}{q}), H(\textcolor{red}{c}, \textcolor{green}{r}), H(\textcolor{red}{d}, \textcolor{green}{s})]$ * **Second Layer:** The next layer uses the previous hashes and the values from $\textcolor{blue}{\text{Column 2}}$: $[h_0, h_1] = [H(h_{00}, h_{01}, \textcolor{blue}{u}), H(h_{10}, h_{11}, \textcolor{blue}{v})]$ * **Root:** The root is computed as $root = H(h_0, h_1)$. The resulting Merkle tree is illustrated below:
Merkle tree structure after commitment Figure 2: Merkle tree structure after commitment
## The Decommit Process The decommitment process enables the prover to generate a Merkle proof for a set of queried indices, allowing the verifier to check that specific elements are included in the committed Merkle tree. The output is a `MerkleDecommitment` struct, which contains the hash and column values required for the verifier to reconstruct the Merkle root at the queried positions. Its implementation is shown below: ```rust theme={null} pub struct MerkleDecommitment { /// Hash values that the verifier needs but cannot deduce from previous computations, in the /// order they are needed. pub hash_witness: Vec, /// Column values that the verifier needs but cannot deduce from previous computations, in the /// order they are needed. /// This complements the column values that were queried. These must be supplied directly to /// the verifier. pub column_witness: Vec, } ``` The `decommit` function implemented for the `MerkleProver` takes as input: * `queries_per_log_size`: A map from log size to a vector of query indices for columns of that size. * `columns`: The column data that was committed to in the Merkle tree. It returns: * A vector of queried values, ordered as they are opened (from largest to smallest layer). * A `MerkleDecommitment` containing the hash and column witnesses needed for verification. Below is the complete implementation of the `decommit` function: ```rust theme={null} pub fn decommit( &self, queries_per_log_size: &BTreeMap>, columns: Vec<&Col>, ) -> (Vec, MerkleDecommitment) { // Prepare output buffers. let mut queried_values = vec![]; let mut decommitment = MerkleDecommitment::empty(); // Sort columns by layer. let mut columns_by_layer = columns .iter() .sorted_by_key(|c| Reverse(c.len())) .peekable(); let mut last_layer_queries = vec![]; for layer_log_size in (0..self.layers.len() as u32).rev() { // Prepare write buffer for queries to the current layer. This will propagate to the // next layer. let mut layer_total_queries = vec![]; // Each layer node is a hash of column values as previous layer hashes. // Prepare the relevant columns and previous layer hashes to read from. let layer_columns = columns_by_layer .peek_take_while(|column| column.len().ilog2() == layer_log_size) .collect_vec(); let previous_layer_hashes = self.layers.get(layer_log_size as usize + 1); // Queries to this layer come from queried node in the previous layer and queried // columns in this one. let mut prev_layer_queries = last_layer_queries.into_iter().peekable(); let mut layer_column_queries = option_flatten_peekable(queries_per_log_size.get(&layer_log_size)); // Merge previous layer queries and column queries. while let Some(node_index) = next_decommitment_node(&mut prev_layer_queries, &mut layer_column_queries) { if let Some(previous_layer_hashes) = previous_layer_hashes { // If the left child was not computed, add it to the witness. if prev_layer_queries.next_if_eq(&(2 * node_index)).is_none() { decommitment .hash_witness .push(previous_layer_hashes.at(2 * node_index)); } // If the right child was not computed, add it to the witness. if prev_layer_queries .next_if_eq(&(2 * node_index + 1)) .is_none() { decommitment .hash_witness .push(previous_layer_hashes.at(2 * node_index + 1)); } } // If the column values were queried, return them. let node_values = layer_columns.iter().map(|c| c.at(node_index)); if layer_column_queries.next_if_eq(&node_index).is_some() { queried_values.extend(node_values); } else { // Otherwise, add them to the witness. decommitment.column_witness.extend(node_values); } layer_total_queries.push(node_index); } // Propagate queries to the next layer. last_layer_queries = layer_total_queries; } (queried_values, decommitment) } ``` Let's break down the function step by step: 1. **Sort Columns by Length:** * As in the `commit` function, columns are sorted in descending order of length. 2. **Layer-by-Layer Decommitment:** * For each layer (from largest to smallest): * Collect all columns of the current size (`layer_columns`) and the previous layer's hashes (`previous_layer_hashes`). * Retrieve the queries for the current layer (`layer_column_queries`) and the previous layer's queries (`prev_layer_queries`). * For each node index to be decommitted in this layer: * Check if the child node hashes of the current node can be computed by the verifier, and if not, add the missing child node hashes to the `hash_witness` in the decommitment. * If the node index is queried, fetch the corresponding column values and append them to `queried_values`. * If not queried, add the column values to the `column_witness` in the decommitment. * The set of node indices decommitted in this layer is propagated as queries to the next layer. ### Example: Decommit Process For the column data in **Figure 1**, consider the query indices $[(2, [0]), (1, [1])]$, where the query indices are maps from log size to a vector of query indices for columns of that size. This corresponds to querying the following elements: * The 0th element of columns of size $2^2=4$: $\textcolor{red}{a}$ from $\textcolor{red}{\text{\textit{Column} 0}}$ and $\textcolor{green}{p}$ from $\textcolor{green}{\text{\textit{Column} 1}}$. * The 1st element of the column of size $2^1=2$: $\textcolor{blue}{v}$ from $\textcolor{blue}{\text{\textit{Column} 2}}$. Because columns of equal length are committed together, the same indices are opened together in the decommitment. For example, for query $(2, [0])$, both $\textcolor{red}{a}$ and $\textcolor{green}{p}$ are opened together. Below is a walkthrough of the main loop in the `decommit` function, showing the state of key variables for each `layer_log_size`: * **layer\_log\_size = 2 (columns of size 4):** * `layer_columns`: $[\textcolor{red}{\text{\textit{Column} 0}}, \textcolor{green}{\text{\textit{Column} 1}}]$ * `previous_layer_hashes`: `None` (first layer) * `prev_layer_queries`: empty * `layer_column_queries`: $[0]$ * For `node_index = 0`: * `node_values`: $[\textcolor{red}{a}, \textcolor{green}{p}]$ * Queried, so append to `queried_values`: $[\textcolor{red}{a}, \textcolor{green}{p}]$ * Add `node_index = 0` to `layer_total_queries` (to propagate to next layer) * At this stage: `queried_values` = $[\textcolor{red}{a}, \textcolor{green}{p}]$, `decommitment` is empty. * **layer\_log\_size = 1 (columns of size 2):** * `layer_columns`: $[\textcolor{blue}{\text{\textit{Column} 2}}]$ * `previous_layer_hashes`: $[h_{00}, h_{01}, h_{10}, h_{11}]$ * `prev_layer_queries`: $[0]$ * `layer_column_queries`: $[1]$ * For `node_index = 0`: * Add $h_{01}$ to `hash_witness` in decommitment. * `node_values`: $[\textcolor{blue}{u}]$ * Not queried, so append to `column_witness` in decommitment. * Add `node_index = 0` to `layer_total_queries`. * At this stage: `queried_values` = $[\textcolor{red}{a}, \textcolor{green}{p}]$, `hash_witness` = $[h_{01}]$, `column_witness` = $[\textcolor{blue}{u}]$, `layer_total_queries` = $[0]$. * For `node_index = 1`: * Add $h_{10}, h_{11}$ to `hash_witness` in decommitment. * `node_values`: $[\textcolor{blue}{v}]$ * Queried, so append to `queried_values`. * Add `node_index = 1` to `layer_total_queries`. * At this stage: `queried_values` = $[\textcolor{red}{a}, \textcolor{green}{p}, \textcolor{blue}{v}]$, `hash_witness` = $[h_{01}, h_{10}, h_{11}]$, `column_witness` = $[\textcolor{blue}{u}]$, `layer_total_queries` = $[0, 1]$ (to propagate to next layer). * **layer\_log\_size = 0 (root):** * `layer_columns`: empty * `previous_layer_hashes`: $[h_{0}, h_{1}]$ * `prev_layer_queries`: $[0, 1]$ * `layer_column_queries`: empty * No values are added to `queried_values`, `hash_witness`, or `column_witness` in this layer. **Final output:** * `queried_values`: $[\textcolor{red}{a}, \textcolor{green}{p}, \textcolor{blue}{v}]$ * `decommitment`: * `hash_witness`: $[h_{01}, h_{10}, h_{11}]$ * `column_witness`: $[\textcolor{blue}{u}]$ In the next section, we describe the verification process to verify the inclusion of the queried values using the Merkle decommitment. # Merkle Verifier Source: https://docs.starknet.io/learn/S-two-book/how-it-works/vcs/merkle_verifier This section covers the verification component of the Merkle commitment scheme. The following struct implements the verifier of the Merkle commitment scheme. ```rust theme={null} pub struct MerkleVerifier { pub root: H::Hash, pub column_log_sizes: Vec, pub n_columns_per_log_size: BTreeMap, } ``` The struct elements are defined as follows: * `root`: root hash of the Merkle tree committing to column data * `column_log_sizes`: a vector containing log size values of all the columns * `n_columns_per_log_size`: a map that associates each column log size with the number of columns of that size ## Verifying the decommitment The `verify` function is the main function defined for the `MerkleVerifier`. It takes the following input: * `queries_per_log_size`: A map from log size to a vector of query indices for columns of that size. * `queried_values`: The queried column values, which is one of the outputs of the `decommit` function. * `decommitment`: `MerkleDecommitment` containing the `hash_witness` and `column_witness` required to check inclusion of the `queried_values` in the Merkle tree. This is also one of the outputs of the `decommit` function. Below is the complete implementation of the `verify` function: ```rust theme={null} pub fn verify( &self, queries_per_log_size: &BTreeMap>, queried_values: Vec, decommitment: MerkleDecommitment, ) -> Result<(), MerkleVerificationError> { let Some(max_log_size) = self.column_log_sizes.iter().max() else { return Ok(()); }; let mut queried_values = queried_values.into_iter(); // Prepare read buffers. let mut hash_witness = decommitment.hash_witness.into_iter(); let mut column_witness = decommitment.column_witness.into_iter(); let mut last_layer_hashes: Option> = None; for layer_log_size in (0..=*max_log_size).rev() { let n_columns_in_layer = *self .n_columns_per_log_size .get(&layer_log_size) .unwrap_or(&0); // Prepare write buffer for queries to the current layer. This will propagate to the // next layer. let mut layer_total_queries = vec![]; // Queries to this layer come from queried node in the previous layer and queried // columns in this one. let mut prev_layer_queries = last_layer_hashes .iter() .flatten() .map(|(q, _)| *q) .collect_vec() .into_iter() .peekable(); let mut prev_layer_hashes = last_layer_hashes.as_ref().map(|x| x.iter().peekable()); let mut layer_column_queries = option_flatten_peekable(queries_per_log_size.get(&layer_log_size)); // Merge previous layer queries and column queries. while let Some(node_index) = next_decommitment_node(&mut prev_layer_queries, &mut layer_column_queries) { prev_layer_queries .peek_take_while(|q| q / 2 == node_index) .for_each(drop); let node_hashes = prev_layer_hashes .as_mut() .map(|prev_layer_hashes| { { // If the left child was not computed, read it from the witness. let left_hash = prev_layer_hashes .next_if(|(index, _)| *index == 2 * node_index) .map(|(_, hash)| Ok(*hash)) .unwrap_or_else(|| { hash_witness .next() .ok_or(MerkleVerificationError::WitnessTooShort) })?; // If the right child was not computed, read it to from the witness. let right_hash = prev_layer_hashes .next_if(|(index, _)| *index == 2 * node_index + 1) .map(|(_, hash)| Ok(*hash)) .unwrap_or_else(|| { hash_witness .next() .ok_or(MerkleVerificationError::WitnessTooShort) })?; Ok((left_hash, right_hash)) } }) .transpose()?; // If the column values were queried, read them from `queried_value`. let (err, node_values_iter) = match layer_column_queries.next_if_eq(&node_index) { Some(_) => ( MerkleVerificationError::TooFewQueriedValues, &mut queried_values, ), // Otherwise, read them from the witness. None => ( MerkleVerificationError::WitnessTooShort, &mut column_witness, ), }; let node_values = node_values_iter.take(n_columns_in_layer).collect_vec(); if node_values.len() != n_columns_in_layer { return Err(err); } layer_total_queries.push((node_index, H::hash_node(node_hashes, &node_values))); } last_layer_hashes = Some(layer_total_queries); } // Check that all witnesses and values have been consumed. if hash_witness.next().is_some() { return Err(MerkleVerificationError::WitnessTooLong); } if queried_values.next().is_some() { return Err(MerkleVerificationError::TooManyQueriedValues); } if column_witness.next().is_some() { return Err(MerkleVerificationError::WitnessTooLong); } let [(_, computed_root)] = last_layer_hashes.unwrap().try_into().unwrap(); if computed_root != self.root { return Err(MerkleVerificationError::RootMismatch); } Ok(()) } ``` Let's break down the function step by step: 1. **Initialize Variables:** * Convert `queried_values` into an iterator for consumption during verification. * Create iterators for `hash_witness` and `column_witness` from the `decommitment`. * Initialize `last_layer_hashes` to track computed hashes from the previous layer. 2. **Layer-by-Layer Verification:** * For each layer (from largest to smallest): * Get the number of columns in the current layer (`n_columns_in_layer`) from the map `n_columns_per_log_size`. * Prepare iterators for previous layer queries (`prev_layer_queries`), previous layer hashes (`prev_layer_hashes`) and current layer column queries (`layer_column_queries`). * For each node index: * **Reconstruct `node_hashes`:** Use computed hashes from the `prev_layer_hashes` or read missing sibling hashes from `hash_witness`. * **Get Node Values:** If the node is queried, read column values from `queried_values`; otherwise, read from `column_witness`. * **Compute Hash:** Use the hash function to compute the hash of the current node from its children hashes and column values. * Store the computed hash for propagation to the next layer. 3. **Final Verification:** * Check that all witnesses and queried values have been fully consumed (no excess data). * Verify that the computed root matches the expected root stored in the verifier. * Return `Ok(())` if verification succeeds, or an appropriate error otherwise. ### Example: Verify the decommitment The same example from the [decommit process](/learn/S-two-book/how-it-works/vcs/merkle_prover#example%3A-decommit-process) is used to verify the output of the `decommit` function. The input to the `verify` function is as follows: * `queries_per_log_size`: $[(2, [0]), (1, [1])]$ * `queried_values`: $[\textcolor{red}{a}, \textcolor{green}{p}, \textcolor{blue}{v}]$ * `decommitment`: * `hash_witness`: $[h_{01}, h_{10}, h_{11}]$ * `column_witness`: $[\textcolor{blue}{u}]$ Below is a walkthrough of the main loop in the `verify` function, showing how the verifier reconstructs the Merkle root: * **layer\_log\_size = 2 (columns of size 4):** * `n_columns_in_layer`: 2 (for $\textcolor{red}{\text{\textit{Column} 0}}$ and $\textcolor{green}{\text{\textit{Column} 1}}$) * `last_layer_hashes`: `None` (first layer) * `prev_layer_queries`: empty * `layer_column_queries`: $[0]$ * For `node_index = 0`: * `node_hashes`: `None` (no previous layer) * `node_values`: Read from `queried_values` → $[\textcolor{red}{a}, \textcolor{green}{p}]$ * Compute hash: $h_{00} = H(\textcolor{red}{a}, \textcolor{green}{p})$ * Add to `layer_total_queries`: $(0, h_{00})$ * At this stage: `last_layer_hashes` = $[(0, h_{00})]$ * **layer\_log\_size = 1 (columns of size 2):** * `n_columns_in_layer`: 1 (for $\textcolor{blue}{\text{\textit{Column} 2}}$) * `last_layer_hashes`: $[(0, h_{00})]$ * `prev_layer_queries`: $[0]$ * `layer_column_queries`: $[1]$ * For `node_index = 0`: * `node_hashes`: Left child is $h_{00}$ (computed from `last_layer_hashes`), right child $h_{01}$ read from `hash_witness` * `node_values`: Read from `column_witness` → $[\textcolor{blue}{u}]$ * Compute hash: $h_0 = H(h_{00}, h_{01}, \textcolor{blue}{u})$ * Add to `layer_total_queries`: $(0, h_0)$ * For `node_index = 1`: * `node_hashes`: Both children $h_{10}, h_{11}$ read from `hash_witness` * `node_values`: Read from `queried_values` → $[\textcolor{blue}{v}]$ * Compute hash: $h_1 = H(h_{10}, h_{11}, \textcolor{blue}{v})$ * Add to `layer_total_queries`: $(1, h_1)$ * At this stage: `last_layer_hashes` = $[(0, h_0), (1, h_1)]$ * **layer\_log\_size = 0 (root):** * `n_columns_in_layer`: 0 (no columns of size 1) * `last_layer_hashes`: $[(0, h_0), (1, h_1)]$ * `prev_layer_queries`: $[0, 1]$ * `layer_column_queries`: empty * For `node_index = 0`: * `node_hashes`: Left child is $h_0$, right child is $h_1$ (both computed from `last_layer_hashes`) * `node_values`: empty (no columns) * Compute hash: $root = H(h_0, h_1)$ * Add to `layer_total_queries`: $(0, root)$ * At this stage: `last_layer_hashes` = $[(0, root)]$ **Final verification:** * Check that all iterators are exhausted: `hash_witness`, `queried_values`, and `column_witness` should all be empty. * Compare the computed `root` with the expected root stored in the `MerkleVerifier`. * If they match, return `Ok(())`; otherwise, return `Err(MerkleVerificationError::RootMismatch)`. # Introduction Source: https://docs.starknet.io/learn/S-two-book/introduction S-two is a state-of-the-art framework for creating STARK proofs that provides the following features: * A frontend designed to be flexible to allow you to express your own constraints * A backend that leverages Circle STARKs over the Mersenne31 prime field for fast prover performance * Seamlessly integrated with Cairo This book will guide you through the process of creating your own constraints and then proving them using S-two. It will also provide in-depth explanations of the inner workings of how S-two implements Circle STARKs. # Why S-two? Source: https://docs.starknet.io/learn/S-two-book/why-stwo Before we dive into why we should choose S-two, let's define some terminology. When we talked about proof systems in the previous section, we mentioned that we can create a proof of a statement using a proof system. In reality, however, we first need to structure the function involved in the statement in a way that it can be proven. This structuring part is often referred to as the **frontend**, while the rest of the process of creating a proof is commonly referred to as the **backend**. With that out of the way, let's dive into some of the advantages of using S-two. First, S-two is a standalone framework that provides both the frontend and backend and therefore handles the entire proving process. There are other frameworks that only provide the frontend or the backend, which has its advantages as its modular structure makes it possible to pick and choose a backend or frontend of one's liking. However, having a single integrated frontend and backend reduces the complexity of the system and is also easier to maintain. In addition, S-two's frontend structures statements as an **Algebraic Intermediate Representation (AIR)**, which is a representation that is especially useful for proving statements that are repetitive (e.g. the CPU in a VM, which essentially repeats the same fetch-decode-execute over and over again). S-two's backend is also optimized for prover performance. This is due to largely three factors. 1. It implements **STARKs**, or hash-based SNARKs, which boasts a faster prover compared to elliptic curve-based SNARKs like Groth16 or PLONK. This improvement comes mainly from running the majority of the computation in a small prime field (32 bits); Elliptic curve-based SNARKs, on the other hand, need to use big prime fields (e.g. 254-bit prime fields), which incur a lot of overhead as most computation does not require that many bits. 2. Even amongst multiple STARK backends, however, S-two provides state-of-the-art prover performance by running the **Mersenne-31 prime field** (modulo $2^{31} - 1$), which is faster than another popular 32-bit prime field like BabyBear (modulo $2^{31} - 2^{27} + 1$). We suggest going through [this post](https://blog.zksecurity.xyz/posts/circle-starks-1/) for a breakdown of why this is the case. 3. Finally, S-two offers **various CPU and GPU optimizations** that improves prover performance as shown in [Figure 1](#figure-1) below. It can also be compiled to WASM, allowing for fast proving in web environments.
*Figure 1: Prover performance optimizations in S-two*
One of the drawbacks of STARKs is that they have a larger proof size compared to elliptic curve-based SNARKs. One way to mitigate this drawback is by batching multiple proofs together to form a single proof. **On zero-knowledge:** As of the time of this writing, S-two does not provide the "zero-knowledge" feature. "Zero-knowledge" here refers to the fact that the proof should not reveal any additional information other than the validity of the statement, which is not true for S-two as it reveals to the verifier commitments to its witness values without hiding them by e.g. adding randomness. This reveals **some information** about the witness values, which may be used in conjunction with other information to infer the witness values. # Why Use a Proof System? Source: https://docs.starknet.io/learn/S-two-book/why-use-a-proof-system At its core, a proof system proves that a statement is valid. For example, it can prove that a function with a certain input results in a certain output, i.e. $C(x)=y$. To check the statement, we can do it either directly by computing $C(x)$ and comparing the result to $y$, or indirectly by verifying the proof. The verifier can benefit from the second option in terms of time and space, if the time to verify the proof is faster than the time to compute the function, or the size of the proof is smaller than the input to the statement. This property of a proof system is often referred to as **succinctness**, and it is exactly why proof systems have seen wide adoption in the blockchain space, where computation on-chain is much more expensive compared to off-chain computation. Using a proof system, it becomes possible to replace a large collection of computation to be executed on-chain with a proof of execution of the same collection of computations and verifying it on-chain. This way, proofs can be generated off-chain using large machines and verified on-chain with much less computation. But there are applications of proof systems beyond just blockchains. Generally speaking, it can be used as auxiliary data to verify that the computation of an untrusted party was done correctly. For example, when we delegate computation to an untrusted server, we can ask it to provide a proof along with the computation result that the result indeed came from running a specific computation. Another example could be to ask a server running an ML model to provide proof that it ran inference on the correct model. The size of the accompanying proof and the time to verify it will be negligible compared to the cost of running the computation, but we gain the guarantee that the computation was done correctly. Another optional feature of proof systems is **zero-knowledge**, which means that the proof reveals nothing about the computation other than its validity. In general, the output $y$ of the computation $C(x)=y$ will be public (i.e. revealed to the verifier), but the input $x$ will be, without loss of generality, private from the verifier. With this feature, the intermediate values computed by the prover while computing $C(x)$ will also be hidden from the verifier. # Chain information Source: https://docs.starknet.io/learn/cheatsheets/chain-info You can get the latest updates delivered to your inbox by subscribing to the [Starknet Devs Newsletter](https://www.starknet.io/starknet-devs-newsletter/). ## Current versions | Environment | Starknet version | Sierra version | Cairo version | | - | - | - | - | | Mainnet | 0.14.4 | 1.9.0 | 2.0.0 - 2.19.0 | | Sepolia | 0.14.4 | 1.9.0 | 2.0.0 - 2.19.0 | ## Current limits (mainnet) The following limits are subject to revisions and change on a regular basis Starting from v0.13.0, blockifier-related constants and limits are defined for each Starknet version in a JSON file called `versioned_constants` in the [blockifier's resources directory](https://github.com/starkware-libs/sequencer/tree/main/crates/blockifier/resources). Starting from v0.13.5, resources are denominated in units of L2 gas, L1 gas, and L1 data gas. | Entity | Description | Limit | | - | - | - | | Block time | The maximum amount of time within which a preconfirmed block is closed, if no other limit is met | 9.5 seconds | | Block recommended time | The amount of time after which preconfirmed block is closed, if there are no transactions in the mempool and no transactions being executed ATM | 2 seconds | | Max L2 gas per block | The maximum amount of L2 gas that a block can contain

**Note:** This limit ensures block production times remains consistent and predictable. | 6 \* 10^9 | | Max transactions per block | The maximum amount of transactions in a single block | 500 | | Max L2 gas per transaction | The maximum number of computational steps, measured in L2 gas, that a transaction can contain when processed on the Starknet network

**Note:** This limit is important for ensuring the efficient execution of transactions and preventing potential congestion. | 1.11\*10^9 | | Max state updates per transaction | The maximum number of storage updates that a single transaction can generate

**Note:** This limit helps maintain network stability and predictable performance. | 2,490 | | Max number of events per transaction | The maximum number of events that a transaction can emit during its execution | 1,000 | | Max number of data felts per event | The maximum number of felts that an event can contain in its `data` array | 300 | | Max number of key felts per event | The maximum number of felts that an event can contain in its `keys` array | 50 | | Max L2 gas for `validate` | The maximum number of computational steps, measured in Cairo steps, for a `validate` function | 10^8 | | Max contract bytecode size | The maximum size of the bytecode or program that a smart contract can have on Starknet | 81,920 felts | | Max contract class size | The maximum size for the Sierra file of a contract class within Starknet

**Note:** This limit is important for ensuring the network's scalability and security. | 4,089,446 bytes | | IP address limits | The amount of contract reads that a single IP address can make (in order to reduce network spam) | 200 per min per IP address | | Signature length | The maximum length of a signature | 4,000 felts | | Calldata length | The maximum length of a transaction calldata | 5,000 felts | ### Block builtin limits Currently, block builtin limits apply only for Sepolia. For more information, see [the release notes for Starknet version 0.13.6](/learn/cheatsheets/version-notes#starknet_v0_13_6_8_jul_25). | Builtin | Limit | | - | - | | Range Check | 57,142,857 | | ECDSA | 3,000 | | Poseidon | 479,961 | | Pedersen | 493,827 | | Bitwise | 6,861,063 | | EC OP | 6,994 | | Keccak | 9,790 | | Range Check 96 | 71,428,571 | | Add Mod | 16,000,000 | | Mul Mod | 6,622,516 | ## Deprecated features A deprecated feature is a feature that is still supported, but support will be removed in a future release of Starknet. | Name | Description | | - | - | | Cairo 0 | [Starknet v0.11.0](/learn/cheatsheets/version-notes#version0.11.0) introduces Cairo 1.0 smart contracts. | | Transaction signing only over two resources | RPC version 0.8, introduced in Starknet 0.13.5, only supports `v3` transactions that sign over three resources (L1 gas, L2 gas and L1 data gas), removing support for transaction versions `v0`, `v1`, and `v2`, and for `v3` transactions that sign only over two resources (L1 gas and L2 gas).

Starting from version v0.14.0, Starknet will stop accepting these transactions altogether, and will only accept `v3` transactions compliant with RPC 0.8. | ## Unsupported and removed features An unsupported feature is a feature that is no longer supported. A removed feature is a feature that has been entirely removed. | Name | Description | | - | - | | Starknet CLI | Support for the Starknet CLI has been removed. Instead use [`Starkli or Starknet Foundry's sncast`](./tools). | | Goerli testnet | Goerli testnet support was removed April 2, 2024. Sepolia testnet replaces Goerli testnet.

Starknet started migrating to Sepolia testnet on November 15th, 2023. For more information on the Goerli deprecation, see [the deprecation announcement on Ethereum's site](https://ethereum.org/nb/developers/docs/networks/#ethereum-testnets).

Full nodes, API services, SDKs, and other Starknet developer tools have migrated to Sepolia as well.

**Note:** Sepolia's state and history are relatively small. Sepolia [supports declaring classes of CairoZero and Cairo v2.0.0 and higher](/learn/cheatsheets/version-notes). | | Starknet feeder gateway | The Starknet feeder gateway, a temporary solution for querying the sequencer's state, is being replaced by Starknet full nodes (Pathfinder, Juno, Deoxys, Papyrus) and RPC services. For more information, see [Full nodes and API services](./tools).

Support for the feeder gateway queries that are not required for full nodes to synchronize on the state of Starknet will stop according to the following schedule:

**Environment** \| **Date**
Integration \| 1 November 2023
Testnet \| 15 November 2023
Mainnet \| 19 December 2023

Queries that are required for full nodes to synchronize on the state of Starknet are still supported.

For more information, see the Community Forum post [*Feeder Gateway Deprecation*](https://community.starknet.io/t/feeder-gateway-deprecation/100233). | | Free L1-> L2 messaging | Previously, sending a message from L1 to L2 had an optional fee associated.

From [Starknet v0.11.0](/learn/cheatsheets/version-notes#version0.11.0), the fee mechanism is enforced and the ability to send L1->L2 messages without the corresponding L2 fee has been removed.

See [here](/learn/protocol/messaging#l1-l2-message-fees) for more details. | | `invoke` transaction v0 | `invoke` transaction v0 has been removed since [Starknet v0.11.0](/learn/cheatsheets/version-notes#version0.11.0). | | `declare` transaction v0 | `declare` transaction v0 has been removed since [Starknet v0.11.0](/learn/cheatsheets/version-notes#version0.11.0). | | `deploy` transaction | The `deploy` transaction has been removed since [Starknet v0.10.3](/learn/cheatsheets/version-notes#version0.10.3).

To deploy new contract instances, you can use the [`deploy system call`](https://book.cairo-lang.org/appendix-08-system-calls.html#deploy). | ## Important addresses ### General #### Mainnet | | | | - | - | | Sequencer base URL | alpha-mainnet.starknet.io | | Core contract | 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4 | | SHARP verifier contract | 0x47312450B3Ac8b5b8e247a6bB6d523e7605bDb60 | #### Sepolia | | | | - | - | | Sequencer base URL | alpha-sepolia.starknet.io | | Core contract | 0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057 | | SHARP verifier contract | 0x07ec0D28e50322Eb0C159B9090ecF3aeA8346DFe | ### Tokens #### Mainnet | | | | - | - | | STRK contract | 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d | | ETH contract | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 | | vSTRK contract | 0x0782f0ddca11d9950bc3220e35ac82cf868778edb67a5e58b39838544bc4cd0f | #### Sepolia | | | | - | - | | STRK contract | 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d | | ETH contract | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 | | vSTRK contract | 0x035c332b8de00874e702b4831c84b22281fb3246f714475496d74e644f35d492 | ### StarkGate #### Mainnet | | | | - | - | | StarkgateManager contract | 0x0c5aE94f8939182F2D06097025324D1E537d5B60 | | StarkgateRegistry contract | 0x1268cc171c54F2000402DfF20E93E60DF4c96812 | | L1 StarknetTokenBridge contract | 0xF5b6Ee2CAEb6769659f6C091D209DfdCaF3F69Eb | | L2 StarknetTokenBridge contract | 0x0616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256 | | Bridged tokens | [https://github.com/starknet-io/starknet-addresses/blob/master/bridged\_tokens/mainnet.json](https://github.com/starknet-io/starknet-addresses/blob/master/bridged_tokens/mainnet.json) | #### Sepolia | | | | - | - | | Bridged tokens | [https://github.com/starknet-io/starknet-addresses/blob/master/bridged\_tokens/sepolia.json](https://github.com/starknet-io/starknet-addresses/blob/master/bridged_tokens/sepolia.json) | ### Staking #### Mainnet | | | | - | - | | Staking contract | 0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7 | | L1 Reward supplier contract | 0xCa1406D57eD09947E68DE121316C87113fBE9ff5 | | L2 Reward supplier contract | 0x009035556d1ee136e7722ae4e78f92828553a45eed3bc9b2aba90788ec2ca112 | | Mint manager contract | 0xCa14076A3cec95448BaD179cc19B351A4204B88B | | Minting curve contract | 0x00ca1705e74233131dbcdee7f1b8d2926bf262168c7df339004b3f46015b6984 | | Attestation contract | 0x10398fe631af9ab2311840432d507bf7ef4b959ae967f1507928f5afe888a99 | | **Stakable tokens** | | | :- | :- | | STRK | 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d | | WBTC | 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac | | SolvBTC | 0x0593e034dda23eea82d2ba9a30960ed42cf4a01502cc2351dc9b9881f9931a68 | | tBTC | 0x04daa17763b286d1e59b97c283c0b8c949994c361e426a28f743c67bdfe9a32f | | LBTC | 0x036834a40984312f7f7de8d31e3f6305b325389eaeea5b1c0664b2fb936461a4 | #### Sepolia | | | | - | - | | Staking contract | 0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 | | L1 Reward supplier contract | 0xE58d25681B9d290D60e4d7f379a05d5BFD973fFB | | L2 Reward supplier contract | 0x02ebbebb8ceb2e07f30a5088f5849afd4f908f04f3f9c97c694e5d83d2a7cc61 | | Mint manager contract | 0x648D1B35a932F5189e7ff97b2F795E03734DE4ce | | Minting curve contract | 0x06043928ca93cff6d6f39378ba391d7152eea707bdd624c1b2074e71af2abaca | | Attestation contract | 0x03f32e152b9637c31bfcf73e434f78591067a01ba070505ff6ee195642c9acfb | | **Stakable tokens** | | | :- | :- | | STRK | 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d | | TestBTC1 token | 0x044ad07751ad782288413c7db42c48e1c4f6195876bca3b6caef449bb4fb8d36 | | TestBTC2 token | 0x07e97477601e5606359303cf50c050fd3ba94f66bd041f4ed504673ba2b81696 | # Compatibility tables Source: https://docs.starknet.io/learn/cheatsheets/compatibility For the RPC, Cairo, and Sierra versions, the version specified is the latest version supported by the respective Starknet version. For the Pathfinder and Juno versions, the version specified is the recommended version for running a full node with the respective Starknet version. ## Starknet versions | | Starknet 0.14.3 | Starknet 0.14.2 | Starknet 0.14.1 | Starknet 0.14.0 | | - | - | :- | :- | :- | | RPC | 0.10.2 | 0.10.2 | 0.10 | 0.9 | | Cairo | 2.19.0 | 2.18.0 | 2.13.2 | 2.12.0 | | Sierra | 1.9.0 | 1.7.0 | 1.7.0 | 1.7.0 | | Pathfinder | 0.22.7 | 0.22.2 | 0.21.3 | 0.18.0 | | Juno | 0.16.4 | 0.15.22 | 0.15.15 | 0.15.1 | ## Starknet RPC versions For the latest Starknet RPC API version, the version specified is the recommended version for working with the respective API version. If a newer version exist, the newest version should be used instead. For older versions, the version specified is the last recommended version of the tool that worked with the respective API version. | | RPC 0.10.2 | RPC 0.10 | RPC 0.9 | RPC 0.8 | | - | - | :- | :- | :- | | Pathfinder | 0.22.7 | 0.21 | 0.18.0 | 0.16.2 | | Juno | 0.16.4 | 0.15.11 | 0.15.1 | 0.14.2 | | Starknet Foundry | 0.62.0 | 0.53 | 0.48.0 | 0.39.0 | | Starknet Devnet | 0.9.0 | 0.7 | 0.5.0-rc.4 | 0.3.0 | | starknet.js | 10.4.0 | 8.9.0 | 8.0.0-beta.4 | 7.0.1 | | starknet.py | 0.30.0 | 0.29.0 | 0.28.0-rc.3 | 0.26.0 | | starknet-rs | 0.19.1 | 0.18.0 | 0.17.2-rc.2 | 0.14.0 | | starknet-jvm | 0.18.0 | 0.17.0-rc.0 | 0.16.0-rc.0 | 0.14.0 | | starknet.swift | 0.16.0 | 0.15.0 | v0.14.0-rc.1 | 0.13.1 | ## # Developer integrations Source: https://docs.starknet.io/learn/cheatsheets/integrations This page is continuously updated. If a project you are using is missing, please consider opening an issue or editing the page. ## Infrastructure * **Full nodes:** [Juno](https://www.nethermind.io/juno), [Madara](https://github.com/madara-alliance/madara), and [Pathfinder](https://docs.swmansion.com/pathfinder) * **RPC providers:** [Alchemy](http://www.alchemy.com/starknet), [All That Node](https://www.allthatnode.com/starknet.dsrv), [BlockPI](http://blockpi.io/starknet), [Chainstack](https://chainstack.com/build-better-with-starknet/), [dRPC](https://drpc.org/chainlist/starknet), [Dwellir](https://www.dwellir.com/), [GetBlock](https://getblock.io/nodes/strk/), [Infura](https://www.infura.io/networks/ethereum/starknet), [Lava](https://www.lavanet.xyz/), [NOWNodes](https://nownodes.io/starknet), [OMNIA](https://omniatech.io/), [OnFinality](https://onfinality.io/networks/starknet), [QuickNode](https://www.quicknode.com/chains/strk), [Spectrum](https://spectrumnodes.com/), and [Zan](https://zan.top/home/node-service) OnFinality also provides a WSS open endpoint, and dRPC and Lava also provide open endpoints to Sepolia. For version support: * In Dwellir, GetBlock, Lava, and Zan, use the `/rpc/vX_Y` suffix (e.g., use `/rpc/v0_9` for v0.9.0) * In Alchemy, see [docs.alchemy.com/reference/starknet-api-faq](https://docs.alchemy.com/reference/starknet-api-faq#what-versions-of-starknet-api-are-supported) * In Chainstack, see [docs.chainstack.com/reference/getting-started-starknet](https://docs.chainstack.com/reference/getting-started-starknet#starknet-json-rpc-version-endpoints) * In QuickNode, see [quicknode.com/docs/starknet](https://www.quicknode.com/docs/starknet#supporting-multiple-versions) For complete information on the Starknet JSON-RPC API, see [`starknet_api_openrpc.json`](https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json). * **Sepolia Faucet:** [Starknet Foundation](https://faucet.starknet.io/) * **Paymaster:** [AVNU](https://github.com/avnu-labs/paymaster) ## Analytics and monitoring * **Block explorers:** [Cartridge](https://explorer.cartridge.gg/), [OKLink](https://www.oklink.com/starknet), [Starkscan](https://starkscan.co), [ViewBlock](https://viewblock.io/starknet), and [Voyager](https://voyager.online) [Starkscan](https://sepolia.starkscan.co/) and [Voyager](https://sepolia.voyager.online) also provide explorers for Sepolia. * **Monitoring tools:** [Starknet Monitor](https://www.starknet-monitor.com/) and [Starknet Status](https://status.starknet.io/) * **Indexers:** [Apibara](https://www.apibara.com/), [Checkpoint](https://checkpoint.box/), [DipDup](https://dipdup.io/), [Footprint Analytics](https://www.footprint.network/), [Goldsky](https://goldsky.com/chains/starknet), [SubQuery](https://subquery.network/), [The Graph](https://thegraph.com/), and [Token Flow](https://tokenflow.live/) * **Enhanced APIs:** [Alchemy](https://docs.alchemy.com/reference/starknet-api-endpoints#nft-api), [ArkProject](https://www.arkproject.dev/products/nft-api), and [Starkscan](https://starkscan.co/api-info) ## Bridges and interoperability * **Bridges:** [ArkProject](https://bridge.arkproject.dev/) (NFT bridge), [Layerswap](https://layerswap.io/app), [Mini Bridge](https://minibridge.chaineye.tools/?src=ethereum\&dst=starknet), [Orbiter Finance](https://www.orbiter.finance/en?src_chain=1\&tgt_chain=SN_MAIN\&src_token=ETH), [Owlto Finance](https://owlto.finance/), [Rango](https://app.rango.exchange/bridge?fromBlockchain=ARBITRUM\&fromToken=ETH\&toBlockchain=STARKNET\&toToken=ETH—0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7), [Rhino.fi](https://app.rhino.fi/bridge?token=ETH\&chainOut=STARKNET\&chain=ETHEREUM), [RetroBridge](https://app.retrobridge.io/?tokenFrom=ETH), and [StarkGate](https://starkgate.starknet.io/bridge/deposit) * **Oracles:** [Chainlink](https://docs.chain.link/data-feeds/starknet/), [Pragma](https://docs.pragma.build/starknet/), and [Pyth](https://docs.pyth.network/price-feeds/use-real-time-data/starknet/) ## User interfaces * **Wallets:** [Braavos](https://braavos.app/), [Keplr](https://www.keplr.app/), [Ledger](https://www.ledger.com/), [MetaMask](https://snaps.consensys.io/starknet), and [Ready](https://www.ready.co/ready-wallet) * **On-ramps:** [Banxa](https://banxa.com/), [Changelly](https://changelly.com/), [Checkout](https://fun.xyz/), [Moonpay](https://www.moonpay.com/), [Onramp Money](https://onramp.money/), [Ramp](https://ramp.network/), [Transak](https://transak.com/), and [Unlimit Crypto](https://www.crypto.unlimit.com/) ## Security * **Auditors:** [ABDK](https://www.abdk.consulting/), [Beosin](https://beosin.com/), [Beyond](https://beyondaudit.github.io/), [Cairo Security Clan](https://cairosecurityclan.com/), [ChainSecurity](https://chainsecurity.com/), [CODESPECT](https://codespect.net/), [ConsenSys](http://consensys.net/diligence), [Defense](https://thesis.co/defense), [Extropy](https://www.extropy.io/), [FuzzingLabs](https://fuzzinglabs.com/), [Ginger Security](https://gingersec.xyz/), [Hashlock](https://hashlock.com/), [Nethermind Security](https://www.nethermind.io/smart-contract-audits), [Oak Security](https://www.oaksecurity.io/), [Open Zeppelin](https://www.openzeppelin.com/), [OtterSec](https://osec.io/), [PeckShield](https://peckshield.com/), [Trail of Bits](http://www.trailofbits.com/), and [Zellic](https://www.zellic.io/) * **Security platforms:** [Hypernative](https://www.hypernative.io/) and [Trustblock](https://trustblock.run/) # Messaging functions and events Source: https://docs.starknet.io/learn/cheatsheets/messaging-reference For information on the messaging mechanism, see [Messaging mechanism](/learn/protocol/messaging/). ## L1 function reference ### `cancelL1ToL2Message` #### Description Cancels an L1 to L2 message. Call this function after calling the [`startL1ToL2MessageCancellation`](#startL1ToL2MessageCancellation) function. The time between the calls to these two functions must be at least the number of seconds defined by the [`messageCancellationDelay`](#messageCancellationDelay) function. Only a sender can cancel a message. If the message is missing, the call reverts. Be aware that the message fee is not refunded. #### State Mutability None. #### Parameters | | | | - | - | | `uint256 toAddress` | The address of the L2 contract. | | `uint256 selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] calldata payload` | The payload of the message. | | `uint256 nonce` | The nonce of the message. | #### Returns | | | | - | - | | `bytes32 msgHash` | The hash of the canceled message. | #### Emitted event [`MessageToL2Canceled`](#MessageToL2Canceled) #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`cancelL1ToL2Message`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L161) ### `consumeMessageFromL2` #### Description Consumes a message that was sent from an L2 contract. Returns the hash of the message. #### State Mutability None. #### Parameters | | | | - | - | | `uint256 fromAddress` | The address of the L2 contract sending the message. | | `uint256[] calldata payload` | The payload of the message. | #### Returns | | | | - | - | | `bytes32 msgHash` | The hash of the consumed message. | #### Emitted event [`ConsumedMessageToL1`](#ConsumedMessageToL1) #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`consumeMessageFromL2`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L132) *** ### `getMaxL1MsgFee` #### Description Returns the maximum fee, in Wei, that Starknet accepts for a single message. If the fee passed is higher than this value, the transaction is not accepted. #### State Mutability `pure` #### Parameters None. #### Returns | | | | - | - | | `uint256 MAX_L1_MSG_FEE` | The maximum fee, in Wei, that Starknet accepts for a single message. | #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`getMaxL1MsgFee`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L31) *** ### `l1ToL2MessageCancellations` #### Description Returns the timestamp of the cancellation request. #### State Mutability `view` #### Parameters | | | | - | - | | `bytes32 msgHash` | The message hash. | #### Returns | | | | - | - | | `uint256 result` | The Ethereum block timestamp. | Returns `0` if `cancelL1ToL2Message` was not called with the message hash `msgHash`. #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`l1ToL2MessageCancellations`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L73) *** ### `l1ToL2MessageNonce` #### Description Returns the nonce of the next message sent to the L2 contract. So if If `n` messages have been sent to Starknet, this function returns `n + 1`. #### State Mutability `view` #### Parameters None. #### Returns | | | | - | - | | `uint256 nonce` | The nonce of the next message sending to L2 contract. | #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`l1ToL2MessageNonce`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L55) *** ### `l1ToL2Messages` #### Description Indicates if a pending message is associated with a given message hash. #### State Mutability `view` #### Parameters | | | | - | - | | `bytes32 msgHash` | The message hash. | #### Returns Returns `uint256 result`, where `result` is one of the following: | | | | - | - | | `message_fee + 1` | A pending message is associated with the `msgHash` parameter. | | `0` | No pending message is associated with the `msgHash` parameter. | #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`l1ToL2Messages`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L39) *** ### `l2ToL1Messages` #### Description Indicates the number of pending messages associated with a given message hash. #### State Mutability `view` #### Parameters | | | | - | - | | `bytes32 msgHash` | The message hash. | #### Returns Returns `uint256 result`, where `result` is the number of pending messages associated with the `msgHash` parameter. #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`l2ToL1Messages`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L43) *** ### `messageCancellationDelay` #### Description Returns the time interval, in seconds, after which you can cancel a message starting from the moment of calling the [`startL1ToL2MessageCancellation`](#startL1ToL2MessageCancellation) function. You can get the real value by calling the [`messageCancellationDelay`](https://etherscan.io/address/0xc662c410c0ecf747543f5ba90660f6abebd9c8c4#readProxyContract#F11) function on a block explorer, such as Etherscan. #### State Mutability `view` #### Parameters None. #### Returns | | | | - | - | | `uint256 result` | The time interval. | #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`messageCancellationDelay`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L59) *** ### `sendMessageToL2` #### Description Sends a message to an L2 contract. The message fee is the cost of executing this function. #### State Mutability `payable` #### Parameters | | | | - | - | | `uint256 toAddress` | The address of the L2 contract. | | `uint256 selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] calldata payload` | The payload of the message. | #### Returns | | | | - | - | | `bytes32 msgHash` | The hash of the message. | | `uint256 nonce` | The nonce of the message. | #### Emitted event [`LogMessageToL2`](#LogMessageToL2) #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`sendMessageToL2`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L110) *** ### `startL1ToL2MessageCancellation` #### Description Starts the cancellation of a message from L1 to L2. You can cancel a message after a predefined about of time from the moment this function is called. The amount of time is set by the [`messageCancellationDelay`](#messageCancellationDelay) function. You can only call this function for a message that is currently pending, and the caller must be the sender of that message. #### State Mutability None. #### Parameters | | | | - | - | | `uint256 toAddress` | The address of the L2 contract. | | `uint256 selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] calldata payload` | The payload of the message. | | `uint256 nonce` | The nonce of the message. | #### Returns | | | | - | - | | `bytes32 msgHash` | The hash of the cancellation message. | #### Emitted event [`MessageToL2CancellationStarted`](#MessageToL2CancellationStarted) #### Function definition Contract: [`StarknetMessaging.sol`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol) * Function: [`startL1ToL2MessageCancellation`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/StarknetMessaging.sol#L147) ## L1 event reference Events are listed in alphabetical order. *** ### `ConsumedMessageToL1` #### Description This event is emitted when a message from L2 to L1 is consumed by the [`consumeMessageFromL2`](#consumeMessageFromL2) function. #### Event attributes | | | | - | - | | `uint256 indexed fromAddress` | The address of the sender on L2. | | `address indexed toAddress` | The address of the receiver on L1. | | `uint256[] payload` | The payload of the consumed message. | #### Event definition * Contract: [`IStarknetMessagingEvents`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol) * Event: [`ConsumedMessageToL1`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol#L19) *** ### `LogMessageToL2` #### Description This event is emitted when a message is sent from L1 to L2 by the [`sendMessageToL2`](#sendMessageToL2) function. #### Event attributes | | | | - | - | | `address indexed fromAddress` | The address of the sender on L1. | | `uint256 indexed toAddress` | The address of the receiver on L2. | | `uint256 indexed selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] payload` | The payload of the message. | | `uint256 nonce` | The nonce of the message. | | `uint256 fee` | The fee associated with the message. | #### Event definition * Contract: [`IStarknetMessagingEvents`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol) * Event: [`LogMessageToL2`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol#L9) *** ### `MessageToL2Canceled` #### Description This event is emitted when an L1 to L2 message is canceled by the [`cancelL1ToL2Message`](#cancelL1ToL2Message) function. #### Event attributes | | | | - | - | | `address indexed fromAddress` | The address of the sender on L1. | | `uint256 indexed toAddress` | The address of the receiver on L2. | | `uint256 indexed selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] payload` | The payload of the canceled message. | | `uint256 nonce` | The nonce of the canceled message. | #### Event definition * Contract: [`IStarknetMessagingEvents`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol) * Event: [`MessageToL2Canceled`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol#L44) *** ### `MessageToL2CancellationStarted` #### Description This event is emitted when the cancellation of an L1 to L2 message is started by the [`startL1ToL2MessageCancellation`](#startL1ToL2MessageCancellation) function. #### Event attributes | | | | - | - | | `address indexed fromAddress` | The address of the sender on L1. | | `uint256 indexed toAddress` | The address of the receiver on L2. | | `uint256 indexed selector` | The function, in the recipient L2 contract, that the message called. | | `uint256[] payload` | The payload of the message to be canceled. | | `uint256 nonce` | The nonce of the message to be canceled. | #### Event definition * Contract: [`IStarknetMessagingEvents`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol) * Event: [`MessageToL2CancellationStarted`](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/solidity/IStarknetMessagingEvents.sol#L35) # StarkGate functions and events Source: https://docs.starknet.io/learn/cheatsheets/starkgate-reference ## L1 functions and events ### `deposit` #### Description Deposits the specified amount of an ERC-20 token to the L1 StarkGate bridge contract. The deposit function does the following: * Transfers the funds from the caller's account to the Starknet bridge contract * Emits the `Deposit` event with the sender's address on L1, the recipient's address on L2, and the amount #### Visibility `external` #### State Mutability `payable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | #### Returns None. #### Emitted event `Deposit` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256 nonce` | The nonce for the L1 transaction. | | `uint256 fee` | The Starknet fee sent with the transaction. | #### Function and event definitions * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`deposit`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L284) * Event: [`Deposit`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L65) *** ### `depositCancelRequest` #### Description Sends a request to StarkGate to cancel a deposit. You can send a cancellation request if the funds you transfer from L1 to L2 do not appear on L2 within a reasonable amount of time. In order to guard against an attack on Starknet, it takes approximately five days to cancel a deposit. When StarkGate receives the cancellation request, a counter begins. When exactly five days have passed, and the funds still do not appear on L2, you can reclaim the deposit using the [`depositReclaim`](#depositReclaim) function. The `depositReclaim` function can only be used once for any deposit cancellation request. #### Visibility `external` #### State Mutability `nonpayable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | | `uint256 nonce` | The nonce of the deposit. | #### Returns None. #### Emitted event `DepositCancelRequest` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256 nonce` | The nonce of the deposit. | #### Function and event definitions * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`depositCancelRequest`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L515) * Event: [`DepositCancelRequest`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L73) *** ### `depositReclaim` #### Description Reclaims a deposit after a five day period has passed from the time that StarkGate received a deposit cancellation request from the `depositCancelRequest` function. #### Visibility `external` #### State Mutability `nonpayable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | | `uint256 nonce` | The nonce of the deposit. | #### Returns None. #### Emitted event `DepositReclaimed` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256 nonce` | The nonce of the deposit. | #### Function and event definitions * Contract: `StarknetTokenBridge.sol` * Function: [`depositReclaim`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L588) * Event: [`DepositReclaimed`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L80) #### See also * [`depositCancelRequest`](#depositCancelRequest) *** ### `depositWithMessage` #### Description Similar to [`deposit`](#deposit), with a message attached. With this function, a deposit transaction can trigger subsequent actions. For example, you can deposit funds and include a message to transfer those funds to another address. `depositWithMessage` lets you execute these two separate transactions with a single user action. After depositing to another recipient, the L1 handler in `token_bridge.cairo` calls the `on_receive` function in the contract of the recipient. If `on_receive` returns `true`, then the `on_receive` function succeeded. If it returns `false`, or if it doesn't return any value because the `on_receive` function is not implemented in the recipient contract, the operation fails and the transaction is reverted. #### Visibility `external` #### State Mutability `payable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | | `uint256[] calldata message` | The message attached to the deposit. | #### Returns None. #### Emitted event `DepositWithMessage` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256[] message` | The message attached to the deposit. | | `uint256 nonce` | The nonce for the L1 transaction. | | `uint256 fee` | The Starknet fee sent with the transaction. | #### Function and event definitions * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`depositWithMessage`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L255) * Event: [`DepositWithMessage`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L37) *** ### `depositWithMessageCancelRequest` #### Description Sends a request to StarkGate to cancel a deposit sent with `depositWithMessage`. Similar to `depositCancelRequest`. #### Visibility `external` #### State Mutability `nonpayable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | | `uint256[] calldata message` | The message attached to the deposit. | | `uint256 nonce` | The nonce of the deposit. | #### Returns None. #### Emitted event `DepositWithMessageCancelRequest` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256[] message` | The message attached to the deposit. | | `uint256 nonce` | The nonce for the L1 transaction. | #### Function and event definitions * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`depositWithMessageCancelRequest`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L534) * Event: [`DepositWithMessageCancelRequest`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L46) #### See also * [`depositWithMessage`](#depositWithMessage) * [`depositCancelRequest`](#depositCancelRequest) *** ### `depositWithMessageReclaim` #### Description Reclaims a deposit sent with a message after a five day period has passed from the time that StarkGate received a deposit cancellation request from the `depositWithMessageCancelRequest` function. #### Visibility `external` #### State Mutability `nonpayable` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 l2Recipient` | The L2 address of the recipient. | | `uint256 nonce` | The nonce of the deposit. | #### Returns None. #### Emitted event `DepositWithMessageReclaimed` **Event attributes** | | | | - | - | | `address indexed sender` | The L1 address of the account that sent the deposit. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the deposit. | | `uint256 indexed l2Recipient` | The L2 address of the recipient. The L2 address of the recipient. | | `uint256[] message` | The message attached to the deposit. | | `uint256 nonce` | The nonce for the L1 transaction. | #### Function and event definitions * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`depositWithMessageReclaim`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L564) * Event: [`DepositWithMessageReclaimed`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L54) #### See also * [`depositCancelRequest`](#depositCancelRequest) * [`depositReclaim`](#depositReclaim) * [`depositWithMessageCancelRequest`](#depositWithMessageCancelRequest) *** ### `enrollTokenBridge` #### Description Creates a Starknet bridge for the specified ERC-20 token contract address in the multi-token bridge contracts and adds the token to the StarkGate Registry. Does not work for any ERC-20 token bridge's contract address that is already in the registry. Enrolling a new bridge creates a new ERC-20 contract on L2. You can see the class hash for this contract using [`get_erc20_class_hash`](#get_erc20_class_hash). Enrolling a new bridge does not add it to the StarkGate GUI. You can use a block explorer to use the newly created bridge. #### Visibility `external` #### State Mutability `payable` The message payload needs to include funds to cover the Starknet (L2) fee for executing this transaction. You can include this payload using a standard wrapper such as web3.js. #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired ERC-20 token. | #### Returns None. #### Emitted event `TokenEnrollmentInitiated` #### Function and event definition * Function: [`enrollTokenBridge`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarkgateManager.sol#L132) in `StarkgateManager.sol` * Event: [`TokenEnrollmentInitiated`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L34) in `StarknetTokenBridge.sol` *** ### `estimateDepositFeeWei` #### Description Returns an estimate of the fee, in Wei, for depositing funds to the L1 StarkGate bridge contract. #### Visibility `external` #### State Mutability `view` #### Parameters None. #### Returns | | | | - | - | | `uint256` | An estimate of the fee, in Wei, for depositing funds to the L1 StarkGate bridge contract. | #### Emitted event None. #### Function definition [`estimateDepositFeeWei`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L142C28-L142C28) in `StarknetTokenBridge.sol` #### Additional resources * [`deposit`](#deposit) * [`depositWithMessage`](#depositWithMessage) *** ### `estimateEnrollmentFeeWei` #### Description Returns an estimate of the fee, in Wei, for creating and registering a new bridge using the `enrollTokenBridge` function. #### Visibility `external` #### State Mutability `view` #### Parameters None. #### Returns | | | | - | - | | `uint256` | An estimate of the fee, in Wei, for creating and registering a new bridge. | #### Emitted event None. #### Function definition [`estimateEnrollmentFeeWei`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L146) in `StarknetTokenBridge.sol` #### Additional resource * [`enrollTokenBridge`](#enrollTokenBridge) *** ### `getBridge` #### Description Returns the address of the bridge for the specified token, or a value indicating if the bridge does not exist, is blocked, or is deactivated. #### Visibility `external` #### State Mutability `view` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired ERC-20 token. | #### Returns | | | | - | - | | `address address` | The address of the bridge for the specified token. | | `Address(0)` | The bridge does not exist. | | `Address(1)` | The bridge is blocked or deactivated. | #### Emitted event None. #### Function definition [`getBridge`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/IStarkgateRegistry.sol#L8) in `IStarkgateRegistry.sol` *** ### `getRegistry` #### Description Returns the address of the StarkGate Registry contract. Only the Manager uses this function. #### Visibility `external` #### State Mutability `view` #### Parameters None #### Returns | | | | - | - | | `address` | The address of the Registry contract. | #### Emitted event None. #### Function definition [`getRegistry`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarkgateManager.sol#L24) in `StarkgateManager.sol` *** ### `getStatus` #### Description Returns the status of a token in StarkGate. #### Visibility `external` #### State Mutability `view` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired ERC-20 token. | #### Returns | | | | - | - | | `TokenStatus` | One of the following values:

0 : Unknown. The bridge does not recognize the token.

1 : Pending. The token has been enrolled to StarkGate, but the deploy transaction has not yet successfully completed. You can deposit funds.

2 : Active. The deploy transaction for this token has completed successfully and StarkGate recognizes the token.

3 : Deactivated. The token has been removed from StarkGate. You cannot deposit funds. | #### Emitted event None. #### Function definition [`getStatus`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L198C23-L198C23) in `StarknetTokenBridge.sol` *** ### `getWithdrawalBridges` #### Description Retrieves a list of all bridge addresses that have ever facilitated withdrawals for the specified token. In a case where an inactive bridge for a specific token might still have funds locked, you can use this function to identify all bridges that ever serviced that token. If you used a bridge for a given token that subsequently was replaced with a new or updated bridge, but you still have funds locked on the first bridge, you might not know the address of the old bridge. #### Visibility `external` #### State Mutability `view` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | #### Returns | | | | - | - | | `address[] memory bridges` | An array of addresses of all bridges that ever serviced `token`. | #### Emitted event None. #### Function definition [`getWithdrawalBridges`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarkgateRegistry.sol#L110) in `StarkgateRegistry.sol`. *** ### `identify` #### Description Returns the name and version of the `StarknetTokenBridge.sol` contract. #### Visibility `external` #### State Mutability `pure` #### Parameters None. #### Returns | | | | - | - | | `string memory` | The name and version of the `StarknetTokenBridge.sol` contract. | #### Emitted event None. #### Function definition [`identify`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L92) in `StarknetTokenBridge.sol` *** ### `isServicingToken` #### Description Checks whether the calling contract is currently providing a service for the specified token. #### Visibility `external` #### State Mutability `view` #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | #### Returns | | | | - | - | | `true` | The calling contract is currently providing a service for the token. | | `false` | The calling contract is not currently providing a service for the token. | #### Emitted event None. #### Function definition [`isServicingToken`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/IStarkgateService.sol#L9C14-L9C30) in `IStarkgateService.sol`. *** ### `withdraw` #### Description Transfers the specified amount of the specified token to the address of the recipient specified in the `l1_recipient` parameter of the `initiate_token_withdraw` function on L2. Anyone can call this function, but only after the withdraw message has been recorded on the Starknet Core Contract. #### Parameters | | | | - | - | | `address token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the withdrawal. | | `address recipient` | (Optional) The recipient. If you don't specify this parameter, the withdraw function uses the sender's address. | #### State mutability `nonpayable` #### Returns None. #### Emitted event `Withdrawal` **Event attributes** | | | | - | - | | `address indexed recipient` | The recipient. | | `address indexed token` | The address of the contract for the desired token. | | `uint256 amount` | The amount of the withdrawal. | #### Function and event definition * Contract: [`StarknetTokenBridge.sol`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol) * Function: [`withdraw`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L481) * Event: [`Withdrawal`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/solidity/StarknetTokenBridge.sol#L62) ## L2 functions and events ### `get_erc20_class_hash` #### Description Returns the current class hash of the implementation used by the ERC-20 contract. Use the class hash as the type when deploying the ERC-20 contract on L2. If StarkWare changes the class hash such that it is no longer the class hash that you used when deploying your bridge contract, this function returns the new class hash. If you want to refer to the class hash that you used when deploying your contract, see your deployed contract on Starknet. #### Parameters None. #### Returns | | | | - | - | | `ClassHash` | The class hash of the ERC-20 token contract. | #### Function definition [`get_erc20_class_hash`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge.cairo#L376-L378) in `token_bridge.cairo`. *** ### `get_identity` #### Description Returns a string in a felt252 type with the identity of StarkGate. #### Parameters None. #### Returns | | | | - | - | | `felt252` | The identity of StarkGate. | #### Function definition [`get_identity`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L8) in `token_bridge_interface.cairo`. *** ### `get_l1_token` #### Description Returns the L1 address that corresponds to the matching L2 address of an ERC-20 token contract. #### Parameters | | | | - | - | | `l2_token_address: ContractAddress` | The L2 address of the ERC-20 token contract. | #### Returns | | | | - | - | | `EthAddress` | The L1 address of the ERC-20 token contract. | | `EthAddressZeroable::zero()` | The token is not found in the bridge. | #### Function definition [`get_l1_token`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L9) in `token_bridge_interface.cairo`. *** ### `get_l2_token` #### Description Returns the L2 address that corresponds to the matching L1 address of an ERC-20 token contract. If the token is not found in the bridge, this function returns `0`. #### Parameters | | | | - | - | | `l1_token_address` | The L1 address of the ERC-20 token contract. | #### Returns | | | | - | - | | `ContractAddress address` | The L2 address of the ERC-20 token contract. | | `ContractAddressZeroable::zero()` | The token is not found in the bridge. | #### Function definition [`get_l2_token`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L10) in `token_bridge_interface.cairo`. *** ### `get_remaining_withdrawal_quota` #### Description Returns the amount that the user can withdraw within the current 24-hour time period. The time period begins at 00:00 UTC. #### Parameters | | | | - | - | | `l1_token_address` | The L1 address of the ERC-20 token contract. | #### Returns | | | | - | - | | `u256` | The amount that can currently be withdrawn from the bridge, in units defined by the ERC-20 token contract. | #### Function definition [`get_remaining_withdrawal_quota`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L11) in `token_bridge_interface.cairo` *** ### `get_version` #### Description Returns the current version of StarkGate. #### Parameters None. #### Returns | | | | - | - | | `felt252` | The current version of StarkGate. | #### Function definition [`get_version`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L7) in `token_bridge_interface.cairo`. *** ### `initiate_token_withdraw` #### Description Initiates a withdrawal from L2. After initiating the withdrawal, the function does the following: 1. Burns the transferred amount of tokens from the balance of the withdrawal's initiator. 2. Sends a message to the relevant L1 bridge with the amount to be transferred, and the recipient's address. #### Parameters | | | | - | - | | `l1_token: EthAddress` | The L1 address of the ERC-20 token contract. | | `l1_recipient: EthAddress` | The L1 address of the recipient. | | `amount uint256` | The amount to transfer. | #### Returns None. #### Function definition [`initiate_token_withdraw`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/token_bridge_interface.cairo#L13) in `token_bridge_interface.cairo`. *** ### `on_receive` #### Description An interface to an implementation of the `on_receive` function that you must provide in your L2 contract in order to enable the `depositWithMessage` function to succeed. The L2 contract that receives the message that is sent with the `depositWithMessage` function must implement a callback function named `on_receive`. Upon completion, the `depositWithMessage` function triggers a call to the `on_receive` callback function on the receiving L2 contract. The `on_receive` function receives the deposit message as input, and it must return `true` for the deposit to succeed. If `on_receive` returns `false`, or if the receiving contract does not implement `on_receive`, the `depositWithMessage` L1 handler fails, and the user can only recover their funds using the [`depositWithMessageCancelRequest`](#depositWithMessageCancelRequest) function. #### Parameters | | | | - | - | | `l2_token: ContractAddress` | The L2 address of the ERC-20 token contract. | | `amount: uint256` | The amount deposited. | | `depositor`: EthAddress | L1 address of the deposit sender. | | `message: Span` | The message that was sent with the `depositWithMessage` function. | #### Returns | | | | - | - | | `true` | The `on_receive` function completed successfully. | | `false` | The `on_receive` function did not complete successfully. The transaction is reverted. | | No value | If the recipients's smart contract does not implement the `on_receive` function, the call fails to execute, and the transaction is reverted. | #### Function definition [`on_receive`](https://github.com/starknet-io/starkgate-contracts/tree/v2.0.1/src/cairo/receiver_interface.cairo#L6) in `receiver_interface.cairo`. # Developer tools Source: https://docs.starknet.io/learn/cheatsheets/tools This page is continuously updated. If a tool you are using is missing, please consider opening an issue or editing the page. ## Core tools * [Scarb](https://docs.swmansion.com/scarb/) is the build toolchain and package manager for Cairo and Starknet * [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/) is the toolchain for developing Starknet smart contracts * [Starknet Devnet](https://0xspaceshard.github.io/starknet-devnet/) is a local testnet for Starknet * [VS Code Cairo extension](https://marketplace.visualstudio.com/items?itemName=starkware.cairo1) is an extension for the Microsoft Visual Studio (VS) Code IDE that provides assistance when writing Cairo * [starknet.js](https://starknet-js.com/), [starknet.py](https://starknetpy.readthedocs.io/en/latest/), [starknet-rs](https://github.com/xJonathanLEI/starknet-rs?tab=readme-ov-file#starknet-rs), [starknet.go](https://pkg.go.dev/github.com/NethermindEth/starknet.go#section-readme), [starknet-jvm](https://github.com/software-mansion/starknet-jvm?tab=readme-ov-file#-starknet-jvm-), [starknet.swift](https://github.com/software-mansion/starknet.swift?tab=readme-ov-file#starknetswift), and [starknet.dart](https://starknetdart.dev/) are Starknet's various software development kits (SDKs) ## dApp tools * [Scaffold Stark](https://scaffoldstark.com/) is designed to make it easier for developers to create, deploy and interact with smart contracts. * [Starknet React](https://github.com/apibara/starknet-react) is a wagmi-inspired collection of React hooks for Starknet. * [Get Starknet](https://github.com/starknet-io/get-starknet) is a Starknet wallet\<>dApp connection bridge, that enables easy discovery and UI for Starknet wallets, including Ready, Braavos, Metamask Snaps, and OKX. * [Starknetkit](https://www.starknetkit.com/) is a Starknet wallet connection kit, built by Ready using Starknet.js and starknet-react. * [Dynamic SDK](https://go.dynamic.xyz/4eFmNMI) combines authentication, smart wallets, and secure key management into one flexible SDK that enables multi-chain coverage across chains and third-party wallets. * [Dojo](https://www.dojoengine.org/) is a developer friendly framework for building provable Games, Autonomous Worlds and other Applications that are natively composable, extensible, permissionless and persistent. * [Chipi SDK](https://sdk.chipipay.com/introduction) is an open-source developer toolkit that enables Starknet applications to create non-custodial wallets using any social login (Google, Apple, Telegram, etc.), sponsor transactions via integration with [AVNU's Paymaster](/learn/cheatsheets/integrations#infrastructure), and build with their favourite auth provider with no black boxes. * [Cavos](https://aegis.cavos.xyz/) is a wallet infrastructure service that enables instant wallet creation, gasless transactions, multi-provider authentication, and cross-platform SDKs * [STRK20 by Example](https://strk20-by-example.org/) is a one-stop hub of runnable examples for integrating STRK20 private transfers into apps, covering the [starknet-privacy SDK](https://github.com/starkware-libs/starknet-privacy) (deposits, transfers, withdrawals, note discovery), DeFi helpers, and wallet integration. ## AI tools * [Cairo Coder](https://www.cairo-coder.com/) is an intelligent code generation service that uses advanced Retrieval-Augmented Generation (RAG) to understand Cairo's syntax, patterns, and best practices, and provide high-quality, functional Cairo code based on natural language descriptions. * [Starknet Agent](https://agent.starknet.io/), an AI-powered search engine that uses advanced machine learning algorithms to search and understand the Starknet documentation, Cairo Book, and other resources and provide clear and accurate answers to any queries. * [Snak](https://www.starkagent.ai/) is a toolkit for creating AI agents that can interact with the Starknet blockchain, supporting multiple AI providers (including Anthropic, OpenAI, Google Gemini, and Ollama), and available both as an NPM package and a ready-to-use NestJS server with a web interface. * [Eliza Starknet plugin](https://www.npmjs.com/package/@elizaos/plugin-starknet) bridges Starknet blockchain capabilities with the [Eliza](https://github.com/elizaOS/eliza/tree/main) ecosystem, enabling both automated and user-directed interactions with the Starknet blockchain, including token operations, trading, portfolio management, and DeFi integrations. * [Daydreams](https://docs.dreams.fun/) is a generative agent framework for executing tasks across any blockchain or API, enabling AI agents to go beyond simple tasks and achieve complex and long-term objectives such as onchain gaming and DeFi strategies. ## Other tools * [Open Zeppelin Contract Wizard](https://wizard.openzeppelin.com/cairo) is a tool that helps you create smart contracts with Open Zeppelin libraries by easily toggling on and off features for popular smart contract patterns. * [Walnut](https://walnut.dev/) is a transaction debugger and simulator for Starknet that supports Starknet Mainnet, Starknet Sepolia and Starknet Appchains and includes transaction call trace analysis, visual transaction step by step debugger, and transaction simulator. * [Starknet Devnet JS](https://github.com/0xSpaceShard/starknet-devnet-js) is a JavaScript package that abstracts the Starknet Devnet API, making it easier to interact with it and write end-to-end tests for L1\<>L2 communications. * [Cairo Playground](https://www.cairo-lang.org/cairovm/) is an online tool that can be used to both experiment with Cairo without deploying a smart contract on Starknet, as well as dive deep into the Cairo virtual machine. * [Stark Utils](https://www.stark-utils.xyz/converter) is a simple web application that aims to be a toolbox for Cairo developers, allowing you to convert any input to a felt, string, hexadecimal value, and more. * [Katana](https://book.dojoengine.org/toolchain/katana) is an extremely fast devnet designed to support local development with the [Dojo Starknet gaming engine](https://github.com/dojoengine/dojo) but can be used as a general purpose devnet as well. * [Starkli](https://book.starkli.rs/) is a fast, easy to install, and intuitive command line tool for interacting with Starknet, allowing to query Starknet and make transactions with ease. * [Universal Sierra Compiler](https://github.com/software-mansion/universal-sierra-compiler) is a utility tool for compiling single Sierra files (unlike Scarb, which compiles full projects) from any Sierra version into CASM. Universal Sierra Compiler is bundled with Starknet Foundry and does not need to be installed separately if Starknet Foundry is installed. * [Cairo Profiler](https://github.com/software-mansion/cairo-profiler) is a utility tool that can be used to create profiles of Cairo executions from execution traces, which can be analyzed and used to show flame graphs and other useful information. Cairo Profiler is bundled with Starknet Foundry and does not need to be installed separately if Starknet Foundry is installed. * [Cairo Lint](https://github.com/software-mansion/cairo-lint) is a collection of lints that can either be used as either a library or as a standalone binary to help both detect and fix common mistakes and improve your Cairo code. Cairo Lint is bundled with Scarb and does not need to be installed separately if Starknet Foundry is installed. * [Starknet Contract Verifier](https://github.com/NethermindEth/starknet-contract-verifier) is a client for the Voyager Starknet block explorer that allows verifying Starknet contract classes. Starknet Contract Verifier is bundled with Starknet Foundry and does not need to be installed separately if Starknet Foundry is installed. # Transaction fields and hash calculations Source: https://docs.starknet.io/learn/cheatsheets/transactions-reference To review how transaction appear in the Starknet API, see [starknet\_api\_openrpc.json](https://github.com/starkware-libs/starknet-specs/blob/b5c43955b1868b8e19af6d1736178e02ec84e678/api/starknet_api_openrpc.json). ## `INVOKE` v3 ### Transaction fields | Field name | Type | Description | | - | - | - | | `chain_id` | `FieldElement` | The id of the chain to which the transaction is sent. | | `fee_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `nonce` | `FieldElement` | The transaction nonce. | | `nonce_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `paymaster_data` | `List` | *For future use.*

Currently this value is always empty.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `resource_bounds` | `Dict[Resource, ResourceBounds]` | Used for enabling the fee market.

A dictionary that maps resource type to resource bounds. The existing resources are L1 gas, L1 data gas, and L2 gas.

***Resource***

A felt. Possible values are the felt representation of the strings `L1_GAS`, `L1_DATA`, or `L2_GAS`.

***ResourceBounds***

A struct containing the following felts:

• `max_amount`: The maximum amount of the resource allowed for usage during the execution.

• `max_price_per_unit`: The maximum price the user is willing to pay for the resource. Prices are denominated in fri, where 1 fri = 10-18 STRK. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `tip` | `FieldElement` | Used to determine the priority between transactions where the l2gas max\_price\_per\_unit is enough to fit within the block, according to EIP 1559 which Starknet follows since version 0.14.0. | | `version` | `FieldElement` | The transaction's version. When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases.

Transaction version, where `n` specifies version `n` transaction. For example:

3 version 3 transaction | ### Hash calculation The `INVOKE` v3 transaction hash is calculated as a Poseidon hash over the given transaction elements, specifically: ```txt theme={null} invoke_v3_tx_hash = h( "invoke", version, sender_address, h(tip, l1_gas_bounds, l2_gas_bounds, l1_data_gas_bounds), h(paymaster_data), chain_id, nonce, data_availability_modes, h(account_deployment_data), h(calldata) h(proof_facts) ) ``` Where: * `invoke` is a constant prefix string, encoded in ASCII. * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain ID](#chain_id). * `proof_facts` are included in the hash computation only when present; otherwise, they are omitted. * `l1_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l2_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L2\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l1_data_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_DATA}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `data_availability_modes` is a concatenation of `fee_data_availability_mode` and `nonce_data_availability_mode`, as follows: $$ \underbrace{\texttt{0...0}}_{\text{188 bits}} \, | \, \underbrace{\texttt{nonce\_data\_availability\_mode}}_{\text{32 bits}} \, | \, \underbrace{\texttt{fee\_data\_availability\_mode}}_{\text{32 bits}} $$ * `h` is the [Poseidon](/learn/protocol/cryptography/#poseidon_hash) hash. ## `DECLARE` v3 The `DECLARE` transaction introduces new contract classes into the state of Starknet, enabling other contracts to deploy instances of those classes or use them in a library call. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). ### Transaction fields | Field name | Type | Description | | - | - | - | | `account_deployment_data` | `List` | *For future use.*

Currently this value is always empty.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `chain_id` | `FieldElement` | The id of the chain to which the transaction is sent. | | `compiled_class_hash` | `FieldElement` | The hash of the compiled class. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `contract_class` | `ContractClass` | The class definition. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `fee_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `nonce` | `FieldElement` | The transaction nonce. | | `nonce_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `paymaster_data` | `List` | *For future use.*

Currently this value is always empty.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `resource_bounds` | `Dict[Resource, ResourceBounds]` | Used for enabling the fee market.

A dictionary that maps resource type to resource bounds. The existing resources are L1 gas, L1 data gas, and L2 gas.

`Resource`

A felt. Possible values are the felt representation of the strings `L1_GAS`, `L1_DATA`, or `L2_GAS`.

`ResourceBounds`

A struct containing the following felts:

• `max_amount`: The maximum amount of the resource allowed for usage during the execution.

• `max_price_per_unit`: The maximum price the user is willing to pay for the resource. Prices are denominated in fri, where 1 fri = 10-18 STRK. | | `sender_address` | `FieldElement` | The address of the account initiating the transaction. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `tip` | `FieldElement` | For future use. Currently this value is always `0`. | | `version` | `FieldElement` | The transaction's version.
When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases.

Transaction version, where `n` specifies version `n` transaction. For example:

3 version 3 transaction | ### Hash calculation The hash of a v3 `DECLARE` transaction is computed as follows: ```txt theme={null} declare_v3_tx_hash = h( "declare", version, sender_address, h(tip, l1_gas_bounds, l2_gas_bounds, l1_data_gas_bounds), h(paymaster_data), chain_id, nonce, data_availability_modes, h(account_deployment_data), class_hash, compiled_class_hash ) ``` Where: * `declare` is a constant prefix string, encoded in ASCII. * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain ID](#chain_id). * `l1_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l2_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L2\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l1_data_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_DATA}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `data_availability_modes` is a concatenation of `fee_data_availability_mode` and `nonce_data_availability_mode`, as follows: $$ \underbrace{\texttt{0...0}}_{\text{188 bits}} \, | \, \underbrace{\texttt{nonce\_data\_availability\_mode}}_{\text{32 bits}} \, | \, \underbrace{\texttt{fee\_data\_availability\_mode}}_{\text{32 bits}} $$ * `h` is the [Poseidon](/learn/protocol/cryptography/#poseidon_hash) hash. * `class_hash` is the hash of the contract class. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html)] for details about how the hash is computed * `compiled_class_hash` is the hash of the compiled class generated by the Sierra→Casm compiler that is used in Starknet ## `DEPLOY_ACCOUNT` v3 Since StarkNet v0.10.1 the `DEPLOY_ACCOUNT` transaction replaces the `DEPLOY` transaction for deploying account contracts. To use it, you should first pre-fund your new account address so that you can pay the transaction fee. You can then send the `DEPLOY_ACCOUNT` transaction. For more information, see [Deploying a new account](/learn/protocol/accounts/#deploying_a_new_account). ### Transaction fields | Field name | Type | Description | | - | - | - | | `account_deployment_data` | `List` | *For future use.*

Currently this value is always empty.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `chain_id` | `FieldElement` | The id of the chain to which the transaction is sent. | | `class_hash` | `FieldElement` | The hash of the desired account class. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `compiled_class_hash` | `FieldElement` | The hash of the compiled class. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `constructor_calldata` | `List` | The arguments to the account constructor. | | `contract_address_salt` | `FieldElement` | A random salt that determines the account address. | | `contract_class` | `ContractClass` | The class definition. For more information, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `fee_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `nonce` | `FieldElement` | The transaction nonce. | | `nonce_data_availability_mode` | `FieldElement` | *For future use.*

Currently this value is always `0`.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `paymaster_data` | `List` | *For future use.*

Currently this value is always empty.

For more information, see [SNIP 8: Transaction V3 Structure](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md) | | `resource_bounds` | `Dict[Resource, ResourceBounds]` | Used for enabling the fee market.

A dictionary that maps resource type to resource bounds. The existing resources are L1 gas, L1 data gas, and L2 gas.

***Resource***

A felt. Possible values are the felt representation of the strings `L1_GAS`, `L1_DATA`, or `L2_GAS`.

***ResourceBounds***

A struct containing the following felts:

• `max_amount`: The maximum amount of the resource allowed for usage during the execution.

• `max_price_per_unit`: The maximum price the user is willing to pay for the resource. Prices are denominated in fri, where 1 fri = 10-18 STRK. | | `sender_address` | `FieldElement` | The address of the account initiating the transaction. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `tip` | `FieldElement` | For future use. Currently this value is always `0`. | | `version` | `FieldElement` | The transaction's version.
When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases.Transaction version, where `n` specifies version `n` transaction. For example:
3 version 3 transaction | ### Hash calculation The hash of a `DEPLOY_ACCOUNT` transaction is computed as follows: ```txt theme={null} deploy_account_v3_tx_hash = h( "deploy_account", version, contract_address, h(tip, l1_gas_bounds, l2_gas_bounds, l1_data_gas_bounds), h(paymaster_data), chain_id, nonce, data_availability_modes, h(constructor_calldata), class_hash, contract_address_salt ) ``` Where: * `deploy_account` is a constant prefix string, encoded in ASCII. * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain ID](#chain_id). * `l1_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l2_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L2\_GAS}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `l1_data_gas_bounds` is constructed as follows: $$ \underbrace{\texttt{L1\_DATA}}_{\text{60 bits}} \, | \, \underbrace{\texttt{max\_amount}}_{\text{64 bits}} \, | \, \underbrace{\texttt{max\_price\_per\_unit}}_{\text{128 bits}} $$ * `data_availability_modes` is a concatenation of `fee_data_availability_mode` and `nonce_data_availability_mode`, as follows: $$ \underbrace{\texttt{0...0}}_{\text{188 bits}} \, | \, \underbrace{\texttt{nonce\_data\_availability\_mode}}_{\text{32 bits}} \, | \, \underbrace{\texttt{fee\_data\_availability\_mode}}_{\text{32 bits}} $$ * `h` is the [Poseidon](/learn/protocol/cryptography/#poseidon_hash) hash. * `class_hash` is the hash of the contract class hash. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) for details about how the hash is computed. * `contract_address` is the address of the newly deployed account. For information on how this address is calculated, see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). ## Additional information ### Signature While Starknet does not have a specific signature scheme built into the protocol, the Cairo language, in which smart contracts are written, does have an efficient implementation for ECDSA signature with respect to a STARK-friendly curve. For more information, see [Cryptography](/learn/protocol/cryptography/). ### Chain ID Chain IDs are given as numbers, representing the ASCII encoding of specific constant strings, as illustrated by the following Python snippet: ```python theme={null} chain_id = int.from_bytes(value, byteorder="big", signed=False) ``` The following constants are currently used. They correspond to the chain IDs that Starknet currently supports: * `SN_MAIN` for Starknet's main network. * `SN_SEPOLIA` for Starknet's public testnet on Sepolia. ## Legacy information ### `INVOKE` v1 #### Transaction fields | Name | Type | Description | | - | - | - | | `sender_address` | `FieldElement` | The address of the sender of this transaction. | | `calldata` | `List` | The arguments that are passed to the `validate` and `execute` functions. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `max_fee` | `FieldElement` | The maximum fee that the sender is willing to pay for the transaction | | `nonce` | `FieldElement` | The transaction nonce. | | `version` | `FieldElement` | The transaction's version.
The value is 1. When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases. | #### Hash calculation The `INVOKE` v1 transaction hash is calculated as a hash over the given transaction elements, specifically: ```txt theme={null} invoke_v1_tx_hash = h( "invoke", version, sender_address, 0, h(calldata), max_fee, chain_id, nonce ) ``` Where: * `invoke` is a constant prefix string, encoded in ASCII. * The placeholder zero is used to align the hash computation for the different types of transactions. * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain-Id](#chain-id). * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash ### `INVOKE` v0 #### Hash calculation The hash of a v0 `INVOKE` transaction is computed as follows: ```txt theme={null} invoke_v0_tx_hash = h( "invoke", version, contract_address, entry_point_selector, h(calldata), max_fee, chain_id ) ``` Where: * `invoke` is a constant prefix string, encoded in (ASCII). * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain-Id](#chain-id). v2 and v3 * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash ### `DECLARE` v2 #### Transaction fields | Name | Type | Description | | - | - | - | | `chain_id` | `FieldElement` | The id of the chain to which the transaction is sent. | | `contract_class` | `ContractClass` | The contract [class](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). | | `compiled_class_hash` | `FieldElement` | The hash of the compiled class | | `sender_address` | `FieldElement` | The address of the account initiating the transaction. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `max_fee` | `FieldElement` | The maximum fee that the sender is willing to pay for the transaction. | | `nonce` | `FieldElement` | The transaction nonce. | | `version` | `FieldElement` | The transaction's version.
The value is 2. When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases. | #### Hash calculation The hash of a v2 `DECLARE` transaction is computed as follows: ```txt theme={null} declare_v2_tx_hash = h( "declare", version, sender_address, 0, h(class_hash), max_fee, chain_id, nonce, compiled_class_hash ) ``` Where: * `h` is the [Poseidon hash function](/learn/protocol/cryptography/#poseidon_hash) * `class_hash` is the hash of the contract class. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) for details about how the hash is computed * `compiled_class_hash` is the hash of the compiled class generated by the Sierra→Casm compiler that is used in Starknet ### `DECLARE` v1 #### Transaction fields The `DECLARE` v1 transaction was used to declare Cairo 0 classes. | Name | Type | Description | | - | - | - | | `contract_class` | `ContractClass` | The class object. | | `sender_address` | `FieldElement` | The address of the account initiating the transaction. | | `max_fee` | `FieldElement` | The maximum fee that the sender is willing to pay for the transaction. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `nonce` | `FieldElement` | The transaction nonce. | | `version` | `FieldElement` | The transaction's version. Possible values are 1 or 0.
When the fields that comprise a transaction change, either with the addition of a new field or the removal of an existing field, then the transaction version increases. | #### Hash calculation The hash of a v1 `DECLARE` transaction is computed as follows: ```txt theme={null} declare_v1_tx_hash = h( "declare", version, sender_address, 0, h(class_hash), max_fee, chain_id, nonce ) ``` Where: * `declare` is a constant prefix string, encoded in ASCII. * `class_hash` is the hash of the contract class. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) for details about how the hash is computed. * The placeholder zero is used to align the hash computation for the different types of transactions. * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain ID](#chain_id). * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash ### `DECLARE` v0 #### Hash calculation The `DECLARE` v0 transaction used to declare Cairo 0 classes. The hash of a v0 `DECLARE` transaction is computed as follows: ```txt theme={null} declare_v0_tx_hash = h( "declare", version, sender_address, 0, h(), max_fee, chain_id, class_hash ) ``` Where: * `declare` is a constant prefix string, encoded in ASCII. * The placeholder zeros are used to align the hash computation for the different types of transactions. * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain-Id](#chain-id). * `class_hash` is the hash of the contract class. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) for details about how the hash is computed. ### `DEPLOY_ACCOUNT` v1 #### Transaction fields | Name | Type | Description | | - | - | - | | `class_hash` | `FieldElement` | The hash of the desired account class. | | `constructor_calldata` | `List` | The arguments to the account constructor. | | `contract_address_salt` | `FieldElement` | A random salt that determines the see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html)\[account address^]. | | `signature` | `List` | Additional information given by the sender, used to validate the transaction. For more information, see [Signature](#signature). | | `max_fee` | `FieldElement` | The maximum fee that the sender is willing to pay for the transaction | | `nonce` | `FieldElement` | The transaction nonce. | | `version` | `FieldElement` | The transaction's version. The value is 1. | #### Hash calculation The hash of a `DEPLOY_ACCOUNT` transaction is computed as follows: ```txt theme={null} deploy_account_v1_tx_hash = h( "deploy_account", version, contract_address, 0, h(class_hash, contract_address_salt, constructor_calldata), max_fee, chain_id, nonce ) ``` Where: * `deploy_account` is a constant prefix string, encoded in ASCII. * The placeholder zero is used to align the hash computation for the different types of transactions. * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [\[chain-id\]](#chain-id). * `class_hash` is the hash of the contract class. See [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) for details about how the hash is computed. ### `DEPLOY` v0 #### Hash calculation If you need to retrieve the hash of an existing `DEPLOY` transaction, you can use this information to calculate the hash of the transaction. Before you can calculate the transaction hash, get the deployed contract address. The `DEPLOY` transaction's hash is calculated as shown in the following pseudo code: ```txt theme={null} deploy_tx_hash = h( "deploy", version, contract_address, sn_keccak("constructor"), h constructor_calldata), 0, chain_id ) ``` Where: * The placeholder zero is used to align the hash computation for the different types of transactions. * `deploy` and `constructor` are constant strings encoded in ASCII. * `h` is the [Pedersen](/learn/protocol/cryptography/#pedersen_hash) hash and `sn_keccak` is [Starknet Keccak](/learn/protocol/cryptography/#starknet_keccak). * `chain_id` is a constant value that specifies the network to which this transaction is sent. For more information, see [Chain ID](#chain_id). * `contract_address` is calculated as described see [the Cairo Book](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). # Version notes Source: https://docs.starknet.io/learn/cheatsheets/version-notes You can get the latest updates delivered to your inbox by subscribing to the [Starknet Devs Newsletter](https://www.starknet.io/starknet-devs-newsletter/). Starknet v0.14.1 is live on Mainnet. This version includes the following changes: * Prover optimization: compiled class hashes will now be computed with the Blake hash function rather than Poseidon. With Stwo, blake2s is roughly 8x more efficient to prove compared to large field Poseidon. Given that compiled class hashes are the heaviest hash consumer during the OS execution, it is our first step in the process of migration to a Stwo-friendly hash. * Declare transactions will now expect `compiled_class_hash` to be [computed](https://github.com/starkware-libs/sequencer/blob/8b140e199e21e87d0480d1c0a8f34b419801c1af/crates/starknet_api/src/contract_class/compiled_class_hash.rs#L150) with the new hash function, and will be rejected otherwise * Gradually, existing classes in the [state commitment](https://docs.starknet.io/learn/protocol/state#state-commitment) will migrate to the new computation. For every successful (i.e. non-reverted) transaction, the classes used throughout the transaction will be updated in the `class_hash` → `compiled_class_hash` trie. * The changes in compiled class hashes will be applied at the end of the block. Note that the execution environment is unaware of compiled class hashes (system calls only specify class hash, never compiled class hash), thus these changes will not affect execution, only the resulting state root. * The feeder gateway will add such classes whose compiled class hash was updated to the `get_state_update` response, under a new field called `migrated_compiled_classes` * Starknet JSON-RPC v0.10.0: you can find the first release candidate along with a summary of changes in the [starknet-specs](https://github.com/starkware-libs/starknet-specs/releases/tag/v0.10.0-rc.0) repository. Starknet v0.14.0 is live on Mainnet. This version includes the following changes: * Block time is shorten from \~30 seconds to \~6 seconds * Decentralized sequencing is introduced, with three sequencers, each with its own mempool, taking turns building blocks and reaching consensus using the [Tendermint](https://arxiv.org/abs/1807.04938) protocol (the interfaces for submitting transactions to the gateway and syncing from the feeder gateway remain unchanged) * v0, v1, v2 and non-RPC v0.8 compatible v3 transactions are no longer supported, and a new `meta_tx_v0` syscall is introduced to enable calling old accounts (i.e., ones without `__validate__`) via v3 transactions * RPC v0.9 is introduced, removing the `REJECTED` transaction status, changing the behavior of the `RECEIVED` transaction status, adding new `CANDIDATE` and `PRE_CONFIRMED` transaction statuses, and introducing the `PRE_CONFIRMED` block * The feeder gateway URL is now `feeder.{chain_name}.starknet.io/feeder_gateway` (while the gateway URL remains `{chain_name}.starknet.io/gateway`), adding a new `get_preconfirmed_block` endpoint, deprecating the `get_oldest_transaction_age` and `get_number_of_transactions_in_backlog` endpoints, and changing the behavior of the `get_transaction_status` endpoint * L1 handlers execution resources are bounded and failed L1 handlers are included as `REVERTED` in blocks * A fee market on the `l2_gas` resource is introduced, following Ethereum's EIP 1559 * Sequencers now maintain a mempool, decoupling the transaction order of arrival from the transaction ordering in blocks * The Starknet OS (SNOS) has been modified to receive multiple blocks as inputs, amortizing costs over a larger number of blocks * Transactions with internal calls to an entry point called `__execute__` are now reverted **Additional resources:** * [v0.14.0 pre-release notes](https://docs.google.com/document/d/1wgqtk9L_12trHBJ5SFSWwxiB4u0duureGiBncrmQiv8/edit?usp=sharing) Starknet v0.13.6 is live on Mainnet. This version is a configuration change done to support integration by the [S-Two prover](https://docs.starknet.io/stwo-book/), including: * Introducing builtin tracking and temporarily disabling Cairo Native: The [Starknet OS](/learn/protocol/snos) now tracks individual builtins usages, [capping the number of builtins that may fit inside each block](/learn/cheatsheets/chain-info#block_builtin_limits). Since [Cairo Native](https://github.com/lambdaclass/cairo_native) does currently not support this kind of tracking, its usage is temporarily disabled and will be enabled again once this capability is supported. * Reducing maximum block size: The amount of L2 gas that a block can contain is [decreased from 5B to 4B L2 gas](/learn/cheatsheets/chain-info#current_limits). Cairo Native hasn't yet been enabled on Mainnet, and disabling it only affects Sepolia. **Additional resources:** * [SN v0.13.6 pre-release notes on Starknet's community forum](https://community.starknet.io/t/sn-v0-13-6-pre-release-notes/115680) * [Introducing SN 0.13.6 on Starknet's community forum](https://community.starknet.io/t/introducing-sn-0-13-6/115678) Starknet v0.13.4 and v0.13.5 are live on Mainnet. All the big improvements came in v0.13.4, and the v0.13.5 upgrade rolled out immediately after, bringing minor adjustments to help the ecosystem gear up for Starknet's next upgrade. Starknet v0.13.4 includes the following changes: * **Cairo Native Execution**: Contracts compiled with Sierra ≥ 1.7.0 can run faster by being executed natively (without the Cairo VM) * **L2 Gas Introduced**: New fee type for L2-specific resources (computation, calldata, events) * **Triple Gas Model**: Transactions now support (and eventually require) bounds for L1 gas, L2 gas, and blob gas * **Improved Error Handling**: Contract panics no longer abort execution and instead return control to the caller * **Stateful Compression**: Smaller L1 blobs via smart key indexing to reduce data availability costs * **New Wallet\<>Dapp API**: Decouples dApps from specific Starknet.js versions using a new `WalletAccount` class * **JSON-RPC v0.8.0**: Adds WebSocket support, better fee/resource tracking, and new endpoints * **New Compiler (Cairo v2.10.0)**: Enables `redeposit_gas`, reduces worst-case fees, adds new syscalls * **Updated Pricing**: Builtins priced by trace complexity, often cheaper for users **Additional resources:** * [Starknet v0.13.5: Compressing state diffs for a future-proof Starknet](https://www.starknet.io/blog/starknet-v0135-blob-compression/) * [Starknet v0.13.4 Pre-release Notes forum post](https://community.starknet.io/t/starknet-v0-13-4-pre-release-notes/115257) * [Fall Roadmap Updates forum post](https://community.starknet.io/t/fall-roadmap-updates/115001) Blob gas on Starknet is now 5x cheaper (!) using: * Squashing discount factor: Users pay for the expected marginal contribution of their transaction to the blobs sent to L1 * State diff compression: Starknet submits compressed state diffs to L1. To better lends itself to the new compression scheme as well as allow its construction to be based on the state diff alone, the uncompressed encoding of contract diff headers changes to: new\_nonce (if changed) | n\_updates | n\_updates\_len | flag Where: * The semantics of `flag` is unchanged (i.e., it indicates whether or not the class was replaced) * `n_updates_len` is 0 if the number of updates is less than 256 (and therefore can fit in 8 bits), and 1 otherwise * `n_updates` can spread over 8 or 64 bits, depending on `n_updates_len` * If the nonce of the contract is unchanged, the value of `new_nonce` is zero (and not the unchanged nonce) **Additional resources:** * [Starknet v0.13.3 post in the Starknet community forum](https://community.starknet.io/t/starknet-v0-13-3/115053) * [Fall roadmap updates post on the Starknet community forum](https://community.starknet.io/t/fall-roadmap-updates/115001) **New features and enhancements:** * Optimistic parallelization in the sequencer. * Applicative recursion ("blockpacking"): pack a contiguous sequence of blocks into a single L1 state update, instead of just one block. This change also affects the Starknet core contract on L1 and the structure of data availability. * New block hash definition: see the reference implementation [here](https://github.com/starkware-libs/sequencer/blob/b6955bbd59635d37a12f9070a3f0b8a8db74f7c1/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L67). **API changes:** * Feeder gateway: All objects containing the block hash will use the new block hash computation. Receipts will contain a new property `total_gas_consumed`, only for transactions after v0.13.2. Three new builtins can appear in the `builtin_instance_counter` property of `execution_resources` in the transaction receipts: `add_mod`, `mul_mod` and `range_check96`. End support for the endpoints `get_block_traces` and `get_transaction_trace`. * JSON RPC: No changes in v0.13.2. The new JSON RPC version v0.8.0 will be released alongside Starknet v0.13.3. **Cairo:** A new compiler version will be released for v0.13.2, Cairo v2.7.0. This includes a Sierra upgrade to v1.6.0. The Starknet-related features include sha256 syscall and Circuit builtin. **Additional resources:** * [Starknet v0.13.2 pre-release notes](https://community.starknet.io/t/starknet-v0-13-2-pre-release-notes/114223) * [Optimistic parallelization revived](https://community.starknet.io/t/optimistic-parallelization-revived/114121) * [Upcoming Feature: Starknet Applicative Recursion](https://community.starknet.io/t/upcoming-feature-starknet-applicative-recursion/113868) * [Cairo v2.7.0 is coming!](https://community.starknet.io/t/cairo-v2-7-0-is-coming/114362) In response to community feedback, Starknet 0.13.1.1 reduces class declaration fees and increases the calldata limit: **Fee reductions:** * CASM bytecode: 28 gas/felt → 1 gas/felt * Sierra bytecode: 28 gas/felt → 1 gas/felt * ABI: 0.875 gas/character → 0.032 gas/character **Limit increases:** * Calldata length: 4,000 felts → 5,000 felts **New features and enhancements:** * **Cheaper data availability (DA):** Starknet uses [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844). State diffs are now blobs, rather than calldata, requiring the addition of `data_gas` to the Starknet block header. * Time-related syscalls when called from an account contract's `__validate__`, `__validate_deploy__`, `__validate_declare__`, or `constructor` function: `block_timestamp` returns the hour, rounded down. `block_number` returns the block number, rounded down to the nearest multiple of 100. * **Optimization:** Load into memory only the functions in a contract that are actually used when generating the proof. **Block header updates:** The block header includes new fields: `l1_da_mode`, `l1_data_gas_price`, and `l1_gas_price`. **API: JSON RPC:** Starknet 0.13.1 is backward compatible with `starknet_api_openrpc.json` v0.6.0. A new version 0.7.0 accommodates the changes introduced by Starknet using EIP-4844. **Pricing changes:** * Computation: A Cairo step now costs 0.0025 gas/step, a 50% reduction. All builtins costs are accordingly reduced by 50%. * Calldata and signatures: Each felt in the calldata and signature arrays of all transaction types now costs 0.128 gas/felt. * Class declaration: Each felt of a sierra\_program and bytecode now costs 28 gas/felt. Each character in the ABI costs 0.875 gas. * Events: An additional felt to the data array costs 0.128 gas/felt. An additional felt to the keys array costs 0.256 gas/felt. **Additional resources:** * [Starknet v0.13.1: EIP4844 Support, More Fee Reductions, Stability, Quality of Life](https://community.starknet.io/t/starknet-v0-13-1-eip4844-support-more-fee-reductions-stability-quality-of-life/112951) * [Data availability with EIP4844](https://community.starknet.io/t/data-availability-with-eip4844/113065) * [Starknet v0.13.1 pre-release notes](https://community.starknet.io/t/starknet-v0-13-1-pre-release-notes/113664) Starknet v0.13.0 is live on Mainnet. Starknet 0.13.0 includes the following changes: * v3 transactions, including fee payment in STRK and reserved fields for future features, such as Volition and payment master * `get_block` API: The `gas_price` field is replaced by the `eth_l1_gas_price` and `strk_l1_gas_price` fields. This change applies also to existing blocks. For more information on the new fields, see the [JSON RPC API Spec on GitHub](https://github.com/starkware-libs/starknet-specs/releases/tag/v0.6.0) * Sierra v1.4.0. This new version of Sierra is part of Crate v2.4.0, in the Cairo 2.4.0 package. For more information, see [Cairo v2.4.0 is out!](https://community.starknet.io/t/cairo-v2-4-0-is-out/109275) on the Community forum. * Improved performance of `secp256k1_mul` and `secp256r1_mul` syscalls * Computation cost is reduced by approximately 50% as a result of reduced Cairo steps and increased use of builtins. L1 data availability cost is reduced by approximately 10%-25%. For an ERC-20 transfer, the DA fee reduction is 25%. Starknet v0.12.3 is live on Mainnet. This release partially removes support for the Starknet feeder gateway. For details, see [Feeder Gateway Deprecation](https://community.starknet.io/t/feeder-gateway-deprecation/100233/1) in Development Proposals on the Starknet community forum. Additionally, this version includes the following changes: * Performance optimizations in the gateway, the computation of the Patricia storage root, and block hash * Support for `secp256r1` syscalls in the Starknet OS. * Restriction for `__validate__` and the constructor of `DeployAccount` transactions: Restrict access to `sequencer_address` in the `get_execution_info` syscall by returning `0`'s for the address. Restrict access to the following syscalls: Cairo contracts: `get_block_hash`, Cairo 0 contracts: `get_sequencer_address` This version is available on both Goerli and Sepolia testnets. Starknet v0.12.2 is live on Mainnet. This version includes the following changes: * Enabling P2P Authentication: An additional endpoint in the sequencer gateway to provide a signature on the state diff commitment and block hash. * Resolving Mismatches in Queries: An extension to the `get_state_update` endpoint in the sequencer gateway that returns both the pending state diff and the pending block together. * Increased maximum Cairo steps per transaction from 1 million to 3 million. Starknet v0.12.1 is live on Mainnet. This version includes the following changes: * Mempool Validation. * Inclusion of Failed Transactions. * Keccak builtin. Starknet v0.12.0 is live on Mainnet. This version contains the following changes: * Use the [rust blockifier](https://github.com/starkware-libs/blockifier) and [LambdaClass's Cairo VM](https://github.com/lambdaclass/cairo-vm) to accelerate the sequencer's time to handle transactions. * Support [version 2.0.0](https://github.com/starkware-libs/cairo/releases/tag/v2.0.0) of the Cairo compiler. * Replace the `PENDING` status of transactions to `ACCEPTED_ON_L2` - once a transaction is in that status it means that it will be included in a block, this applies to transactions - blocks still have the `PENDING` status. * Add an experimental `get_block_hash` syscall. * Change HTTP error code from 500 to 400 on API errors. Starknet v0.11.2 is live on Mainnet. This version contains the following changes: * Upgrade Cairo 1.0 version to v1.0.0-rc0 (Cairo 1.0 activated on Starknet!) Starknet v0.11.1 is live on Mainnet. This version contains the following changes: * Upgrade Cairo 1.0 version to v1.0.0-rc0. * Charged transaction fee is now based on an average Ethereum gas price instead of a single sample (estimation API is unaffected). * API changes: Remove the state root in `get_state_update` for pending blocks to allow faster responses in future versions. * Testing framework: Allow declaring (and interacting with) Cairo 1.0 contracts. Split `deploy()` to two phases declare and deploy: `deprecated_declare()` (for Cairo 0 contract) or `declare()` (for Cairo 1.0 contracts) and `deploy()` (for both). * Add current block hash to the Starknet Core Contract (currently not verified by the Starknet OS): Breaking change: The `LogStateUpdate` event's data is changed to include blockHash. Starknet v0.11.0 is live on Mainnet. In Starknet v0.11.0, you can declare, deploy and run Cairo 1.0 smart contracts. We also introduce a new system call that allows a smooth transitioning of existing contracts to a Cairo 1.0 implementation. Historically, contract classes have been defined in terms of Cairo assembly, or Casm for short (the class definition also included more information needed for execution, e.g., hint data). The novelty of Cairo 1.0 is the introduction of Sierra (Safe Intermediate Representation), an intermediate layer between Cairo 1.0 and Casm. The introduction of Cairo 1.0 and Sierra has several effects on the system. Below we list the effects on each component; of particular note are: * A new version of the `declare` transaction, which allows sending the new class structure * The state commitment will now include contract classes * Changes to the onchain data format * New system call - `replace_class` The `deploy` transaction is no longer supported on this version. This version contains the following changes: Starknet * Performance - Separate the state commitment computation from the execution of the transactions * Add `starknet-class-hash` command to compute the class hash of a compiled Starknet contract Cairo: * Autoformatter: Automatically break lines inside expressions * This version introduces sequencer parallelization! This is the first step in our roadmap of performance upgrades. Details about the specific mechanism of parallelization and the roadmap in general are described in [this medium post](https://medium.com/starkware/starknet-performance-roadmap-bb7aae14c7de). * A new endpoint, `estimate_fee_bulk`, is added to the feeder gateway. This will allow estimating the fee of several transactions at once, where each transaction is executed relative to the resulting state from applying the previous one. * Sequencing performance improvements * Builtin ratio changes, which affects builtin costs * Add `estimate_fee_bulk` API that computes the fee of multiple transactions that will be executed consecutively As part of this version, we will also increase the finality of transactions in the pending block, by fixing the timestamp at the time of the block creation. This will solve the issue of transactions moving from pending to rejected on account of too old timestamp This version contains the following changes: Starknet: * Add `DeployAccount` transaction (which will replace the Deploy transaction for deploying account contracts). To use it, you should first add enough funds to your account address to pay the transaction fee, and then you can invoke DeployAccount * Split the `starknet deploy_account` CLI command into `starknet new_account` and `starknet deploy_account` * Account contracts that are expected to be deployed this way should implement the`__validate_deploy__()` entry point, which should check the signature of the `DeployAccount` transaction * Improve L1 fee computation: the fee is computed according to the diff of the storage state * API: Remove `entry_point_type` field from transaction information Cairo: * Add `uint256_mul_div_mod` to `uint256.cairo` This version introduces the next step in Starknet's account abstraction design, specifically the validate/execute separation. See [here](https://www.notion.so/starkware/Starknet-0-10-0-4ac978234c384a30a195ce4070461257) for more information. This version contains the following changes: Starknet: * Contract (breaking changes): @external and @view functions should be imported directly by the main compiled file. Otherwise, they will not be usable as external functions. Forbid using the same storage variable name in two modules * New transaction version (version 1) for `invoke` and `declare` transactions: Transactions of version 0 are deprecated and will not be supported in Starknet from the next version (v0.11.0). Please update your systems to use the new version In order to use transactions of version 1 you will need to upgrade your account contracts * Add nonce field to the transactions. Nonce validation is now part of the Starknet protocol and is enforced to be executed sequentially * `Invoke`: Split `__execute__` to two functions:` __validate__` (only validates the transaction) and `__execute__` (only executes the transaction). Remove the selector (which is now always `__execute__`) field, following the above change. * Declare: `declare` transaction should now be sent from an account (and is validated using `__validate_declare__` in the account contract) * Support fee for sending L1 messages. At this point, it's not mandatory and messages with no fee will still be handled. Starting from the next version it will become mandatory. Cairo: Syntax changes in Cairo (to make it more similar to rust and C++): You can use the cairo-migrate script to convert old code to the new syntax. Use the `-i` flag to apply the changes to the files. End statements with `;`. Use `{ … }` for code blocks (instead of `:` and `end`). Add `()` around the condition of if statements. Remove the member keyword in structs. Change comment to use `//` instead of `#`. Use `..., ap++` instead of `...; ap++` in low level Cairo code. Support return types that are not tuples. Return statement accepts expressions, rather than only tuples. Support using functions as expressions. Fix a bug in the secp signature verification code. Add Cairo code for the recursive STARK verifier. Technical changes: * Move from python3.7 to python3.9 This version contains the following changes: Starknet: API changes: * Add `get_block_traces` API - returns all the transaction traces of a given block * Add a list of declared contracts in `get_state_update` * Add a 0x prefix for class hash in the API * Add `starknet_version` field for blocks (only applies to new blocks) Starknet CLI: * Change the default block number to pending * Using a wallet is the default, `--no_wallet` must be specified explicitly to override this * Deploying contracts: Add `deploy_contract` function to the account contract created by `starknet deploy_account`. Use this function to deploy contract (unless using `--no_wallet`). In particular, `deploy` should be used after declaring the contract (it expects the contract class hash) * Support `--dry_run` to get the transaction information without signing or sending it * Support `deploy_from_zero` in the `deploy` syscall to deploy a contract to an address that does not depend on the deployer Cairo: * Support and in if statements (`if x == y and z == w`). At the moment other boolean combinations are not supported This version introduces the contract class/instance paradigm into Starknet. See [here](https://docs.starknet.io/documentation/architecture_and_learn/architecture/Contracts/contract-classes/) for more information. This version contains the following changes: Starknet: * Enforce fees - `max_fee` must not be set to zero, and selector must be `__execute__` * Split the concepts of contract class and contract instance. * Add `declare` transaction type * New API and CLI commands: `declare` - Declares a contract class. `get_class_by_hash` - Returns the contract class given its hash. `get_class_hash_at` - Returns the class hash for a given contract instance address * Rename `delegate_call` to `library_call`, and change the contract address argument to class hash. * Add a `deploy` system call. * Rename `ContractDefinition` to `ContractClass` * Reduce the compiled contract file's size by removing unnecessary identifiers (this optimization can be disabled using `--dont_filter_identifiers`) Cairo: * Initial support for the `EC-op` builtin (scalar multiplication over the STARK curve). Not supported in Starknet yet. * Add additional helper methods to `blake2s.cairo`, including big-endian support Technical changes: * Change function's `return` type from a struct to a named tuple. In particular, `foo.Return.SIZE` is no longer supported. # Accounts Source: https://docs.starknet.io/learn/protocol/accounts ## Overview Accounts represent users onchain, and enable them to interact with the blockchain. [Unlike Ethereum](/learn/protocol/accounts#ethereum's-account-structure), Starknet facilitates account abstraction natively, via its unique account [structure](/learn/protocol/accounts#starknet's-account-structure) and [nonce management](/learn/protocol/accounts#account-nonces). Accounts can be [easily deployed on Starknet](/learn/protocol/accounts#deploying-a-new-account), both with or without having a preexisting account. ## Ethereum's account structure Within Ethereum individual user accounts are known as Externally Owned Accounts (EOAs). EOAs differ from smart contracts in that EOAs are not controlled by code, but rather by a pair of private and public keys. The account's address is derived from those keys, and only by possessing the private key can you initiate transactions from an account. While simple, because the signature scheme is fixed, EOAs have some drawbacks, including the following: * Control over the private key gives complete control over the account, so you must keep your seed phrase secure yet accessible. * Wallet functionality has limited flexibility [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) is a design proposal for Ethereum that outlines *\_account abstraction*, whereby all accounts are managed via a dedicated smart contract on the Ethereum network, as a way to increase flexibility and usability. Using account abstraction you can program how your account functions, including: * Determine what it means for a signature to be valid, or what contracts your account is allowed to interact with, also known as *signature abstraction* * Pay transaction fees in different tokens, also known as *fee abstraction* * Design your own replay protection mechanism and allow sending multiple uncoupled transactions in parallel, also known as *nonce abstraction* Without account abstractions, EOAs cannot send two transactions in parallel, but rather must wait for confirmation of the first before sending the second, as otherwise the second transaction can be rejected due to an invalid nonce. With account abstraction, sequential nonces is not required. Two prominent examples of how account abstraction might be used to program account functionality include: * **Social recovery:** retrieving your wallet if in case its lost via a selected social network, vastly improving the typical experience of wallet recovery * **Operating your account via facial recognition:** Using your phone's native hardware to sign transactions, making it practically impossible to take control of another user's account, even if your phone is stolen ## Starknet's account structure Starknet's account structure is inspired by Ethereum's EIP-4337 where smart contract accounts with arbitrary verification logic are used instead of EOAs. A valid account contract in Starknet is simply a contract that includes the following functions: | Function name | When required | | - | - | | `__validate__` | Always required | | `__execute__` | Always required. The signatures of `__validate__` and `__execute__` must be identical. See critical validation requirements below. | | `__validate_declare__` | Required for the account to be able to send a `DECLARE` transaction. This function must receive exactly one argument, which is the class hash of the declared class. | | `__validate_deploy__` | Required to allow deploying an instance of the account contract with a `DEPLOY_ACCOUNT` transaction. The arguments of `__validate_deploy__` must be the class hash of the account to be deployed and the salt used for computing the account's contract address, followed by the constructor arguments. | | `constructor` | All contracts have a `constructor` function. It can be explicitly defined in the contract, or if not explicitly defined, the sequencer uses a default `constructor` function, which is empty. | Two critical validations must happen in `__execute__`, and their absence can lead to draining of the account's funds: (1) `assert!(get_caller_address().is_zero())` This asserts that the account's `__execute__` is not called from another contract, thus skipping validations (in later versions we may disallow calling `__execute__` from another contract at the protocol level) (2) `assert!(get_tx_info().unbox().version.into() >= 1_u32)` This asserts that the transaction's version is at least 1, preventing the account from accepting INVOKE v0 transactions. It is critical to explicitly disallow the deprecated v0 transaction type, as v0 transactions assume that the signature verification happens in `__execute__`, and are thus skipping `__validate__` entirely. You can only use the `__validate_deploy__` function in an account contract to validate the DEPLOY\_ACCOUNT transaction for that same contract. That is, this function runs at most once throughout the lifecycle of the account. ### Flow When the sequencer receives a transaction, it calls the corresponding function with the appropriate input from the transaction's data, as follows: * For a `DECLARE` transaction, the sequencer validates the transaction by calling the `__validate_declare__` function. * For an `INVOKE` transaction, the sequencer calls the `__validate__` function with the transaction's calldata as input, after being deserialized to the arguments in the `__validate__` function's signature. After successfully completing validation, the sequencer calls the `__execute__` function with the same arguments. * For a `DEPLOY_ACCOUNT` transaction, the sequencer calls the `constructor` function with the transaction's `constructor_calldata` as input, after being deserialized to the arguments in the constructor signature. After the successful execution of the constructor, the sequencer validates the transaction by calling the `__validate_deploy__` function. For an `INVOKE` and `DEPLOY_ACCOUNT` transactions, it is up to the sender to make sure that the calldata is serializied correctly according to either the `__validate__` or `constructor` function's signature. Separating the validation and execution stages guarantees payment to sequencers for work completed and protects them from DoS attacks. For more information on transaction types and lifecycle, see [Transactions](/learn/protocol/transactions/). For more information on the each transaction's fields, see [Transaction fields and hash calculations reference](/learn/cheatsheets/transactions-reference/). ### Limitations The logic of the `__validate__`, `__validate_deploy__`, `__validate_declare__`, and `constructor` functions can be mostly arbitrary, except for the following limitations: For the `constructor` function, limitations apply only when run in a `DEPLOY_ACCOUNT` transaction (in particular, **not** when an account is deployed from an existing class via the `deploy` syscall) * You cannot use more than 1,000,000 Cairo steps * You cannot use more than 100,000,000 gas * You cannot call the following syscalls: * `get_class_hash_at` * `get_sequencer_address` (this syscall is only supported for Cairo 0 contracts) Starting from Starknet version 0.14.0, calling the `deploy` syscall from the `__validate__`, `__validate_deploy__`, `__validate_declare__`, and `constructor` functions will also not be possible. * You cannot call functions in external contracts This restriction enforces a single storage update being able to invalidate only transactions from a single account. However, be aware that an account can always invalidate its own past transactions by e.g. changing its public key. This limitation implies that the fees you need to pay to invalidate transactions in the mempool are directly proportional to the number of unique accounts whose transactions you want to invalidate. * When calling the `get_execution_info` syscall: * `sequencer_address` is set to zero * `block_timestamp` returns the time (in UTC), rounded to the most recent hour * `block_number` returns the block number, rounded down to the nearest multiple of 100 These limitations are designed to prevent the following DoS attacks on the sequencer: * An attacker could cause the sequencer to perform a large amount of work before a transaction fails validation. Two examples of such attacks are: * Spamming `INVOKE` transactions whose `__validate__` requires many steps, but eventually fails * Spamming `DEPLOY_ACCOUNT` transactions that are invalid as a result of the constructor or `__validate_deploy__` failing. * The above attacks are solved by making sure that the validation step is not resource-intensive, e.g. by keeping the maximal number of steps low. However, even if the validation is simple, the following "mempool pollution" attack could still be possible: 1. An attacker fills the mempool with transactions that are valid at the time they are sent. 2. The sequencer is ready to execute them, thinking that by the time it includes them in a block, they will still be valid. 3. Shortly after the transactions are sent, the attacker sends one transaction that somehow invalidates all the previous ones and makes sure it's included in a block, e.g. by offering higher fees for this one transaction. An example of such an attack is having the implementation of `__validate__` checks that the value of a storage slot is `1`, and the attacker's transaction later sets it to `0`. Restricting validation functions from calling external contracts prevents this attack. ### Failures When the `__validate__`, `__validate_deploy__`, or `__validate_declare__` functions fail, the account in question does not pay any fee, and the transaction's status is `REJECTED`. When the `__execute__` function fails, the transaction's status is `REVERTED`. Similar to Ethereum, a reverted transaction is included in a block, and the sequencer is eligible to charge a fee for the work done up to the point of failure. To learn more about transaction statuses, see [Transactions](/learn/protocol/transactions#transaction_statuses). ### SNIP-6 While not mandatory at the protocol level, you can use a richer standard interface for accounts, defined in [Starknet Improvement Proposal 6 (SNIP-6)](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md). SNIP-6 was developed by community members at [OpenZeppelin](https://www.openzeppelin.com/), in close collaboration with wallet teams and other core Starknet developers. ### Example Thanks to account abstraction, the logic of `__execute__` and the different validation functions is up to the party implementing the account. To review a concrete implementation, you can check out OpenZeppelin's [account component](https://github.com/OpenZeppelin/cairo-contracts/blob/v0.14.0/src/account/account.cairo#L72), which also adheres to [SNIP6](/learn/protocol/accounts#snip-6). ## Account nonces Similar to Ethereum, every contract in Starknet, including account contracts, has a nonce. The nonce of a transaction sent from an account must match the nonce of that account, which changes after the transaction is executed — even if it was reverted. Nonces serves two important roles: * They guarantee transaction hash uniqueness, which is important for a good user experience * They provide replay protection to the account, by binding the signature to a particular nonce and preventing a malicious party from replaying the transaction Also similarly to Ethereum, Starknet currently determines the nonce structure at the protocol level to be sequential (i.e., the nonce of a transaction sent from an account is incremented by one after the transaction is executed). In the future, Starknet will consider a more flexible design, extending account abstraction to nonce abstraction. However, unlike Ethereum, only the nonce of account contracts — that is, those adhering to [Starknet's account structure](/learn/protocol/accounts#starknet's-account-structure) — can be non-zero in Starknet. In contrast, in Ethereum, regular smart contracts can also increment their nonce by deploying smart contracts (i.e., executing the `CREATE` and `CREATE2` opcodes). ## Deploying a new account New accounts can be deployed in the following ways: * Sending a `DEPLOY_ACCOUNT` transaction, which does not require a preexisting account. * Using the [Universal Deployer Contract (UDC)](https://old-docs.openzeppelin.com/contracts-cairo/3.0.0-alpha.2/udc), which requires an existing account to send the `INVOKE` transaction Upon receiving one of these transactions, the sequencer performs the following steps: 1. Runs the respective validation function in the contract, as follows: * When deploying with the `DEPLOY_ACCOUNT` transaction type, the sequencer executes the `__validate_deploy__` function in the deployed contract. * When deploying using the UDC, the sequencer executes the `__validate__` function in the contract of the sender's address. 2. Executes the constructor with the given arguments. 3. Charges fees from the new account address. If you use a `DEPLOY_ACCOUNT` transaction, the fees are paid from the address of the deployed account. If you use the UDC, which requires an `INVOKE` transaction, the fees are paid from the sender's account. For information on the differences between V1 and V3 `INVOKE` transactions, see [`INVOKE` transaction](/learn/protocol/transactions#transaction_types) in *Transaction types*. 4. Sets the account's nonce as follows: * `1`, when deployed with a `DEPLOY_ACCOUNT` transaction * `0`, when deployed with the UDC # Blocks Source: https://docs.starknet.io/learn/protocol/blocks ## Overview A Starknet block is a list of transactions and a block header that contains [multiple fields](/learn/protocol/blocks#block-header-fields), including the [hash of the previous block](/learn/protocol/blocks#block-hash) and a [commitment to their state diff](/learn/protocol/blocks#state-diff-commitment), as well as [commitments to the transactions, events, and receipts](/learn/protocol/blocks#transactions%2C-events%2C-and-receipts-commitments) it includes. ## Block header fields | Name | Type | Description | | - | - | - | | `block_number` | `u64` | The number of this block (i.e., its height) | | `parent_block_hash` | `felt252` | The [hash](/learn/protocol/blocks#block-hash) of the block's parent | | `global_state_root` | `felt252` | A [commitment](/learn/protocol/state#state-commitment) to the new state | | `sequencer_address` | `ContractAddress` | The Starknet address of the sequencer that created the block | | `block_timestamp` | `Timestamp` | The time at which the sequencer began building the block, in seconds since the Unix epoch | | `transaction_count` | `u32` | The number of transactions in the block | | `events_count` | `u32` | The number of events in the block. | | `state_diff_length` | `u32` | The total amount of storage diffs, nonce updates, deployed contracts, and declared classes | | `state_diff_commitment` | `felt252` | A [commitment](/learn/protocol/blocks#state-diff-hash) to the state diff of the block | | `transactions_commitment` | `felt252` | A [commitment](/learn/protocol/blocks#transactions%2C-events%2C-and-receipts-commitments) to the transactions included in the block | | `events_commitment` | `felt252` | A [commitment](/learn/protocol/blocks#transactions%2C-events%2C-and-receipts-commitments) to the events included in the block | | `receipts_commitment` | `felt252` | A [commitment](/learn/protocol/blocks#transactions%2C-events%2C-and-receipts-commitments) to the receipts of the transactions included in the block | | `l1_gas_price` | `(u128, u128)` | The price of L1 gas that was used while constructing the block, where the first value is the price in wei and the second is the price in fri (see [Fees](/learn/protocol/fees/) for more details) | | `l1_data_gas_price` | `(u128, u128)` | The price of L1 blob gas that was used while constructing the block, where the first value is the price in wei and the second is the price in fri (see [Fees](/learn/protocol/fees/) for more details) | | `l2_gas_price` | `(u128, u128)` | The price of L2 gas that was used while constructing the block, where the first value is the price in wei and the second is the price in fri (see [Fees](/learn/protocol/fees/) for more details) | | `l1_da_mode` | `String` | `CALLDATA` or `BLOB`, depending on how Starknet state diffs are sent to L1 | | `protocol_version` | `String` | The version of the Starknet protocol used when creating the block | ## Block hash A block hash is defined by: ``` h( "STARKNET_BLOCK_HASH1", block_number, global_state_root, sequencer_address, block_timestamp, transaction_count || event_count || state_diff_length || l1_da_mode, state_diff_commitment, transactions_commitment events_commitment, receipts_commitment, gas_prices_hash, protocol_version, 0, parent_block_hash ) ``` Where: * `h` is the [Poseidon hash function](/learn/protocol/cryptography#poseidon-hash) * `transaction_count || event_count || state_diff_length || l1_da_mode` is the concatenation of the 64-bit representations of `transaction_count`, `event_count`, and `state_diff_length` with 0 if `l1_da_mode` is `CALLDATA` and 1 if `l1_da_mode` is `BLOB` * `gas_prices_hash` is defined by: ``` h( "STARKNET_GAS_PRICES0", l1_gas_price_wei, l1_gas_price_fri, l1_data_gas_price_wei, l1_data_gas_price_fri, l2_gas_price_wei, l2_gas_price_fri ) ``` where `h` is the [Poseidon hash function](/learn/protocol/cryptography#poseidon-hash) and `l1_gas_price_wei`, `l1_gas_price_fri`, `l1_data_gas_price_wei`, `l1_data_gas_price_fri`, `l2_gas_price_wei`, and `l2_gas_price_fri` are the wei and fri denominated prices for the three resource types. For a reference implementation, see [`block_hash_calculator.rs`](https://github.com/starkware-libs/sequencer/blob/bb361ec67396660d5468fd088171913e11482708/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L68) in the Starknet sequencer's GitHub repository ## State diff commitment The state diff commitment is defined by: ``` h( "STARKNET_STATE_DIFF0", s, deployed_or_replaced_address_1, ... , deployed_or_replaced_address_s, d, declared_or_migrated_class_hash_1, declared_or_migrated_compiled_class_hash_1..., declared_or_migrated_class_hash_d, declared_or_migrated_class_hash_d, 1, 0, n, c_1, k_1_1, v_1_1, ... k_1_m1, v_1_m1, ... ,c_n, k_n_1, v_n_1, ... ,k_n_mn, v_n_mn, k, account1, new_nonce1, ... ,accountk, new_noncek ) ``` Where: * `h` is the Poseidon hash function * `s` is the number of deployed and replaced contracts in the block * `deployed_or_replaced_address1, ... , deployed_or_replaced_addresss` are the deployed and replaced addresses sorted in numerical increasing order according to the addresses * For a replaced address, each value is `replaced_contract_address, new_class_hash` * `d` is the number of declared and migrated classes in the block * `declared_or_migrated_class_hash1, declared_or_migrated_compiled_class_hash1..., declared_or_migrated_class_hashd, declared_or_migrated_class_hashd` are the declared or replaced class hashes sorted in a numerical increasing order. * `declared_or_migrated_class_hash` is the hash of the Sierra code * `declared_or_migrated_compiled_class_hash` is the hash of the CASM code * `1` and `0` are placeholders that may be used in the future * `n` is the number of contracts whose storage was updated * `c_i, k_i_1, v_i_1, … , k_i_mi,v_i_mi` are `mi` updates `(k_i_1, v_i_1), … ,(k_i_mi, v_i_mi)` to contract addresses `c_i`. Where `k_i_j` is the `j`-th key and `v_i_j` is the `j`-th new value * `k` is the number of accounts whose nonce was updatedTransactions, events, and receipts commitments ## **Transactions, events, and receipts commitments** The commitment to transactions, the commitment to events and the commitment to receipts are all roots of height-64 binary Merkle Patricia tries where the leaf at index `i` corresponds to: * For transactions: `h(transaction_hash, signature)` for the `i`'th transaction included in the block, where [transaction\_hash is calculated as follows](/learn/cheatsheets/transactions-reference#hash-calculation) and `h` is the [Poseidon hash function](/learn/protocol/cryptography#poseidon-hash). Note that the signature is itself a (possibly empty) array of field elements. * For events: The [event hash](/learn/protocol/blocks#event-hash) of the `i`'th emitted event included in the block * For receipts: The [receipt hash](/learn/protocol/blocks#receipt-hash) of the `i`'th transaction receipt included in the block ### Event hash The hash of an [event](https://book.cairo-lang.org/ch101-03-contract-events.html) emitted by a contract whose address is `emitter_address` and a transaction whose hash is `tx_hash` is defined by: ``` h( emitter_address, tx_hash, h(keys), h(data) ) ``` Where `h` is the [Poseidon hash function](/learn/protocol/cryptography#poseidon-hash). ### Receipt hash The hash of a [transaction receipt](/learn/protocol/transactions#transaction-receipt) is defined by: ``` h( transaction_hash, actual_fee, h(messages), sn_keccak(revert_reason), h(l2_gas_consumed, l1_gas_consumed, l1_data_gas_consumed) ) ``` Where: * `h` is the [Poseidon hash function](/learn/protocol/cryptography#poseidon-hash) * `h(messages)` for `messages = (from1, to1, payload1), … , (fromn, ton, payloadn)` is defined by: ``` h( n, from1, to1, h(len(payload1) || payload1), ... , fromn, ton, h(len(payloadn) || payloadn) ) ``` # Cryptography Source: https://docs.starknet.io/learn/protocol/cryptography ## Overview Starknet is built on advanced cryptographic primitives that enable scalable, trustless computation using STARK proofs. These include a custom [prime field](#the_stark_field) and [elliptic curve](#the_stark_curve) and multiple [hash functions](#hash_functions) optimized for zero-knowledge performance. ## The STARK field The STARK field is the finite field $\mathbb{F}_{P}$, where: $P$ = $2^{251} + 17 \cdot 2^{192} + 1$ = 3618502788666131213697322783095070105623107215331596699973092056135872020481 The `felt252` type in Cairo refers to elements of the STARK field. ## The STARK curve The STARK curve is an elliptic curve defined over the STARK field by: $y^{2} \equiv x^{3} + \alpha \cdot x + \beta \left(\right. mod P \left.\right)$ where: * $\alpha$ = 1 * $\beta$ = 3141592653589793238462643383279502884197169399375105820974944592307816406665 The curve's order is 3618502788666131213697322783095070105526743751716087489154079457884512865583, and the generator point $G$ in the Elliptic Curve Digital Signature Algorithm (ECDSA) implementation that is used in Cairo with respect to it is defined by: * $G_{x}$ = 874739451078007766457464989774322083649278607533249481151382481072868806602 * $G_{y}$ = 152666792071518830868575557812948353041420400780739481342941381225525861407 The STARK curve is commonly used in smart contracts, but is not distinguished by the Starknet protocol. ## Hash functions There are three hash functions used throughout Starknet's specifications that map inputs to elements in the [STARK field](/learn/protocol/cryptography#the-stark-field). ### Starknet Keccak Starknet's version of the Keccak hash function, commonly denoted by $\text{sn}_\text{keccak}$, is defined as the first 250 bits of Ethereum's [keccak256](https://github.com/ethereum/eth-hash). ### Pedersen hash Starknet's version of the Pedersen hash function is then defined by: $h \left(\right. a , b \left.\right) = \left(\left[\right. \text{shift}_\text{point} + a_{l o w} \cdot P_{0} + a_{h i g h} \cdot P 1 + b_{l o w} \cdot P 2 + b_{h i g h} \cdot P 3 \left]\right.\right)_{x}$ where: * $a_{l o w}$ and $b_{l o w}$ are the 248 low of $a$ and $b$, respectively * $a_{h i g h}$ and $b_{h i g h}$ are the 4 high bits of $a$ and $b$, respectively * The values of the constants $\text{shift}_\text{point} , P_{0} , P_{1} , P_{2} , P_{3}$ can be found in [fast\_pedersen\_hash.py](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/crypto/signature/fast_pedersen_hash.py) * $\left[\right. P \left]\right._{x}$ denotes the $x$ coordinate of point $P$ #### Array hashing Let $h$ denote the pedersen hash function, then given an array $a_{1} , . . . , a_{n}$ of $n$ field elements we define $h \left(\right. a_{1} , . . . , a_{n} \left.\right)$ to be: $h \left(\right. . . . h \left(\right. h \left(\right. 0 , a_{1} \left.\right) , a_{2} \left.\right) , . . . , a_{n} \left.\right) , n \left.\right)$ ### Poseidon hash Poseidon is a family of hash functions designed to be very efficient as algebraic circuits. As such, they can be very useful in ZK-proving systems such as STARKs. Starknet's version of the Poseidon hash function is based on a three-element state Hades permutation and defined of up to 2 elements by: $\text{poseidon} \left(\right. x \left.\right) := \left(\left[\right. \text{hades}_\text{permutation} \left(\right. x , 0 , 1 \left.\right) \left]\right.\right)_{0}$ $\text{poseidon} \left(\right. x , y \left.\right) := \left(\left[\right. \text{hades}_\text{permutation} \left(\right. x , y , 2 \left.\right) \left]\right.\right)_{0}$ Where $\left[\right. \cdot \left]\right._{j}$ denotes taking the $j$'th coordinate of a tuple. #### Array hashing Let $\text{hades} : \mathbb{F}_{P}^{3} \rightarrow \mathbb{F}_{P}^{3}$ denote the Hades permutation with Starknet's parameters, then given an array $a_{1} , . . . , a_{n}$ of $n$ field elements we define $\text{poseidon} \left(\right. a_{1} , . . . , a_{n} \left.\right)$ to be the first coordinate of $H \left(\right. a_{1} , . . . , a_{n} ; 0 , 0 , 0 \left.\right)$, where: $$ H(a_1, \ldots, a_n; s_1, s_2, s_3) = \begin{cases} H(a_3, \ldots, a_n; \text{hades}(s_1 + a_1, s_2 + a_2, s_3)) & \text{if } n \geq 2 \\ \text{hades}(s_1 + a_1, s_2 + 1, s_3) & \text{if } n = 1 \\ \text{hades}(s_1 + 1, s_2, s_3) & \text{if } n = 0 \end{cases} $$ For reference implementations of the above see [poseidon\_hash.py](https://github.com/starkware-libs/cairo-lang/blob/12ca9e91bbdc8a423c63280949c7e34382792067/src/starkware/cairo/common/poseidon_hash.py#L46) and [poseidon.cairo](https://github.com/starkware-libs/cairo-lang/blob/12ca9e91bbdc8a423c63280949c7e34382792067/src/starkware/cairo/common/builtin_poseidon/poseidon.cairo#L28) in the cairo-lang GitHub repository. #### Additional resources * [Parameters for defining the Poseidon permutation used in Starknet](https://github.com/starkware-industries/poseidon/blob/main/poseidon3.txt) * [Implementation in C and assembly by CryptoExperts](https://github.com/CryptoExperts/poseidon) # Data availability Source: https://docs.starknet.io/learn/protocol/data-availability ## Overview Starknet is a Validity Rollup, which means that after consolidating and proving a set of changes to Starknet's state, it updates the latest proven state on Ethereum. Alongside the proof, it sends the [formatted](#state_diff_format) difference between the previous and new states, also known as *state diff*, allowing anyone monitoring Ethereum to use this data and reconstruct Starknet's current state. ## State diff format ### Pre-v0.11.0 Before Starknet version 0.11.0, state diffs contained information on every contract whose storage was updated, along with additional information on contract deployments. Those differences were sent as part of the calldata of the transaction that registered the proof on Ethereum, in a `uint256[]` array that included: * The number of contracts that were deployed * The address and class hash of each deployed contract * The number of contracts whose storage was updated * For each contract whose storage was updated: * Its address * A `uint256` value that encodes both the number of its storage updates and its updated nonce, defined as follows: $$ \underset{\text{128 bits}}{\underbrace{0 \ldots 0}} \mid \underset{\text{64 bits}}{\underbrace{\text{new nonce}}} \mid \underset{\text{64 bits}}{\underbrace{\#\text{ of storage updates}}}_{\text{LSB}} $$ * The key and new value for each storage update This format is demonstrated by the following example: ```json theme={null} [ 2, (1) 2472939307328371039455977650994226407024607754063562993856224077254594995194, (2) 1336043477925910602175429627555369551262229712266217887481529642650907574765, (3) 1, (4) 2019172390095051323869047481075102003731246132997057518965927979101413600827, (5) 18446744073709551617, (6) 5, (7) 102, (8) ] ``` 1. The number of deployed contracts 2. The address of the first, and only, contract whose state changed 3. The class hash of the first, and only, contract whose state changed 4. The number of contracts whose storage was updated 5. The address of the first, and only, contract whose storage was updated 6. The `uint256` value that encodes the number of storage updates and updated nonce of the first, and only, contract whose storage was updated, which equals $2^{64} + 1 = 0^{128} \left|\right. 0^{63} 1 \left|\right. 0^{63} 1$ and therefore: * One storage cell was updated * The updated nonce is `1` 7. The key of the first, and only, storage update of the first, and only, contract whose storage was updated 8. The new value of the first, and only, storage update of the first, and only, contract whose storage was updated ### v0.11.0 In Starknet version 0.11.0, state diffs changed to contain information on every contract whose storage was updated and additional information on contract deployments. This information was still sent as part of the calldata of the transaction that registered the proof on Ethereum, in a `uint256[]` array that included: * The number of contracts whose storage was updated * For contract whose storage was updated: * Its contract address * A single 32-byte word that includes its updated nonce and meta information about the storage updates, defined as follows: $$ \underset{\text{127 bits}}{\underbrace{0 \ldots 0}} \mid \underset{\text{1 bit}}{\underbrace{\text{class information flag}}} \mid \underset{\text{64 bits}}{\underbrace{\text{new nonce}}} \mid \underset{\text{64 bits}}{\underbrace{\#\text{ of storage updates}}}_{\text{LSB}} $$ where the class information flag's value is `1` if the contract was deployed or replaced and `0` otherwise (i.e., there were only storage updates). When set to `1`, the new class hash occupies the next entry in the array * The key and new value for each storage update * The number of classes that were declared * The class hash and compiled class hash of each declared class This format is demonstrated by the following example: ```json theme={null} [ 1, (1) 2019172390095051323869047481075102003731246132997057518965927979101413600827, (2) 18446744073709551617, (3) 100, (4) 200, (5) 1, (6) 1351148242645005540004162531550805076995747746087542030095186557536641755046, (7) 558404273560404778508455254030458021013656352466216690688595011803280448032 (8) ] ``` 1. The number of contracts whose storage was updated 2. The address of the first, and only, contract whose storage changed 3. The 32-byte word that encodes the updated nonce and meta information about the storage updates of the first, and only, contract whose storage was updated, which equals $2^{64} + 1 = 0^{127} \left|\right. 0 \left|\right. 0^{63} 1 \left|\right. 0^{63} 1$ and therefore:- One storage cell was update * The new nonce is `1` * The class information flag is `0`, which means that the contract was not deployed or replaced and therefore the next word shouldn't be treated as the class hash 4. The key of the first, and only, storage update of the first, and only, contract whose storage was updated 5. The new value of the first and only storage update of the first and only contract whose storage was updated These two elements, `100` and `200`, encode the storage update, where the value of key `100` is set to `200`. 6. The number of classes that were declared 7. The class hash of the first, and only, declared class 8. The compiled class hash of the first, and only, declared class ### v0.13.1 In Starknet version 0.13.1, sending state diffs to Ethereum changed from using calldata to using either calldata or blobs. Under normal conditions, using blobs is default method, but in extreme situations where blob prices significantly exceed those of calldata, the Starknet sequencer can switch to use calldata instead. See [Data availability with EIP-4844](https://community.starknet.io/t/data-availability-with-eip4844/) on the Starknet Community Forum or review [an example blob published on Ethereum by the Starknet sequencer](https://etherscan.io/tx/0x8a227491bc78424c2cac1b203c95cdd99ede5112d41f0e7eab26f3c8aa9c658d/) for more details. The format for state diffs remains the same as in version 0.11.0, but the data sent to Ethereum changed to a Fast Fourier Transform (FFT) of the original data. To recover Starknet's state diff based on blobs or calldata published onchain, an Inverse Fast Fourier Transform (IFFT) on the data must first be performed, afterwhich decoding can proceed as usual. ### v0.13.3 In Starknet version 0.13.3, sending state diffs to Ethereum changed from sending raw state diffs to sending compressed state diffs. The employed compression scheme is a simple lookup table variant, where a list of 252-bit field elements is transformed into a (usually smaller) list of 252-bit field elements as follows: 1. Unique field elements in the data are split into buckets of 15, 31, 62, 83, 125, and 252 bits (i.e. felts that require less than 15 bits go into the 15 bits bucket, felts that require 16 to 31 bits go into the 31 bits bucket, and so on). 2. Each bucket is packed according to its number of bits (e.g., the 31 bits bucket allows the packing of 8 elements into a single felt). 3. A list of pointers whose length is the length of the original data is constructed, where the `i`'th pointer is the bucket of the `i`'th element if the `i`'th element is a first occurrence, or a special index that indicates a repetition otherwise. The list of pointers can be packed to \~ 1/84 of the original list length since we only need 3 bits to indicate the bucket and we can fit 84 of those into a felt. 4. A list of repeating value pointers is constructed, by adding `(bucket_index, index_in_bucket)` for every repetition in the original data. To illustrate the above, consider the following example: Let indices 0,1, …, 5 correspond to buckets 252, 125, …, 15, and let 6 denote a special bucket of repetitions. For the data list `[2^250, 10, 100, 2^63, 2^63+1, 10, 100]`, we construct the following: * Bucket 252: `[2^250]` * Bucket 83: `[2^63, 2^63+1]` * Bucket 15: `[10, 100]` * Pointers: `[0, 5, 5, 3, 3, 6, 6]` * Repeating value pointers: `[(5, 0), (5, 1)]` (We have two repetitions: the first for 10, which is the first element in bucket index 5, and the second for 100, which is the second element in the same bucket) The final compressed list packs each bucket and each list individually and adds some necessary metadata. This simple-to-write compression was chosen over the common Brotli or gzip compressions employed by other chains for similar purposes because the compression must be proven (i.e., either the compression or decompression must be implemented within the Starknet OS, and therefore its efficiency is crucial). You can find a Python implementation of it in the [cairo-lang repository](https://github.com/starkware-libs/cairo-lang). To better lends itself to the new compression scheme, as well as allow its construction to be based on the state diff alone, the uncompressed encoding of contract diff headers also changed as follows: $\left|\right. \underset{\text{64 bits}}{\underbrace{\text{new nonce }(\text{if changed})}} \left|\right. \underset{\text{64 bits}}{\underbrace{\#\text{ of storage updates}}} \left|\right. \underset{\text{1 bit}}{\underbrace{\text{class information flag}}} \left|\right. \underset{\text{1 bit}}{\underbrace{\text{updates information flag}}}_{\text{LSB}}$ where: * The updates information flag is `0` if the number of updates is less than 256 (and therefore can fit in 8 bits), and `1` otherwise * The semantics of class information flag is unchanged (i.e., it indicates whether or not the class was replaced) * If the nonce of the contract is unchanged, the value of the new nonce is zero This definition can slightly deviate from the previous semantics, if an account contract was modified externally (e.g., via `execute_from_outside`). In this case, the contract's nonce is unchanged, yet it appears in the state update (since its storage was updated). Pre-v0.13.3, the current nonce of the account would have appeared although it is unchanged, while in v0.13.3 the value of new nonce is zero. This change helps with making the encoding derivable solely from the state diff itself, without referring to the global state of the chain. ### v0.13.4 In Starknet v0.13.4, a second layer, termed *stateful compression*, was added to the compression of state diffs. Stateful compression is based on the observation that most parts that were “incompressible” by the previous compression — now termed *stateless compression* — are storage keys and contract addresses, which can be indexed based on their first occurrence in a state diff, thus encoding them with potentially much less than the full 32bytes. To achieve this, a new system contract at address `0x2` was introduced, defined as follows: * Storage slot `0x0` of the contract is the value of a global counter, initialized to `128` in the beginning of the first block of v0.13.4. * Whenever a non-indexed storage key or contract address appears in a state diff, it is mapped to the current value of the counter, and the counter is increased. Storage keys that require at most 127 bits and addresses of system contracts (currently, `0x1` and `0x2`) are not mapped and continue to be referred to directly. * The (uncompressed) state-diff includes the corresponding counter values from the value-to-index mapping rather than the original values. As its name suggests, stateful compression introduces dependency between state diffs submitted to Ethereum. That is, state diffs of a given block cannot be decoded without knowing the state diffs of the previous blocks. This dependency is only relevant for post-v0.13.4 state diffs, while pre-v0.13.4 state diffs remain self-contained. Starknet v0.13.4 introduces dependency between state diffs, making post-v0.13.4 state diffs non-decodable without knowledge of previous post-v0.13.4 state diffs. # Fees Source: https://docs.starknet.io/learn/protocol/fees ## Overview Every transaction on Starknet requires a small fee to process. The components contributing to this fee include [L2 computation, L2 data, and L1 data](/learn/protocol/fees#fee-components), which are measured in [L2 gas, L1 gas, and L1 data gas](/learn/protocol/fees#fee-resources). A transaction's fee can be [estimated](/learn/protocol/fees#fee-estimations) and [limited](/learn/protocol/fees#fee-limits), and is ultimately [charged](/learn/protocol/fees#fee-charges) based on a predefined [formula](/learn/protocol/fees#overall-fee). This section describes fees that are paid on L2 starting in Starknet 0.13.0. For information about messaging fees that are paid on L1, see [L1→L2 message fees](/learn/protocol/messaging/#l1-l2-message-fees). ## Fee components The following components contribute to a transaction's fee: * [L2 computation](/learn/protocol/fees#l2-computation) * [L2 data](/learn/protocol/fees#l2-data) (calldata, events and code) * [L1 data](/learn/protocol/fees#l1-data), which includes: * The cost of posting the state diffs induced by the transaction to L1 (for more details, see [Data availability](/learn/protocol/data-availability/)) * L2→L1 messages (which are eventually sent to the Starknet core contract as L1 calldata by the Starknet sequencer) ## Fee units The three components contributing to a transaction's fee are measured by the following three units: * *L2 gas*, measuring L2 resources, including computation and data * *L1 data gas*, measuring the L1 data required to post the state diff induced by the transaction to L1 * *L1 gas*, measuring the L1 gas required for sending L2→L1 messages ## Fee estimations The fee for a transaction can be estimated by using the [`starknet_estimateFee endpoint`](https://github.com/starkware-libs/starknet-specs/blob/v0.7.1/api/starknet_api_openrpc.json#L612), and interfaces for fee estimations are also exposed by the various [Starknet SDKs](../cheatsheets/tools). ## Fee limits Starting in Starknet v0.14.0, all transactions have to specify STRK-denominated `max_amount` and `max_price_per_unit` for each of the three [fee unit](/learn/protocol/fees#fee-units). A necessary condition for a transaction to enter a block is that `max_price_per_unit` is greater than or equal to the [block's corresponding price](/learn/protocol/fees#gas-prices) for each resource, but if included sequencers are then enforced by the [Starknet OS](/learn/protocol/snos/) (and as such, enforced by a proof) to charge no more than the max price specified by the transaction. `v3` transactions already provide these bounds (as part of the `resource_bounds` dictionary) Sequencers usually charges less than the max price specified in the transaction, as charges are in accordance with the [formula for determining a transaction's fee](/learn/protocol/fees#overall-fee). ## Fee charges The fee for a transaction is charged atomically with the transaction execution on L2, by the Starknet OS injecting a transfer of STRK with an amount equal to the fee paid, the sender equal to the transaction submitter, and the sequencer as a receiver. Starknet does not currently implement burning of fees, and all fees charged are received by the sequencer. ## Fee formula Starknet's fee formula is highly non-trivial. To mitigate this, the first section details the formula for determining a transaction's overall fee, while subsequent ones dive into the different fee components and explain how this formula was derived. Some parts may require reading more than once, but don't hesitate to [reach out](https://github.com/starknet-io/starknet-docs/issues/new?assignees=landauraz\&title=Feedback%20for%20%22The%20Starknet%20operating%20system%22) if you feel further clarification is needed. While sequencer are [enforced not to overcharge fees](/learn/protocol/fees#fee_limits), they are not yet enforced to charge fees charged according to Starknet's fee formula. ### Overall fee The following formula describes the overall fee of a transaction: $$ \begin{align*} & \text{l1}_{\text{gas price}} \cdot \Big( \\ &\quad \text{message}_{\text{calldata cost}} \cdot 3t + \\ &\quad (\text{message}_{\text{calldata cost}} + \text{l1}_{\text{log data cost}}) \cdot \sum_{i = 1}^{t} q_{i} + \\ &\quad (\text{l1}_{\text{storage write cost}} + \text{log}_{\text{message to l1 cost}}) \cdot t \Big) \\ &+ (\text{l2}_{\text{gas price}} + \text{tip}) \cdot \left( \text{sierra}_{\text{gas consumed}} + \max_k v_k w_k + \text{l2}_{\text{payload costs}} \right) \\ &+ \begin{cases} \text{l1}_{\text{data gas price}} \cdot \text{felt}_{\text{size in bytes}} \cdot \left( \ell + 2(n - 1) + 2(m - 1) + 2D \right), & \text{if } \text{l1}_{\text{da mode}} = \text{BLOB} \\ \text{l1}_{\text{gas price}} \cdot \left( \text{da}_{\text{calldata cost}} \cdot \left( \ell + 2(n - 1) + 2(m - 1) + 2D \right) - \right. \\ \quad \text{contract}_{\text{update discount}} \cdot (n - 1) - \text{sender}_{\text{balance update discount}}, & \text{if } \text{l1}_{\text{da mode}} = \text{CALLDATA} \end{cases} \end{align*} $$ where: * $\text{l1}_{\text{gas price}}$ is the L1 gas price of the block that includes the transaction (see [Gas prices](/learn/protocol/fees#gas-prices) for more information) * $\text{message}_{\text{calldata cost}}$ is 1,124 gas * $t$ is the number of L2→L1 messages sent by the transaction * $\text{l1}_{\text{log data cost}}$ is 256 gas * $t,\ q_{1}, \ldots, q_{t}$ are the payload sizes of the L2→L1 messages sent by the transaction * $\text{l1}_{\text{storage write cost}}$ is 20,000 gas (the cost of writing to a new storage slot on Ethereum) * $\text{log}_{\text{message to l1 cost}}$ is 1,637 gas (see [L2→L1 messages](/learn/protocol/fees#l2→l1-messages) for more information) * $\text{l2}_{\text{gas price}}$ is the L2 gas price of the block that includes the transaction (see [Gas prices](/learn/protocol/fees#gas-prices) for more information) * $\text{tip}$ is the tip specified by the transaction * $\text{sierra}_{\text{gas consumed}}$ is the amount of [Sierra gas](/learn/protocol/fees#sierra-gas) charged for computation of the transaction * $v$ is a vector that represents resource usage of the transaction (Cairo steps or number of applications of each builtin), where each of its entries, $v_{k}$, corresponds to the usage of a different resource type (see [VM resources](/learn/protocol/fees#vm-resources) for more information) The fee formula of a transaction can track both raw VM resources (reflected by $v_{k}$) and Sierra gas, depending on what classes it goes through (see [L2 computation](/learn/protocol/fees#l2-computation) for more details). * $w$ is the `CairoResourceFeeWeights` vector (see [VM resources](/learn/protocol/fees#vm-resources) for more information) * $\text{l2}_{\text{payload costs}}$ is the gas cost of the data sent by the transaction over Starknet, which includes calldata, code, and event emission (see [L2 data](/learn/protocol/fees#l2-data) for more information) * $\text{l1}_{\text{da mode}}$ is either $\text{CALLDATA}$ or $\text{BLOB}$, depending on how the state diff of the block that includes the transaction is sent to L1 (see [Data availability](/learn/protocol/data-availability/) for more information) * $\text{l1}_{\text{data gas price}}$ is the L1 data gas price of the block that includes the transaction (see [Gas prices](/learn/protocol/fees#gas-prices) for more information) * $\text{felt}_{\text{size in bytes}}$ is 32 (the number of bytes required to encode a single STARK field element) * $ℓ$ is the number of contracts whose class was changed by the transaction, which happens on contract deployment and when applying the `replace_class` syscall * $n$ is the number of unique contracts updated by the transaction, which also includes changes to classes of existing contracts and contract deployments, even if the storage of the newly deployed contract is untouched (in other words, $n \geq ℓ$) Notice that $n \geq 1$ always holds, because the fee token contract is always updated, which does not incur any fee. * $m$ is the number of storage values updated by the transaction, not counting multiple updates for the same key Notice that $m \geq 1$ always holds, because the sequencer's balance is always updated, which does not incur any fee. * $D$ is 1 if the transaction is of type `DECLARE` and 0 otherwise, as declare transactions need to post on L1 the new class hash and compiled class hash which are added to the state * $\text{da}_{\text{calldata cost}}$ is 551 gas, derived as follows: * 512 gas per 32-byte word for calldata * \~100 gas for onchain hashing that happens for every word sent * A 10% discount for not incurring additional costs for repeated updates to the same storage slot within a single block * $\text{contract}_{\text{update discount}}$ is 312 gas (see [Storage updates](/learn/protocol/fees#storage-updates) for more information) * $\text{sender}_{\text{balance update discount}}$ is $240$ gas (see [Storage updates](/learn/protocol/fees#storage-updates) for more information) ### Gas prices Each Starknet block has three integers associated with it: `l1_gas_price`, `l2_gas_price`, and `l1_data_gas_price`. `l1_gas_price` and `l1_data_price` are computed by taking the average of the last 60 L1 base gas or data gas prices sampled every 60 seconds by the sequencer, plus 1 Gwei. For `l1_data_gas_price`, this number is then multiplied by a scaling factor of 0.135 that approximate for the average rate compression achieved from posting the data to Ethereum. `l2_gas_price` is calculated in a similar way to Ethereum's [EIP 1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md), , which means computing `l2_gas_price` as follows: $$ \max \left( \left( 1 + \frac{\text{prev}_{\text{L2 gas use}} - \text{TARGET}}{\text{TARGET}} \cdot C \right) \cdot \text{prev}_{\text{L2 gas price}},\ \text{MIN}_{\text{PRICE}} \right) $$ where: * $\text{prev}_\text{L2 gas use}$ is the total L2 gas used in the previous block * $\text{TARGET}$ is 80% of [Starknet's block capacity](/learn/cheatsheets/chain-info/#current_limits) * C equals $\frac{1}{48}$ * $\text{prev}_\text{L2 gas price}$ is the previous block's `l2_gas_price` * $\text{MIN}_\text{PRICE}$ is currently 3gFri, or $3*10^{-9}$ STRK. $\text{MIN}_\text{PRICE}$ is subjected to change with time, as a function of the transaction processing cost. This assures that: * If the total gas used in the previous block is equal to $\text{TARGET}$, then `l2_gas_price` won't change * If the total gas used in the previous block is larger or smaller than $\text{TARGET}$, then `l2_gas_price` will respectively increase or decrease. Since $\text{TARGET}$ is larger than half of the block size, price will decrease faster when there is no congestion compared to its increase rate when there is congestion. * When there is no congestion in the network, the `l2_gas_price` will be equal to $\text{MIN}_\text{PRICE}$ ### L2 computation Measuring the L2 computation component of a transaction differs depending on the contract class version of the caller: * For Sierra ≥ 1.7.0, computation is measured in [Sierra gas](/learn/protocol/fees#sierra-gas) * For CairoZero classes or Sierra ≤ 1.6.0, computation is measured in [VM resources](/learn/protocol/fees#vm-resources) Sierra gas is only tracked if the parent call was also tracking Sierra gas, which means that if the account contract is Sierra 1.6.0 or older, VM resources will be tracked **throughout the entire transaction**. This condition may be relaxed in the future. #### Sierra gas The following is a very rough description of Sierra's built-in gas accounting mechanism. For a comprehensive analysis, see [*Analysis of the gas accounting algorithm of Cairo 1.0 by CryptoExperts*](https://github.com/starkware-libs/cairo/blob/main/docs/other/gas_blog_post.pdf). A Sierra program has a simple structure: types and function declaration, followed by a sequence of applications of *libfuncs*, Sierra's basic logical units (similar to opcodes, e.g. `u8_add` is a libfunc). The Cairo compiler defines a libfunc costs table, which is measured in “Sierra gas” and has a 1-1 ratio with L2 gas (i.e., a libfunc which costs 500 Sierra gas adds 500 to a transaction's overall L2 gas) Despite the 1-1 ratio between Sierra gas and L2 gas, L2 gas accounts for “everything L2”, while Sierra gas strictly deals with computation, hence the distinction in terminology. The cost of each libfunc is determined by its expanded CASM generated via the Sierra→CASM compiler based on a 100-1 ratio with Cairo steps (i.e., if a libfunc's assembly includes 10 Cairo steps, it will cost 1000 Sierra gas), while the costs of the various builtins are defined as follows: | Builtin | Sierra gas cost | | - | - | | Range check | 70 | | Pedersen | 4,050 | | Poseidon | 491 | | Bitwise | 583 | | ECDSA | 10,561 | | EC\_OP | 4,085 | | Keccak | 136,189 | | ADD\_MOD | 230 | | MUL\_MOD | 604 | To review pricing for various syscalls, see the `versioned constants.json` files in the [sequencer's resources directory](https://github.com/starkware-libs/sequencer/tree/main/crates/blockifier/resources). To handle gas usage, Sierra has special libfuncs for gas-handling, such as the `withdraw_gas` libfunc. For functions with neither branching nor recursion, the Cairo→Sierra compiler adds a single `withdraw_gas(C)` call in the beginning of the function, where `C` is the sum over the costs of the libfuncs included in the function. For functions with branching, the compiler adds a call to `withdraw_gas(C)` before the actual branching, where `C` is the maximal branch cost. In its latest version, the compiler also adds a call to `redeposit_gas(C)` on the cheaper branches, where `C` is unused gas on that branch. For functions with recursion (or other cases where costs can only be known in runtime), things get trickier. The naive way to handle such cases would be to add a `withdraw_gas` instruction after every libfunc, but since `withdraw_gas` itself has some cost (decreasing a counter and handling the insufficient gas case) this would incur a large burden on the program. Instead, the compiler constructs the call graph induced by the program, and asserts that every cycle includes a `withdraw_gas(X)` instruction, where `X` should cover the cost of a single run through the cycle, greatly reducing the overhead compared to the naive mechanism. #### VM resources A Cairo program execution yields an execution trace, and when proving a Starknet block, we aggregate all the transactions appearing in that block to the execution trace up to some maximal length $L$, derived from the specs of the proving machine and the desired proof latency. Tracking the execution trace length associated with each transaction is simple, as Cairo step requires the same constant number of trace cells. Therefore, in a world without builtins, the fee associated with the L2 computation component of a transaction $t x$ should be correlated with $\text{TraceCells} \left[\right. t x \left]\right. / L$. The aforementioned observation is no longer true for Starknet's next-gent prover S-two, which handles some opcodes more efficiently than others. However, we neglect this intricacy for the purposes of this discussion. When we introduce builtins into the equation, we need to consider an a priori limit for each builtin in the proof. This set of limits is known as the proof's *layout*, which determines the ratio between steps and each builtin. Today, Starknet's prover is able to dynamically choose a layout based on a given block resource's consumption, i.e. there is no longer an a priori fixed layout. However, pricing for old classes still behaves as if we are using a fixed layout. For example, consider that the prover can process a trace with the following limits:
Up to 500M Cairo steps Up to 20M Pedersen hashes Up to 4M signature verifications Up to 10M range checks
which means that a proof is closed and sent to L1 when any of these slots is filled. Now, suppose that a transaction uses 10K Cairo steps and 500 Pedersen hashes. At most 20M/500 = 40K such transactions can fit into the hypothetical trace, therefore its gas price should correlate with 1/40K of the cost of submitting proof (notice that this estimate ignores the number of Cairo steps as it is not the limiting factor, since 500M/10K > 20M/500). With this example in mind, it is possible to formulate the exact fee associated with L2 computation. For each transaction, the sequencer calculates a vector, `CairoResourceUsage`, that contains the following: * The number of Cairo steps * The number of applications of each Cairo builtin (e.g., 5 range checks and 2 Pedersen hashes) and crosses this information with a `CairoResourceFeeWeights` vector, a predefined weights vector in accordance with the proof parameters, in which each resource type has an entry that specifies the relative gas cost of that component in the proof. The sequencer then charges only according to the limiting factor, making the final fee defined by: $\underset{k}{max} \left[\right. \text{CairoResourceUsage}_{k} \cdot \text{CairoResourceFeeWeights}_{k} \left]\right.$ where $k$ enumerates the Cairo resource components. Going back to the above example, if the cost of submitting a proof with 20M Pedersen hashes is roughly 5M gas, then the weight of the Pedersen builtin is 5,000,000/20,000,0000 = 25 gas per application. #### VM resources vs. Sierra gas The difference in tracking Sierra gas vs. tracking VM resources can be summed up as follows: * For VM resources builtin weights reflect the proof layout, while for Sierra gas they reflect trace cell consumption * For VM resources only the maximal resource (e.g., most used builtin) is considered, while for Sierra gas the sum of all resources (i.e., all libfuncs) is considered This means that when the tracking Sierra gas, step-heavy transactions will most likely be slightly more expensive, as builtins will be taken into account *in addition* to Cairo steps. On the other hand, builtin-heavy transactions will become much cheaper — depending on the builtin that maximized the old fee and with the exception of the Pedersen builtin. ### L1 data #### Storage updates Whenever a transaction updates some value in the storage of some contract, the following data is sent to L1: * One 32-bye word if the transaction is a `DEPLOY` transaction (since we need to specify the deployed contract's class hash) * Two 32-byte words per contract * Two 32-byte words for every updated storage value Only the most recent value reaches L1, making the transaction's fee depend on the number of *unique* storage updates. If the same storage cell is updated multiple times within the transaction, the fee remains that of a single update. For information on the exact data and its construction, see [Data availability](/learn/protocol/data-availability#v0-11-0). Therefore, the storage update fee for a transaction is defined as follows: $$ \text{data}_{\text{gas price}} \cdot \text{felt}_{\text{size in bytes}} \cdot \left( \ell + 2(n - 1) + 2(m - 1) + 2D \right) $$ This formula only refer to the case of submitting data to L1 via blobs, for the calldata case, see [Overall fee](/learn/protocol/fees#overall-fee)). where: * $\text{felt}_{\text{size in bytes}}$ is 32, which is the number of bytes required to encode a single STARK field element. * $ℓ$ is the number of contracts whose class was changed, which happens on contract deployment and when applying the `replace_class` syscall. * $n$ is the number of unique contracts updated, which also includes changes to classes of existing contracts and contract deployments, even if the storage of the newly deployed contract is untouched. In other words, $n \geq ℓ$. Notice that $n \geq 1$ always holds, because the fee token contract is always updated, which does not incur any fee. * $m$ is the number of values updated, not counting multiple updates for the same key. Notice that $m \geq 1$ always holds, because the sequencer's balance is always updated, which does not incur any fee. * $D$ is 1 if the transaction is of type `DECLARE` and 0 otherwise. Declare transactions need to post on L1 the new class hash and compiled class hash which are added to the state. Improvements to the above pessimistic estimation might be gradually implemented in future versions of Starknet. For example, if different transactions within the same block update the same storage cell, there is no need to charge for both transactions, because only the last value reaches L1. In the future, Starknet might include a refund mechanism for such cases. #### L2→L1 messages When a transaction that raises the `send_message_to_l1` syscall is included in a state update, the following data reaches L1: * L2 sender address * L1 destination address * Payload size * Payload (list of field elements) Therefore, the gas cost associated with a single L2→L1 message is defined as follows: $$ \text{message}_{\text{calldata cost}} \cdot \left( 3 + \text{payload}_{\text{size}} \right) + \text{l1}_{\text{log data cost}} \cdot \text{payload}_{\text{size}} + \text{log}_{\text{message to l1 cost}} + \text{l1}_{\text{storage write cost}} $$ Where: * $\text{message}_{\text{calldata cost}}$ is 1,124 gas, which is the sum of 512 gas for submitting the state update to the core contract and 612 gas for submitting the state update to the verifier contract (which incurs \~100 additional gas for hashing) * $\text{l1}_{\text{log data cost}}$ is 256 gas, paid for every payload element during the emission of the `LogMessageToL1` event * $\text{log}_{\text{message to l1 cost}}$ is 1,637 gas, which is the fixed cost involved in emitting a `LogMessageToL1` event with two topics and a two-word data array, resulting in a total of\ $375 + 2 \cdot 375 + 2 \cdot 256$ gas (log opcode cost, topics cost, and data array cost) * $\text{l1}_{\text{storage write cost}}$ is 20K gas per message, paid in order to store the message hash on the Starknet core contract and enable the target L1 contract to consume the message ### L2 data L2 data composes of: * Calldata, including transaction calldata (in the case of `INVOKE` transactions or `L1_HANDLER`), constructor calldata (in the case of `DEPLOY_ACCOUNT` transactions), and signatures * Events, including data and keys of emitted events * ABI, including classes ABI in `DECLARE` transactions (only relevant for `DECLARE` transactions of version ≥ 2) * Casm bytecode (for all available `DECLARE` transactions, where in version \< 2 this refers to the compiled class) * Sierra bytecode (relevant only for `DECLARE` transactions of version ≥ 2) The L1 gas cost of each component in as follows: When a transaction's L2 cost is paid for by L2 gas, the following numbers are translated via the standard conversion rate of 1 L1 gas = 40K L2 gas. | Resource | L2 Gas cost | | - | - | | Event key | 10,240 gas/felt | | Event data | 5,120 gas/felt | | Calldata | 5,120 gas/felt | | CASM bytecode | 40,000 gas/felt | | Sierra bytecode | 40,000 gas/felt | | ABI | 1,280 gas/character | # Introduction to Starknet's protocol Source: https://docs.starknet.io/learn/protocol/intro Starknet is a decentralized, permissionless Layer 2 (L2) validity rollup that scales Ethereum by utilizing STARK-based zero-knowledge proofs to bundle thousands of transactions off-chain for verified settlement on the mainnet.  As of 2026, the network evolved into a privacy-centric hub with the launch of the [STRK20](/build/starkzap/privacy/strk20) framework, which allows assets like stablecoins to maintain confidential balances and transfers while remaining regulatory-compliant. ## User interactions As with other blockchains, users interact with Starknet by sending [transactions](/learn/protocol/transactions/) from their [account](/learn/protocol/accounts/). On Starknet, accounts are smart contracts, a model known as native account abstraction. This allows for flexible authorization logic such as multisig, session keys, or passkey-based authentication — all without changes to the protocol itself. The transactions users send are usually invoke transactions, which essentially contain a list of \[(contract, selector, parameters)] for execution. Notice that multicall is thus natively supported on Starknet. For more details on invoke transactions as well as rarer types such as declare, see [transactions](/learn/protocol/transactions/) Some transactions may involve communication between Ethereum and Starknet (or even be initiated by an Ethereum transaction), and are handled through [L1↔L2 messaging](/learn/protocol/messaging/). An example is deposits to Starknet from Ethereum through secure bridges such as [StarkGate](/learn/protocol/starkgate/). ## Block production and validation Transactions are arriving in the Starknet sequencer's mempool, and from there are collected and ordered into [blocks](/learn/protocol/blocks/). To ensure that state transitions are valid, Starknet uses [SHARP](/learn/protocol/sharp/) to generate and aggregate proofs that running the [SNOS](/learn/protocol/snos/) program on the set of the block's transactions indeed yields the Starknet state at the end of the block. These proofs (with further proof aggregation, happening inside SHARP), compress many blocks' execution into a succinct artifact, which is submitted to Ethereum to be verified, so that Starknet's execution can be trusted without re-running it. Starknet also ensures access to the data used in the computation via [data availability](/learn/protocol/data-availability/), publishing compressed state diffs to Ethereum so the full state can be reconstructed and verified. ## Tokenomics and consensus [STRK](/learn/protocol/strk/), Starknet's native token, is used to pay [fees](/learn/protocol/fees/) for transaction execution. Minimal fee level is set to cover the cost of using network resources. STRK is also used to power Starknet's [staking protocol](/learn/protocol/staking/), where STRK stakers help secure the sequencing layer and validate block production. This mechanism is designed to support decentralization and provide economic guarantees around block inclusion and ordering. ## Summary All together, these elements form a tightly integrated protocol, enabling scalable and expressive applications with low fees and strong security — all without compromising on decentralization or alignment with Ethereum. Now that you've got the big picture, put on your goggles and deep dive into whichever concept or component intrigues you most! 🤿 # L1 ↔ L2 messaging Source: https://docs.starknet.io/learn/protocol/messaging ## Overview Starknet's ability to interact with L1 is crucial, and is used by bridges (e.g., for depositing your tokens in the L1 bridge contract, and automatically triggers the minting of the same token on L2), [DeFi pooling](https://starkware.co/resource/defi-pooling/) and [more](https://www.starknet.io/en/ecosystem/dapps). Messaging — both [from L1 to L2](/learn/protocol/messaging#l2-→-l1-messages) and from [L2 to L1](/learn/protocol/messaging#l2-→-l1-message-protocol) — is the mechanism that enables this interaction. Starknet's messaging mechanism is both *asynchronous* and *asymmetric*. * *Asynchronous*: Your contract code, whether Cairo or Solidity, cannot await the result of the message being sent on the other layer within your contract code's execution. * *Asymmetric*: Sending a message from Ethereum to Starknet, L1→L2, is fully automated by the Starknet sequencer, so the message is automatically delivered to the target contract on L2. However, when sending a message from Starknet to Ethereum, L2→L1, the sequencer only sends the hash of the message. You must then consume the message manually using a transaction on L1. Additional resources * [The `send_message_to_L1` syscall](https://book.cairo-lang.org/appendix-08-system-calls.html#send_message_to_l1) * [The Starknet Core contract's `sendMessageToL2` function](https://github.com/starkware-libs/cairo-lang/blob/54d7e92a703b3b5a1e07e9389608178129946efc/src/starkware/starknet/solidity/IStarknetMessaging.sol#L13) * [L1-L2 Messaging in the Cairo Book](https://book.cairo-lang.org/ch16-04-L1-L2-messaging.html) ## L2 → L1 messages ### L2 → L1 message protocol The L2 → L1 message protocol consists of the following stages: 1. During the execution of a transaction, a contract on Starknet sends a message from L2 to L1 by calling the `send_message_to_L1` syscall. 2. The sequencer attaches the message parameters to the block that includes the syscall invocation. The message parameters include the address of the sender on L2, the address of the recipient contract on L1, and the message data. For example: ```cairo theme={null} let mut payload: Array = ArrayTrait::new(); let to_address: EthAddress = 1_felt252.try_into().unwrap(); payload.append(1); // potentially add more elements to payload (payload[1], payload[2], etc.) send_message_to_l1_syscall(to_address: to_address.into(), payload: payload.span()); ``` 3. The prover proves the state update that includes this transaction. 4. The sequencer updates the L1 state. 5. The message is stored on L1 in the Starknet Core Contract and a counter on the Core Contract increases by one. 6. The `processMessage` function, which is part of the Starknet Core Contract, emits the `LogMessageToL1` event, which contains the message parameters. 7. The message recipient on L1 can access and consume the message by calling the [`consumeMessageFromL2`](https://github.com/starkware-libs/cairo-lang/blob/4e233516f52477ad158bc81a86ec2760471c1b65/src/starkware/starknet/eth/StarknetMessaging.sol#L119) function, which includes the message parameters within the transaction. This function, which is part of the Starknet Core Contract, verifies the following: * The hashes of the L2 sent message parameters, now stored on the Core Contract, and the L1 received message parameters, are the same. * The entity calling the function is indeed the recipient on L1. In such a case, the counter corresponding to the message hash in the Starknet Core Contract decreases by one. For more information, see the [`consumeMessageFromL2`](https://github.com/starkware-libs/cairo-lang/blob/4e233516f52477ad158bc81a86ec2760471c1b65/src/starkware/starknet/eth/StarknetMessaging.sol#L130C7-L130C7#) function in `StarknetMessaging.sol`. The protocol is summarized in the following illustration: ### L2 → L1 message structure The structure of an L2 → L1 message is defined under `MSG_TO_L1` in the [Starknet API JSON RPC](https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json) specification as follows: | | | | - | - | | `from_address` (`felt252`) | The address of the L2 contract sending the message. | | `to_address` (`EthAddress`) | The target L1 address the message is sent to. | | `payload` (`Array`) | The payload of the message. | ### L2 → L1 message hashing The hash of an L2 → L1 message is computed on L1 as follows: ``` keccak256( abi.encodePacked( FromAddress, uint256(ToAddress), Payload.length, Payload ) ); ``` Sending an L2 to L1 message always incurs a fixed cost of 20,000 L1 gas, because the hash of the message being sent must be written to L1 storage in the Starknet Core Contract. ## L1 → L2 messages ### L1 → L2 message protocol The L1 → L2 message protocol consists of the following stages: 1. An L1 contract induces a message to an L2 contract on Starknet by calling the [`sendMessageToL2`](https://github.com/starkware-libs/cairo-lang/blob/54d7e92a703b3b5a1e07e9389608178129946efc/src/starkware/starknet/solidity/IStarknetMessaging.sol#L13) function on the Starknet Core Contract with the message parameters. The Starknet Core Contract hashes the message parameters and updates the L1→L2 message mapping to indicate that a message with this hash was indeed sent. The L1 contract records the fee that the sender paid. For more information, see [L1 → L2 message fees](#l1-l2-message-fees). 2. The message is then decoded into a Starknet transaction that invokes a function annotated with the `l1_handler` decorator on the target contract. Transactions like this on L2 are called [L1 handler transactions](#l1_handler_transaction). 1. The Starknet sequencer, upon receiving enough L1 confirmations for the transaction that sent the message, initiates the corresponding L2 transaction. 2. The L2 transaction invokes the relevant `l1_handler` function. 3. The L1 Handler transaction that was created in the previous step is added to a proof. 4. The Core Contract receives the state update. 5. The message is cleared from the Core Contract's storage to consume the message. Clearing the Core Contract's storage does the following: * incurs a fixed cost of 5,000 L1 gas * emits an L1 event logging the message consumption At this point, the message is handled. An L1→L2 message consists of the following: * L1 sender's address * L2 recipient's contract address * Function selector * Calldata array * Message nonce The message nonce is maintained on the Starknet Core Contract on L1, and is incremented whenever a message is sent to L2. The nonce is used to avoid a hash collision between different L1 handler transactions that is caused by the same message being sent on L1 multiple times. For more information, see [L1→L2 structure](/learn/protocol/messaging#l2-→-l1-message-structure). ### L1 → L2 message structure For completeness, the following table describes the precise structure of both the message as it appears on L1 and the induced transaction as it appears on L2: | FromAddress | ToAddress | Selector | Payload | Nonce | | | - | - | - | - | - | - | | `EthereumAddress` | `FieldElement` | `FieldElement` | `List` | `FieldElement` | | ### L1 → L2 message hashing The hash of the message is computed on L1 as follows: ``` keccak256( abi.encodePacked( uint256(FromAddress), ToAddress, Nonce, Selector, Payload.length, Payload ) ); ``` ### L1 → L2 message cancellation The flow described here should only be used in edge cases such as bugs on the Layer 2 contract preventing message consumption. Consider that Alice sends an L1 asset to a Starknet bridge to transfer it to L2, which generates the corresponding L1→L2 message. Now, consider that the L2 message consumption doesn't function, which might happen due to a bug in the dApp's Cairo contract. This bug could result in Alice losing custody of their asset forever. To mitigate this risk, the contract that initiated the L1→L2 message can cancel it by declaring the intent to cancel, waiting five days, and then completing the cancellation. This delay protects the sequencer from a DoS attack in the form of repeatedly sending and canceling a message before it is included in L1, rendering the L2 block which contains the activation of the corresponding L1 handler invalid. The steps in this flow are as follows: 1. The user that initiated the L1→L2 message calls the [`startL1ToL2MessageCancellation`](https://github.com/starkware-libs/cairo-lang/blob/4e233516f52477ad158bc81a86ec2760471c1b65/src/starkware/starknet/eth/StarknetMessaging.sol#L134) function in the Starknet Core Contract. 2. The user waits five days until she can finalize the cancellation. 3. The user calls the [`cancelL1ToL2Message`](https://github.com/starkware-libs/cairo-lang/blob/4e233516f52477ad158bc81a86ec2760471c1b65/src/starkware/starknet/eth/StarknetMessaging.sol#L147) function. ### L1 → L2 message fees An L1 → L2 message induces a transaction on L2, which, unlike regular transactions, is not sent by an account. This calls for a different mechanism for paying the transaction's fee, for otherwise the sequencer has no incentive of including L1 handler transactions inside a block. To avoid having to interact with both L1 and L2 when sending a message, L1 → L2 messages are payable on L1, by sending ETH with the call to the payable function `sendMessageToL2` on the Starknet Core Contract. The sequencer takes this fee in exchange for handling the message. The sequencer charges the fee in full upon updating the L1 state with the consumption of this message. The fee itself is calculated in the [same manner](/learn/protocol/fees#overall-fee) as "regular" L2 transactions. ### L1 handler transaction #### Transaction fields | Version | ContractAddress | Selector | Calldata | Nonce | | | - | - | - | - | - | - | | `FieldElement` | `FieldElement` | `FieldElement` | `List` | `FieldElement` | | In an L1 handler transaction, the first element of the calldata is always the sender's Ethereum address. #### Hash calculation The hash of the corresponding L1 handler transaction on L2 is computed as follows: ```cairo theme={null} l1_handler_tx_hash = h( "l1_handler", version, contract_address, entry_point_selector, h(calldata), 0, chain_id, nonce ) ``` Where: * `l1_handler` is a constant prefix, encoded in bytes (ASCII), as big-endian. * `version` is the transaction version. Only version 0 is currently supported. * `chain_id` is a constant value that specifies the network to which this transaction is sent. * *h* is the [Pedersen](/learn/protocol/cryptography#pedersen-hash) hash (note that since we're hashing an array, the # of inputs needs to be appended to the hash). * `0` indicates that L1 to L2 message fees are charged on L1. #### Caveats and limitations * Since L1 handler transactions are not initiated by an account, invoking [`get_caller_address`](https://github.com/starkware-libs/cairo/blob/2203a47f8a098cd4718d03bd109ca014049419e7/corelib/src/starknet/info.cairo#L49) or similar account-related functions returns the address `0x0`. * Starting from Starknet version 0.14.0: * L1 handlers that fail execution will appear in a block as `REVERTED`, instead of only be written in the feeder gateway's database as `REJECTED`. The purpose of this change is to simplify the tracking of messages L1 → L2, and the implementation of `getMessageStatus` in particular. * The `l1_gas`, `l2_gas`, and `l1_data_gas` execution resources of L1 handlers will be bounded as follows: * `l2_gas` will bounded by $10^{8}$ (the same as `__validate__`'s) * `l1_gas` and `l1_data_gas` will be bounded by $4 * 10^{4}$ and $2 * 10^{4}$, respectively (to allow for sending one L2 → L1 message) #### Supported versions | Current version | Deprecated versions | Unsupported versions | | - | - | - | | v0 | N/A | N/A | # Security Council Source: https://docs.starknet.io/learn/protocol/security-council ## Overview The Starknet Security Council (SC) is a 12-member body that holds administrative control over Starknet's core contracts on both Starknet and Ethereum. Its purpose is to decentralize the ability to upgrade, pause, and unpause those contracts, so that no single organization can unilaterally change the protocol, while still allowing the network to respond to a live security incident within minutes. ## Member entities The Council's 12 seats are held by individuals affiliated with the following organizations: | Organization | | - | | StarkWare | | Nethermind | | Zellic | | Hypernative | | Pragma | | AVNU | | Cartridge | | Ready | | Karnot | | Space Duck | | Flying Muffins | | Protolabs | ## Additional resources * [SNIP 25](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-25.md) on GitHub * [SNIP 25 discussion on the Starknet community forum](https://community.starknet.io/t/snip25-starknet-security-council-v1-0/115060) * [Starknet Security Council's new framework for network security](https://www.starknet.io/blog/starknets-security-council/) on the Starknet blog # SHARP Source: https://docs.starknet.io/learn/protocol/sharp ## Overview SHARP is [StarkWare's proof aggregator](#what_is_sharp), which uses [proof recursion](#proof_recursion_in_sharp) and [the S-two prover](#sharp_and_s_two) to make proving affordable for everyone, and is used by Starknet to reduce costs and improve efficiency within the network. [Atlantic](https://docs.herodotus.cloud/atlantic/introduction) is a managed ZK Prover service that allows developers to easily integrate with SHARP. Additional resources * [*Save Costs and Enhance Ethereum's Scalability: If it's Written in Cairo, SHARP Can Prove it*](https://starkware.co/blog/joining-forces-sharp/) on the Starknet blog * [*StarkWare's SHARP: Revolutionizing Ethereum Scaling with Recursive STARK Proofs*](https://www.youtube.com/watch?v=5kLm18E5ltg) in the StarkWare Scalability Series * [*SHARP: StarkWare's Shared Prover*](https://www.youtube.com/watch?v=01tIeGBHsek) from Starknet Summit 2023 * [*SHARP - the backbone of Starknet* Starknet](https://www.youtube.com/watch?v=VMNreeZkAgI) and [*Introduction to SHARP verifier smart contact*](https://www.youtube.com/watch?v=jPxD9h7BdzU) community calls * [*Starknet and zkSync: A comparative analysis*](https://www.nethermind.io/blog/starknet-and-zksync-a-comparative-analysis) by Nethermind * [*Verifying Cairo proofs on Ethereum*](https://blog.zksecurity.xyz/posts/stark-evm-adapter/) by zkSecurity * [*Proof aggregation techniques*](https://blog.lambdaclass.com/proof-aggregation-techniques/) by LambdaClass ## What is SHARP? Being a provable programming language, Cairo allows generating STARK proofs for the validity of its execution, which can then be verified onchain to guarantee their correctness. SHARP (SHARed Prover) is a powerful system designed to generate a unified STARK proof for multiple Cairo programs and verify it on Ethereum. Leveraging the exponential asymmetry of the STARK protocol, this allows applications to share the cost of verifying proofs onchain with other applications, as well as "break down" their computations into several smaller computations without increasing costs. For Starknet, this results in sending only a single proof per multiple blocks to Ethereum, and paying for its onchain verification only with respect to their relatize share in it. ## How does SHARP work? ### High-level overview SHARP's architecture consists of several services that work together to generate proofs, which include: 1. The Gateway, through which external *Cairo position-independent executions (Cairo PIEs)* can be submitted to SHARP 2. The Job Creator, which processes external Cairo PIEs into *jobs* and prevents duplications 3. The Validator, which runs validation checks on each job, ensuring they can be proven successfully 4. The Scheduler, which determines which Cairo job are to be aggregated together 5. The Cairo Runner, which executes Cairo jobs and creates their Cairo PIEs 6. The Prover ([either Stone or S-two](#stone_and_s_two)), which generates STARK proofs of Cairo PIEs 7. The Dispatcher, which either packages proofs before sending them onchain, or [verifies them using the Cairo verifier, creates a corresponding recursive job, and sends it back to the validator](#proof_recursion) 8. The Blockchain Writer, which is responsible for sending packages from the dispatcher [onchain for verification](#onchain_verifier) 9. The Catcher, which monitors onchain transactions to ensure that they have been accepted The following is a high-level overview of SHARP's architecture flow: ### Stone and S-two SHARP uses [StarkWare's first-gen Stone prover](https://starkware.co/blog/open-sourcing-the-battle-tested-stone-prover/) to generate STARK proofs for all proofs it aggregates. Starting from Starknet version 0.14.0, SHARP will use [StarkWare's next-gen S-Two prover](https://starkware.co/blog/s-two-prover/) for generating all proofs except those of [recursive tree roots](#proof_recursion), which will still to be generated using Stone. Continuing to use Stone for proving recursive tree roots is done to avoid changing SHARP's [onchain verifiers](#onchain_verifier). ### Proof recursion Since its inception, SHARP has undergone a handful of efficiency upgrades, with the most powerful being the shift from a linear to a recursive model. In the linear model, SHARP waited for enough programs to "fill up" a proof, and only then started the proving process for the whole bundle of programs. In the recursive model, SHARP proves each statement upon its arrival, and instead of verifying it onchain, it verifies it offchain using a verifier program written in Cairo. Each two such verifications are then aggregated together and sent back to SHARP and the Cairo verifier, restarting the process. This continues recursively, with each new proof being sent to the Cairo verifier until a trigger is reached. At this point, the last proof in the series is sent to the Solidity verifier on Ethereum. At first glance, recursive proofs may seem more complex and time-consuming. However, there are several benefits to this approach: 1. Parallelization: Recursive proofs enable work parallelization, reducing user latency and improving SHARP efficiency 2. Cheaper onchain costs: Parallelization enables SHARP to create larger proofs, which in turn result in reduced onchain costs 3. Lower cloud costs: Since each job is smaller, the required memory for processing is reduced, resulting in lower cloud costs 4. Cairo support: Recursive proofs only require support in Cairo, without the need to add support to the onchain verifiers ### Onchain verifier SHARP's onchain verifiers is a set of multiple smart contracts responsible for verifying STARK proofs generated by SHARP, the key of which are: * [`GpsStatementVerifier`](https://etherscan.io/address/0x47312450b3ac8b5b8e247a6bb6d523e7605bdb60), which serves as the primary verifier contract and relies on smaller verifier contracts that contain specific verification logic. For future enhancements or adjustments, a [`Proxy`](https://etherscan.io/address/0x47312450B3Ac8b5b8e247a6bB6d523e7605bDb60) and [`CallProxy`](https://etherscan.io/address/0xD4C4044ACa68ebBcB81B13cC2699e1Bca2d3F458) contracts are used allow seamless updates to the `GpsStatementVerifier` without compromising its foundational logic. * [`MemoryPageFactRegistry`](https://etherscan.io/address/0xfd14567eaf9ba941cb8c8a94eec14831ca7fd1b4), which maintains hashes of the proven programs' memory pages, primarily used to register outputs for data availability * [`MerkleStatementContract`](https://etherscan.io/address/0x5899efea757e0dbd6d114b3375c23d7540f65fa4), which verifies the Merkle paths in the STARK proof * [`FriStatementContract`](https://etherscan.io/address/0x3e6118da317f7a433031f03bb71ab870d87dd2dd), which is responsible for verifying the FRI layers in the STARK proof The verification flow consists of the following steps: 1. The Dispatcher sends: * Memory pages (usually many) to the `MemoryPageFactRegistry` contract * Merkle statements (typically between 3 and 5) to the `MerkleStatementContract` contract * Fri statements (generally ranging from 5 to 15) to the `FriStatementContract` contract 2. The Dispatcher send the proof to the `GpsStatementVerifier` contract 3. Starknet monitors the `GpsStatementVerifier` contract and updates its state once verification is complete # SNOS Source: https://docs.starknet.io/learn/protocol/snos ## Overview As an L2 validity rollup, Starknet's state on Ethereum can't be updated without proving that all the blocks between the current state and the new state are valid. SNOS (**S**tark**N**et **O**perating **S**ystem) is [the key element in Starknet's architecture](#what_is_snos?) that [verifies the validity of Starknet blocks](#how_does_snos_work?) and is required for [updating Starknet's state](#using_snos_to_update_starknets_state). SNOS has two [implementations](#implementations), a fully-open source one in Rust and a partially open-source one in Python. SNOS is a key element in Starknet's architecture, but also one of its more complicated components. Some parts may require reading more than once, but don't hesitate to [reach out](https://github.com/starknet-io/starknet-docs/issues/new?assignees=landauraz\&title=Feedback%20for%20%22The%20Starknet%20operating%20system%22) if you feel further clarification is needed. ## What is SNOS? In Cairo, only statements of the form *"a specific Cairo program with specific inputs and produced specific outputs"* can be proven, which means that the statement *"A Starknet block is valid"* needs to expressed in the form of a Cairo program, inputs, and outputs. This is where SNOS comes in: it is a Cairo Zero program that verifies the validity of blocks by getting an initial state and a list of transactions as input and outputting the state that is the result of applying these transactions on the initial state. SNOS is the program required for updating Starknet's state on Ethereum ([up to some caveats](#os-and-core-contract)). As such, is it also the final arbiter on what does it mean for a transaction to execute correctly. For example, while a malicious sequencer can deviate from [the `INVOKE` transaction's behavior](/learn/protocol/transactions/#transaction_flow) by skipping the execution of `__validate__` before `__execute__` in the calling account contract, this specification is [enforced by SNOS](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/execution/execute_transactions.cairo#L390) and therefore this sequencer will not be able to produce the proof for the block in which this transaction was included. ## How does SNOS work? ### High-level overview The following is a rough sketch of the SNOS program, starting at [os.cairo](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/os.cairo#L38)'s `main` function: In broad strokes, SNOS's execution consists of the following steps: 1. Preprocessing: preparing the SNOS's inputs, which include the various hints that will be needed throughout the execution (for example, the Merkle path of every accessed storage slot and the code of every accessed contract) 2. Running the transactions: the bulk of SNOS's execution, where transactions are executed sequentially such that for each transaction: * The associated account contract is loaded into the memory and called * Inner contracts are called, such that [the entry `(contract_address, class_hash)` is added to a global state updates dictionary](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/execution/execute_entry_point.cairo#L149) for each loaded contract (which is needed to assert that the loaded code corresponds to the class hash that is part of the state commitment) Contract calls are in fact done non-deterministically, i.e. the contract's response are guessed and verified later. For more details, see [Syscall mechanism](#syscall-mechanism). * State updates are accumulated in the global state updates dictionary 3. Verifying that the new commitment corresponds to applying the updates accumulated in the global state updates dictionary to the old commitment (for more details, see the [`state_update` function in `state.cairo`](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/state/state.cairo#L40)). 4. Encoding the state diff and adding it to the output segment for [data availability](/learn/protocol/data-availability/) purposes (for more details, see the [`serialize_os_output` function in `output.cairo`](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/output.cairo#L71)) ### Syscall mechanism A contract can invoke [system calls](https://book.cairo-lang.org/appendix-08-system-calls.html) throughout its execution, which pass the control from the currently executing contract to SNOS itself. Such operations are required when a contract needs information that does not exist within its code, e.g. accessing the Starknet state to read a storage value or to call another contract. SNOS's code heavily relies on non-determinism to handle system calls. Whenever a contract invokes some syscall, the request, alongside the guessed response, is recorded in a syscalls array. At the end of every entry point execution, we go over the syscalls array and verify that the responses were correct (for more details, see the [`execute_entry_point` function in `execute_transactions.cairo`](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/execution/execute_entry_point.cairo#L286)). For syscalls such as [`get_execution_info`](https://book.cairo-lang.org/appendix-08-system-calls.html#get_execution_info), which returns the block hash and number, correctness means consistency with the value given to SNOS as input. For contract calls, however, one needs to execute the called contract and verify that the actual response and the guessed response are identical. But how can we guess the responses to all the contract calls before executing them? For that, it is important to distinguish two different styles-of-execution that a Starknet block goes through: * The first one is done by a sequencer, when constructing the block from incoming transactions * The second one is done by a prover, when executing SNOS in order to generate a proof for the (already finalized) block Therefore, when running the OS, we already know what is going to happen: the block has already been executed by the sequencer, and so we know exactly what contracts and storage slots will be accessed, what each internal call will return, and so on. It's worth noting that in the first execution, the sequencer can run in whatever way he chooses. In fact, he doesn't even have to run through the Cairo VM at all, but rather [precompile contracts to machine code and run them natively](https://github.com/lambdaclass/cairo_native). Moreover, sequencers can also impose restrictions that are not enforced by SNOS. Such discrepancy between the two executions may seem like a bug, but as long as these restrictions only protect the sequencers themselves, rather than the correctness of execution, there is no reason for SNOS to enforce them. A good example for such restriction is the [limits on validation functions](/learn/protocol/accounts/#limitations_of_validation) that protect the sequencer from DoS attacks. It is crucial, however, that both executions agree on the execution semantics, since, as mentioned in [What is SNOS?](#what_is_snos), SNOS is the final arbiter and if it disagrees on the validity of the block, the sequencer will not be able to produce a proof for that block (and the only way forward would be a reorg on Starknet). ### Multiblock input Starting from Starknet version 0.14.0, block times on Starknet will be substantially shorter, leading to more frequent but also smaller blocks. As every run of SNOS on a single block incurs a fixed cost which is independent of the size of the block, producing e.g. twice as many blocks also doubles this fixed cost. To mitigate this, SNOS will be modified in version 0.14.0 to receive multiple blocks as inputs. This also amortizes other non-negligible costs over a larger number of blocks: For example, if both block 10 and 11 use the same contract, SNOS loads and hashes its code only once instead of twice. ## Using SNOS to update Starknet's state The Starknet Core contract is the contract responsible for storing and updating Starknet's state on Ethereum. As of Starknet v0.13.2 and as part of [SHARP's applicative recursion feature](https://community.starknet.io/t/starknet-v0-13-2-pre-release-notes/114223#starknet-applicative-recursion-3), proofs of SNOS's execution are not enough to update the Starknet Core contract. Instead, it is required to submit an "applicative proof" of a different program called [the applicative bootloader](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/cairo/bootloaders/applicative_bootloader/applicative_bootloader.cairo#L15). The way the applicative bootloader works is by verifying a proof of one or more executions of a base program $B$ and then using their outputs as input to an aggregator program $A$. In the case of Starknet, $B$ is the SNOS program and $A$ is a new cairo program that squashes the state diffs of several blocks (the code of which can be found in the [cairo-lang GitHub repository](https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/aggregator/main.cairo#L8)). This way, individual executions of SNOS for some block range can be "squashed" into a single program whose valid execution attests to the validity of all blocks within that range and whose output is the squashed state diff of these blocks. In order to verify that an "applicative proof" used the correct SNOS and aggregator programs, their program hashes must be stored in the Starknet Core contract. As each Starknet version is associated with a given SNOS program, this means that breaking protocol changes must be accompanied by an update to SNOS's (and possibly the aggregator's) program hash registered in the Starknet Core contract. You can read the program hashes currently registered in the Starknet Core contract by using its [`programHash`](https://etherscan.io/address/0xc662c410c0ecf747543f5ba90660f6abebd9c8c4#readProxyContract#F13) (for SNOS's program hash) and [`aggregatorProgramHash`](https://etherscan.io/address/0xc662c410c0ecf747543f5ba90660f6abebd9c8c4#readProxyContract#F1) (for the aggregator's program hash) functions. Finally, the Starknet Core contract is also responsible for verifying the few things that SNOS can't, including: * Verifying that the state given to SNOS as input is Starknet's current state on Ethereum * Verifying that all [L1→L2 messages](/learn/protocol/messaging/#l1-l2-messages) were sent on Ethereum ## Implementations The Cairo code of SNOS is available in the [cairo-lang GitHub repository](https://github.com/starkware-libs/cairo-lang/tree/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os). However, this repository does not include all the hints implementation, which are necessary to locally run SNOS. The current implementation of these hints in Python is now deprecated, and will no longer be maintained in future Starknet versions. As part of the transition of Starknet's infrastructure to Rust, SNOS's Pythonic hints implementation is deprecated, and will no longer be maintained in future Starknet versions. Instead, a new Rust implementation of the hints, including initializing all inputs of SNOS via a Starknet full node connection, is available in the [SNOS GitHub repository](https://github.com/keep-starknet-strange/snos/tree/cb2a6d26faeb658492756fe100bbdf5b1600c768). At the time of writing, this codebase supports the execution of SNOS for Starknet version 0.13.2. # Staking Source: https://docs.starknet.io/learn/protocol/staking ## Overview While Starknet is currently still centralized, it is [gradually](#roadmap) moving towards employing a [staking protocol](#protocol), handing over the responsibilities of producing, attesting, and proving blocks to validators. To facilitate this gradual implementation, the protocol's architecture is divided into several [components](#components), ensuring maximum flexibility and ease of upgrades. However, anyone holding STRK or BTC can already stake their tokens and earn rewards based on their level of participation by following [a few simple steps](#procedures). If you are only interested in learning how to stake on Starknet, feel free to skip straight to [Procedures](#procedures). Starting Q3 2025, Starknet's staking protocol enables BTC holders to lock their assets on Starknet and earn staking rewards in STRK, by supporting a curated selection of tokenized BTC representations ("wrappers") for direct BTC staking on Starknet. To review all BTC wrappers, use the staking contract's [`get_active_tokens`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#get_active_tokens) function. Additional resources * Staking dashboards by [Endur](https://dashboard.endur.fi/), [Voyager](https://voyager.online/staking-dashboard), [AlignedStake](https://www.aligned-stake.com/), and [Staking Rewards](https://www.stakingrewards.com/stake-app?input=starknet) * [Staking](https://www.starknet.io/staking) on starknet.io * [`starknet-staking`](https://github.com/starkware-libs/starknet-staking/tree/%40staking/contracts-v1.0.1-dev.510) on GitHub (deployed tag) * [Starknet launches phase 1 of its staking initiative on mainnet](https://www.starknet.io/blog/staking-phase-1/) on the Starknet blog * [SNIP 28: Staking V2 proposal](https://community.starknet.io/t/snip-28-staking-v2-proposal/115250) on the Starknet community forum * [SNIP 31: Bitcoin staking on Starknet](https://community.starknet.io/t/bitcoin-staking-on-starknet/115696) ## Roadmap Naturally, handing over the responsibility for maintaining and securing Starknet to validators in one day is neither feasible nor desirable. Instead, the staking protocol is implemented in incremental phases, with each phase bringing additional requirements from validators until they fully maintain and secure the network. The protocol is currently in the second of four phases, illustrated in following figure: The staking protocol is currently in its second phase on both Sepolia and Mainnet. ## Protocol The following sections describe the details of Starknet's staking protocol. The parameters used in the protocol are summarized in the following table: | Parameter | Mainnet | Sepolia | | - | - | - | | Minimum stake for validators | 20K STRK | 1 STRK | | Effective inflation coefficient ($\hat{C}$) | 4% | 2.13% | | Withdrawal security lockup | 7 days | 5 minutes | | Epoch length ($E$) | 1132 blocks | 231 blocks | | Epoch duration | 3600 seconds | 1200 seconds | | Attestation window ($W$) | 50 blocks | 30 blocks | | Number of epochs used for latency ($k$) | 1 epoch | 1 epoch | | Weight of BTC in staking power ($\alpha$) | 0.25 | 0.25 | ### Roles Starknet's staking protocol features two options for participation: * Staking directly as validators: Staking a minimum of 20,000 STRK and earning rewards by handling any responsibilities the protocol requires * Staking indirectly as delegators: Delegating STRK or BTC to validators who allow delegation and sharing in their rewards without handling any of their responsibilities Validators can choose whether to allow delegation or not. ### Epochs Starting from its second phase, the staking protocol introduces the notion of *epochs* (similarly to many other protocols). Epochs represent checkpoints, such that changes made in epoch $i$ are applied in epoch $i+k$, where $k$ is a new latency parameter. For example, the following figure illustrates a scenario where in epoch $i$ validator $A$ has 50K STRK staked and someone delegates an additional 10K STRK to them, and in epoch $i+1$ someone undelegates 30K STRK from $A$: ### Validator power The staking power of a validator staking $s$ amount of STRK and $b$ amount of BTC is defined as follows: $$ (1 - \alpha) \cdot \frac{s}{S} + \alpha \cdot \frac{b}{B} $$ where: * $\alpha = 0.25$ is a parameter determining BTC's weight in the staking power * $S$ and $B$ are the total STRK and BTC staked, respectively ### Validator responsibilities Starting from its second phase, the staking protocol requires validators to attest to blocks by running one of the followings: * The [Juno](https://juno.nethermind.io/hardware-requirements) full node with [Nethermind's attestation tool](https://github.com/NethermindEth/starknet-staking-v2/blob/main/README.md) * The [Pathfinder](https://docs.swmansion.com/pathfinder/getting-started/hardware-requirements) full node with [attestation tool](https://github.com/software-mansion/starknet-validator-attestation/blob/main/README.md) How does the block attestation mechanism work? In each epoch, each validator is assigned a single block whose relative number within the epoch is defined as follows: $$ h(\text{staked amount},\text{epoch id},\text{validator address}) \mod (E-W) $$ where: * $E$ is the number of blocks in an epoch, termed *epoch length* * $W$ is the number of blocks applicable for attestation submittal, termed *attestation window* During each epoch, validators have the opportunity to attest to their assigned block by submitting an `attest` transaction, which must be included within the attestation window. For example, if $W = 20$ and $N$ is the relative block number assigned to validator $A$, then $A$ must submit an `attest` transaction between the blocks whose relative number within the epoch are $N+1$ and $N+20$. In the second phase of the protocol, each Validator is required to perform only one attestation per epoch. The `attest` transaction includes the block hash of the attested block, ensuring validators actively use full nodes, as they need to continuously track block hashes. Additionally, the attestation is publicly verifiable, ensuring validators' reliability is publicly tested — a crucial prerequisite before handing them any core responsibilities. ### Rewards Staking rewards are issued in newly minted STRK tokens, based on a minting curve. The minting curve balances participation and inflation by adjusting the distributed rewards according to the total STRK locked in the protocol. The total minting rate percentage ($M$) is defined as follows: $$ M = \frac{\hat{C}}{10} \cdot \sqrt{\sigma} $$ where: * $\hat{C}$ is the effective inflation coefficient * $\sigma$ is the percentage of STRK staked out of the total STRK supply Annual reward percentages (APR) can therefore be calculated using the following formulas: * For STRK: $100 \cdot \frac{M}{\sigma} \cdot (1-\alpha)$ * For BTC: $(\alpha \cdot T \cdot \frac{M}{100} \cdot \frac{\text{STRK price}}{\text{BTC price}}) / B$ where: * $B$ is the total BTC staked * $T$ is the total STRK supply Rewards are distributed per epoch to validators in proportion to their staking power only if they performed their attestations in the epoch, on an "all or nothing" basis (i.e., validators that submitted a transaction during the epoch that proves they tracked the network will receive all the rewards for the epoch based on their staked amount, while validators that didn't will get no rewards for the epoch's entire duration). Rewards that go directly to the validator will accumulate in their account, and the rest will accumulate for delegators according to their share in the pool. [As previously described](#epochs), stakers that enter the protocol on epoch $i$ will start getting rewards only on epoch $i+k$, and stakers that signal an intent to exit the protocol on epoch $i$ will still get rewards until epoch $i$. ### Commissions The staking protocols enables validator to set a commission that is deducted from their delegators' rewards. Starting from its second phase, the staking protocol allows validators to increase their commission. To avoid an unexpected increase in commissions, validators must commit to a certain maximum commission $M$ and the last date (in epochs) that this commitment is relevant for. Until this date arrives, validators cannot increase their commission beyond $M$, but can freely change their commission in the range $[0,M]$. ### Latencies The following latencies are set in place: * To disincentivise sudden large withdrawals that could destabilize the network, funds are subject to a 7-day lockup after signaling an unstake intent, during which no rewards are earned and funds cannot be withdrawn. * Starting from the second phase of the protocol, to prevent delegator from switching too quickly between validators while still promoting a competitive delegation market, a switch intent that is signaled on epoch $i$ takes effect only on epoch $i+1$. ### Addresses To reduce exposure to threats and enhance security, multiple addresses are defined for both validators and delegators: * Validator addresses: * Staking address: Used for staking, adding stake, and unstaking. This address is only needed when entering or exiting the protocol and handles large amounts of STRK, and therefore is best kept by a cold wallet with minimal activity. * Rewards address: Used for receiving rewards. This address is only needed when receiving rewards, and therefore is best kept by a cold wallet. * Operational address: Used for operational purposes, such as block attestations (starting from the protocol's second phase), block proposing (starting from the protocol's third phase), etc. This address is used frequently and doesn't handle large amounts of funds, and therefore is best kept by a hot wallet. Starting from the protocol's second phase, however, hacking the operational address can lead to a lose of yield for the validator and its delegators. * Pools addresses: Used for pooling stake delegations for a specific token. For each token that a Staker is open to receiving delegations in, a unique delegation pool contract (with its own address) is created for users to delegate funds to. These pool contracts are automatically deployed for the Staker when they invoke the staking contract’s `set_open_for_delegation` function. If your operational address uses local signing, the account associated with it must be deployed and unprotected (e.g., no Ready Wallet Guardian or Braavos hardware signer). * Delegator addresses: * Staking address: Used for delegating stake, adding stake and unstaking. This address is only needed when entering or exiting the protocol and handles large amounts of STRK, and therefore is best kept by a cold wallet with minimal activity. * Rewards Address: Used for receiving rewards. This address is only needed when receiving rewards, and therefore is best kept by a cold wallet. The protocol uses a hierarchical approach between the staking and rewards addresses, and both validators and delegator can also use their staking address whenever their rewards address is required. ## Components The implementation of Starknet's staking protocol is divided into several contracts, summarized in the following figure: This modular architecture allows for targeted upgrades and improvements without affecting the entire system. Access control mechanisms are also in place to ensure that only authorized parties can make critical changes, further enhancing the security of the staking process. The following table details the key components of the protocol: | Contract | Description | | - | - | | Staking | The staking contract is the core of the staking system, managing the entire lifecycle of staking, from initial staking to claiming rewards and unstaking.
The staking contract also stores the `StakerInfo` data structure, which holds detailed information about each validator, including their staked amount, unclaimed rewards, delegation details, and operational parameters, and helps to ensure that validators' information is accurately tracked and updated. | | Delegation pooling | All delegation interactions, such as entering or exiting a pool, are enabled through the delegation pooling contract, which tracks each delegator's contribution, calculates their rewards, and manages the delegation lifecycle.
The delegation pooling contract also stores the `PoolMemberInfo` data structure, which holds information about each delegator's contributions, rewards, and status within the pool, and helps manage and calculate the delegation and reward distribution processes for pool members. | | Reward Supplier | The reward supplier contract is responsible for calculating and supplying the staking rewards based on the minting curve, ensuring the rewards are distributed fairly and in accordance with the protocol's economic parameters. | | Minting Curve | The minting curve contract defines the economic model that governs reward distribution, ensuring that the network's inflation is managed while incentivizing participation of stakers. | | Attestation | The attestation contract manages the tracking of successful validator attestations, by verifying whether the validator has correctly attested to their assigned block within a designated attestation window. | ## Procedures The following tables detail the procedures enabled by the staking protocol for both [validators](#staking_as_validators) and [delegators](#staking_as_delegators), along with the instructions to perform them. To invoke onchain contracts, use [`Starknet Foundry's sncast`](https://foundry-rs.github.io/starknet-foundry/starknet/sncast-overview.html), [Starkli](/learn/cheatsheets/tools/), or a [block explorer](/learn/cheatsheets/integrations/). To get the onchain addresses of the staking and STRK contracts, see [Important addresses](/learn/cheatsheets/chain-info/#staking). ### Staking as validators | Procedure | Instructions | Notes | | - | - | - | | Staking | Invoke the staking contract's [`stake`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#stake) function | • You should make sure you are [running a fully synchronized node](#validator-responsibilities) before staking
• You must first approve the transfer of the amount of STRK tokens to be staked to the staking contract by invoking the STRK contract's `approve` function
• `operational_address` should have sufficient funds to pay for attestation transactions
• `amount` should be equal or greater than the [minimum stake for validators](#protocol) and denominated in FRI (i.e., 1\*1018 = 1 STRK)
• Attesting to blocks is only possible starting from the epoch following a successful stake | | Initializing or updating commission | Invoke the staking contract's [`set_commission`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#set_commission) function | • `commission` should be entered as a percentage with precision, where 10,000 represents 100% (e.g., to set a 5% commission, you enter 500)
• Commissions can be increased only after [setting a commission commitment](#commissions) using [`set_commission_commitment`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#set_commission_commitment) | | Opening delegation | Invoke the staking contract's [`set_open_for_delegation`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#set_open_for_delegation) function | Opening delegation is is only possible after initializing the commission using `set_commission` | | Claiming rewards | Invoke the staking contract's [`claim_rewards`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#claim_rewards) function | | | Increasing stake | Invoke the staking contract's [`increase_stake`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#increase_stake) function | • `amount` should be denominated in FRI (i.e., 1\*1018 = 1 STRK)
• You must first approve the transfer of STRK tokens to the staking contract by invoking the STRK contract's `approve` function | | Changing reward address | Invoke the staking contract's [`change_reward_address`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#change_reward_address) function | | | Changing operational address | Invoke the staking contract's [`declare_operational_address`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#declare_operational_address) and [`change_operational_address`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#change_operational_address) functions | `declare_operational_address` should be invoked by your new operational address and `change_operational_address` should be invoked by your staking address | | Unstaking | Invoke the staking contract's [`unstake_intent`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#unstake_intent) and [`unstake_action`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#unstake_action) functions | • Once an unstake intent is signaled:
• Funds are removed from the total balance and are no longer part of the staking protocol
• The same staking address cannot be used to "restake" (i.e., `unstake_action` is **irreversible**)
• `unstake_action` should be invoked only after the appropriate [waiting period](#latencies) has ended | ### Staking as delegators The following procedures are only intended for developers who are either interested (for whatever reason) in staking as delegators without using a [staking dashboard](#overview), or are building one. | Procedure | Instructions | Notes | | - | - | - | | Entering a delegation pool | Invoke the delegation pool contract's [`enter_delegation_pool`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#enter_delegation_pool) function | • `amount` should be denominated in the token's base unit (e.g., FRI for STRK)
• You must first approve the transfer of STRK tokens to the delegation pool contract by invoking the STRK contract's `approve` function | | Claiming rewards | Invoke the delegation pool contract's [`claim_rewards`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#claim_rewards-1) function | | | Adding to a delegation pool | Invoke the delegation pool contract's [`add_to_delegation_pool`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#add_to_delegation_pool) function | • Adding to a delegation pool is only possible after entering it using `enter_delegation_pool`
• `amount` should be denominated in the token's base unit (e.g., FRI for STRK)
• You must first approve the transfer of STRK tokens to the delegation pool contract by invoking the STRK contract's `approve` function | | Switching delegation pools | Invoke the delegation pool contract's [`switch_delegation_pool`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#switch_delegation_pool) function | To prevent delegator from switching too quickly between validators while still promoting a competitive delegation market, a switch intent that is signaled on epoch $i$ takes effect only on epoch $i+1$. | | Changing reward address | Invoke the delegation pool contract's [`change_reward_address`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#change_reward_address-1) function | | | Exiting a delegation pool | Invoke the delegation pool contract's [`exit_delegation_pool_intent`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#exit_delegation_pool_intent) and [`exit_delegation_pool_action`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#exit_delegation_pool_action) function | `exit_delegation_pool_action` should be invoked only after the appropriate [waiting period](#latencies) has ended | # StarkGate Source: https://docs.starknet.io/learn/protocol/starkgate ## Overview [StarkGate](https://starkgate.starknet.io), developed by StarkWare, bridges ETH and ERC-20 tokens between Ethereum and Starknet. Its various [components](#components) enable anyone to easily [deposit funds to Starknet (with and without a message attached), withdraw funds to Ethereum, and add a token to StarkGate](#procedures). Following [Maker DAO's announcement](https://twitter.com/MakerDAO/status/1746977683190251591?s=20), StarkWare launched a new set of DAI bridge and token contracts under StarkGate. This new DAI token retains the same contract 'symbol' and 'name' as the existing one, and to differentiate between the two, the old DAI is referred to as “DAI v0” while the new DAI is simply referred to as “DAI”. Withdrawal of DAI v0 tokens is still possible with no limitations, but depositing DAI v0 using the DAI v0 bridge is disabled. Users are **strongly** encouraged to migrate to the new DAI token, as it cannot support StarkGate's latest features (such as Smart Deposits and Withdrawal Limits) and will stop being compatible with Starknet altogether over time. To migrate from DAI v0 to DAI, either swap your DAI v0 for DAI using one of Starknet's [swap apps and aggregators](https://www.starknet.io/dapps/?type=live\&tags=216) or withdraw your current DAI v0 tokens to L1 using and re-deposit them using StarkGate (which automatically issues the new DAI). ## Components The following table details StarkGate's key components: | Contract | Description | | - | - | | Starknet Token Bridge(on both L1 and L2) | The primary StarkGate bridge contract, where the main user bridge functionality are defined. | | Starkgate Manager | Responsible for adding tokens to the multi-bridge. | | Starkgate Registry | Contains the addresses of all supported bridges and tokens and enables to stop servicing an existing bridge.An interface to the Starkgate Registry is available through IStarkgateRegistry.sol. | | Starkgate Service | An interface to check if a contract is servicing a token. | In addition, prior to StarkGate 2.0 (Mainnet: January 2024) tokens were bridged with a unique and custom pair of L1 and L2 bridge contracts, where the L1 contract is an instance of [`LegacyBridge.sol`](https://github.com/starknet-io/starkgate-contracts/blob/cairo-1/src/solidity/LegacyBridge.sol) and the L2 contract is an instance of [`legacy_bridge_eic.cairo`](https://github.com/starknet-io/starkgate-contracts/blob/cairo-1/src/cairo/legacy_bridge_eic.cairo). These legacy token bridges are supported by StarkGate 2.0 in a fully backward-compatible fashion. For example, the [L1 bridge contract for USDC](https://etherscan.io/address/0xf6080d9fbeebcd44d89affbfd42f098cbff92816#writeProxyContract) has the following two `deposit` functions: | | | | - | - | | deposit
(0x0efe6a8b) | The multi-bridge contract, which includes support for all tokens within a single contract, requiring that you enter the address of the token in the deposit function. | | deposit
(0xe2bbb158) | The legacy contract, which is labeled Support Legacy ABI. This function does not include the token (address) parameter, because the contract that contains this function only supports USDC. Therefore, the address of the token is superfluous. | For the complete list of legacy bridges, see the [`bridged_tokens`](https://github.com/starknet-io/starknet-addresses/tree/master/bridged_tokens) directory on GitHub.
The following is an overall illustration of StarkGate's various components: ## Procedures The following sections detail the procedures enabled by StarkGate, along with the instructions to perform them. For adding a token to Starknet, see [Joel Mun's blog](https://9oelm.github.io/2024-03-28-making-sense-of-starknet-architecture-and-l1-l2-messaging/#enroll-a-token-bridge). Note that enrolling a new bridge does not add it to StarkGate's GUI. All procedures are subject to [fees](/learn/protocol/messaging/#l1-l2-message-fees) to account for L1→L2 message costs. Fees for depositing funds or enrolling a token bridge on StarkGate can be estimated using the [`estimateDepositFeeWei`](/learn/cheatsheets/starkgate-reference/#estimateDepositFeeWei) or [`estimateEnrollmentFeeWei`](/learn/cheatsheets/starkgate-reference/#estimateEnrollmentFeeWei) function. To invoke onchain contracts, use [Starknet Foundry's `sncast`](https://foundry-rs.github.io/starknet-foundry/starknet/sncast-overview.html), [Starkli](../cheatsheets/tools), or a [block explorer](../cheatsheets/integrations). To get the onchain addresses of StarkGate's contracts, see [Important addresses](/learn/cheatsheets/chain-info/#starkgate). For more information on StarkGate's various functions, see [StarkGate functions and events](/learn/cheatsheets/starkgate-reference/). ### Depositing funds Using StarkGate to deposit L1 funds into the L2 Starknet requires StarkGate's `deposit` function. The `deposit` function does the following: 1. Transfers the funds from the user's Ethereum account to the StarkGate L1 contract. 2. Emits a `Deposit` event that includes the L1 and L2 addresses of the user, and the amount deposited. 3. Sends a message to the corresponding L2 bridge with the amount deposited, and the recipient's address. Subsequently, the funds should be transferred to Starknet so that you can begin using them. For more information on what happens during the transfer process, see [Deposit lifecycle](#deposit_lifecycle). #### Cancelling a deposit To ensure self-custody, StarkGate enables you to cancel a deposit if, after depositing funds with the `deposit` function on L1, you don't see your funds appear on L2 within a reasonable amount of time. In order to guard against an attack, it takes approximately five days to cancel a deposit. From the moment StarkGate receives the cancellation request, a counter begins. When exactly five days have passed, and the funds still do not appear on L2, you can reclaim the deposit. To cancel a deposit, call the `depositCancelRequest` request function. When StarkGate receives the cancellation request, a counter begins to count five days. When exactly five days have passed, and the funds still do not appear on L2, you can reclaim the deposit by calling `depositReclaim`. As long as the `depositReclaim` was not performed, the deposit may be processed, even if the cancellation delay time has already passed. Only the depositor is allowed to cancel a deposit, and only before depositReclaim was performed. #### Depositing funds with a message A deposit with a message is a deposit that moves funds from L1 to L2 and then triggers subsequent actions. For example, a user can deposit funds and transfer those funds to another recipient, such as an exchange. The `depositWithMessage` function enables sending a deposit with a message. `depositWithMessage` is similar to the `deposit` function, with an additional 256-bit message, which can contain instructions for executing additional actions. Upon completion, the `depositWithMessage` function triggers a call to a callback function, named `on_receive`, on the L2 contract that receives the deposit. The `on_receive` function receives the deposit message as input. `on_receive` must return `true` for the deposit to succeed. If `on_receive` returns `false`, or if the recipient contract does not include the `on_receive` function, the `depositWithMessage` function's L1 handler fails. The user can recover their funds using the `depositWithMessageCancelRequest` function. To enable deposits with messages in your application: 1. Implement the `on_receive` function in the L2 contract that should receive deposits. 2. Use the `depositWithMessage` function to transfer funds from L1 to L2. #### Deposit lifecycle Step 1: Initiating a deposit on L1 1. A call to the L1 `deposit` function initiates a deposit. 2. The function does the following: * Transfers the funds from the user's account to the Starknet bridge. * Emits a `Deposit` event that includes the L1 and L2 addresses of the user, and the amount deposited. * Sends a message to the corresponding L2 bridge with the amount deposited, and the recipient's address. Starknet's sequencer is now aware of the deposit transaction. 3. The sequencer waits for enough L1 block confirmations to fill its quota to run before the corresponding deposit transaction is initiated on L2. During this period of time, the status of the L2 deposit transaction is [`NOT_RECEIVED`](/learn/protocol/transactions/#transaction_lifecycle). Step 2: Triggering a deposit on L2 1. The sequencers refer to the deposit request by triggering the L1 handler using the [`handle_deposit`](https://github.com/starkware-libs/starkgate-contracts/blob/28f4032b101003b2c6682d753ea61c86b732012c/src/starkware/starknet/apps/starkgate/cairo/token_bridge.cairo#L135) function on the L2 bridge. 2. The `handle_deposit` function verifies that the deposit indeed came from the corresponding L1 bridge. It then calls the relevant token's contract on Starknet and mints the specified amount of the token on L2 for the user. 3. The sequencers complete constructing the block. The status of the deposit request is now [`ACCEPTED_ON_L2`](/learn/protocol/transactions/#transaction_lifecycle). Step 3: Proving the block that includes the deposit 1. Starknet's provers prove the validity of the block and submit a state update to L1. 2. The message confirming transfer of the funds is cleared from the Starknet Core Contract, and the fact that the user has transferred their funds is part of the now finalized state of Starknet. If the message wasn't on L1 to begin with, that is, if the deposit request was fraudulently created on Starknet, the state update fails. ### Withdrawing funds Using StarkGate to withdraw funds from Starknet requires StarkGate's `initiate_token_withdraw` function to initiate a withdrawal. The function does the following: * Burns the transferred amount of tokens from the L2 balance of the withdrawal's initiator. * Sends a message to the relevant L1 bridge with the amount to be transferred and the recipient's Ethereum address. Subsequently, the funds should be transferred to the recipient's Ethereum address, and should be available after the next L1 state update. For more information on what happens during the transfer process, see [Withdrawal lifecycle](#withdrawal_lifecycle). #### Withdrawal limit By default, StarkGate imposes no limit on withdrawals. However, in order to mitigate risks associated with critical vulnerabilities that could result in the loss of user funds, StarkGate can enable a withdrawal limit. If a serious security issue arises, the security agent in the StarkGate contract can limit withdrawals to 5% of the Total Value Locked (TVL) per day for any affected token by calling the `setWithdrawLimitPCT()` function in the `WithdrawalLimit.sol` contract. A dedicated team can then investigate and resolve the issue. Only a security admin quorum can disable the withdrawal limit. The quorum will consist of Starknet Foundation members, Starknet ecosystem contributors, and StarkWare representatives. This diverse group will ensure that decisions reflect the Starknet community's broad interests. This approach, blending manual oversight with automated detection, aims to minimize potential losses. #### Withdrawal lifecycle Step 1: Initiating a withdrawal on L2 1. A call to the L2 `initiate_token_withdraw` function initiates a withdrawal. 2. The function does the following: * Burns the transferred amount of tokens from the balance of the withdrawal's initiator. * Sends a message to the relevant L1 bridge with the amount to be transferred and the recipient's address. Step 2: Proving the block that includes the withdrawal 1. The sequencer completes the block construction 2. Starknet's provers prove the validity of the block and submit a state update to L1. 3. The message from the previous step is stored in the Starknet Core Contract. Step 3: Transferring the funds on L1 After the withdrawal message has been recorded on the Starknet Core Contract, anyone can finalize the transfer on L1 from the bridge back to the user, by calling the `withdraw` function. This step is permissionless, anyone can do it. The recipient's address is part of the recorded message on L1, so they receive the funds regardless of who calls the `withdraw` function on L1. # State Source: https://docs.starknet.io/learn/protocol/state ## Overview Starknet's state [is composed of contract classes and instances](#state_structure) and [tracks any changes to them](#state_transition). Its [commitment](#state_commitment) uses a cryptographic structure optimized for zero-knowledge proofs, enabling efficient proof generation of state updates and forming the backbone of Starknet's scalability and security. ## State structure Starknet's state consists of: * A mapping between the hashes of contract classes and their definitions * A mapping between the addresses of contract instances and their [states](#contract_instance_state) ### Contract instance state A contract instance state consists of: * A class hash, which defines the functionality of the contract * A key-value mapping (where the key and values are field elements), which comprises the contract's storage * A nonce, which tracks the number of transactions sent from this contract ### Special addresses Starknet uses special contract addresses to provide distinct capabilities beyond regular contract deployment. Two such addresses are `0x0` and `0x1`. These addresses are reserved for specific purposes and are characterized by their unique behavior in comparison to traditional contract addresses. #### Address `0x0` Address `0x0` functions as the default `caller_address` for external calls, including interactions with the L1 handler or deprecated Deploy transactions. Unlike regular contracts, address `0x0` does not possess a storage structure and does not accommodate storage mapping. #### Address `0x1` Address `0x1` is another special contract address within Starknet's architecture. It functions as a storage space for mapping block numbers to their corresponding block hashes. The storage structure at this address is organized as follows: | | | | - | - | | Keys | Block numbers between $\texttt{first\_v0\_12\_0\_block}$ and $\texttt{current\_block} - 10$. | | Values | Corresponding block hashes for the specified blocks. | | Default Values | For all other block numbers, the values are set to `0`. | The storage organization of address `0x1` supports the efficient retrieval of block hashes based on block numbers within a defined range and is also used by the [`get_block_hash`](https://book.cairo-lang.org/appendix-08-system-calls.html#get_block_hash) system call. ## State transition A transaction $t x$ transitions the system from state $S$ to state $S^{'}$ if: * $t x$ is an `INVOKE` transaction, and the storage of $S^{'}$ is the result of executing the target contract code with respect to the previous state $S$. The arguments, contract instance's address, and the specific function entry point are part of the transaction. * $t x$ is a `DEPLOY_ACCOUNT` transaction, and $S^{'}$ contains the new contract instance's state at the contract instance's address. Additionally, the storage of $S$ is updated according to the execution of the contract instance's constructor. * $t x$ is a `DECLARE` transaction, and $S^{'}$ contains the class hash and definition in the contract class's mapping ## State commitment The state commitment is a digest that represents the state. In Starknet, the state commitment combines the roots of two binary [Merkle-Patricia tries](#merkle_patricia_trie) of height 251 in the following manner: ```txt theme={null} state_commitment = hPos( "STARKNET_STATE_V0", contract_trie_root, class_trie_root ) ``` Where: * `hPos` is the [Poseidon](/learn/protocol/cryptography/#poseidon_hash) hash function. * `STARKNET_STATE_V0` is a constant prefix string encoded in ASCII (and represented as a field element). * `contract_trie_root` is the root of the [*contract trie*](#contracts_trie), a Merkle-Patricia trie whose leaves are the contracts' states. * `class_trie_root` is the root of the [*class trie*](#classes_trie), a Merkle-Patricia trie whose leaves are the compiled class hashes. ### The contract trie As with Ethereum, this trie is a two-level structure, whose leaves correspond to distinct contracts. The address of each contract determines the path from the trie's root to its corresponding leaf, whose content encodes the contract's state. The information stored in the leaf is as follows: ``` hPed( hPed( hPed( class_hash, storage_root ), nonce ), 0 ) ``` Where: * `hPed` is the [Pedersen](/learn/protocol/cryptography/#hash-functions#pedersen_hash) hash function. * `class_hash` is the hash of [the contract's definition](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html). * `storage_root` is the root of another Merkle-Patricia trie of height 251 that is constructed from the contract's storage. * `nonce` is the current nonce of the contract. ### The class trie The class trie encodes the information about all existing [classes](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) in Starknet's state. This trie maps [class hashes](https://book.cairo-lang.org/ch100-01-contracts-classes-and-instances.html) to their compiled class hashes. The information stored in a leaf at a path corresponding to some class hash is as follows: ```text theme={null} hPos( CONTRACT_CLASS_LEAF_V0, compiled_class_hash ) ``` Where: * `hPos` is the [Poseidon](/learn/protocol/cryptography/#poseidon_hash) hash function * `CONTRACT_CLASS_LEAF_V0` is a constant prefix string encoded in ASCII (and represented as a field element). * `compiled_class_hash` is the hash of the Cairo assembly resulting from compiling the given class via the Sierra-to-Casm compiler. *Compiled class hash* The compiled class hash identifies the output of a specific Casm compilation as unique. Cairo classes that are part of the state commitment are defined with Sierra, an intermediate representation between Cairo and Cairo assembly (Casm). However, the prover only works with Casm. So in order to prevent needing to compile from Sierra to Casm in every block in which the class is used, the state commitment must have some information about the corresponding Cairo assembly. The compiled class hash provides this information. For more information, see [the Cairo Book](https://book.cairo-lang.org/appendix-09-sierra.html). The party that declares the contract signs the compiled class hash, which they obtain using an SDK, as part of the [`DECLARE`](/learn/protocol/transactions/#transaction_types) transaction. If the transaction is included in a block, then the compiled class hash becomes part of the state commitment. In the future, when Sierra-to-Casm compilation becomes part of the Starknet OS, this value might be updated via a proof of the Sierra-to-Casm compiler execution, showing that compiling the same class with a newer compiler version results in some new compiled class hash. ### Merkle-Patricia trie The state commitment scheme uses a binary Merkle-Patricia trie with the Pedersen hash function. #### About nodes Each node in the trie is represented by a triplet $\left(\right. l e n g t h , p a t h , v a l u e \left.\right)$, where: | | | | - | - | | $l e n g t h$ | is the length of the path, measured in nodes. | | $p a t h$ | is the path from the current node to its unique non-empty subtrie.

$p a t h$ is an integer in the set $\{0, \ldots, 2^{\mathit{length}} - 1\}$, and the binary expansion of $p a t h$ indicates how to proceed along the trie, as follows:

1. Expand $p a t h$ to its binary representation.

2. Starting with the most significant bit, representing the root of the trie, traverse the tree node by node, where the bit values $0$ and $1$ indicate left and right, respectively. | | $v a l u e$ | is the value of the node, which can be either data, or the hash of two non-empty child nodes. | An empty node is one whose triplet values are $\left(\right. 0 , 0 , 0 \left.\right)$. Leaf nodes and internal nodes can be empty. A subtrie rooted at a node $\left(\right. l e n g t h , p a t h , v a l u e \left.\right)$ has a single non-empty subtrie, rooted at the node obtained by following the path specified by $p a t h$. Length is specified, and cannot be deduced from $p a t h$, because the numbers in the triplet $\left(\right. l e n g t h , p a t h , v a l u e \left.\right)$ are field elements of fixed size, 251 bits each.For a node where $l e n g t h > 0$, $p a t h$ leads to the highest node whose left and right children are not empty. #### Trie construction The following rules specify how the trie is constructed from a given set of leaves: The hash of a node $N = \left(\right. l e n g t h , p a t h , v a l u e \left.\right)$, denoted by $H \left(\right. N \left.\right)$, is: $$ H(N) = \begin{cases} \mathit{value}, & \text{if } \mathit{length} = 0 \\ h_{\mathit{red}}(\mathit{value}, \mathit{path}) + \mathit{length}, & \text{otherwise} \end{cases} $$ All arithmetic operations in the above description of $H$ are done in the STARK field, as described in [The STARK field](/learn/protocol/cryptography/#stark-field). #### Mathematical definition of the nodes in the trie The triplet representing the parent of the nodes $l e f t = \left(\right. ℓ_{L} , p_{L} , v_{L} \left.\right)$, $r i g h t = \left(\right. ℓ_{R} , p_{R} , v_{R} \left.\right)$ is defined as follows: $$ \mathit{parent} = \begin{cases} (0, 0, 0), & \text{if } \mathit{left} = \mathit{right} = (0, 0, 0) \\ (\ell_L + 1, p_L, v_L), & \text{if } \mathit{right} = (0, 0, 0) \text{ and } \mathit{left} \neq (0, 0, 0) \\ (\ell_R + 1, p_R + 2^{\ell_R}, v_R), & \text{if } \mathit{right} \neq (0, 0, 0) \text{ and } \mathit{left} = (0, 0, 0) \\ (0, 0, h_{\mathit{red}}(H(\mathit{left}), H(\mathit{right}))), & \text{otherwise} \end{cases} $$ #### Example trie The diagram [A three-level Merkle-Patricia trie](#3-level_trie) illustrates the construction of a three-level-high Merkle-Patricia trie from the leaves whose values are $\left(\right. 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 \left.\right)$: Where $r = h_{P e d} \left(\right. H \left(\right. 2 , 2 , 1 \left.\right) , H \left(\right. \left(\right. 2 , 1 , 1 \left.\right) \left.\right)$. Notice that the example does not skip from the root, whose length is zero, so the final state commitment to the trie is $H \left(\right. \left(\right. 0 , 0 , r \left.\right) \left.\right) = r$. Suppose that you want to prove, with respect to the state commitment just computed, that the value of the leaf whose path is given by $101$ is $1$. In a standard Merkle trie, the proof would consist of data from three nodes, which are siblings along the path to the root. In a Merkle-Patricia trie, because the trie is sparse, you only need to send the two children of the root, which are $\left(\right. 2 , 2 , 1 \left.\right)$ and $\left(\right. 2 , 1 , 1 \left.\right)$. These two children are enough to reproduce the state commitment $r$, and because you know that the height of the trie is three, and that it is fixed, you know that the path $01$ of length $2$ specified by the right-hand child, $\left(\right. 2 , 1 , 1 \left.\right)$, leads to the desired leaf. # STRK Source: https://docs.starknet.io/learn/protocol/strk ## Overview [Similar to other blockchains](#background), Starknet uses the STRK token for [paying fees to enable operation of the network, maintaining and securing the network by enabling staking for consensus, and deciding on Starknet's values and technology goals by voting for governance proposals](#purposes), of which [ten billion tokens are planned to or have already been distributed](#supply_and_distribution),  as well as, further tokens minted through an inflation mechanism. The primary purpose of the Starknet token, STRK, is to facilitate operations and activities on Starknet and it is not intended to serve as an investment. Major economic mechanisms of STRK, also known as *tokenomics*, [are subject to change based on governance decisions made by the larger community of Starknet and are described here for informational purposes only](#risks_and_disclaimers). For more details on Starknet's governance processes, see the [Starknet Governance Hub](https://governance.starknet.io/). ## Background Blockchains are valuable because they are data structures maintained by diverse and, ideally, large groups of otherwise unaffiliated persons. This gives them resilience: Any one participant can disappear, but the data structure is preserved. This also gives them censorship resistance: No single person can unilaterally decide to forbid certain persons from using the network. To achieve this, blockchains work through a combination of cryptography and economic incentives: Cryptography limits what actors in the system can do (for example, transactions must be validly signed to be included in the chain) and economic incentives encourage actors to voluntarily perform actions that maintain the network's capabilities when spending their own resources (for example, miners or stakers actively publish new blocks to the chain because they can receive fees and new tokens as a reward). Starknet achieves secure low-cost transactions by using the STARK cryptographic proof system to reduce the size of transaction data while preserving and verifying the integrity of that data. To achieve resilience and censorship resistance, Starknet uses the STRK token to incentivize network participants to sequence transactions for users of the network and to ensure that there is a provably fair proof-of-stake mechanism to determine who should sequence and submit a proof for the network blocks. ## Purposes * **Transaction fees:** Originally, fees in Starknet were paid only in Ether (ETH), which was followed by the functionality of paying fees for transactions on the network using both STRK, as well as ETH. As of the release of v.0.14.0 on September 1, 2025, transaction fees on Starknet can only be paid with STRK.\ A portion of the fees paid in STRK may be converted to ETH by the receiving sequencer, in order to cover Ethereum L1 gas costs, which, due to the specifications of the Ethereum protocol, must be paid in ETH. * **Staking:**  Certain services that are critical to the liveness and security of Starknet may require the staking of Starknet tokens. These services might be offered by multiple providers, and could include sequencing, reaching temporary L2 consensus before L1 finality is reached, STARK-proving services, and data availability provisioning, to name a few examples. Some of these [protocol changes](https://starkware.co/resource/starknet-decentralization-a-roadmap-in-broad-strokes/) are still under discussion within the larger governance community and are targeted for [2025 -2026](https://starkware.co/resource/starknet-decentralization-a-roadmap-in-broad-strokes/). As of the last update of this paper, staking is live and validators are required to attest blocks to ensure the network’s stability and security - for more information on Starknet staking, see [here](https://www.starknet.io/staking/). * **Governance:** Proposals for improving Starknet might require a minimal token support threshold. Voting, either directly or via delegation, will be required for changes to the protocol that are essential to Starknet's liveness, security, and maintenance. Today, for example, major updates to the Starknet Operating System require the approval of token holders. For more information about Governance see the [Starknet Governance Hub](https://governance.starknet.io/) As discussed above, the Starknet tokens are digital assets intended to support the operation and usage of Starknet and are not offered as an investment. As such, the Starknet tokens do not represent any equity in StarkWare or the Starknet Foundation, nor do they provide any participation right in StarkWare or grant any right of claim from StarkWare or the Starknet Foundation. ## Supply and distribution Ten billion Starknet tokens were initially created by StarkWare in May 2022 and minted onchain on November 30, 2022. The existing ten billion tokens have been or are planned to be distributed according to the following: | | | | - | - | | **20.04%: Early Contributors** | Tokens allocated for StarkWare's team members and early contributors. These tokens are subject to a lock-up schedule, as further detailed below. | | **18.17%: Investors** | Tokens allocated for StarkWare's investors. These tokens are subject to a lock-up schedule, as further detailed below. | | **10.76%: StarkWare** | Tokens allocated for StarkWare for operation services such as to pay fees, provide other services on Starknet, and engage other service providers. | | **12.93%: Grants including Development Partners (aka DPs)** | Tokens allocated for grants for research or work done to develop, test, deploy and maintain the Starknet protocol. The process for applications and allocations related to Starknet Foundation Grants will be outlined in a post at a later date. | | **9.00%: Community Provisions** | Tokens distributed to those who contributed to Starknet and powered or developed its underlying technology. | | **9.00%: Community Rebates** | Tokens allocated for rebates in Starknet tokens to partially cover the costs of onboarding to Starknet from Ethereum. | | **10.00%: Foundation Strategic Reserves** | Tokens allocated for the Starknet Foundation to fund ecosystem activities that are aligned with the [Foundation's mission](https://www.starknet.io/en/content/introducing-the-starknet-foundation). | | **8.10%: Foundation Treasury** | Token allocated for the Starknet Foundation's treasury available for operations and other future initiatives by the Starknet Foundation. | | **2.00%: Donations** | Tokens reserved for donations to institutions and organizations, such as universities, NGOs, etc, as decided by the Starknet Foundation. | To align long-term incentives of the Investors and Early Contributors with the interests of the Starknet community, and following common practice in decentralized ecosystems, all tokens allocated to Investors and Early Contributors is subject to the following lock-up schedule, where percentages are based on the total token supply: * Up to 0.64% (64 million tokens) have been unlocked on the 15th of each month, starting April 15, 2024, and going through March 15, 2025, for a total of 7.68% (768 million tokens) unlocked by March 15, 2025. * Up to 1.27% (127 million tokens) have been and will be unlocked on the 15th of each month, starting April 15, 2025, and going through March 15, 2027, for a total of 30.48% (3.048 billion tokens) unlocked by March 15, 2027. The above graph excludes newly circulating tokens resulting from inflation or staking (see below).Token allotments currently retained by the Starknet Foundation, while contractually unlocked, are not considered circulating unless granted, donated, or otherwise allocated out of originating wallets through future grants, provisions, donations, developer initiatives, or other programs. Through this lock-up period, token holders cannot transfer, sell, or pledge their STRK tokens. Delegation of voting is permitted with locked tokens. As of the last date of update of this paper, staking with locked tokens is not permitted. The total supply of tokens increases over time with the minting of new tokens by the protocol, as staking rewards, block rewards, or other rewards associated with the staking process. Such minting is made pursuant to a schedule determined with the community (for further reading: [here](https://governance.starknet.io/voting-proposals/5) and [here](https://community.starknet.io/t/snip-18-staking-s-first-stage-on-starknet/114334)). The supply in circulation, therefore, does not remain fixed. However, as long as StarkWare is the sole operator of the Starknet sequencer, there will be no issuance of new tokens for the purpose of block rewards. For more information, see [*A token-minting proposal to manage inflation*](https://starkware.co/resource/a-token-minting-proposal-to-manage-inflation/). ## Risks and disclaimers Starknet is a developing decentralized protocol and the economic mechanisms described herein are subject to change based on decisions made by the larger community of Starknet builders and users. Starknet relies upon third parties to adopt and implement software and protocols as users and contributors of Starknet. It also relies, in whole or partly, on third parties to develop, supply and otherwise support it. There is no assurance or guarantee that such third parties will continue to participate in the network or that the network will continue to function as intended. The technical documents provided herein describe certain planned and specified economic fundamentals of a digital asset, STRK. These materials are intended for informational purposes only and are meant to outline the usage and functionalities of the asset within Starknet. It is important to understand that the primary purpose of STRK is to pay for fees, provide a mechanism for securing consensus, and allow for decentralized governance on Starknet; it is not intended to serve as an investment. Starknet relies upon third parties to adopt and implement the software and protocols as users of Starknet. It also relies, in whole or partly, on third parties to develop, supply and otherwise support it. As a Layer 2 network over Ethereum, Starknet also relies upon third parties maintaining and operating the Ethereum network. There is no assurance or guarantee that those third parties will complete their work, properly carry out their obligations, and/or otherwise meet anyone’s needs. STRK, as the native token of Starknet, may be subject to the risks of the Starknet network, including, without limitation, the following: (i) the technology associated with Starknet may not function as intended; (ii) the details of the Starknet token economics including the total supply and distribution schedule may be changed due to decisions made, amongst other, by the consensus of participants of the Starknet network; (iii) Starknet may fail to attract sufficient interest from key stakeholders or users; (iv) Starknet may not progress satisfactorily and Starknet tokens may not be useful or valuable; (v) Starknet may suffer from attacks by hackers or other individuals; and (vi) Starknet is comprised of open-source technologies that depend on a network of computers to run certain software programs to process transactions, and because of this model StarkWare and the Starknet Foundation have very limited control over Starknet. Risks related to blockchain technology in general and Starknet in particular may impact the usefulness of Starknet, and, in turn, the utility or value of STRK. The software and hardware, technology and technical concepts and theories applicable to Starknet and STRK are still in an early development stage and there is no warranty that Starknet will achieve any specific level of functionality or success, nor that the underlying technology will be uninterrupted or error-free, and there is an inherent risk that the technology could contain weaknesses, vulnerabilities or bugs causing, potentially, the complete loss of any Starknet tokens held by Starknet users. As with most commonly used public blockchains, STRKs held in user accounts are most commonly accessed using a private key that corresponds to the address at which they are stored. However, alternative access mechanisms, such as biometrics, multisignature setups, or other authentication methods, are also available depending on the account’s contract code. If the private key, or the "seed" used to create the address and corresponding private key are lost or stolen, the tokens associated with that address might be unrecoverable and might be permanently lost. Public blockchain-based systems, including Starknet and the underlying Ethereum network, depend on independent verifiers, and therefore may be vulnerable to consensus attacks including, but not limited to, double-spend attacks, majority voting power attacks, race condition attacks, and censorship attacks. These attacks, if successful, could result in the permanent loss of STRK. Starknet, STRK, and blockchain technology are nascent, and there may be additional risks not described above or that may be new or unanticipated. We recommend only using Starknet or holding STRK if you are familiar with the technology and aware of the risks. This document and its contents are not, and should not be construed as, an offer to sell, or the solicitation of an offer to buy, any tokens, nor should it or any part of it form the basis or be relied on in connection with any contract or commitment whatsoever. This document is not advice of any kind, including legal, investment, financial, tax, or any other professional advice. Nothing in this document should be read or interpreted as a guarantee or promise of how the Starknet network or its STRK will develop, be utilized, or accrue value. All information in this document is provided on an “as is” basis without any representation or warranty of any kind. This document only outlines current plans, which could change at the discretion of various parties, and the success of which will depend on many factors outside of StarkWare and the Starknet Foundation’s control. Such future statements necessarily involve known and unknown risks, which may cause actual performance and results in future periods to differ materially from what we have described or implied in this document. StarkWare and the Starknet Foundation disclaim all warranties, express or implied, to the fullest extent permitted by law with respect to the functionality of Starknet and STRK. ## Further reading Learn more in the [**STRK MICAr White Paper**](https://drive.google.com/file/d/1GtHtJbpAEgkh2FpZkdJjidwA4DkpSYEO/view?usp=sharing), published on behalf of Tzolkin GmbH # Transactions Source: https://docs.starknet.io/learn/protocol/transactions ## Overview Transactions are cryptographically signed instructions from accounts that update the state of the Starknet network. Starknet supports only [three types](#transaction_types) of transactions, with each type going through the same [lifecycle](#transaction_lifecycle) to receive its [execution and finality statuses](#transaction_statuses). Every transaction that was executed successfully is issued a [receipt](#transaction_receipt), which includes its statuses, as well as other information about it. Additional information * [Transaction fields and hash calculations reference](/learn/cheatsheets/transactions-reference/) in *References* * [`starknet_api_openrpc.json`](https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json) in the `starknet-specs` GitHub repository ## Transaction types Starknet supports the following types of transactions: | | | | - | - | | `DECLARE` | Introduces a new contract class to Starknet, enabling other contracts to either deploy instances of it or use it via a library call For more information about contract classes and instances, see the Cairo Book. | | `INVOKE` | Invokes a function in an existing contract instance, undergoing validation and execution initiated by the caller account's [`__validate__ and __execute__ functions`](/learn/protocol/accounts/#starknets_account_structure) (respectively). The validation stage verifies that the caller account approves the transaction. Because an account's \_*validate*\_ and \_*execute*\_ functions can contain any logic, the caller account ultimately determines how to handle the INVOKE transaction. | | `DEPLOY_ACCOUNT` | Deploys a new account contract to Starknet. For more information about deploying a new account, see Accounts. | The [`L1HandlerTransaction type`](/learn/protocol/messaging/#l1_handler_transaction) is also a valid transaction type within Starknet, be it cannot be broadcast through the Starknet API like other transaction types, as it was specifically designed for internal Starknet operations (particularly, handling messages from L1 to L2). The `DEPLOY` transaction type existed in Starknet for deploying account before being replaced by `DEPLOY_ACCOUNT`. `DEPLOY` only had one version (v0) and is now unsupported by Starknet. Each transaction type is versioned, with versions increasing when the fields that comprise the transaction change, either with the addition of a new field or the removal of an existing field. The following table summarizes the current, deprecated, and unsupported versions of each transaction type: | Type | Current version | Deprecated versions | Unsupported versions | | - | - | - | - | | `INVOKE` | v3 | None | v1, v0 | | `DECLARE` | v3 | None | v2, v1, v0 | | `DEPLOY_ACCOUNT` | v3 | None | v1 | Do not submit a transaction that uses an unsupported transaction type, as it cannot be included in a proof, and therefore cannot become part of a Starknet block. Sending transaction that use deprecated versions is still supported, but support will be removed in an upcoming Starknet release. ## Transaction lifecycle The high-level steps in the Starknet transaction lifecycle are as follows: 1. **Transaction submission:** A transaction is submitted to a sequencer by a full node. 2. **Mempool validation:** The sequencer's [mempool](#transaction_mempool) performs preliminary validation of the transaction, such as ensuring that its invoker's balance exceeds the transaction's `max_fee` value, assuring the transaction's calldata length is within the legal limit, and more. If the transaction is invalid, it does not proceed. 3. **Sequencer validation:** The sequencer performs preliminary validation of the transaction before executing it to ensure that the transaction is still valid. If the transaction is invalid, it does not proceed. 4. **Execution:** The sequencer batches all transactions that passed the preliminary validation into a block, and applies them to the state sequentially. If a transaction fails during execution, it is included in the block with as reverted. 5. **Proof generation and verification:** The prover uses the [Starknet operating system](/learn/protocol/snos/) to computes the proof for the correct execution of all transactions in the block, and transmits it to the L1 verifier, which verifies it. At this point, the L1 state is updated to include the transaction. ### Mempools Up to and including Starknet v0.13.5, transactions were received and ordered by the sequencer in a first-in-first-out (FIFO) fashion. Starting from Starknet v0.14.0, each sequencer will instead maintain a mempool, decouping the transaction order of arrival from the transaction ordering in blocks, for which they are can decide their own policy. The introduction of mempools will have the following implications on how transactions are processed: * **Nonces**: Transactions with a nonce greater than the sender's current nonce + $x$ will be rejected by the mempool, where: * A sender's current nonce is the sender's nonce in the latest finalized state at the time the transaction is received * X is 200 for `INVOKE` and `DEPLOY_ACCOUNT` transactions and $x = 0$ for `DECLARE` transactions (i.e., no declares with future nonces) * **Fee escalation**: A transaction in the mempool can be replaced by a transaction sent from the same account with the same nonce and both `tip` and `max_l2_gas_price` increased by at least 10%. If the transaction was submitted with zero tip, it can still be replaced by another transaction with zero tip. There are unlikely edge cases in which a transaction is not replaced despite submitting a valid transaction to replace it, such as the existing transaction entering a block while the new one is still processed in the gateway. * **Transaction time-to-live (TTL)**: Transactions that cannot be included in a block and have exceeded their TTL, currently configured to be 5 mins, are periodically evicted from the mempool * **`DECLARE transactions time-to-mature (TTM)`**: Before being inserted into the mempool, `DECLARE` transactions are sent to a "waiting room" until they exceed their TTM, where TTM \< TTL but is still TBD. While in the "waiting room", `DECLARE` transactions can't be replaced, and transactions sent with the same nonce as a transaction in the "waiting room" by the same account are rejected ## Transaction statuses Since Starknet version 0.14.0, the finality statuses are the following: | Type | Status | Description | | - | - | - | | **Finality** | `NOT_RECEIVED` | The transaction is not yet known to any sequencer. | | | `RECEIVED` | A full node has successfully submitted the transaction to a sequencer. As there is currently no P2P protocol between full nodes for updating on each other about received transactions, querying a different node than the one that submitted the transaction for its status will result in a Transaction hash not found error.

Therefore, relying on RECEIVED statuses requires initiating sticky HTTP sessions with your full node provider.
| | | `CANDIDATE` | The transaction was written to the feeder gateway's storage by a sequencer. As the transaction hasn't been executed yet, no execution information is available and only the transaction hash is written to the feeder gateway's storage. | | | `PRE_CONFIRMED` | The transaction was successfully executed by a sequencer and its receipt was written to the feeder gateway's storage. | | | `ACCEPTED_ON_L2` | The transaction was included in a block finalized by the consensus protocol. | | | `ACCEPTED_ON_L1` | The Starknet state on L1 moved to a block height which is greater than or equal to the height of the block containing the transaction. | | **Execution** | `REVERTED` | The transaction failed during execution by a sequencer, and was included in a block as reverted. Since the DEPLOY\_ACCOUNT and DECLARE transactions don't have an execution phase, they cannot be reverted. | | | `SUCCEEDED` | The transaction was successfully executed by a sequencer and is included in a block. | ### State implications of reverted transactions When a transaction is marked as `REVERTED`, the following state implications occur: | | | | - | - | | Nonce increases | The nonce value for the account of the failed transaction iterates despite the failure. | | Fee charge | The sequencer charges a fee for the execution of the transaction up to the point of failure, and a respective `Transfer` event is emitted. | | Partial reversion | All changes that occurred during the validation stage are not reverted. However, all changes that occurred during the execution stage are reverted, including all messages to L1 or any events that were emitted during this stage. Events might still be emitted from the validation stage or the fee charge stage. | | Fee calculation | The fee charged for `REVERTED` transactions is the smaller of the following two values:

• The maximum fee that the user is willing to pay, either `max_fee` (pre-v3 transactions) or $\texttt{max\_amount} \cdot \texttt{max\_price\_per\_unit}$ (v3 transactions).

• The total consumed resources. Consumed resources are resources used for the execution of the transaction up to the point of failure, including Cairo steps, builtins, syscalls, L1 messages, events, and state diffs during the validation and execution stages. | ## Transaction receipt A transaction receipt can be obtained by using the Starknet API's `starknet_getTransactionReceipt` endpoint and contains the following fields: | | | | - | - | | `transaction_hash` | The hash of the transaction. | | `actual_fee` | The actual fee paid for the transaction. | | `finality_status` | The finality status of the transaction. | | `execution_status` | The execution status of the transaction. | | `block_hash` | The hash of the block that includes the transaction | | `block_number` | The sequential number of the block that includes the transaction | | `messages_sent` | A list of messages sent to L1. | | `events` | The events emitted. | | `execution_resource` | A summary of the execution resources used by the transaction. | | `type` | The type of the transaction. | The following is an example of a transaction receipt: ```json theme={null} { "jsonrpc": "2.0", "result": { "actual_fee": "0x221db5dbf6db", "block_hash": "0x301fc0d09c5810600af7bb9610be10596ad6f4e6d28a60d397dd148f0962a88", "block_number": 906096, "events": [ { "data": [ "0x181de8b0cd32999a5cc962c5f724bc0f6a322f02957b80e1d5fef49a87588b7", "0x0", "0x9184e72a000", "0x0" ], "from_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", "keys": [ "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" ] }, { "data": [ "0x764da020183e28a48ee38a9474f84e7e5ff13194", "0x9184e72a000", "0x0", "0x181de8b0cd32999a5cc962c5f724bc0f6a322f02957b80e1d5fef49a87588b7" ], "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", "keys": [ "0x194fc63c49b0f07c8e7a78476844837255213824bd6cb81e0ccfb949921aad1" ] }, { "data": [ "0x181de8b0cd32999a5cc962c5f724bc0f6a322f02957b80e1d5fef49a87588b7", "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", "0x221db5dbf6db", "0x0" ], "from_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", "keys": [ "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" ] } ], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", "messages_sent": [ { "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", "payload": [ "0x0", "0x764da020183e28a48ee38a9474f84e7e5ff13194", "0x9184e72a000", "0x0" ], "to_address": "0xc3511006c04ef1d78af4c8e0e74ec18a6e64ff9e" } ], "transaction_hash": "0xdeadbeef", "type": "INVOKE" }, "id": 1 } ``` # Attesting to blocks Source: https://docs.starknet.io/secure/quickstart/attesting-to-blocks ## Overview Welcome to the third installment of the *Help secure Starknet* guide! 🛡️ Starting from its second phase, the staking protocol requires validators to prove they are actively preserving the history of the network by submitting attestations to randomly assigned blocks in each epoch. This installment of the series will therefore walk you through [running attestation service](#running-pathfinders-attestation-service). To run a Nethermind's attestation service, see [the instructions in its GitHub repository](https://github.com/NethermindEth/starknet-staking-v2/tree/main?tab=readme-ov-file#starknet-staking-v2). ## Running attestation service Luckily, running [attestation service](https://github.com/software-mansion/starknet-validator-attestation/tree/main) is as simple as running: ```shell theme={null} docker run -it --rm --network host \ -e VALIDATOR_ATTESTATION_OPERATIONAL_PRIVATE_KEY=$OPERATIONAL_PRIVATE_KEY \ ghcr.io/software-mansion/starknet-validator-attestation \ --staking-contract-address 0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 \ --attestation-contract-address 0x03f32e152b9637c31bfcf73e434f78591067a01ba070505ff6ee195642c9acfb \ --staker-operational-address $OPERATIONAL_ADDRESS \ --node-url http://localhost:9545/rpc/v0_8 \ --local-signer ``` If successful, the result should look similar to the following: ```shell theme={null} Current attestation info staker_address=0x48f8ddc0bc864f33d4c47b79a1f0e1460e0777d0b0224d8c291f1039523306e operational_address=0x48f8ddc0bc864f33d4c47b79a1f0e1460e0777d0b0224d8c291f1039523306e stake=100000000000000000000 epoch_id=1201 epoch_start=712773 epoch_length=40 attestation_window=16 2025-04-22T11:04:22.716449Z INFO starknet_validator_attestation::state: New epoch started staker_address=0x48f8ddc0bc864f33d4c47b79a1f0e1460e0777d0b0224d8c291f1039523306e operational_address=0x48f8ddc0bc864f33d4c47b79a1f0e1460e0777d0b0224d8c291f1039523306e stake=100000000000000000000 epoch_id=1205 epoch_start=712933 epoch_length=40 attestation_window=16 2025-04-22T11:11:00.263344Z INFO starknet_validator_attestation::state: Attestation transaction sent transaction_hash=0x79f9f5ec8dbfca48a132e8d23caad15455c6e0dc98ec517a7013c374d7d5501 2025-04-22T11:11:03.017827Z INFO starknet_validator_attestation::state: Attestation confirmed staker_address=0x48f8ddc0bc864f33d4c47b79a1f0e1460e0777d0b0224d8c291f1039523306e epoch_id=1205 ``` # Becoming a validator Source: https://docs.starknet.io/secure/quickstart/becoming-a-validator ## Overview Welcome to the second installment of the *Help secure Starknet* guide! 🛡️ After running your own full node, you can take an additional step to help secure Starknet by becoming a validator. While running a node already contributes to the network's decentralization, becoming a validator allows you to earn rewards while taking on [additional responsibilities](/secure/quickstart/attesting-to-blocks). This installment of the series will therefore walk you through the validator onboarding process, including [staking STRK tokens](#staking-strk), [setting your commission](#setting-commission), [opening a delegation pool](#opening-a-delegation-pool), and [verifying that the procedure was successful](#verifying-stakes). The steps in this installment are demonstrated on Sepolia. For the equivalent addresses on Mainnet, see [Chain information](/learn/cheatsheets/chain-info#important-addresses). ## Prerequisites Besides [the prerequisites for running a full node](/secure/quickstart/running-a-node#prerequisites), becoming a Starknet validator requires three accounts deployed on either Sepolia or Mainnet. To follow this guide, these account should be configured as follows: To understand the role of the each account, read more about [validator addresses in the staking protocol](/learn/protocol/staking#addresses). * An account with its address exported using: ```shell theme={null} export OPERATIONAL_ADDRESS= ``` * An account with its address exported using: ```shell theme={null} export REWARDS_ADDRESS= ``` * An `sncast` account named `staker` holding at least [the minimum required amount of STRK tokens](/learn/protocol/staking#protocol) with its address exported using: ```shell theme={null} export STAKING_ADDRESS= ``` For deploying and funding an `sncast` account on Sepolia, see [Deploying a new Sepolia account](/build/quickstart/sepolia#deploying-a-new-sepolia-account). ## Staking STRK To become a validator, you must stake least [the minimum required amount of STRK tokens](/learn/protocol/staking#protocol) by locking them into Starknet's Staking contract. ### Approving STRK transfer To stake your STRK tokens, you first need to approve the transfer of STRK tokens from your staking address to the Staking contract. To do so, use your staking address to invoke the STRK token contract's `approve` function with the following parameters: 1. The Staking contract's address 2. The number of STRK tokens to stake For example, the following can be used to approve the transfer of 1 STRK to the Staking contract on Sepolia: ```shell theme={null} sncast --account=staker invoke \ --contract-address=0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d \ (1) --function=approve \ --arguments=0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1,1000000000000000000 \ (2) --network=sepolia ``` 1. The STRK contract's address on Sepolia 2. The Staking contract's address on Sepolia and 1 STRK (STRK has 18 decimals) ### Locking STRK Once the transfer is approved, you can lock your STRK tokens into the Staking contract by using your staking address to invoke the Staking contract's `stake` function with the following parameters: 1. The address to set as your rewards address 2. The address to set as your operational address 3. The number of STRK tokens to stake The number of STRK tokens to stake must match the number specified in the `approve` function. For example, the following can be used to stake 1 STRK with delegation pooling enabled and 1% commission on Sepolia: ```shell theme={null} sncast --account=staker invoke \ --contract-address=0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 \ (1) --function=stake \ --arguments=$REWARDS_ADDRESS,$OPERATIONAL_ADDRESS,1000000000000000000 \ (2) --network=sepolia ``` 1. The Staking contract's address on Sepolia 2. 1 staked STRK (STRK has 18 decimals), delegation pooling enabled, and 100/10,000 = 1% commission ## Setting commission Once your STRK tokens are locked, you can initialize your commission by using your staking address to invoke the Staking contract's `set_commission` function with the following parameters: 1. The commission rate to set for your delegation pool, as a percentage with precision where 10,000 represents 100% For example, the following can be used to initialize a 1% commission on Sepolia: ```terminal theme={null} sncast --account=staker invoke \ --contract-address=0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 \ (1) --function=set_commission \ --arguments=100 \ (2) --network=sepolia ``` 1. The Staking contract's address on Sepolia 2. 100/10,000 = 1% commission ## Opening delegation pools Once your commission is initialized, you can open a delegation pool by using your staking address to invoke the Staking contract's `set_open_for_delegation` function with the following parameters: 1. The token address to set for your delegation pool To review the addresses of all BTC tokenizations ("BTC wrappers"), use the staking contract's [`get_active_tokens`](https://github.com/starkware-libs/starknet-staking/blob/main/docs/spec.md#get_active_tokens) function. For example, the following can be used to open an STRK delegation pool on Sepolia: ```terminal theme={null} sncast --account=staker invoke \ --contract-address=0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 \ (1) --function=set_open_for_delegation \ --arguments=0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d \ (2) --network=sepolia ``` 1. The Staking contract's address on Sepolia 2. The STRK contract's address on Sepolia ## Getting information To verify that the staking procedure has been successful, you can call the Staking contract's `get_staker_info_v1` function with your staking address as parameter. For example, the following can be used to verify your staking on Sepolia: ```shell theme={null} sncast call \ --contract-address=0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1 \ (1) --function=get_staker_info_v1 \ --arguments=$STAKING_ADDRESS \ --network=sepolia ``` 1. The Staking contract's address on Sepolia If all goes well, the result should be similar to the following: ```shell theme={null} response: [ 0x0, (1) 0xdeadbeef1, (2) 0xdeadbeef2, (3) 0x1, (4) 0xde0b6b3a7640000, (5) 0x0, (6) 0x0, (7) 0x5aa0ca4c068a87f894e8d3918e16ea616df631c28f9c39eae040abfb4966881, (8) 0x0, (9) 0x64 (10) ] ``` 1. Indicates successfully getting the staker's info 2. The validator's rewards address 3. The validator's operational address 4. The validator's stake 5. The validator's index 6. The validator's unclaimed rewards 7. Indicates successfully getting the delegation pool's info 8. The delegation pool's stake 9. The delegation pool's unclaimed rewards 10. The delegation pool's commission ($64_{16} = 100$) # Recommended next steps after helping secure Starknet Source: https://docs.starknet.io/secure/quickstart/next-steps # Introduction Welcome to the fourth and final installment of the *Help secure Starknet* guide! 🛡️ By now you've successfully set up your own Starknet full node and possibly even became a validator (way to go!). This last installment of the series will therefore list the various resources you dive into next. ## Essential learning materials * [Node API specifications](https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json) * [Staking protocol specifications](/learn/protocol/staking) * [Starknet cheatsheets](/learn/cheatsheets/chain-info) ## Full nodes documentations * [Pathfinder Documentation](https://docs.swmansion.com/pathfinder) * [Juno Documentation](https://www.nethermind.io/juno) ## Community and support * [Starknet Staking Telegram](https://t.me/+CLZl_F_Nj2RlNjU0) * [Starknet Discord Community](https://discord.gg/starknet-community) * [Starknet Community Forum](https://community.starknet.io/) # Summary Good luck and welcome aboard! We're incredibly grateful for your contribution to strengthening and safeguarding Starknet 💪 # Help secure Starknet guide overview Source: https://docs.starknet.io/secure/quickstart/overview ## Introduction Welcome to the official *Help secure Starknet* guide! 🛡️ This comprehensive tutorial will walk you through the entire process of becoming a Starknet validator, from meeting the prerequisites to successfully running your validator node. While Starknet is currently still centralized, it is gradually moving towards employing a staking protocol, handing over the responsibilities of producing, attesting, and proving blocks to validators. By the end of this guide, you'll have hands-on experience with the core tools and processes needed to participate in Starknet's decentralized validation network. ## What you'll learn This guide is structured as a series of interconnected tutorials that build upon each other: * [Running a full node](/secure/quickstart/running-a-node) - Setting up and maintaining your validator node * [Becoming a validator](/secure/quickstart/becoming-a-validator) - How to stake your tokens to become eligible for validation * [Attesting to blocks](/secure/quickstart/attesting-to-blocks) - Learn how to participate in block attestation as a validator * [Recommended next steps](/secure/quickstart/next-steps) - Advanced topics and ongoing maintenance ## Prerequisites Before starting this tutorial, you should have: * Basic familiarity with command-line interfaces and server administration * A computer or server running Linux (Ubuntu 20.04+ recommended) * Sufficient STRK tokens for staking (minimum requirements vary) * A reliable internet connection for node synchronization * Understanding of blockchain concepts and validation mechanisms Some experience with running blockchain nodes is helpful but not required - this guide covers the essentials! ## Getting help If you encounter any issues while following this guide: 1. Review the [Starknet staking specifications](/learn/protocol/staking) for technical details 2. Join our [Telegram support channel](https://t.me/+CLZl_F_Nj2RlNjU0) for validator-specific questions 3. Ask for help in the [Starknet Discord community](https://discord.gg/starknet-community) 4. Browse the [Starknet community forum](https://community.starknet.io/) for additional resources # Running a full node Source: https://docs.starknet.io/secure/quickstart/running-a-node ## Overview Welcome to the first installment of the *Help secure Starknet* guide! 🛡️ By operating your own full node, you become part of the distributed network that validates transactions, preserves blockchain history, and ensures Starknet remains decentralized and censorship-resistant. This guide will walk you through running your own full node using either [Juno](https://github.com/NethermindEth/juno) or [Pathfinder](https://docs.swmansion.com/pathfinder). ## Prerequisites ### Installing docker The easiest option to run a Starknet full node is using Docker. To install Docker, simply visit [docs.docker.com/get-started/get-docker](https://docs.docker.com/get-started/get-docker/) and choose whatever OS you're using. ### Exporting an Ethereum URL Running a Starknet full node requires an Ethereum websocket RPC URL. You can get your free Ethereum websocket RPC URL by creating an account with [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or [Quicknode](https://www.quicknode.com/). Afterwhich, you can export it by running: ```shell theme={null} export ETHEREUM_URL= ``` # Running Juno You can run a Juno node using several methods: * [Docker container](#docker-container) * [Standalone binary](#standalone-binary) * [Building from source](#building-from-source) You can use a snapshot to quickly synchronise your node with the network. Check out the [Database Snapshots](#downloading-a-snapshot) guide to get started. ## Docker container ### 1. Get the Docker image Juno Docker images can be found at the [nethermind/juno](https://hub.docker.com/r/nethermind/juno) repository on Docker Hub. Download the latest image: ```bash theme={null} docker pull nethermind/juno ``` You can also build the image locally: ```bash theme={null} # Clone the Juno repository git clone https://github.com/NethermindEth/juno cd juno # Build the Docker image docker build -t nethermind/juno:latest . ``` ### 2. Run the Docker container ```bash theme={null} # Prepare the snapshots directory mkdir -p $HOME/snapshots # Run the container docker run -d \ --name juno \ -p 6060:6060 \ -v $HOME/snapshots/juno_mainnet:/snapshots/juno_mainnet \ nethermind/juno \ --http \ --http-port 6060 \ --http-host 0.0.0.0 \ --eth-node \ --db-path /snapshots/juno_mainnet ``` You can view logs from the Docker container using the following command: ```bash theme={null} docker logs -f juno ``` ## Standalone binary Download standalone binaries from [Juno's GitHub Releases](https://github.com/NethermindEth/juno/releases/latest) as ZIP archives for Linux (amd64 and arm64) and macOS (amd64). For macOS (arm64) or Windows users, consider [running Juno using Docker](#docker-container). ```bash theme={null} # Prepare the snapshots directory mkdir -p $HOME/snapshots # Run the binary ./juno \ --http \ --http-port 6060 \ --http-host 0.0.0.0 \ --eth-node \ --db-path $HOME/snapshots/juno_mainnet ``` You should replace `` with your actual Ethereum node address. If you're using Infura, your Ethereum node address might look something like: `wss://mainnet.infura.io/ws/v3/your-infura-project-id`. Make sure you are using the WebSockets URL `ws`/`wss` and not the http URL `http`/`https`. To view logs from the Docker container, use the following command: ```shell theme={null} docker logs -f juno ``` ## Building from source You can build the Juno binary or Docker image from the source code to access the latest updates or specific versions. ### Prerequisites * [Golang 1.25](https://go.dev/doc/install) or later * [Rust](https://www.rust-lang.org/tools/install) 1.87.0 or higher. * C compiler: `gcc` or `clang` * [jemalloc](https://github.com/jemalloc/jemalloc) ```mdx-code-block theme={null} import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; ``` ```bash theme={null} sudo apt-get install -y build-essential make libjemalloc-dev libjemalloc2 pkg-config libbz2-dev ``` ```bash theme={null} brew install jemalloc pkg-config ``` ### 1. Clone the repository Clone Juno's source code from our [GitHub repository](https://github.com/NethermindEth/juno): ```bash theme={null} git clone https://github.com/NethermindEth/juno cd juno ``` You can use `git tag -l` to view specific version tags. ### 2. Build the binary or Docker image ```bash theme={null} # Install juno dependencies make install-deps # Build the binary make juno # Build the Docker image docker build -t nethermind/juno:latest . ``` ### 3. Run the binary Locate the standalone binary in the `./build/` directory: ```bash theme={null} # Prepare the snapshots directory mkdir -p $HOME/snapshots # Run the binary ./build/juno \ --http \ --http-port 6060 \ --http-host 0.0.0.0 \ --db-path $HOME/snapshots/juno_mainnet \ --eth-node ``` # Running Pathfinder Running a full node involves using your local machine's storage to maintain a full database of the blockchain, all the way from the genesis block. Database snapshots let you quickly start your node without having to download all blocks from the very beginning, and instead use a pre-made version of the database that's already in sync up to a certain block. ## Downloading a snapshot To download Pathfinder's snapshot, you first need to download the [rclone](https://rclone.org/) file manager by running: ```shell theme={null} sudo -v ; curl https://rclone.org/install.sh | sudo bash ``` This installation script can be used to install rclone on Linux/macOS/BSD systems. For other installation methods, see [rclone.org/install](https://rclone.org/install/). Once rclone is downloaded, create a new rclone configuration file: ```shell theme={null} touch $HOME/.config/rclone/rclone.conf ``` and add the following content: ```shell theme={null} [pathfinder-snapshots] type = s3 provider = Cloudflare env_auth = false access_key_id = 7635ce5752c94f802d97a28186e0c96d secret_access_key = 529f8db483aae4df4e2a781b9db0c8a3a7c75c82ff70787ba2620310791c7821 endpoint = https://cbf011119e7864a873158d83f3304e27.r2.cloudflarestorage.com acl = private ``` Next, create a new `$HOME/pathfinder` directory, navigate into it, and use rclone to copy [Pathfinder's latest database snapshot](https://rpc.pathfinder.swmansion.com/snapshots/latest) to your local directory by running: ```shell theme={null} mkdir $HOME/pathfinder cd $HOME/pathfinder rclone copy -P pathfinder-snapshots:pathfinder-snapshots/ snapshot.sqlite.zst ``` Once the database snapshot is downloaded, compare the file's checksum value with the one shown at [rpc.pathfinder.swmansion.com/snapshots/latest](https://rpc.pathfinder.swmansion.com/snapshots/latest) by running: ```shell theme={null} sha256sum snapshot.sqlite.zst ``` If the checksum values match, extract the downloaded database snapshot by running: ```shell theme={null} zstd -T0 -d snapshot.sqlite.zst -o snapshot.sqlite ``` ## Using the snapshot Now that the database snapshot is extracted, you can start your node by running: ```shell theme={null} docker run \ --name pathfinder \ --restart unless-stopped \ --detach \ -p 9545:9545 \ --user "$(id -u):$(id -g)" \ -e RUST_LOG=info \ -e PATHFINDER_ETHEREUM_API_URL=$ETHEREUM_URL \ -v $HOME/pathfinder:/usr/share/pathfinder/data \ swmansion/pathfinder ``` Once the docker container is up and running, you can check the docker image's logs by running: ```shell theme={null} docker logs -f pathfinder ``` If successful, the result should resemble the following: ```shell theme={null} 2025-05-12T18:29:45 INFO Downloading block 766533 2025-05-12T18:30:36 INFO Updated Starknet state with block 766534 2025-05-12T18:31:07 INFO Updated Starknet state with block 766535 2025-05-12T18:31:35 INFO Updated Starknet state with block 766536 2025-05-12T18:32:12 INFO Downloading block 766537 ``` To stop the node's execution, run: ```shell theme={null} docker stop pathfinder ```