参考ethereum ERC20标准,发行一个Starknet token
oz合约目前版本还不支持components方式编写,直接参考oz合约代码
等oz版本更新,再进一步实现
//! SPDX-License-Identifier: MIT
//! OpenZeppelin Contracts for Cairo v0.8.0-beta.0 (token/erc20/erc20.cairo)
//!
//! # ERC20 Contract and Implementation
//!
//! This ERC20 contract includes both a library and a basic preset implementation.
//! The library is agnostic regarding how tokens are created; however,
//! the preset implementation sets the initial supply in the constructor.
//! A derived contract can use [_mint](_mint) to create a different supply mechanism.
#[starknet::contract]
mod SNErc20 {
use integer::BoundedInt;
use openzeppelin::token::erc20::interface::IERC20;
use openzeppelin::token::erc20::interface::IERC20CamelOnly;
use starknet::ContractAddress;
use starknet::get_caller_address;
#[storage]
struct Storage {
ERC20_name: felt252,
ERC20_symbol: felt252,
ERC20_total_supply: u256,
ERC20_balances: LegacyMap<ContractAddress, u256>,
ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256>,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Transfer: Transfer,
Approval: Approval,
}
/// Emitted when tokens are moved from address `from` to address `to`.
#[derive(Drop, starknet::Event)]
struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
value: u256
}
/// Emitted when the allowance of a `spender` for an `owner` is set by a call
/// to [approve](approve). `value` is the new allowance.
#[derive(Drop, starknet::Event)]
struct Approval {
#[key]
owner: ContractAddress,
#[key]
spender: ContractAddress,
value: u256
}
mod Errors {
const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0';
const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0';
const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0';
const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0';
const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0';
const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0';
}
/// Initializes the state of the ERC20 contract. This includes setting the
/// initial supply of tokens as well as the recipient of the initial supply.
#[constructor]
fn constructor(
ref self: ContractState,
name: felt252,
symbol: felt252,
initial_supply: u256,
recipient: ContractAddress
) {
self.initializer(name, symbol);
self._mint(recipient, initial_supply);
}
//
// External
//
#[external(v0)]
impl ERC20Impl of IERC20<ContractState> {
/// Returns the name of the token.
fn name(self: @ContractState) -> felt252 {
self.ERC20_name.read()
}
/// Returns the ticker symbol of the token, usually a shorter version of the name.
fn symbol(self: @ContractState) -> felt252 {
self.ERC20_symbol.read()
}
/// Returns the number of decimals used to get its user representation.
fn decimals(self: @ContractState) -> u8 {
18
}
/// Returns the value of tokens in existence.
fn total_supply(self: @ContractState) -> u256 {
self.ERC20_total_supply.read()
}
/// Returns the amount of tokens owned by `account`.
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
self.ERC20_balances.read(account)
}
/// Returns the remaining number of tokens that `spender` is
/// allowed to spend on behalf of `owner` through [transfer_from](transfer_from).
/// This is zero by default.
/// This value changes when [approve](approve) or [transfer_from](transfer_from)
/// are called.
fn allowance(
self: @ContractState, owner: ContractAddress, spender: ContractAddress
) -> u256 {
self.ERC20_allowances.read((owner, spender))
}
/// Moves `amount` tokens from the caller's token balance to `to`.
/// Emits a [Transfer](Transfer) event.
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let sender = get_caller_address();
self._transfer(sender, recipient, amount);
true
}
/// Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
/// Emits a [Transfer](Transfer) event.
fn transfer_from(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256
) -> bool {
let caller = get_caller_address();
self._spend_allowance(sender, caller, amount);
self._transfer(sender, recipient, amount);
true
}
/// Sets `amount` as the allowance of `spender` over the caller’s tokens.
fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
self._approve(caller, spender, amount);
true
}
}
/// Increases the allowance granted from the caller to `spender` by `added_value`.
/// Emits an [Approval](Approval) event indicating the updated allowance.
#[external(v0)]
fn increase_allowance(
ref self: ContractState, spender: ContractAddress, added_value: u256
) -> bool {
self._increase_allowance(spender, added_value)
}
/// Decreases the allowance granted from the caller to `spender` by `subtracted_value`.
/// Emits an [Approval](Approval) event indicating the updated allowance.
#[external(v0)]
fn decrease_allowance(
ref self: ContractState, spender: ContractAddress, subtracted_value: u256
) -> bool {
self._decrease_allowance(spender, subtracted_value)
}
#[external(v0)]
impl ERC20CamelOnlyImpl of IERC20CamelOnly<ContractState> {
/// Camel case support.
/// See [total_supply](total-supply).
fn totalSupply(self: @ContractState) -> u256 {
ERC20Impl::total_supply(self)
}
/// Camel case support.
/// See [balance_of](balance_of).
fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {
ERC20Impl::balance_of(self, account)
}
/// Camel case support.
/// See [transfer_from](transfer_from).
fn transferFrom(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256
) -> bool {
ERC20Impl::transfer_from(ref self, sender, recipient, amount)
}
}
/// Camel case support.
/// See [increase_allowance](increase_allowance).
#[external(v0)]
fn increaseAllowance(
ref self: ContractState, spender: ContractAddress, addedValue: u256
) -> bool {
increase_allowance(ref self, spender, addedValue)
}
/// Camel case support.
/// See [decrease_allowance](decrease_allowance).
#[external(v0)]
fn decreaseAllowance(
ref self: ContractState, spender: ContractAddress, subtractedValue: u256
) -> bool {
decrease_allowance(ref self, spender, subtractedValue)
}
//
// Internal
//
#[generate_trait]
impl InternalImpl of InternalTrait {
/// Initializes the contract by setting the token name and symbol.
/// To prevent reinitialization, this should only be used inside of a contract's constructor.
fn initializer(ref self: ContractState, name: felt252, symbol: felt252) {
self.ERC20_name.write(name);
self.ERC20_symbol.write(symbol);
}
/// Internal method that moves an `amount` of tokens from `from` to `to`.
/// Emits a [Transfer](Transfer) event.
fn _transfer(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256
) {
assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);
assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);
self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount);
self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
self.emit(Transfer { from: sender, to: recipient, value: amount });
}
/// Internal method that sets `amount` as the allowance of `spender` over the
/// `owner`s tokens.
/// Emits an [Approval](Approval) event.
fn _approve(
ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256
) {
assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO);
assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);
self.ERC20_allowances.write((owner, spender), amount);
self.emit(Approval { owner, spender, value: amount });
}
/// Creates a `value` amount of tokens and assigns them to `account`.
/// Emits a [Transfer](Transfer) event with `from` set to the zero address.
fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);
self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount);
self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount });
}
/// Destroys a `value` amount of tokens from `account`.
/// Emits a [Transfer](Transfer) event with `to` set to the zero address.
fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) {
assert(!account.is_zero(), Errors::BURN_FROM_ZERO);
self.ERC20_total_supply.write(self.ERC20_total_supply.read() - amount);
self.ERC20_balances.write(account, self.ERC20_balances.read(account) - amount);
self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount });
}
/// Internal method for the external [increase_allowance](increase_allowance).
/// Emits an [Approval](Approval) event indicating the updated allowance.
fn _increase_allowance(
ref self: ContractState, spender: ContractAddress, added_value: u256
) -> bool {
let caller = get_caller_address();
self
._approve(
caller, spender, self.ERC20_allowances.read((caller, spender)) + added_value
);
true
}
/// Internal method for the external [decrease_allowance](decrease_allowance).
/// Emits an [Approval](Approval) event indicating the updated allowance.
fn _decrease_allowance(
ref self: ContractState, spender: ContractAddress, subtracted_value: u256
) -> bool {
let caller = get_caller_address();
self
._approve(
caller,
spender,
self.ERC20_allowances.read((caller, spender)) - subtracted_value
);
true
}
/// Updates `owner`s allowance for `spender` based on spent `amount`.
/// Does not update the allowance value in case of infinite allowance.
/// Possibly emits an [Approval](Approval) event.
fn _spend_allowance(
ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256
) {
let current_allowance = self.ERC20_allowances.read((owner, spender));
if current_allowance != BoundedInt::max() {
self._approve(owner, spender, current_allowance - amount);
}
}
}
}
[[target.starknet-contract]]
casm = true
SNErc20 = { path = "src/lib" }
//declare
sncast declare -c SNErc20
command: declare
class_hash: 0x6e07c0cfe87a3ee5754cfe0281cdc8298adb7e8cd8d93fe2bab8d87986ccd3a
transaction_hash: 0x3174197e5605370524830390c1b4dff798f8294bbc303af802b556b406f3eed
//deploy
sncast deploy -g 0x6e07c0cfe87a3ee5754cfe0281cdc8298adb7e8cd8d93fe2bab8d87986ccd3a -c 0x736e646f67 0x736e646f67 (10000000000000000000000,0) 0x3180b441f524c2de2515094fc85ffef278d9764f6ebbb87db68928c094177ff
command: deploy
contract_address: 0x4e75e2903557159b11aa3b495af515f4db943588ceeb5cf89f433d8e2d60756
transaction_hash: 0x245cbeecddfafdea0fa2700a1ef3988d4517df91ea14da81e53c0013d5e165d
deploy参数解释
0x736e646f67 sndog字符串的16进制
0x736e646f67 sndog字符串的16进制
(10000000000000000000000,0) 总量10000个,这是Cario u256参数的写法,具体可以参考这篇文章 https://medium.com/@idogwuchi/u256-data-types-in-cairo-a-new-developers-guide-to-starknet-smart-contracts-55f788ec2147
0x3180b441f524c2de2515094fc85ffef278d9764f6ebbb87db68928c094177ff 接收地址
//call 查询balance 0x02b218603925916db821199f7acb1fcc33f51748317b6783ba8d5743dab65838为我Braavos钱包地址
sncast call -a 0x4e75e2903557159b11aa3b495af515f4db943588ceeb5cf89f433d8e2d60756 -f balance_of -c 0x02b218603925916db821199f7acb1fcc33f51748317b6783ba8d5743dab65838
command: call
response: [0x0, 0x0]
//invoke 给这个地址转2000个sndog token
sncast invoke -a 0x4e75e2903557159b11aa3b495af515f4db943588ceeb5cf89f433d8e2d60756 -f transfer -c 0x02b218603925916db821199f7acb1fcc33f51748317b6783ba8d5743dab65838 (2000000000000000000000,0)
command: invoke
transaction_hash: 0x58855547068e58ce8b608156a469301d739c63b3d215997ad96e1a534eae89b
//call 查询balance
sncast call -a 0x4e75e2903557159b11aa3b495af515f4db943588ceeb5cf89f433d8e2d60756 -f balance_of -c 0x02b218603925916db821199f7acb1fcc33f51748317b6783ba8d5743dab65838
command: call
response: [0x6c6b935b8bbd400000, 0x0]
需要先添加token合约

最后解释下,Cairo合约提倡的函数命名是下划线,而非驼峰命名方式,但是由于历史原因,Braavos钱包目前只认驼峰命名,所以看合约代码里会有两组不同的函数命名方式;
如果不写驼峰函数命名,钱包里会不显示余额;