From f08df833fbf76e8c48ba394608f3afcebb6c35bb Mon Sep 17 00:00:00 2001 From: Aegean Date: Mon, 17 Jun 2024 13:48:55 +0300 Subject: [PATCH 1/4] feat: simple storage with starknet-js --- .../simple_storage_starknetjs/.env.example | 1 + .../simple_storage_starknetjs/.gitignore | 1 + .../simple_storage_starknetjs/abi.json | 616 ++++++++++++++++++ .../contracts/Scarb.lock | 6 + .../contracts/Scarb.toml | 11 + .../contracts/src/lib.cairo | 1 + .../contracts/src/storage.cairo | 24 + .../simple_storage_starknetjs/index.js | 30 + src/applications/simple_storage_starknetjs.md | 259 ++++++++ 9 files changed, 949 insertions(+) create mode 100644 listings/applications/simple_storage_starknetjs/.env.example create mode 100644 listings/applications/simple_storage_starknetjs/.gitignore create mode 100644 listings/applications/simple_storage_starknetjs/abi.json create mode 100644 listings/applications/simple_storage_starknetjs/contracts/Scarb.lock create mode 100644 listings/applications/simple_storage_starknetjs/contracts/Scarb.toml create mode 100644 listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo create mode 100644 listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo create mode 100644 listings/applications/simple_storage_starknetjs/index.js create mode 100644 src/applications/simple_storage_starknetjs.md diff --git a/listings/applications/simple_storage_starknetjs/.env.example b/listings/applications/simple_storage_starknetjs/.env.example new file mode 100644 index 00000000..32869cab --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/.env.example @@ -0,0 +1 @@ +PRIVATE_KEY = "PASTE_PRIVATE_KEY_HERE" \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/.gitignore b/listings/applications/simple_storage_starknetjs/.gitignore new file mode 100644 index 00000000..2eea525d --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/abi.json b/listings/applications/simple_storage_starknetjs/abi.json new file mode 100644 index 00000000..761ae6c2 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/abi.json @@ -0,0 +1,616 @@ +{ + "sierra_program": [ + "0x1", + "0x5", + "0x0", + "0x2", + "0x6", + "0x3", + "0x98", + "0x68", + "0x18", + "0x52616e6765436865636b", + "0x800000000000000100000000000000000000000000000000", + "0x436f6e7374", + "0x800000000000000000000000000000000000000000000002", + "0x1", + "0x16", + "0x2", + "0x53746f726555313238202d206e6f6e2075313238", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", + "0x4f7574206f6620676173", + "0x4172726179", + "0x800000000000000300000000000000000000000000000001", + "0x536e617073686f74", + "0x800000000000000700000000000000000000000000000001", + "0x4", + "0x537472756374", + "0x800000000000000700000000000000000000000000000002", + "0x0", + "0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62", + "0x5", + "0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3", + "0x6", + "0x9", + "0x753332", + "0x800000000000000700000000000000000000000000000000", + "0x53746f7261676541646472657373", + "0x53746f726167654261736541646472657373", + "0x4275696c74696e436f737473", + "0x53797374656d", + "0x800000000000000f00000000000000000000000000000001", + "0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672", + "0x800000000000000300000000000000000000000000000003", + "0xe", + "0x456e756d", + "0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6", + "0x7", + "0xf", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x75313238", + "0x426f78", + "0x800000000000000700000000000000000000000000000003", + "0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7", + "0x14", + "0x13", + "0x66656c74323532", + "0x4761734275696c74696e", + "0x35", + "0x7265766f6b655f61705f747261636b696e67", + "0x77697468647261775f676173", + "0x6272616e63685f616c69676e", + "0x7374727563745f6465636f6e737472756374", + "0x656e61626c655f61705f747261636b696e67", + "0x73746f72655f74656d70", + "0x61727261795f736e617073686f745f706f705f66726f6e74", + "0x656e756d5f696e6974", + "0x15", + "0x6a756d70", + "0x7374727563745f636f6e737472756374", + "0x656e756d5f6d61746368", + "0x756e626f78", + "0x72656e616d65", + "0x75313238735f66726f6d5f66656c74323532", + "0x64697361626c655f61705f747261636b696e67", + "0x64726f70", + "0x12", + "0x61727261795f6e6577", + "0x636f6e73745f61735f696d6d656469617465", + "0x11", + "0x61727261795f617070656e64", + "0x10", + "0x17", + "0xd", + "0x6765745f6275696c74696e5f636f737473", + "0xc", + "0x77697468647261775f6761735f616c6c", + "0x73746f726167655f626173655f616464726573735f636f6e7374", + "0x3f21dadc69f28434fd7a4035b4cee8d901bf7ef13138a70c3f754bf85806657", + "0x753132385f746f5f66656c74323532", + "0x73746f726167655f616464726573735f66726f6d5f62617365", + "0x8", + "0xa", + "0x73746f726167655f77726974655f73797363616c6c", + "0x736e617073686f745f74616b65", + "0x3", + "0x73746f726167655f726561645f73797363616c6c", + "0xeb", + "0xffffffffffffffff", + "0x74", + "0xb", + "0x63", + "0x5d", + "0x19", + "0x1a", + "0x1b", + "0x2b", + "0x1c", + "0x1d", + "0x1e", + "0x1f", + "0x20", + "0x21", + "0x22", + "0x23", + "0x24", + "0x25", + "0x4f", + "0x26", + "0x27", + "0x28", + "0x29", + "0x2a", + "0x2c", + "0x2d", + "0x46", + "0x2e", + "0x2f", + "0x30", + "0x31", + "0x32", + "0x33", + "0x34", + "0x36", + "0x37", + "0x38", + "0x39", + "0x3a", + "0x3b", + "0x3c", + "0x3d", + "0x3e", + "0x3f", + "0x40", + "0x67", + "0x41", + "0x42", + "0x43", + "0x44", + "0x45", + "0x47", + "0x48", + "0x49", + "0x4a", + "0x4b", + "0x4c", + "0xdd", + "0x97", + "0xd0", + "0xc3", + "0xb7", + "0xc8", + "0x82", + "0x876", + "0x11100f050e0d06050c0b0a0706050403090706050403080706050403020100", + "0x101f121e10021d191c191b191a191812071705040316051512111014051312", + "0x6050e2815121e10192726070605040325052405231220220f052105151220", + "0x536120505351234160505331232123112302f022e192d2c052b052a122922", + "0x5053b0507380507372c05053a123938050535140505351207380507370605", + "0x542410505402b05054014050540123f123e060505350605053d0605053c38", + "0x50535470505350507460507372505053a2105053a06050545440505430605", + "0x5351705053550050543124f124e4d07054c124b4a05053512494605053548", + "0x40540505431207460507372405053a1605053a0f0505400f05055312525105", + "0x70512125705121212560f05053505050543125516050540070505432c0505", + "0x125705120f12500557055405541212570512071224160758140f0757070512", + "0x55105241212570512071259054a5117075707500516120f0557050f051412", + "0x125705120712124405125112210557054a05171248055705170550124a0557", + "0x210557054605171248055705590550124605570525054a1225055705125912", + "0x57052c0525122c0557054405211212570512071241055a4405570721054812", + "0x1257051207125c0047545b06380757072b0f0744122b0557052b0546122b05", + "0x570512411212570512071260055f5e5d075707480516123805570538051412", + "0x471261055705120612125705060538121257055e052b121257055d052c1212", + "0x5d1264055705125c1263055705626107001262055705620546126205570512", + "0x140557051405601238055705380514126605570565055e1265055705636407", + "0x124112125705120712660714380f0566055705660562120705570507056112", + "0x67143854651267055705670564126705570512631212570560052c12125705", + "0x126e055705060567126d0557051266121257051207126c6b076a6968075707", + "0x126f0557056f056c125f0557055f056b125f0557051269126f0557056d0568", + "0x125705120712757473547271700757076e6f5f0769146d1268055705680514", + "0x7905570578055f1212570577056f12787707570576056e1276055705120612", + "0x557057005601268055705680514127a0557052f0571122f05570579057012", + "0x5c121257051207127a7170680f057a0557057a056212710557057105611270", + "0x1268055705680514127d0557057c055e127c055705757b075d127b05570512", + "0x7127d7473680f057d0557057d056212740557057405611273055705730560", + "0x57057f0546127f0557051273127e0557051206121257050605381212570512", + "0x82055e12820557058081075d1281055705125c12800557057f7e0700127f05", + "0x5621207055705070561126c0557056c0560126b0557056b05141283055705", + "0x1257055c0538121257050005381212570512071283076c6b0f058305570583", + "0x41057412125705120712128505125112840557054705141212570548052c12", + "0x86055705120612125705124112840557050f05141212570548052c12125705", + "0x89055705125c12880557058786070012870557058705461287055705127512", + "0x5705140560128405570584051412720557058a055e128a0557058889075d12", + "0x12125705120712720714840f05720557057205621207055705070561121405", + "0x700128c0557058c0546128c0557051273128b055705120612125705540576", + "0x12900557058f055e128f0557058d8e075d128e055705125c128d0557058c8b", + "0x90055705900562120705570507056112240557052405601216055705160514", + "0x570512071224160791140f0757070512070512125705121212900724160f05", + "0x7125905925117075707500516120f0557050f051412500557055405541212", + "0x557051247124a05570512061212570551052b1212570517052c1212570512", + "0x52125075d1225055705125c1221055705484a070012480557054805461248", + "0x705611214055705140560120f0557050f0514124405570546055e12460557", + "0x12570559052c121257051207124407140f0f05440557054405621207055705", + "0x12063807932b2c07570741140f546512410557054105641241055705126312", + "0x5c056b125c0557051269120005570547056812470557051266121257051207", + "0x5d545707005c072b0f77122c0557052c0514120005570500056c125c055705", + "0x61125d0557055d05601260055705600546121257051207126362615494605e", + "0x512061212570512071268676654956564075707602c0744125e0557055e05", + "0x6f126e6d0757056c056e126c0557056b690700126b05570565056712690557", + "0x1412700557055f0571125f0557056f0570126f0557056e055f121257056d05", + "0x570055705700562125e0557055e0561125d0557055d056012640557056405", + "0x557051206121257056805381212570567053812125705120712705e5d640f", + "0x57056605141274055705737107001273055705730546127305570512781271", + "0x96051251127805570574057912770557055e056112760557055d0560127505", + "0x77055705620561127605570561056012750557052c05141212570512071212", + "0x557052f055e122f0557057879075d1279055705125c127805570563057912", + "0x57057a0562127705570577056112760557057605601275055705750514127a", + "0x546127c0557051273127b0557051206121257051207127a7776750f057a05", + "0x127f0557057d7e075d127e055705125c127d0557057c7b0700127c0557057c", + "0x70557050705611206055705060560123805570538051412800557057f055e", + "0x512061212570554057612125705120712800706380f058005570580056212", + "0x5125c12830557058281070012820557058205461282055705127312810557", + "0x5601216055705160514128705570586055e12860557058384075d12840557", + "0x47120f07870724160f05870557058705621207055705070561122405570524", + "0x9754070512464847120f164847120f1254070512464847120f1648" + ], + "sierra_program_debug_info": { + "type_names": [ + [ + 0, + "RangeCheck" + ], + [ + 1, + "Const" + ], + [ + 2, + "Const" + ], + [ + 3, + "Const" + ], + [ + 4, + "Array" + ], + [ + 5, + "Snapshot>" + ], + [ + 6, + "core::array::Span::" + ], + [ + 7, + "Tuple>" + ], + [ + 8, + "Const" + ], + [ + 9, + "u32" + ], + [ + 10, + "StorageAddress" + ], + [ + 11, + "StorageBaseAddress" + ], + [ + 12, + "BuiltinCosts" + ], + [ + 13, + "System" + ], + [ + 14, + "core::panics::Panic" + ], + [ + 15, + "Tuple>" + ], + [ + 16, + "core::panics::PanicResult::<(core::array::Span::,)>" + ], + [ + 17, + "Const" + ], + [ + 18, + "u128" + ], + [ + 19, + "Unit" + ], + [ + 20, + "Box" + ], + [ + 21, + "core::option::Option::>" + ], + [ + 22, + "felt252" + ], + [ + 23, + "GasBuiltin" + ] + ], + "libfunc_names": [ + [ + 0, + "revoke_ap_tracking" + ], + [ + 1, + "withdraw_gas" + ], + [ + 2, + "branch_align" + ], + [ + 3, + "struct_deconstruct>" + ], + [ + 4, + "enable_ap_tracking" + ], + [ + 5, + "store_temp" + ], + [ + 6, + "array_snapshot_pop_front" + ], + [ + 7, + "enum_init>, 0>" + ], + [ + 8, + "store_temp>>" + ], + [ + 9, + "store_temp>>" + ], + [ + 10, + "jump" + ], + [ + 11, + "struct_construct" + ], + [ + 12, + "enum_init>, 1>" + ], + [ + 13, + "enum_match>>" + ], + [ + 14, + "unbox" + ], + [ + 15, + "rename" + ], + [ + 16, + "store_temp" + ], + [ + 17, + "u128s_from_felt252" + ], + [ + 18, + "disable_ap_tracking" + ], + [ + 19, + "drop>>" + ], + [ + 20, + "drop>" + ], + [ + 21, + "drop" + ], + [ + 22, + "array_new" + ], + [ + 23, + "const_as_immediate>" + ], + [ + 24, + "array_append" + ], + [ + 25, + "struct_construct" + ], + [ + 26, + "struct_construct>>" + ], + [ + 27, + "enum_init,)>, 1>" + ], + [ + 28, + "store_temp" + ], + [ + 29, + "store_temp" + ], + [ + 30, + "store_temp,)>>" + ], + [ + 31, + "get_builtin_costs" + ], + [ + 32, + "store_temp" + ], + [ + 33, + "withdraw_gas_all" + ], + [ + 34, + "storage_base_address_const<1784720371058305772862806735021375459770416459932804101453333227736919991895>" + ], + [ + 35, + "u128_to_felt252" + ], + [ + 36, + "storage_address_from_base" + ], + [ + 37, + "const_as_immediate>" + ], + [ + 38, + "store_temp" + ], + [ + 39, + "store_temp" + ], + [ + 40, + "storage_write_syscall" + ], + [ + 41, + "snapshot_take>" + ], + [ + 42, + "drop>" + ], + [ + 43, + "struct_construct>" + ], + [ + 44, + "struct_construct>>" + ], + [ + 45, + "enum_init,)>, 0>" + ], + [ + 46, + "const_as_immediate>" + ], + [ + 47, + "drop" + ], + [ + 48, + "const_as_immediate>" + ], + [ + 49, + "drop>" + ], + [ + 50, + "storage_read_syscall" + ], + [ + 51, + "const_as_immediate>" + ], + [ + 52, + "store_temp>" + ] + ], + "user_func_names": [ + [ + 0, + "simple_storage::storage::SimpleStorage::__wrapper__SimpleStorage__set" + ], + [ + 1, + "simple_storage::storage::SimpleStorage::__wrapper__SimpleStorage__get" + ] + ] + }, + "contract_class_version": "0.1.0", + "entry_points_by_type": { + "EXTERNAL": [ + { + "selector": "0x17c00f03de8b5bd58d2016b59d251c13056b989171c5852949903bc043bc27", + "function_idx": 1 + }, + { + "selector": "0x2f67e6aeaad1ab7487a680eb9d3363a597afa7a3de33fa9bf3ae6edcb88435d", + "function_idx": 0 + } + ], + "L1_HANDLER": [], + "CONSTRUCTOR": [] + }, + "abi": [ + { + "type": "impl", + "name": "SimpleStorage", + "interface_name": "simple_storage::storage::ISimpleStorage" + }, + { + "type": "interface", + "name": "simple_storage::storage::ISimpleStorage", + "items": [ + { + "type": "function", + "name": "set", + "inputs": [ + { + "name": "x", + "type": "core::integer::u128" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u128" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "simple_storage::storage::SimpleStorage::Event", + "kind": "enum", + "variants": [] + } + ] +} \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/contracts/Scarb.lock b/listings/applications/simple_storage_starknetjs/contracts/Scarb.lock new file mode 100644 index 00000000..d9817fa8 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/contracts/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "simple_storage" +version = "0.1.0" diff --git a/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml b/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml new file mode 100644 index 00000000..9dc03461 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml @@ -0,0 +1,11 @@ +[package] +name = "simple_storage" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet=">=2.4.1" + +[[target.starknet-contract]] \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo b/listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo new file mode 100644 index 00000000..80aaac2b --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo @@ -0,0 +1 @@ +mod storage; diff --git a/listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo b/listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo new file mode 100644 index 00000000..72fc81e8 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo @@ -0,0 +1,24 @@ +#[starknet::interface] +trait ISimpleStorage { + fn set(ref self: T, x: u128); + fn get(self: @T) -> u128; +} + +#[starknet::contract] +mod SimpleStorage { + #[storage] + struct Storage { + stored_data: u128 + } + + #[abi(embed_v0)] + impl SimpleStorage of super::ISimpleStorage { + fn set(ref self: ContractState, x: u128) { + self.stored_data.write(x); + } + + fn get(self: @ContractState) -> u128 { + self.stored_data.read() + } + } +} diff --git a/listings/applications/simple_storage_starknetjs/index.js b/listings/applications/simple_storage_starknetjs/index.js new file mode 100644 index 00000000..896d8561 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/index.js @@ -0,0 +1,30 @@ +import { Account, RpcProvider, json, Contract } from 'starknet'; +import fs from 'fs'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const provider = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.public.blastapi.io' }); +const accountAddress = '0x067981c7F9f55BCbdD4e0d0a9C5BBCeA77dAcB42cccbf13554A847d6353F728e'; +const privateKey = process.env.PRIVATE_KEY; + +const account = new Account(provider, accountAddress, privateKey, "1"); + +const compiledContractAbi = json.parse( + fs.readFileSync('./abi.json').toString('ascii') +); + +const contractAddress = '0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97'; +const storageContract = new Contract(compiledContractAbi.abi, contractAddress, provider); + + +let getData = await storageContract.get(); +console.log('Stored_data before set():', getData.toString()); + +storageContract.connect(account); +const myCall = storageContract.populate('set', [59]); +const res = await storageContract.set(myCall.calldata); +await provider.waitForTransaction(res.transaction_hash); + + +getData = await storageContract.get(); +console.log('Stored_data after set():', getData.toString()); diff --git a/src/applications/simple_storage_starknetjs.md b/src/applications/simple_storage_starknetjs.md new file mode 100644 index 00000000..4e3c1ed2 --- /dev/null +++ b/src/applications/simple_storage_starknetjs.md @@ -0,0 +1,259 @@ +# Simple Storage (Starknet-js + Cairo) + +In this tutorial, you will write and deploy a SimpleStorage Cairo contract on Starknet Sepolia Testnet. Then, you will learn how you can interact with the contract using Starknet-js. + +In order to fully understand and implement the content of this tutorial, here are the prerequisites: +- Basic knowledge of Cairo: you can get familiar with the Cairo language, and learn how to build a simple smart contract with Cairo [here](https://book.cairo-lang.org/). + +- Scarb for compiling Cairo code and packaging support: follow [here](https://docs.swmansion.com/scarb/download.html). + +- Starkli for the declaration and deployment of Cairo contracts: follow [here](https://book.starkli.rs/installation). + +## 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 **viewing** the current stored number and **setting** a new number. + +Let's create a Scarb package in order to write and compile the SimpleStorage contract. Make sure that Scarb is installed (installation link provided in the prerequisites): + +```console +//move to the directory of your choice +$ scarb new simple_storage +``` + +With this command, a template for Cairo development environment is generated. Under the `src` directory, `lib.cairo` file is created. In order to start writing our contract, follow these steps: +- Under the `src` directory, create a file named `storage.cairo` where we will write our contract. +- Delete the content of the `lib.cairo` file, and add `storage.cairo` as a module for the compilation of our Cairo contract: +```rs +// Directory: src/lib.cairo +mod storage; +``` +- You can copy and paste the following Cairo code in `storage.cairo`. In this [link](https://book.cairo-lang.org/ch13-02-anatomy-of-a-simple-contract.html), you can find explanations for each component of the contract: + +```rs +// Directory: src/storage.cairo +#[starknet::interface] +trait ISimpleStorage { + fn set(ref self: T, x: u128); + fn get(self: @T) -> u128; +} + +#[starknet::contract] +mod SimpleStorage { + #[storage] + struct Storage { + stored_data: u128 + } + + #[abi(embed_v0)] + impl SimpleStorage of super::ISimpleStorage { + fn set(ref self: ContractState, x: u128) { + self.stored_data.write(x); + } + + fn get(self: @ContractState) -> u128 { + self.stored_data.read() + } + } +} +``` +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. + +Next, we need to update the dependencies of our Scarb folder. Open `Scarb.toml`, and add the following dependencies. These dependencies need to be added in order for our Cairo code to be deployed on Starknet as a smart contract. + +```toml + +[package] +name = "simple_storage" +version = "0.1.0" +edition = "2023_11" + +# Add the dependencies below! + +[dependencies] +starknet=">=2.4.1" + +[[target.starknet-contract]] +``` + +Now, we have to compile our contract in order to declare and deploy on the Starknet Sepolia Testnet. Run the following command: + +``` +$ scarb build +``` + +After you run the command, you should see a new folder called "target" in your your project folder. Your contract's Application Binary Interface (ABI) and Sierra program are found in that folder: `./target/simple_storage_SimpleStorage.contract_class.json`. We will need this file to interact with the contract using Starknet-js. + +We are now ready to declare and deploy our contract on Starknet Sepolia Testnet! + + +## Declaring and Deploying the SimpleStorage contract +We will utilize Starkli in order to deploy and declare our smart contract on the Starknet Sepolia Testnet. Make sure that Starkli is installed in your device (check prerequisites above to find the link for Starkli installation). You can follow [this](https://medium.com/starknet-edu/starkli-the-new-starknet-cli-86ea914a2933) guide for Starkli or check out their documentation given above. + +For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. + +### Creating a new account: +You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. +```console +$ mkdir ./starkli-wallet +``` + +Create a new signer. You will be instructed to enter a password to encrypt your private key: +```console +$ starkli signer keystore new ./starkli-wallet/keystore.json +``` +After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. + +Export the keystore path in order not to call --keystore in every command: +```console +$ export STARKNET_KEYSTORE="./starkli-wallet/keystore.json" +``` +Initialize the account with the following command using OpenZeppelin's class deployed on Starknet. + +```console +$ starkli account oz init ./starkli-wallet/account.json +``` +After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: +```console +$ starkli account deploy ./starkli-wallet/account.json +``` +This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need testnet Ether on Starknet Sepolia which could be obtained from [this](https://starknet-faucet.vercel.app/) testnet faucet. Once the transaction is confirmed on the faucet page, click ENTER on the command line, and the account will be deployed on Starknet Sepolia! Find your account on the [Voyager Sepolia block explorer](https://sepolia.voyager.online/). + +### Declaring & Deploying your Contract: +Firstly, you need to declare your contract which will create a class on Starknet Sepolia. Note that we will use the Sierra program in `./target/simple_storage_SimpleStorage.contract_class.json`. + +**Note:** The command below is written to run in the directory of the Scarb folder. + +```console +$ starkli declare --keystore /path/to/starkli-wallet/keystore.json --account /path/to/starkli-wallet/account.json --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json +``` + +After this command, the class hash is declared. You should be able to find the hash under the command: +```console +Class hash declared: +0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` + +Now, it's time to deploy the contract. Add the clash hash after `--watch`: +```console +$ starkli deploy --keystore /Users/egeaybars123/keystore/keystore --account /Users/egeaybars123/account/account.json --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` +You should now see the address of the deployed contract. Note down the address of the contract; we will use it for interacting with the contract using Starknet-js. + +## 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): + +```console +$ npm init +``` + +Now, `package.json` file is created. Change the type of the package to a module. Add the line below in the object in `package.json`: + +```json +"type": "module" +``` + +Let's add Starknet-js as a dependency: + +```console +$ npm install starknet@next +``` +Create a file named `index.js` 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 +import { Account, RpcProvider, json, Contract } from 'starknet'; +import fs from 'fs'; +import * as dotenv from 'dotenv'; +dotenv.config(); +``` + +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 +const provider = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.public.blastapi.io' }); +const accountAddress = 'PASTE_ACCOUNT_ADDRESS_HERE'; +``` +The next step is creating an Account object that we will use to sign our transactions with our private key, so we need to import our private key. You can access your private key from your keystore with the following command using Starkli: + +```console +$ starkli signer keystore inspect-private /path/to/starkli-wallet/keystore.json --raw +``` +Create a `.env` file in your project folder, and paste your private key as shown in the following line: +```bash +PRIVATE_KEY = "PASTE_PRIVATE_KEY_HERE" +``` +**Note**: It is HIGHLY recommended to add `.gitignore`, and include your .env file there if you will be pushing your project to GitHub. Otherwise, your private key will be compromised. + +Now, import your private key from the environment variables and create your Account object. + +```js +const privateKey = process.env.PRIVATE_KEY; +// "1" is added to show that our account is deployed using Cairo 1.0. +const account = new Account(provider, accountAddress, privateKey, "1"); +``` + +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. + +Create `abi.json` file in your folder, and copy & paste the contents of `./target/simple_storage_SimpleStorage.contract_class.json` in your Scarb folder. The beginning of the content of the ABI file should look like this: + +```json +{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x436f6e7374","0x800000000000000000000000000000000000000000000002","0x1","0x16","0x2","0x53746f726555313238202d206e6f6e2075313238"} + +... +``` + +By importing the content of the ABI and the address of the contract, create a Contract object: +```js +//json parsing of the Starknet-js is used. +const compiledContractAbi = json.parse( + fs.readFileSync('./abi.json').toString('ascii') +); +const contractAddress = 'PASTE_CONTRACT_ADDRESS_HERE'; +const storageContract = new Contract(compiledContractAbi.abi, contractAddress, provider); +``` + +Now, we are good to go! By calling the `fn get(self: @ContractState) -> u128` function, we will be able to read the stored_data variable. Let's view the content of the stored_data using Starknet-js: + +```js +let getData = await storageContract.get(); +console.log('Stored_data before set():', getData.toString()); +``` + +Because our contract's ABI contains the information that such `get()` function exists in our contract, we can call that function using the Contract object. + +In order to run your code, run the command `node index.js` in your project directory. After a short amount of time, you should see a "0" as the stored data. Let's set a new number to it! + +```js +storageContract.connect(account); +``` + +Connecting your account to the contract is essential because we will use our account's private key to sign the transaction which will write to our stored_data variable. + +```js +const myCall = storageContract.populate('set', [59]); +``` +Then, construct the data which we will put in our transaction. The constructed data contains which function will be triggered with the provided argument. The function name is `set`, and the number we would like to store is 59; populate() function will create the data needed for our transaction. + +```js +const res = await storageContract.set(myCall.calldata); +``` +In order to sign and broadcast the transaction, call our function `set` from the contract object (it can be called because it's in the ABI of our contract) by providing the calldata as an argument. + +The transaction is signed and broadcasted to the network now. It takes a few seconds for the transaction to be confirmed. + +```js +await provider.waitForTransaction(res.transaction_hash); +``` +The line above will wait for the transaction to be confirmed. It is required to wait for the confirmation in order to see the new number in the stored_data variable. Let's add another code snippet to view the stored number after the transaction: + +```js +getData = await storageContract.get(); +console.log('Stored_data after set():', getData.toString()); +``` + +Let's run our code with `node index.js`, and see what happens in the terminal: + +```console +Stored_data before set(): 0 +Stored_data after set(): 59 +``` + +Congrats! You have written your contract, deployed it and interacted with it using Starknet-js! From 7f9dfe02153aa6c9a5cea52251191323197ca055 Mon Sep 17 00:00:00 2001 From: Aegean Date: Wed, 19 Jun 2024 15:26:41 +0300 Subject: [PATCH 2/4] feat: add how_to_deploy & fix tutorial content for simple_storage --- .../contracts/Scarb.toml | 9 ++-- .../simple_storage_starknetjs/index.js | 2 +- src/applications/simple_storage_starknetjs.md | 53 +++---------------- .../interacting/how_to_deploy.md | 52 ++++++++++++++++++ 4 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 src/getting-started/interacting/how_to_deploy.md diff --git a/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml b/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml index 9dc03461..f22b7903 100644 --- a/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml +++ b/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "simple_storage" -version = "0.1.0" +version.workspace = true edition = "2023_11" -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +[lib] [dependencies] -starknet=">=2.4.1" +starknet.workspace = true + +[scripts] +test.workspace = true [[target.starknet-contract]] \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/index.js b/listings/applications/simple_storage_starknetjs/index.js index 896d8561..254f7fe3 100644 --- a/listings/applications/simple_storage_starknetjs/index.js +++ b/listings/applications/simple_storage_starknetjs/index.js @@ -3,7 +3,7 @@ import fs from 'fs'; import * as dotenv from 'dotenv'; dotenv.config(); -const provider = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.public.blastapi.io' }); +const provider = new RpcProvider({ nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno' }); const accountAddress = '0x067981c7F9f55BCbdD4e0d0a9C5BBCeA77dAcB42cccbf13554A847d6353F728e'; const privateKey = process.env.PRIVATE_KEY; diff --git a/src/applications/simple_storage_starknetjs.md b/src/applications/simple_storage_starknetjs.md index 4e3c1ed2..6fe4a0b1 100644 --- a/src/applications/simple_storage_starknetjs.md +++ b/src/applications/simple_storage_starknetjs.md @@ -1,32 +1,12 @@ # Simple Storage (Starknet-js + Cairo) -In this tutorial, you will write and deploy a SimpleStorage Cairo contract on Starknet Sepolia Testnet. Then, you will learn how you can interact with the contract using Starknet-js. - -In order to fully understand and implement the content of this tutorial, here are the prerequisites: -- Basic knowledge of Cairo: you can get familiar with the Cairo language, and learn how to build a simple smart contract with Cairo [here](https://book.cairo-lang.org/). - -- Scarb for compiling Cairo code and packaging support: follow [here](https://docs.swmansion.com/scarb/download.html). - -- Starkli for the declaration and deployment of Cairo contracts: follow [here](https://book.starkli.rs/installation). +In this tutorial, you will write and deploy a SimpleStorage Cairo contract on Starknet Sepolia Testnet. Then, you will learn how you can interact with the contract using Starknet-js. Let's get started! ## 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 **viewing** the current stored number and **setting** a new number. +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. -Let's create a Scarb package in order to write and compile the SimpleStorage contract. Make sure that Scarb is installed (installation link provided in the prerequisites): +We will use the following SimpleStorage contract. In this [link](https://book.cairo-lang.org/ch13-02-anatomy-of-a-simple-contract.html) from the Cairo book, you can find very detailed explanations for each component of the contract: -```console -//move to the directory of your choice -$ scarb new simple_storage -``` - -With this command, a template for Cairo development environment is generated. Under the `src` directory, `lib.cairo` file is created. In order to start writing our contract, follow these steps: -- Under the `src` directory, create a file named `storage.cairo` where we will write our contract. -- Delete the content of the `lib.cairo` file, and add `storage.cairo` as a module for the compilation of our Cairo contract: -```rs -// Directory: src/lib.cairo -mod storage; -``` -- You can copy and paste the following Cairo code in `storage.cairo`. In this [link](https://book.cairo-lang.org/ch13-02-anatomy-of-a-simple-contract.html), you can find explanations for each component of the contract: ```rs // Directory: src/storage.cairo @@ -90,7 +70,7 @@ We will utilize Starkli in order to deploy and declare our smart contract on the For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. -### Creating a new account: +### Creating a new account You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. ```console $ mkdir ./starkli-wallet @@ -117,26 +97,9 @@ $ starkli account deploy ./starkli-wallet/account.json ``` This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need testnet Ether on Starknet Sepolia which could be obtained from [this](https://starknet-faucet.vercel.app/) testnet faucet. Once the transaction is confirmed on the faucet page, click ENTER on the command line, and the account will be deployed on Starknet Sepolia! Find your account on the [Voyager Sepolia block explorer](https://sepolia.voyager.online/). -### Declaring & Deploying your Contract: -Firstly, you need to declare your contract which will create a class on Starknet Sepolia. Note that we will use the Sierra program in `./target/simple_storage_SimpleStorage.contract_class.json`. - -**Note:** The command below is written to run in the directory of the Scarb folder. +### Declaring & Deploying the SimpleStorage Contract -```console -$ starkli declare --keystore /path/to/starkli-wallet/keystore.json --account /path/to/starkli-wallet/account.json --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json -``` - -After this command, the class hash is declared. You should be able to find the hash under the command: -```console -Class hash declared: -0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 -``` - -Now, it's time to deploy the contract. Add the clash hash after `--watch`: -```console -$ starkli deploy --keystore /Users/egeaybars123/keystore/keystore --account /Users/egeaybars123/account/account.json --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 -``` -You should now see the address of the deployed contract. Note down the address of the contract; we will use it for interacting with the contract using Starknet-js. +Please refer to **How To Deploy** in the Getting Started part. Note down the address of your contract, you will need it in Starknet-js in 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): @@ -195,9 +158,9 @@ Now, let's create a Contract object in order to interact with our contract. In o Create `abi.json` file in your folder, and copy & paste the contents of `./target/simple_storage_SimpleStorage.contract_class.json` in your Scarb folder. The beginning of the content of the ABI file should look like this: ```json -{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x436f6e7374","0x800000000000000000000000000000000000000000000002","0x1","0x16","0x2","0x53746f726555313238202d206e6f6e2075313238"} +{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x436f6e7374","0x800000000000000000000000000000000000000000000002","0x1","0x16","0x2","0x53746f726555313238202d206e6f6e2075313238",]} -... +//... ``` By importing the content of the ABI and the address of the contract, create a Contract object: diff --git a/src/getting-started/interacting/how_to_deploy.md b/src/getting-started/interacting/how_to_deploy.md new file mode 100644 index 00000000..c9601196 --- /dev/null +++ b/src/getting-started/interacting/how_to_deploy.md @@ -0,0 +1,52 @@ +## Declaring and Deploying the SimpleStorage contract +We will utilize Starkli in order to deploy and declare our smart contracts on Starknet. Make sure that Starkli is installed in your device. You can follow [this](https://medium.com/starknet-edu/starkli-the-new-starknet-cli-86ea914a2933) guide for Starkli or check out their documentation given above. + +For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. + +### Creating a new account: +You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. +```console +$ mkdir ./starkli-wallet +``` + +Create a new signer. You will be instructed to enter a password to encrypt your private key: +```console +$ starkli signer keystore new ./starkli-wallet/keystore.json +``` +After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. + +Export the keystore path in order not to call --keystore in every command: +```console +$ export STARKNET_KEYSTORE="./starkli-wallet/keystore.json" +``` +Initialize the account with the following command using OpenZeppelin's class deployed on Starknet. + +```console +$ starkli account oz init ./starkli-wallet/account.json +``` +After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: +```console +$ starkli account deploy ./starkli-wallet/account.json +``` +This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need testnet Ether on Starknet Sepolia which could be obtained from [this](https://starknet-faucet.vercel.app/) testnet faucet. Once the transaction is confirmed on the faucet page, click ENTER on the command line, and the account will be deployed on Starknet Sepolia! Find your account on the [Voyager Sepolia block explorer](https://sepolia.voyager.online/). + +### Declaring & Deploying your Contract: +Firstly, you need to declare your contract which will create a class on Starknet Sepolia. Note that we will use the Sierra program in `./target/ProjectName_ContractName.contract_class.json` in your Scarb folder. + +**Note:** The command below is written to run in the directory of the Scarb folder. + +```console +$ starkli declare --keystore /path/to/starkli-wallet/keystore.json --account /path/to/starkli-wallet/account.json --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json +``` + +After this command, the class hash for your contract is declared. You should be able to find the hash under the command: +```console +Class hash declared: +0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` + +Now, it's time to deploy the contract. Add the clash hash given above after `--watch`: +```console +$ starkli deploy --keystore /Users/egeaybars123/keystore/keystore --account /Users/egeaybars123/account/account.json --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` +You should now see the address of the deployed contract. Congratulations, you have deployed your contract on Starknet Sepolia Testnet! \ No newline at end of file From 594fb15a72757f45b57f83a7b9c436c4ef7187bc Mon Sep 17 00:00:00 2001 From: Aegean Date: Thu, 20 Jun 2024 08:42:16 +0300 Subject: [PATCH 3/4] fix: update links & add section in summary --- src/SUMMARY.md | 1 + src/applications/simple_storage_starknetjs.md | 4 ++-- src/getting-started/interacting/how_to_deploy.md | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 10436298..c5233057 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -58,6 +58,7 @@ Summary - [Constant Product AMM](./applications/constant-product-amm.md) - [TimeLock](./applications/timelock.md) - [Staking](./applications/staking.md) +- [Simple Storage with Starknet-js](./applications/simple_storage_starknetjs.md) diff --git a/src/applications/simple_storage_starknetjs.md b/src/applications/simple_storage_starknetjs.md index 6fe4a0b1..6d1a37a8 100644 --- a/src/applications/simple_storage_starknetjs.md +++ b/src/applications/simple_storage_starknetjs.md @@ -5,7 +5,7 @@ In this tutorial, you will write and deploy a SimpleStorage Cairo contract on St ## 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 this [link](https://book.cairo-lang.org/ch13-02-anatomy-of-a-simple-contract.html) from the Cairo book, you can find very detailed explanations for each component of the contract: +We will use the following SimpleStorage contract. In the [Storage Variables](../getting-started/basics/variables.md) page, you can find explanations for each component of the contract: ```rs @@ -99,7 +99,7 @@ This command wants you to fund the address (given in the instructions below the ### Declaring & Deploying the SimpleStorage Contract -Please refer to **How To Deploy** in the Getting Started part. Note down the address of your contract, you will need it in Starknet-js in the following part. +Please refer to our [How To Deploy](../getting-started/interacting/how_to_deploy.md) page. Note down the address of your contract, you will need it in Starknet-js in 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): diff --git a/src/getting-started/interacting/how_to_deploy.md b/src/getting-started/interacting/how_to_deploy.md index c9601196..e3117869 100644 --- a/src/getting-started/interacting/how_to_deploy.md +++ b/src/getting-started/interacting/how_to_deploy.md @@ -1,5 +1,5 @@ -## Declaring and Deploying the SimpleStorage contract -We will utilize Starkli in order to deploy and declare our smart contracts on Starknet. Make sure that Starkli is installed in your device. You can follow [this](https://medium.com/starknet-edu/starkli-the-new-starknet-cli-86ea914a2933) guide for Starkli or check out their documentation given above. +## Declaring and Deploying Your Contract +We will utilize Starkli in order to deploy and declare our smart contracts on Starknet. Make sure that Starkli is installed in your device. You can follow [this](https://github.com/xJonathanLEI/starkli) repository for Starkli or check out their [docs](https://book.starkli.rs/). For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. From 0f415806fbe05fb15e36d0ec9246276bf121bf05 Mon Sep 17 00:00:00 2001 From: julio4 Date: Mon, 24 Jun 2024 20:18:54 +0900 Subject: [PATCH 4/4] feat: revisions #222 --- Scarb.lock | 4 + .../{contracts => }/Scarb.lock | 0 .../{contracts => }/Scarb.toml | 0 .../simple_storage_starknetjs/index.js | 49 +++-- .../{contracts => }/src/lib.cairo | 0 .../{contracts => }/src/storage.cairo | 4 + src/SUMMARY.md | 1 + src/applications/simple_storage_starknetjs.md | 182 +++--------------- .../interacting/how_to_deploy.md | 51 +++-- 9 files changed, 112 insertions(+), 179 deletions(-) rename listings/applications/simple_storage_starknetjs/{contracts => }/Scarb.lock (100%) rename listings/applications/simple_storage_starknetjs/{contracts => }/Scarb.toml (100%) rename listings/applications/simple_storage_starknetjs/{contracts => }/src/lib.cairo (100%) rename listings/applications/simple_storage_starknetjs/{contracts => }/src/storage.cairo (91%) diff --git a/Scarb.lock b/Scarb.lock index ef3d064e..dcae197b 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -105,6 +105,10 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a8 name = "scarb" version = "0.1.0" +[[package]] +name = "simple_storage" +version = "0.1.0" + [[package]] name = "simple_vault" version = "0.1.0" diff --git a/listings/applications/simple_storage_starknetjs/contracts/Scarb.lock b/listings/applications/simple_storage_starknetjs/Scarb.lock similarity index 100% rename from listings/applications/simple_storage_starknetjs/contracts/Scarb.lock rename to listings/applications/simple_storage_starknetjs/Scarb.lock diff --git a/listings/applications/simple_storage_starknetjs/contracts/Scarb.toml b/listings/applications/simple_storage_starknetjs/Scarb.toml similarity index 100% rename from listings/applications/simple_storage_starknetjs/contracts/Scarb.toml rename to listings/applications/simple_storage_starknetjs/Scarb.toml diff --git a/listings/applications/simple_storage_starknetjs/index.js b/listings/applications/simple_storage_starknetjs/index.js index 254f7fe3..47f45c76 100644 --- a/listings/applications/simple_storage_starknetjs/index.js +++ b/listings/applications/simple_storage_starknetjs/index.js @@ -1,30 +1,49 @@ -import { Account, RpcProvider, json, Contract } from 'starknet'; -import fs from 'fs'; -import * as dotenv from 'dotenv'; +// ANCHOR: imports +import { Account, RpcProvider, json, Contract } from "starknet"; +import fs from "fs"; +import * as dotenv from "dotenv"; dotenv.config(); +// ANCHOR_END: imports -const provider = new RpcProvider({ nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno' }); -const accountAddress = '0x067981c7F9f55BCbdD4e0d0a9C5BBCeA77dAcB42cccbf13554A847d6353F728e'; -const privateKey = process.env.PRIVATE_KEY; +// ANCHOR: provider +const provider = new RpcProvider({ + nodeUrl: "https://free-rpc.nethermind.io/sepolia-juno", +}); +// ANCHOR_END: provider +const accountAddress = + "0x067981c7F9f55BCbdD4e0d0a9C5BBCeA77dAcB42cccbf13554A847d6353F728e"; +// ANCHOR: account +const privateKey = process.env.PRIVATE_KEY; +// "1" is added to show that our account is deployed using Cairo 1.0. const account = new Account(provider, accountAddress, privateKey, "1"); +// ANCHOR_END: account +const contractAddress = + "0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97"; +// ANCHOR: contract const compiledContractAbi = json.parse( - fs.readFileSync('./abi.json').toString('ascii') + fs.readFileSync("./abi.json").toString("ascii") ); +const storageContract = new Contract( + compiledContractAbi.abi, + contractAddress, + provider +); +// ANCHOR_END: contract -const contractAddress = '0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97'; -const storageContract = new Contract(compiledContractAbi.abi, contractAddress, provider); - - +// ANCHOR: get let getData = await storageContract.get(); -console.log('Stored_data before set():', getData.toString()); +console.log("Stored_data:", getData.toString()); +// ANCHOR_END: get +// ANCHOR: set storageContract.connect(account); -const myCall = storageContract.populate('set', [59]); +const myCall = storageContract.populate("set", [59]); const res = await storageContract.set(myCall.calldata); await provider.waitForTransaction(res.transaction_hash); - +// Get the stored data after setting it getData = await storageContract.get(); -console.log('Stored_data after set():', getData.toString()); +console.log("Stored_data after set():", getData.toString()); +// ANCHOR_END: set diff --git a/listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo b/listings/applications/simple_storage_starknetjs/src/lib.cairo similarity index 100% rename from listings/applications/simple_storage_starknetjs/contracts/src/lib.cairo rename to listings/applications/simple_storage_starknetjs/src/lib.cairo diff --git a/listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo b/listings/applications/simple_storage_starknetjs/src/storage.cairo similarity index 91% rename from listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo rename to listings/applications/simple_storage_starknetjs/src/storage.cairo index 72fc81e8..38349241 100644 --- a/listings/applications/simple_storage_starknetjs/contracts/src/storage.cairo +++ b/listings/applications/simple_storage_starknetjs/src/storage.cairo @@ -1,3 +1,4 @@ +// ANCHOR: contract #[starknet::interface] trait ISimpleStorage { fn set(ref self: T, x: u128); @@ -22,3 +23,6 @@ mod SimpleStorage { } } } +// ANCHOR_END: contract + + diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c5233057..d642f9be 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -23,6 +23,7 @@ Summary - [Custom types in entrypoints](./getting-started/basics/custom-types-in-entrypoints.md) - [Documentation](./getting-started/basics/documentation.md) - [Deploy and interact with contracts](./getting-started/interacting/interacting.md) + - [How to deploy](./getting-started/interacting/how_to_deploy.md) - [Contract interfaces and Traits generation](./getting-started/interacting/interfaces-traits.md) - [Calling other contracts](./getting-started/interacting/calling_other_contracts.md) - [Factory pattern](./getting-started/interacting/factory.md) diff --git a/src/applications/simple_storage_starknetjs.md b/src/applications/simple_storage_starknetjs.md index 6d1a37a8..db574e41 100644 --- a/src/applications/simple_storage_starknetjs.md +++ b/src/applications/simple_storage_starknetjs.md @@ -1,114 +1,30 @@ # Simple Storage (Starknet-js + Cairo) -In this tutorial, you will write and deploy a SimpleStorage Cairo contract on Starknet Sepolia Testnet. Then, you will learn how you can interact with the contract using Starknet-js. Let's get started! +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](../getting-started/basics/variables.md) page, you can find explanations for each component of the contract: - ```rs -// Directory: src/storage.cairo -#[starknet::interface] -trait ISimpleStorage { - fn set(ref self: T, x: u128); - fn get(self: @T) -> u128; -} - -#[starknet::contract] -mod SimpleStorage { - #[storage] - struct Storage { - stored_data: u128 - } - - #[abi(embed_v0)] - impl SimpleStorage of super::ISimpleStorage { - fn set(ref self: ContractState, x: u128) { - self.stored_data.write(x); - } - - fn get(self: @ContractState) -> u128 { - self.stored_data.read() - } - } -} -``` -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. - -Next, we need to update the dependencies of our Scarb folder. Open `Scarb.toml`, and add the following dependencies. These dependencies need to be added in order for our Cairo code to be deployed on Starknet as a smart contract. - -```toml - -[package] -name = "simple_storage" -version = "0.1.0" -edition = "2023_11" - -# Add the dependencies below! - -[dependencies] -starknet=">=2.4.1" - -[[target.starknet-contract]] -``` - -Now, we have to compile our contract in order to declare and deploy on the Starknet Sepolia Testnet. Run the following command: - -``` -$ scarb build -``` - -After you run the command, you should see a new folder called "target" in your your project folder. Your contract's Application Binary Interface (ABI) and Sierra program are found in that folder: `./target/simple_storage_SimpleStorage.contract_class.json`. We will need this file to interact with the contract using Starknet-js. - -We are now ready to declare and deploy our contract on Starknet Sepolia Testnet! - - -## Declaring and Deploying the SimpleStorage contract -We will utilize Starkli in order to deploy and declare our smart contract on the Starknet Sepolia Testnet. Make sure that Starkli is installed in your device (check prerequisites above to find the link for Starkli installation). You can follow [this](https://medium.com/starknet-edu/starkli-the-new-starknet-cli-86ea914a2933) guide for Starkli or check out their documentation given above. - -For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. - -### Creating a new account -You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. -```console -$ mkdir ./starkli-wallet -``` - -Create a new signer. You will be instructed to enter a password to encrypt your private key: -```console -$ starkli signer keystore new ./starkli-wallet/keystore.json -``` -After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. - -Export the keystore path in order not to call --keystore in every command: -```console -$ export STARKNET_KEYSTORE="./starkli-wallet/keystore.json" -``` -Initialize the account with the following command using OpenZeppelin's class deployed on Starknet. - -```console -$ starkli account oz init ./starkli-wallet/account.json -``` -After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: -```console -$ starkli account deploy ./starkli-wallet/account.json +{{#rustdoc_include ../../listings/applications/simple_storage_starknetjs/src/storage.cairo:contract}} ``` -This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need testnet Ether on Starknet Sepolia which could be obtained from [this](https://starknet-faucet.vercel.app/) testnet faucet. Once the transaction is confirmed on the faucet page, click ENTER on the command line, and the account will be deployed on Starknet Sepolia! Find your account on the [Voyager Sepolia block explorer](https://sepolia.voyager.online/). -### Declaring & Deploying the SimpleStorage 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. -Please refer to our [How To Deploy](../getting-started/interacting/how_to_deploy.md) page. Note down the address of your contract, you will need it in Starknet-js in the following part. +Only deployed instances of the contract can be interacted with. You can refer to the [How to Deploy](../getting-started/interacting/how_to_deploy.md) 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): ```console $ npm init ``` -Now, `package.json` file is created. Change the type of the package to a module. Add the line below in the object in `package.json`: +Now, `package.json` file is created. Change the type of the package to a module. ```json "type": "module" @@ -119,104 +35,66 @@ Let's add Starknet-js as a dependency: ```console $ npm install starknet@next ``` + Create a file named `index.js` 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 -import { Account, RpcProvider, json, Contract } from 'starknet'; -import fs from 'fs'; -import * as dotenv from 'dotenv'; -dotenv.config(); +{{#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 -const provider = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.public.blastapi.io' }); -const accountAddress = 'PASTE_ACCOUNT_ADDRESS_HERE'; +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:provider}} +const accountAddress = // 'PASTE_ACCOUNT_ADDRESS_HERE'; ``` -The next step is creating an Account object that we will use to sign our transactions with our private key, so we need to import our private key. You can access your private key from your keystore with the following command using Starkli: + +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: ```console $ starkli signer keystore inspect-private /path/to/starkli-wallet/keystore.json --raw ``` + Create a `.env` file in your project folder, and paste your private key as shown in the following line: ```bash -PRIVATE_KEY = "PASTE_PRIVATE_KEY_HERE" +{{#include ../../listings/applications/simple_storage_starknetjs/.env.example}} ``` -**Note**: It is HIGHLY recommended to add `.gitignore`, and include your .env file there if you will be pushing your project to GitHub. Otherwise, your private key will be compromised. -Now, import your private key from the environment variables and create your Account object. +> Warning: Using `.env` files is not recommended for production environments, please use `.env` files only for development purposes! It is HIGHLY recommended to add `.gitignore`, 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 -const privateKey = process.env.PRIVATE_KEY; -// "1" is added to show that our account is deployed using Cairo 1.0. -const account = new Account(provider, accountAddress, privateKey, "1"); +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. -Create `abi.json` file in your folder, and copy & paste the contents of `./target/simple_storage_SimpleStorage.contract_class.json` in your Scarb folder. The beginning of the content of the ABI file should look like this: +We will copy `./target/simple_storage_SimpleStorage.contract_class.json` to `abi.json` in the Scarb project folder. The beginning of the content of the ABI file should look like this: ```json -{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x436f6e7374","0x800000000000000000000000000000000000000000000002","0x1","0x16","0x2","0x53746f726555313238202d206e6f6e2075313238",]} - -//... -``` - -By importing the content of the ABI and the address of the contract, create a Contract object: -```js -//json parsing of the Starknet-js is used. -const compiledContractAbi = json.parse( - fs.readFileSync('./abi.json').toString('ascii') -); -const contractAddress = 'PASTE_CONTRACT_ADDRESS_HERE'; -const storageContract = new Contract(compiledContractAbi.abi, contractAddress, provider); +{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18", //... ``` -Now, we are good to go! By calling the `fn get(self: @ContractState) -> u128` function, we will be able to read the stored_data variable. Let's view the content of the stored_data using Starknet-js: +We can then create the Account object and the Contract object in our `index.js` file: ```js -let getData = await storageContract.get(); -console.log('Stored_data before set():', getData.toString()); +const contractAddress = 'PASTE_CONTRACT_ADDRESS_HERE'; +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:contract}} ``` -Because our contract's ABI contains the information that such `get()` function exists in our contract, we can call that function using the Contract object. - -In order to run your code, run the command `node index.js` in your project directory. After a short amount of time, you should see a "0" as the stored data. Let's set a new number to it! +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 -storageContract.connect(account); +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:get}} ``` -Connecting your account to the contract is essential because we will use our account's private key to sign the transaction which will write to our stored_data variable. - -```js -const myCall = storageContract.populate('set', [59]); -``` -Then, construct the data which we will put in our transaction. The constructed data contains which function will be triggered with the provided argument. The function name is `set`, and the number we would like to store is 59; populate() function will create the data needed for our transaction. +In order to run your code, run the command `node index.js` in your project directory. After a short amount of time, you should see a "0" as the stored data. -```js -const res = await storageContract.set(myCall.calldata); -``` -In order to sign and broadcast the transaction, call our function `set` from the contract object (it can be called because it's in the ABI of our contract) by providing the calldata as an argument. +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` 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 now. It takes a few seconds for the transaction to be confirmed. +The transaction is signed and broadcasted to the network and it can takes a few seconds for the transaction to be confirmed. ```js -await provider.waitForTransaction(res.transaction_hash); +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:set}} ``` -The line above will wait for the transaction to be confirmed. It is required to wait for the confirmation in order to see the new number in the stored_data variable. Let's add another code snippet to view the stored number after the transaction: - -```js -getData = await storageContract.get(); -console.log('Stored_data after set():', getData.toString()); -``` - -Let's run our code with `node index.js`, and see what happens in the terminal: - -```console -Stored_data before set(): 0 -Stored_data after set(): 59 -``` - -Congrats! You have written your contract, deployed it and interacted with it using Starknet-js! diff --git a/src/getting-started/interacting/how_to_deploy.md b/src/getting-started/interacting/how_to_deploy.md index e3117869..232d97a0 100644 --- a/src/getting-started/interacting/how_to_deploy.md +++ b/src/getting-started/interacting/how_to_deploy.md @@ -1,52 +1,79 @@ ## Declaring and Deploying Your Contract -We will utilize Starkli in order to deploy and declare our smart contracts on Starknet. Make sure that Starkli is installed in your device. You can follow [this](https://github.com/xJonathanLEI/starkli) repository for Starkli or check out their [docs](https://book.starkli.rs/). -For this tutorial, we will create a new account. If you already have an account, you can skip this step and move to the part where we declare our contract. +We will use Starkli to declare and deploy a smart contract on Starknet. Make sure that [Starkli](https://github.com/xJonathanLEI/starkli) is installed on your device. You can check out the [starkli book](https://book.starkli.rs/) for more information. + +We will need an account, so first we will create one. If you already have one, you can skip this step and move directly to the part where we declare our contract. ### Creating a new account: + You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. + ```console $ mkdir ./starkli-wallet ``` -Create a new signer. You will be instructed to enter a password to encrypt your private key: +Create a new signer. You will be instructed to enter a password to encrypt your private key: + ```console $ starkli signer keystore new ./starkli-wallet/keystore.json ``` -After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. -Export the keystore path in order not to call --keystore in every command: +After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. + +Export the keystore path in order not to call `--keystore` in every command: + ```console $ export STARKNET_KEYSTORE="./starkli-wallet/keystore.json" ``` + Initialize the account with the following command using OpenZeppelin's class deployed on Starknet. ```console $ starkli account oz init ./starkli-wallet/account.json ``` -After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: + +After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: + ```console $ starkli account deploy ./starkli-wallet/account.json ``` -This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need testnet Ether on Starknet Sepolia which could be obtained from [this](https://starknet-faucet.vercel.app/) testnet faucet. Once the transaction is confirmed on the faucet page, click ENTER on the command line, and the account will be deployed on Starknet Sepolia! Find your account on the [Voyager Sepolia block explorer](https://sepolia.voyager.online/). + +This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need Starknet Sepolia testnet ethers which could be obtained from [this faucet](https://starknet-faucet.vercel.app/). + +Once the transaction is confirmed on the faucet page, press ENTER, and the account will be deployed on Starknet Sepolia! Try to find your account on [Voyager Sepolia](https://sepolia.voyager.online/)! ### Declaring & Deploying your Contract: + Firstly, you need to declare your contract which will create a class on Starknet Sepolia. Note that we will use the Sierra program in `./target/ProjectName_ContractName.contract_class.json` in your Scarb folder. +> If you are deploying a contract code that is already used, you can skip the declaration step because the class hash is already declared on the network. One example of this is when you are deploying common contract instances such as ERC20 or ERC721 contracts. + **Note:** The command below is written to run in the directory of the Scarb folder. ```console -$ starkli declare --keystore /path/to/starkli-wallet/keystore.json --account /path/to/starkli-wallet/account.json --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json +$ starkli declare \ + --keystore /path/to/starkli-wallet/keystore.json \ + --account /path/to/starkli-wallet/account.json \ + --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json ``` -After this command, the class hash for your contract is declared. You should be able to find the hash under the command: +After this command, the class hash for your contract is declared. You should be able to find the hash under the command: + ```console Class hash declared: 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 ``` -Now, it's time to deploy the contract. Add the clash hash given above after `--watch`: +Check the [Voyager Class Page](https://sepolia.voyager.online/class/0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4). +Now, it's time to deploy the contract. Add the clash hash given above after `--watch`: + ```console -$ starkli deploy --keystore /Users/egeaybars123/keystore/keystore --account /Users/egeaybars123/account/account.json --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +$ starkli deploy \ + --keystore /path/to/starkli-wallet/keystore.json \ + --account /path/to/starkli-wallet/account.json \ + --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 ``` -You should now see the address of the deployed contract. Congratulations, you have deployed your contract on Starknet Sepolia Testnet! \ No newline at end of file + +You should now see the address of the deployed contract. Congratulations, you have deployed your contract on Starknet Sepolia Testnet! +Check the [Voyager Contract Page](https://sepolia.voyager.online/contract/0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97) to see your contract! +Additionally, you can also find all contract instances of a given class on the Voyager Class Page as well, for example, [this page](https://sepolia.voyager.online/class/0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4#contracts4).