├── .gitignore ├── protostar.toml ├── utils.py ├── tests └── test_main.cairo ├── src ├── main.cairo └── erc721.cairo ├── agent.md └── starknet_note.md /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | build/ -------------------------------------------------------------------------------- /protostar.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | protostar-version = "0.9.2" 3 | lib-path = "lib" 4 | 5 | [contracts] 6 | main = ["src/main.cairo"] 7 | 8 | ["protostar.build"] 9 | cairo-path = ["./lib/cairo_contracts/src"] 10 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | MAX_LEN_FELT = 31 2 | 3 | 4 | def str_to_felt(text): 5 | if len(text) > MAX_LEN_FELT: 6 | raise Exception("Text length too long to convert to felt.") 7 | 8 | return int.from_bytes(text.encode(), "big") 9 | 10 | 11 | def felt_to_str(felt): 12 | length = (felt.bit_length() + 7) // 8 13 | return felt.to_bytes(length, byteorder="big").decode("utf-8") 14 | 15 | 16 | def str_to_felt_array(text): 17 | return [str_to_felt(text[i:i+MAX_LEN_FELT]) for i in range(0, len(text), MAX_LEN_FELT)] 18 | 19 | 20 | def uint256_to_int(uint256): 21 | return uint256[0] + uint256[1]*2**128 22 | 23 | 24 | def uint256(val): 25 | return (val & 2**128-1, (val & (2**256-2**128)) >> 128) 26 | 27 | 28 | def hex_to_felt(val): 29 | return int(val, 16) 30 | -------------------------------------------------------------------------------- /tests/test_main.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | from src.main import balance, increase_balance 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | 5 | @external 6 | func test_increase_balance{syscall_ptr: felt*, range_check_ptr, pedersen_ptr: HashBuiltin*}() { 7 | let (result_before) = balance.read(); 8 | assert result_before = 0; 9 | 10 | increase_balance(42); 11 | 12 | let (result_after) = balance.read(); 13 | assert result_after = 42; 14 | return (); 15 | } 16 | 17 | @external 18 | func test_cannot_increase_balance_with_negative_value{ 19 | syscall_ptr: felt*, range_check_ptr, pedersen_ptr: HashBuiltin* 20 | }() { 21 | let (result_before) = balance.read(); 22 | assert result_before = 0; 23 | 24 | %{ expect_revert("TRANSACTION_FAILED", "Amount must be positive") %} 25 | increase_balance(-42); 26 | 27 | return (); 28 | } 29 | -------------------------------------------------------------------------------- /src/main.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | from starkware.cairo.common.math import assert_nn 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | 5 | @storage_var 6 | func balance() -> (res: felt) { 7 | } 8 | 9 | @external 10 | func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 11 | amount: felt 12 | ) { 13 | with_attr error_message("Amount must be positive. Got: {amount}.") { 14 | assert_nn(amount); 15 | } 16 | 17 | let (res) = balance.read(); 18 | balance.write(res + amount); 19 | return (); 20 | } 21 | 22 | @view 23 | func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (res: felt) { 24 | let (res) = balance.read(); 25 | return (res,); 26 | } 27 | 28 | @constructor 29 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 30 | balance.write(0); 31 | return (); 32 | } 33 | -------------------------------------------------------------------------------- /agent.md: -------------------------------------------------------------------------------- 1 | # Starknet Toolings 2 | 3 | 1. Protostar 4 | - How to deploy your ArgentX wallet on Starknet testnet 5 | - Protostar declare command: 6 | ``` 7 | @ > protostar declare --account-address 0x0736E99ce85cfD72142842d4D7c6760A835eF95Ed5271FFb2a6e51Db44087eC1 --network testnet --max-fee auto ./build/main.json 8 | ``` 9 | 2. Starknet CLI 10 | 3. Deploying a contract using ArgentX wallet 11 | 12 | protostar calculate-account-address --account-class-hash 0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918 --account-address-salt 1 13 | 14 | protostar declare --account-address 0x0736E99ce85cfD72142842d4D7c6760A835eF95Ed5271FFb2a6e51Db44087eC1 --max-fee auto --network testnet ./build/main.json 15 | 16 | protostar deploy 0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc --network testnet --max-fee auto --account-address 0x0736E99ce85cfD72142842d4D7c6760A835eF95Ed5271FFb2a6e51Db44087eC1 17 | 18 | protostar declare ./build/main.json --account-address [ACCOUNT_ADDRESS_FROM_ARGENTX_ WALLET] --max-fee auto --network testnet 19 | 20 | protostar deploy 0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc --network testnet --max-fee auto --account-address 0x0736E99ce85cfD72142842d4D7c6760A835eF95Ed5271FFb2a6e51Db44087eC1 -------------------------------------------------------------------------------- /starknet_note.md: -------------------------------------------------------------------------------- 1 | # How to Use Starknet CLI to Deploy Smart Contract 2 | 3 | ## Install a Python Package 4 | 5 | ``` 6 | @ > pip3 install openzeppelin-cairo-contracts 7 | ``` 8 | 9 | ## Export Environment Variable 10 | 11 | ``` 12 | @ > export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount 13 | ``` 14 | ## Create a New Account 15 | 16 | ``` 17 | @ > starknet new_account 18 | Account address: 0x036aba3e15486bd1cd336acc147db2163fb1d92d1a93d77857d985981da9b645 19 | Public key: 0x03d9953bcb7dec9528691b09c2b69f995fd867ef49b6d2b9a535b612a586cd6a 20 | Move the appropriate amount of funds to the account, and then deploy the account 21 | by invoking the 'starknet deploy_account' command. 22 | 23 | NOTE: This is a modified version of the OpenZeppelin account contract. The signature is computed 24 | differently. 25 | ``` 26 | 27 | ## Deploy The New Account 28 | 29 | ``` 30 | @ > starknet deploy_account 31 | Sending the transaction with max_fee: 0.001013 ETH (1013253482408779 WEI). 32 | Sent deploy account contract transaction. 33 | 34 | Contract address: 0x009ab9c33e8ec288f16152ad3e133af5aa8f5fa3a423c93d48eb52a860b2b528 35 | Transaction hash: 0x5afb0e67e804c9abfb8acd639a912da661e4e06cde60de1648813bfa4228c4f 36 | ``` 37 | 38 | ## Fund the New Account 39 | 40 | You need to fund the new account so that it can submit transactions later on. You may do so by sending a small amount from your ArgentX wallet, which will also deploy the new account on Starknet testnet. 41 | 42 | ## Prepare The ERC721 Contract 43 | 44 | We can download the copy of https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.5.0/src/openzeppelin/token/erc721/presets/ERC721MintableBurnable.cairo and put it in the src/ folder. 45 | 46 | ## Compile 47 | 48 | ``` 49 | @ > starknet-compile src/erc721.cairo --output build/erc721.json --abi build/erc721_abi.json 50 | ``` 51 | 52 | ## Declare 53 | 54 | As we are using cairo v0 contract, --deprecated option is required to make the `declare` work. 55 | ``` 56 | @ > starknet declare --contract build/erc721.json --deprecated 57 | ``` 58 | 59 | ## Deploy 60 | 61 | ``` 62 | @ > starknet deploy --inputs 71804493054284 4279881 1545509733194854778647923347933833388212704958270379292394134815523094509125 --class_hash 0xb38daee6e2c9efc29eb48e29d063843e004cce72cde635e75546075c94483c 63 | ``` -------------------------------------------------------------------------------- /src/erc721.cairo: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts for Cairo v0.5.0 (token/erc721/presets/ERC721MintableBurnable.cairo) 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.uint256 import Uint256 8 | 9 | from openzeppelin.access.ownable.library import Ownable 10 | from openzeppelin.introspection.erc165.library import ERC165 11 | from openzeppelin.token.erc721.library import ERC721 12 | 13 | // 14 | // Constructor 15 | // 16 | 17 | @constructor 18 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 19 | name: felt, symbol: felt, owner: felt 20 | ) { 21 | ERC721.initializer(name, symbol); 22 | Ownable.initializer(owner); 23 | return (); 24 | } 25 | 26 | // 27 | // Getters 28 | // 29 | 30 | @view 31 | func supportsInterface{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 32 | interfaceId: felt 33 | ) -> (success: felt) { 34 | return ERC165.supports_interface(interfaceId); 35 | } 36 | 37 | @view 38 | func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) { 39 | return ERC721.name(); 40 | } 41 | 42 | @view 43 | func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) { 44 | return ERC721.symbol(); 45 | } 46 | 47 | @view 48 | func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(owner: felt) -> ( 49 | balance: Uint256 50 | ) { 51 | return ERC721.balance_of(owner); 52 | } 53 | 54 | @view 55 | func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenId: Uint256) -> ( 56 | owner: felt 57 | ) { 58 | return ERC721.owner_of(tokenId); 59 | } 60 | 61 | @view 62 | func getApproved{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 63 | tokenId: Uint256 64 | ) -> (approved: felt) { 65 | return ERC721.get_approved(tokenId); 66 | } 67 | 68 | @view 69 | func isApprovedForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 70 | owner: felt, operator: felt 71 | ) -> (isApproved: felt) { 72 | let (isApproved: felt) = ERC721.is_approved_for_all(owner, operator); 73 | return (isApproved=isApproved); 74 | } 75 | 76 | @view 77 | func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 78 | tokenId: Uint256 79 | ) -> (tokenURI: felt) { 80 | let (tokenURI: felt) = ERC721.token_uri(tokenId); 81 | return (tokenURI=tokenURI); 82 | } 83 | 84 | @view 85 | func owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) { 86 | return Ownable.owner(); 87 | } 88 | 89 | // 90 | // Externals 91 | // 92 | 93 | @external 94 | func approve{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( 95 | to: felt, tokenId: Uint256 96 | ) { 97 | ERC721.approve(to, tokenId); 98 | return (); 99 | } 100 | 101 | @external 102 | func setApprovalForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 103 | operator: felt, approved: felt 104 | ) { 105 | ERC721.set_approval_for_all(operator, approved); 106 | return (); 107 | } 108 | 109 | @external 110 | func transferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( 111 | from_: felt, to: felt, tokenId: Uint256 112 | ) { 113 | ERC721.transfer_from(from_, to, tokenId); 114 | return (); 115 | } 116 | 117 | @external 118 | func safeTransferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( 119 | from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt* 120 | ) { 121 | ERC721.safe_transfer_from(from_, to, tokenId, data_len, data); 122 | return (); 123 | } 124 | 125 | @external 126 | func mint{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( 127 | to: felt, tokenId: Uint256 128 | ) { 129 | Ownable.assert_only_owner(); 130 | ERC721._mint(to, tokenId); 131 | return (); 132 | } 133 | 134 | @external 135 | func burn{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(tokenId: Uint256) { 136 | ERC721.assert_only_token_owner(tokenId); 137 | ERC721._burn(tokenId); 138 | return (); 139 | } 140 | 141 | @external 142 | func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( 143 | tokenId: Uint256, tokenURI: felt 144 | ) { 145 | Ownable.assert_only_owner(); 146 | ERC721._set_token_uri(tokenId, tokenURI); 147 | return (); 148 | } 149 | 150 | @external 151 | func transferOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 152 | newOwner: felt 153 | ) { 154 | Ownable.transfer_ownership(newOwner); 155 | return (); 156 | } 157 | 158 | @external 159 | func renounceOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 160 | Ownable.renounce_ownership(); 161 | return (); 162 | } 163 | --------------------------------------------------------------------------------