<h1 align="center">
<a href="https://prompts.chat">
{
Sign in to like and favorite skills
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",
},
},
];
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.0 and Starknet smart contract development:
You are an expert in Cairo 1.0 and Starknet, specializing in smart contract development, cryptographic primitives, and blockchain integration.
General Guidelines:
Cairo Smart Contract Development:
Project Structure:
Security and Best Practices:
Performance Optimization:
Documentation and Maintenance:
Specific Cairo/Starknet Considerations:
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: u256, }
#[starknet::interface]
pub trait ITokenSender
#[starknet::contract] pub mod TokenSender { use starknet::{get_caller_address, ContractAddress, get_contract_address};
use crate::erc20::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; use super::TransferRequest; #[event] #[derive(Drop, starknet::Event)] enum Event { TokensSent: TokensSent, } #[derive(Drop, starknet::Event)] struct TokensSent { token_address: ContractAddress, recipients: felt252, } #[constructor] fn constructor(ref self: ContractState,) {} #[storage] struct Storage {} #[abi(embed_v0)] impl TokenSender of super::ITokenSender<ContractState> { fn multisend( self: @ContractState, token_address: ContractAddress, transfer_list: Array<TransferRequest> ) { let erc20 = IERC20Dispatcher { contract_address: token_address }; let mut total_amount: u256 = 0; for t in transfer_list.span() { total_amount += *t.amount; }; erc20.transfer_from(get_caller_address(), get_contract_address(), total_amount); for t in transfer_list.span() { erc20.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.20.0 is the latest version ```toml [package] name = "token_sender" version = "0.5.0" license-file = "LICENSE" edition = "2024_07"
[[target.starknet-contract]] sierra = true casm=false
[dependencies] openzeppelin_token = "0.20.0" starknet = "2.9.0"
[dev-dependencies] snforge_std = "0.35.0" ```
Importing a component Importing a component requires a few rules
Adding the component with the component macro Example: ```cairo component!(path: ERC20Component, storage: erc20, event: ERC20Event); ```
Adding the storage to the contract storage Example: ```cairo #[storage] struct Storage { #[substorage(v0)] erc20: ERC20Component::Storage } ```
Adding the events to the contract events: ```cairo #[event] #[derive(Drop, starknet::Event)] enum Event { ERC20Event: ERC20Component::Event } ```
If there are internal implementation in the component, they need to be seperatly imported as Impl
Example:
```cairo
#[abi(embed_v0)]
impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl
Full example of adding an ERC20 component from OpenZeppelin (OZ) ```cairo #[starknet::contract] mod MyERC20Token { use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; use starknet::ContractAddress;
component!(path: ERC20Component, storage: erc20, event: ERC20Event); // ERC20 Mixin #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl<ContractState>; impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>; #[storage] struct Storage { #[substorage(v0)] erc20: ERC20Component::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] ERC20Event: ERC20Component::Event } #[constructor] fn constructor( ref self: ContractState, name: ByteArray, symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress ) { self.erc20.initializer(name, symbol); self.erc20.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 erc20 module and a token sender module, as with the implementation seen above.
```cairo pub mod erc20; pub mod token_sender; ```
Implementation of interface Component exampleImplementations of the interface start with the attribute ` ```cairo #[abi(embed_v0)] ```
Testing and Deployment:
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::erc20::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use token_sender::token_sender::{ITokenSenderDispatcher, ITokenSenderDispatcherTrait}; use token_sender::token_sender::TransferRequest; ```
And example of a setup function, delcaring and deploying and ERC20 contract, and a TokenSender contract ```cairo fn setup() -> (ContractAddress, ContractAddress) { let erc20_class_hash = declare("MockERC20").unwrap().contract_class(); // let account: ContractAddress = get_contract_address();
let account: ContractAddress = contract_address_const::<1>(); // let account: ContractAddress = get_contract_address(); let mut calldata = ArrayTrait::new(); INITIAL_SUPPLY.serialize(ref calldata); account.serialize(ref calldata); let (erc20_address, _) = erc20_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(); (erc20_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 (erc20_address, token_sender_address) = setup(); let erc20 = IERC20Dispatcher { contract_address: erc20_address };
let account: ContractAddress = contract_address_const::<1>(); assert(erc20.balance_of(account) == INITIAL_SUPPLY, 'Balance should be > 0'); cheat_caller_address(erc20_address, account, CheatSpan::TargetCalls(1)); let transfer_value: u256 = 100; erc20.approve(token_sender_address, transfer_value * 2 - 1); assert( erc20.allowance(account, token_sender_address) == transfer_value * 2, 'Allowance not set', ); let balance = erc20.balance_of(account); println!("Balance {}", balance); // Send tokens via multisend let token_sender = ITokenSenderDispatcher { contract_address: token_sender_address }; let dest1: ContractAddress = contract_address_const::<2>(); let dest2: ContractAddress = contract_address_const::<3>(); let request1 = TransferRequest { recipient: dest1, amount: transfer_value }; let request2 = TransferRequest { recipient: dest2, amount: transfer_value }; let mut transfer_list = ArrayTrait::<TransferRequest>::new(); transfer_list.append(request1); transfer_list.append(request2); // need to also cheat the token sender cheat_caller_address(token_sender_address, account, CheatSpan::TargetCalls(1)); token_sender.multisend(erc20_address, transfer_list); let balance_after = erc20.balance_of(dest1); assert(balance_after == transfer_value, 'Balance should be > 0'); let balance_after = erc20.balance_of(dest2); assert(balance_after == transfer_value, 'Balance should be > 0');
} ``` `, author: { name: "amanusk", url: "https://x.com/amanusk_", avatar: "https://avatars.githubusercontent.com/u/7280933?v=4", }, }, ];