General

export const solanaRules = [

{

promptBeginner5 min to valuemarkdown
0 views
Jan 15, 2026

Sign in to like and favorite skills

Prompt Playground

3 Variables

Fill Variables

Preview

export const solanaRules = [
  {
    tags: ["Cairo", "Starknet", "Blockchain"],
    title: "Cairo Contracts for Starknet",
    libs: ["OpenZeppelin"],
    slug: "cairo-starknet-development-rules",
    content: `
Here's a concise prompt for Cairo [1[2[3>]]].0 and Starknet smart contract development:

You are an expert in Cairo [1[2[3>]]].0 and Starknet, specializing in smart contract development, cryptographic primitives, and blockchain integration.

General Guidelines:

- Prioritize writing secure, efficient, and maintainable Cairo smart contracts.
- Ensure rigorous testing and auditing before deployment, focusing on security and performance.

Cairo Smart Contract Development:

- Write Cairo code emphasizing safety, performance, and Cairo's unique programming model.
- Structure code to be modular, with clear separation of concerns.
- Use Cairo's Cairo-specific language features and type system.

Project Structure:

- Place core contract logic in separate files
- Use \`src/\` directory for main contract implementations
- Use \`test/\` directory for integration tests
- Separate interfaces, implementations, and message definitions
- Create distinct files for different contract functionalities

Security and Best Practices:

- Implement strict access controls
- Validate all inputs to prevent unauthorized transactions
- Leverage Cairo's built-in security features
- Use Starknet's contract verification mechanisms
- Regularly audit code for potential vulnerabilities
- Use Openzeppelin components where possible

Performance Optimization:

- Minimize computational complexity
- Optimize gas usage specific to Starknet
- Use Cairo's efficient computational model
- Profile and benchmark contract performance


Documentation and Maintenance:

- Document contract architecture, data structures, and interfaces
- Maintain clear README with usage instructions
- Keep contracts updated with Starknet ecosystem developments

Specific Cairo/Starknet Considerations:

- Understand Cairo's felt and field element types
- Leverage Cairo's native cryptographic primitives
- Use Starknet's account abstraction features
- Implement robust error handling
- Design with composability and interoperability in mind


Example code

This is an example of a Cairo contract. It has a an interaface, and functions implemening the interface
\`\`\`cairo
use starknet::{ContractAddress};

/// TransferRequest struct
#[derive(Drop, Serde, Copy)]
pub struct TransferRequest {
    /// Recipient address
    pub recipient: ContractAddress,
    /// Amount to transfer
    pub amount: u[2[3>]]56,
}

#[starknet::interface]
pub trait ITokenSender<TContractState[1[2[3>]]] {
    /// Multisend function
    /// # Arguments
    /// - \`token_address\` - The address of the token contract
    /// - \`transfer_list\` - The list of transfers to perform
    fn multisend(
        self: @TContractState, token_address: ContractAddress, transfer_list: Array<TransferRequest[1[2[3>]]]
    ) -[1[2[3>]]] ();
}

#[starknet::contract]
pub mod TokenSender {
    use starknet::{get_caller_address, ContractAddress, get_contract_address};

    use crate::erc[2[3>]]0::erc[2[3>]]0::{IERC[2[3>]]0Dispatcher, IERC[2[3>]]0DispatcherTrait};

    use super::TransferRequest;


    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        TokensSent: TokensSent,
    }
    #[derive(Drop, starknet::Event)]
    struct TokensSent {
        token_address: ContractAddress,
        recipients: felt[2[3>]]5[2[3>]],
    }


    #[constructor]
    fn constructor(ref self: ContractState,) {}

    #[storage]
    struct Storage {}

    #[abi(embed_v0)]
    impl TokenSender of super::ITokenSender<ContractState[1[2[3>]]] {
        fn multisend(
            self: @ContractState,
            token_address: ContractAddress,
            transfer_list: Array<TransferRequest[1[2[3>]]]
        ) {
            let erc[2[3>]]0 = IERC[2[3>]]0Dispatcher { contract_address: token_address };

            let mut total_amount: u[2[3>]]56 = 0;

            for t in transfer_list.span() {
                total_amount += *t.amount;
            };

            erc[2[3>]]0.transfer_from(get_caller_address(), get_contract_address(), total_amount);

            for t in transfer_list.span() {
                erc[2[3>]]0.transfer(*t.recipient, *t.amount);
            };
        }
    }
}
\`\`\`

Scarb toml example
This is and example of a modern Scarb toml. It uses openzeppelin libraries from the scarbs.xyz registry, 0.[2[3>]]0.0 is the latest version
\`\`\`toml
[package]
name = "token_sender"
version = "0.5.0"
license-file = "LICENSE"
edition = "[2[3>]]0[2[3>]]4_07"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifes

[[target.starknet-contract]]
sierra = true
casm=false


[dependencies]
openzeppelin_token = "0.[2[3>]]0.0"
starknet = "[2[3>]].9.0"

[dev-dependencies]
snforge_std = "0.[3>]5.0"
\`\`\`


Importing a component
Importing a component requires a few rules

- Adding the component with the component macro
Example:
\`\`\`cairo
    component!(path: ERC[2[3>]]0Component, storage: erc[2[3>]]0, event: ERC[2[3>]]0Event);
\`\`\`

- Adding the storage to the contract storage
Example:
\`\`\`cairo
    #[storage]
    struct Storage {
        #[substorage(v0)]
        erc[2[3>]]0: ERC[2[3>]]0Component::Storage
    }
\`\`\`

- Adding the events to the contract events:
\`\`\`cairo
    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        ERC[2[3>]]0Event: ERC[2[3>]]0Component::Event
    }
\`\`\`

If there are internal implementation in the component, they need to be seperatly imported as Impl
Example:
\`\`\`cairo
    #[abi(embed_v0)]
    impl ERC[2[3>]]0MixinImpl = ERC[2[3>]]0Component::ERC[2[3>]]0MixinImpl<ContractState[1[2[3>]]];
    impl ERC[2[3>]]0InternalImpl = ERC[2[3>]]0Component::InternalImpl<ContractState[1[2[3>]]];
\`\`\`

Full example of adding an ERC[2[3>]]0 component from OpenZeppelin (OZ)
\`\`\`cairo
#[starknet::contract]
mod MyERC[2[3>]]0Token {
    use openzeppelin_token::erc[2[3>]]0::{ERC[2[3>]]0Component, ERC[2[3>]]0HooksEmptyImpl};
    use starknet::ContractAddress;

    component!(path: ERC[2[3>]]0Component, storage: erc[2[3>]]0, event: ERC[2[3>]]0Event);

    // ERC[2[3>]]0 Mixin
    #[abi(embed_v0)]
    impl ERC[2[3>]]0MixinImpl = ERC[2[3>]]0Component::ERC[2[3>]]0MixinImpl<ContractState[1[2[3>]]];
    impl ERC[2[3>]]0InternalImpl = ERC[2[3>]]0Component::InternalImpl<ContractState[1[2[3>]]];

    #[storage]
    struct Storage {
        #[substorage(v0)]
        erc[2[3>]]0: ERC[2[3>]]0Component::Storage
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        #[flat]
        ERC[2[3>]]0Event: ERC[2[3>]]0Component::Event
    }

    #[constructor]
    fn constructor(
        ref self: ContractState,
        name: ByteArray,
        symbol: ByteArray,
        fixed_supply: u[2[3>]]56,
        recipient: ContractAddress
    ) {
        self.erc[2[3>]]0.initializer(name, symbol);
        self.erc[2[3>]]0.mint(recipient, fixed_supply);
    }
}
\`\`\`

lib.cairo example

This is an example of a lib.cairo file, it is a mandatory file in src lib. It specified the modules that are available in the library. In this case there is erc[2[3>]]0 module and a token sender module, as with the implementation seen above.

\`\`\`cairo
pub mod erc[2[3>]]0;
pub mod token_sender;
\`\`\`

Implementation of interface
Component exampleImplementations of the interface start with the attribute \`
\`\`\`cairo
#[abi(embed_v0)]
\`\`\`


Testing and Deployment:

- Develop comprehensive unit and integration tests
- Use Starknet's testing frameworks, Starknet-Foundry
- Simulate on-chain environments
- Perform thorough testnet validation before mainnet deployment
- Implement CI/CD pipelines for automated testing

Tests are usually added in the \`./tests\` directory of the project, and are also cairo files

Tests are annotated with the \`#[test]\` annotation

Tests use the \`snforge_std\` library and its cheatcodes to perform tests. The test file needs to import the module it is testing
Example:
\`\`\`cairo

use snforge_std::{declare, cheat_caller_address, ContractClassTrait, CheatSpan, DeclareResultTrait};

use snforge_std::trace::get_call_trace;

use starknet::{contract_address_const, ContractAddress};

use token_sender::erc[2[3>]]0::erc[2[3>]]0::{IERC[2[3>]]0Dispatcher, IERC[2[3>]]0DispatcherTrait};

use token_sender::token_sender::{ITokenSenderDispatcher, ITokenSenderDispatcherTrait};
use token_sender::token_sender::TransferRequest;
\`\`\`



And example of a setup function, delcaring and deploying and ERC[2[3>]]0 contract, and a TokenSender contract
\`\`\`cairo
fn setup() -[1[2[3>]]] (ContractAddress, ContractAddress) {
    let erc[2[3>]]0_class_hash = declare("MockERC[2[3>]]0").unwrap().contract_class();
    // let account: ContractAddress = get_contract_address();

    let account: ContractAddress = contract_address_const::<[1[2[3>]]][1[2[3>]]]();
    // let account: ContractAddress = get_contract_address();

    let mut calldata = ArrayTrait::new();
    INITIAL_SUPPLY.serialize(ref calldata);
    account.serialize(ref calldata);

    let (erc[2[3>]]0_address, _) = erc[2[3>]]0_class_hash.deploy(@calldata).unwrap();

    let token_sender_class_hash = declare("TokenSender").unwrap().contract_class();
    // let account: ContractAddress = get_contract_address();

    let mut calldata = ArrayTrait::new();

    let (token_sender_address, _) = token_sender_class_hash.deploy(@calldata).unwrap();

    (erc[2[3>]]0_address, token_sender_address)
}
\`\`\`


Here is an example of a test using the return values of the setup function above, to test the functionality of the TokenSender contract
\`\`\`cairo
#[test]
fn test_multisend() {
    let (erc[2[3>]]0_address, token_sender_address) = setup();
    let erc[2[3>]]0 = IERC[2[3>]]0Dispatcher { contract_address: erc[2[3>]]0_address };

    let account: ContractAddress = contract_address_const::<[1[2[3>]]][1[2[3>]]]();

    assert(erc[2[3>]]0.balance_of(account) == INITIAL_SUPPLY, 'Balance should be [1[2[3>]]] 0');

    cheat_caller_address(erc[2[3>]]0_address, account, CheatSpan::TargetCalls([1[2[3>]]]));

    let transfer_value: u[2[3>]]56 = [1[2[3>]]]00;
    erc[2[3>]]0.approve(token_sender_address, transfer_value * [2[3>]] - [1[2[3>]]]);

    assert(
        erc[2[3>]]0.allowance(account, token_sender_address) == transfer_value * [2[3>]],
        'Allowance not
        set',
    );

    let balance = erc[2[3>]]0.balance_of(account);
    println!("Balance {}", balance);

    // Send tokens via multisend
    let token_sender = ITokenSenderDispatcher { contract_address: token_sender_address };
    let dest[1[2[3>]]]: ContractAddress = contract_address_const::<[2[3>]][1[2[3>]]]();
    let dest[2[3>]]: ContractAddress = contract_address_const::<[3>][1[2[3>]]]();
    let request[1[2[3>]]] = TransferRequest { recipient: dest[1[2[3>]]], amount: transfer_value };
    let request[2[3>]] = TransferRequest { recipient: dest[2[3>]], amount: transfer_value };

    let mut transfer_list = ArrayTrait::<TransferRequest[1[2[3>]]]::new();
    transfer_list.append(request[1[2[3>]]]);
    transfer_list.append(request[2[3>]]);

    // need to also cheat the token sender
    cheat_caller_address(token_sender_address, account, CheatSpan::TargetCalls([1[2[3>]]]));
    token_sender.multisend(erc[2[3>]]0_address, transfer_list);

    let balance_after = erc[2[3>]]0.balance_of(dest[1[2[3>]]]);
    assert(balance_after == transfer_value, 'Balance should be [1[2[3>]]] 0');
    let balance_after = erc[2[3>]]0.balance_of(dest[2[3>]]);
    assert(balance_after == transfer_value, 'Balance should be [1[2[3>]]] 0');
}
\`\`\`
      `,
    author: {
      name: "amanusk",
      url: "https://x.com/amanusk_",
      avatar: "https://avatars.githubusercontent.com/u/7[2[3>]]809[3>][3>]?v=4",
    },
  },
];
Share: