From 1c7b612a145b2e29b4fe1fe2f38525826e743c57 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:39:43 -0400 Subject: [PATCH 1/3] feat: replace HelloWorld with GuessTheNumber - The HelloWorld contract is so simple as to be obtuse. It's confusing in its pointlessness. And it doesn't show off anything interesting about blockchain programming. - GuessTheNumber represents a most-basic starting point for a contract that can show some interesting blockchain functionality. Already, it shows: - storing data - constructor - admin-guarded methods - forthcoming contracttrait composability pattern (https://github.com/stellar/rs-soroban-sdk/pull/1522) - forthcoming admin SEP (https://github.com/AhaLabs/admin-sep) - This represents the probable starting point for the contract. In its current state, it has many purposeful ommissions/shortcomings. - `unwrap` in `guess` causes error if admin did not yet call `reset`; need to extract number-setting logic to separate function and call it in both `reset` and `__constructor` - no XLM is currently owned by contract or awarded to winning guess; need to require XLM transfer from admin in `reset` as well as nominal fee to call `guess` (which gets added to pot) - can inspect contract storage to see stored number; need to obfuscate with fun crypto tricks - These shortcomings would be addressed through an onboarding tutorial in the Stellar docs and maybe in the UI of the app itself, which so far has some very basic info about interacting with the contract --- Cargo.lock | 326 ++++++++++++++++++++++--- contracts/guess-the-number/Cargo.toml | 23 ++ contracts/guess-the-number/src/lib.rs | 35 +++ contracts/guess-the-number/src/test.rs | 64 +++++ contracts/hello_world/Cargo.toml | 22 -- contracts/hello_world/src/lib.rs | 14 -- contracts/hello_world/src/test.rs | 21 -- environments.toml | 25 +- package-lock.json | 73 +++++- src/components/GuessTheNumber.tsx | 59 +++++ src/pages/Home.tsx | 115 +++++---- 11 files changed, 615 insertions(+), 162 deletions(-) create mode 100644 contracts/guess-the-number/Cargo.toml create mode 100644 contracts/guess-the-number/src/lib.rs create mode 100644 contracts/guess-the-number/src/test.rs delete mode 100644 contracts/hello_world/Cargo.toml delete mode 100644 contracts/hello_world/src/lib.rs delete mode 100644 contracts/hello_world/src/test.rs create mode 100644 src/components/GuessTheNumber.tsx diff --git a/Cargo.lock b/Cargo.lock index b0a217b..2c71cfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,14 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "admin-sep" +version = "0.0.0" +source = "git+https://github.com/AhaLabs/admin-sep?rev=bf195f4d67cc96587974212f998680ccf9a61cd7#bf195f4d67cc96587974212f998680ccf9a61cd7" +dependencies = [ + "soroban-sdk 23.0.0-rc.2.4", +] + [[package]] name = "ahash" version = "0.8.12" @@ -228,6 +236,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "chrono" version = "0.4.41" @@ -433,6 +452,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -539,7 +564,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" name = "fungible-token-interface-example" version = "0.0.1" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-fungible", "stellar-pausable", "stellar-pausable-macros", @@ -580,6 +605,14 @@ dependencies = [ "subtle", ] +[[package]] +name = "guess-the-number" +version = "0.0.1" +dependencies = [ + "admin-sep", + "soroban-sdk 23.0.0-rc.2.4", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -601,6 +634,12 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -747,6 +786,17 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "memchr" version = "2.7.4" @@ -757,7 +807,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" name = "nft-enumerable-example" version = "0.0.1" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-default-impl-macro", "stellar-non-fungible", ] @@ -954,6 +1004,17 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "serde", + "serde_json", +] + [[package]] name = "sec1" version = "0.7.3" @@ -1016,6 +1077,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -1090,6 +1152,18 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7f1f1908c9cdb7740eb9bb7a467770ff26fa4c82e49fdb4de88027b5fb93c" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "soroban-env-common" version = "22.1.3" @@ -1102,10 +1176,29 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros", + "soroban-env-macros 22.1.3", "soroban-wasmi", "static_assertions", - "stellar-xdr", + "stellar-xdr 22.1.0", + "wasmparser", +] + +[[package]] +name = "soroban-env-common" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20fd873e412036f93916c946d497216d8a997a5f8c13a342127fe0043cde49" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 23.0.0-rc.2", + "soroban-wasmi", + "static_assertions", + "stellar-xdr 23.0.0-rc.2", "wasmparser", ] @@ -1115,7 +1208,17 @@ version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a07dda1ae5220d975979b19ad4fd56bc86ec7ec1b4b25bc1c5d403f934e592e" dependencies = [ - "soroban-env-common", + "soroban-env-common 22.1.3", + "static_assertions", +] + +[[package]] +name = "soroban-env-guest" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18662e41bcfc9914ec365c3743ac040513403f1ca2cc7bad0a7a846f179391c" +dependencies = [ + "soroban-env-common 23.0.0-rc.2", "static_assertions", ] @@ -1147,11 +1250,47 @@ dependencies = [ "sec1", "sha2", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", + "soroban-builtin-sdk-macros 22.1.3", + "soroban-env-common 22.1.3", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.9", + "wasmparser", +] + +[[package]] +name = "soroban-env-host" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a03f55efb228e2b687276a4171291c814b7ee38d2aed926d7cb5a786bf269a" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros 23.0.0-rc.2", + "soroban-env-common 23.0.0-rc.2", "soroban-wasmi", "static_assertions", - "stellar-strkey", + "stellar-strkey 0.0.13", "wasmparser", ] @@ -1166,7 +1305,22 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr", + "stellar-xdr 22.1.0", + "syn 2.0.101", +] + +[[package]] +name = "soroban-env-macros" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c716dcf8797dd70517cc79af7b788934a17ced73a156bd6511f51930d4cd7" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr 23.0.0-rc.2", "syn 2.0.101", ] @@ -1179,8 +1333,21 @@ dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 22.1.3", + "soroban-env-host 22.1.3", + "thiserror", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "23.0.0-rc.2.4" +source = "git+https://github.com/AhaLabs/rs-soroban-sdk?rev=5a99659f1483c926ff87ea45f8823b8c00dc4cbd#5a99659f1483c926ff87ea45f8823b8c00dc4cbd" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common 23.0.0-rc.2", + "soroban-env-host 23.0.0-rc.2", "thiserror", ] @@ -1199,11 +1366,33 @@ dependencies = [ "rustc_version", "serde", "serde_json", - "soroban-env-guest", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk-macros", - "stellar-strkey", + "soroban-env-guest 22.1.3", + "soroban-env-host 22.1.3", + "soroban-ledger-snapshot 22.0.8", + "soroban-sdk-macros 22.0.8", + "stellar-strkey 0.0.9", +] + +[[package]] +name = "soroban-sdk" +version = "23.0.0-rc.2.4" +source = "git+https://github.com/AhaLabs/rs-soroban-sdk?rev=5a99659f1483c926ff87ea45f8823b8c00dc4cbd#5a99659f1483c926ff87ea45f8823b8c00dc4cbd" +dependencies = [ + "arbitrary", + "bytes-lit", + "crate-git-revision", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest 23.0.0-rc.2", + "soroban-env-host 23.0.0-rc.2", + "soroban-ledger-snapshot 23.0.0-rc.2.4", + "soroban-sdk-macros 23.0.0-rc.2.4", + "stellar-strkey 0.0.13", ] [[package]] @@ -1219,10 +1408,29 @@ dependencies = [ "quote", "rustc_version", "sha2", - "soroban-env-common", - "soroban-spec", - "soroban-spec-rust", - "stellar-xdr", + "soroban-env-common 22.1.3", + "soroban-spec 22.0.8", + "soroban-spec-rust 22.0.8", + "stellar-xdr 22.1.0", + "syn 2.0.101", +] + +[[package]] +name = "soroban-sdk-macros" +version = "23.0.0-rc.2.4" +source = "git+https://github.com/AhaLabs/rs-soroban-sdk?rev=5a99659f1483c926ff87ea45f8823b8c00dc4cbd#5a99659f1483c926ff87ea45f8823b8c00dc4cbd" +dependencies = [ + "darling", + "heck", + "itertools", + "macro-string", + "proc-macro2", + "quote", + "sha2", + "soroban-env-common 23.0.0-rc.2", + "soroban-spec 23.0.0-rc.2.4", + "soroban-spec-rust 23.0.0-rc.2.4", + "stellar-xdr 23.0.0-rc.2", "syn 2.0.101", ] @@ -1233,7 +1441,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ad0867aec99770ed614fedbec7ac4591791df162ff9e548ab7ebd07cd23a9c" dependencies = [ "base64 0.13.1", - "stellar-xdr", + "stellar-xdr 22.1.0", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec" +version = "23.0.0-rc.2.4" +source = "git+https://github.com/AhaLabs/rs-soroban-sdk?rev=5a99659f1483c926ff87ea45f8823b8c00dc4cbd#5a99659f1483c926ff87ea45f8823b8c00dc4cbd" +dependencies = [ + "base64 0.22.1", + "stellar-xdr 23.0.0-rc.2", "thiserror", "wasmparser", ] @@ -1248,8 +1467,23 @@ dependencies = [ "proc-macro2", "quote", "sha2", - "soroban-spec", - "stellar-xdr", + "soroban-spec 22.0.8", + "stellar-xdr 22.1.0", + "syn 2.0.101", + "thiserror", +] + +[[package]] +name = "soroban-spec-rust" +version = "23.0.0-rc.2.4" +source = "git+https://github.com/AhaLabs/rs-soroban-sdk?rev=5a99659f1483c926ff87ea45f8823b8c00dc4cbd#5a99659f1483c926ff87ea45f8823b8c00dc4cbd" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec 23.0.0-rc.2.4", + "stellar-xdr 23.0.0-rc.2", "syn 2.0.101", "thiserror", ] @@ -1294,7 +1528,7 @@ name = "stellar-constants" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", ] [[package]] @@ -1312,17 +1546,10 @@ name = "stellar-fungible" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-constants", ] -[[package]] -name = "stellar-hello-world-contract" -version = "0.0.1" -dependencies = [ - "soroban-sdk", -] - [[package]] name = "stellar-macro-helpers" version = "0.3.0" @@ -1338,7 +1565,7 @@ name = "stellar-non-fungible" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-constants", ] @@ -1347,7 +1574,7 @@ name = "stellar-pausable" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", ] [[package]] @@ -1372,6 +1599,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "stellar-strkey" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" +dependencies = [ + "crate-git-revision", + "data-encoding", +] + [[package]] name = "stellar-xdr" version = "22.1.0" @@ -1385,7 +1622,26 @@ dependencies = [ "hex", "serde", "serde_with", - "stellar-strkey", + "stellar-strkey 0.0.9", +] + +[[package]] +name = "stellar-xdr" +version = "23.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632bf15309c2992c059fb499275a706f6afdfd68c690508d5d9bfa96c771477f" +dependencies = [ + "arbitrary", + "base64 0.22.1", + "cfg_eval", + "crate-git-revision", + "escape-bytes", + "ethnum", + "hex", + "serde", + "serde_with", + "sha2", + "stellar-strkey 0.0.13", ] [[package]] diff --git a/contracts/guess-the-number/Cargo.toml b/contracts/guess-the-number/Cargo.toml new file mode 100644 index 0000000..be62f3f --- /dev/null +++ b/contracts/guess-the-number/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "guess-the-number" +description = "Admin sets up the pot, anyone can guess to win it" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[package.metadata.stellar] +# Set contract metadata for authors, homepage, and version based on the Cargo.toml package values +cargo_inherit = true + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk = { git = "https://github.com/AhaLabs/rs-soroban-sdk", rev = "5a99659f1483c926ff87ea45f8823b8c00dc4cbd" } +admin-sep = { git = "https://github.com/AhaLabs/admin-sep", rev = "bf195f4d67cc96587974212f998680ccf9a61cd7" } + +[dev-dependencies] +soroban-sdk = { git = "https://github.com/AhaLabs/rs-soroban-sdk", rev = "5a99659f1483c926ff87ea45f8823b8c00dc4cbd", features = ["testutils"] } diff --git a/contracts/guess-the-number/src/lib.rs b/contracts/guess-the-number/src/lib.rs new file mode 100644 index 0000000..7680039 --- /dev/null +++ b/contracts/guess-the-number/src/lib.rs @@ -0,0 +1,35 @@ +#![no_std] +use admin_sep::{Administratable, Upgradable}; +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, Symbol}; + +#[contract] +pub struct GuessTheNumber; + +#[contractimpl] +impl Administratable for GuessTheNumber {} + +#[contractimpl] +impl Upgradable for GuessTheNumber {} + +const THE_NUMBER: Symbol = symbol_short!("n"); + +#[contractimpl] +impl GuessTheNumber { + pub fn __constructor(env: &Env, admin: &Address) { + Self::set_admin(env, admin); + } + + /// Update the number. Only callable by admin. + pub fn reset(env: &Env) { + Self::require_admin(env); + let new_number: u64 = env.prng().gen_range(1..=10); + env.storage().instance().set(&THE_NUMBER, &new_number); + } + + /// Guess a number between 1 and 10 + pub fn guess(env: &Env, a_number: u64) -> bool { + a_number == env.storage().instance().get::<_, u64>(&THE_NUMBER).unwrap() + } +} + +mod test; diff --git a/contracts/guess-the-number/src/test.rs b/contracts/guess-the-number/src/test.rs new file mode 100644 index 0000000..8ceb909 --- /dev/null +++ b/contracts/guess-the-number/src/test.rs @@ -0,0 +1,64 @@ +#![cfg(test)] +extern crate std; + +use super::*; +use soroban_sdk::{ + testutils::{Address as _, MockAuth, MockAuthInvoke}, + Address, Env, IntoVal, Val, Vec, +}; + +#[test] +fn only_admin_can_reset() { + let env = &Env::default(); + + let admin = Address::generate(env); + let user = Address::generate(env); + + let client = generate_client(env, &admin); + + set_caller(&client, "reset", &user, ()); + assert!(client.try_reset().is_err()); + + set_caller(&client, "reset", &admin, ()); + assert!(client.try_reset().is_ok()); +} + +#[test] +fn one_guess_in_ten_returns_true() { + let env = &Env::default(); + + let admin = Address::generate(env); + + let client = generate_client(env, &admin); + + // set the number + set_caller(&client, "reset", &admin, ()); + client.reset(); + + let trues: std::vec::Vec<_> = (1u64..=10).filter(|number| client.guess(number)).collect(); + assert_eq!(trues.len(), 1); +} + +fn generate_client<'a>(env: &Env, admin: &Address) -> GuessTheNumberClient<'a> { + let contract_id = env.register(GuessTheNumber, (admin,)); + GuessTheNumberClient::new(env, &contract_id) +} + +fn set_caller(client: &GuessTheNumberClient, method: &str, caller: &Address, args: T) +where + T: IntoVal>, +{ + // clear previous auth mocks + client.env.set_auths(&[]); + + // mock auth as passed-in address + client.env.mock_auths(&[MockAuth { + address: caller, + invoke: &MockAuthInvoke { + contract: &client.address, + fn_name: method, + args: args.into_val(&client.env), + sub_invokes: &[], + }, + }]); +} diff --git a/contracts/hello_world/Cargo.toml b/contracts/hello_world/Cargo.toml deleted file mode 100644 index 52c78d8..0000000 --- a/contracts/hello_world/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "stellar-hello-world-contract" -description = "A simple hello world contract" -edition.workspace = true -license.workspace = true -repository.workspace = true -publish = false -version.workspace = true - -[package.metadata.stellar] -# Set contract metadata for authors, homepage, and version based on the Cargo.toml package values -cargo_inherit = true - -[lib] -crate-type = ["cdylib"] -doctest = false - -[dependencies] -soroban-sdk = { workspace = true } - -[dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/hello_world/src/lib.rs b/contracts/hello_world/src/lib.rs deleted file mode 100644 index f3eb78a..0000000 --- a/contracts/hello_world/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_std] -use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec}; - -#[contract] -pub struct HelloContract; - -#[contractimpl] -impl HelloContract { - pub fn hello(env: Env, to: String) -> Vec { - vec![&env, String::from_str(&env, "Hello"), to] - } -} - -mod test; diff --git a/contracts/hello_world/src/test.rs b/contracts/hello_world/src/test.rs deleted file mode 100644 index 8b7f44b..0000000 --- a/contracts/hello_world/src/test.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![cfg(test)] - -use super::*; -use soroban_sdk::{vec, Env, String}; - -#[test] -fn test() { - let env = Env::default(); - let contract_id = env.register(HelloContract, ()); - let client = HelloContractClient::new(&env, &contract_id); - - let words = client.hello(&String::from_str(&env, "Dev")); - assert_eq!( - words, - vec![ - &env, - String::from_str(&env, "Hello"), - String::from_str(&env, "Dev"), - ] - ); -} diff --git a/environments.toml b/environments.toml index c3eddd2..60405c2 100644 --- a/environments.toml +++ b/environments.toml @@ -9,6 +9,11 @@ name = "me" # Required. Keys for this account will be saved to `./.stellar/ident default = true # Optional. Whether to use this account as the `--source` for commands that need one. [development.contracts] +fungible_token_interface_example = { client = true, constructor_args = "--owner me --initial_supply 1000000000000000000000000" } +nft_enumerable_example = { client = true, constructor_args = "--owner me" } + +# Rather than in one list, TOML allows specifying contracts in their own "sections" +[development.contracts.guess_the_number] # Generate a contract client (NPM package) for this contract. This means: # - compile (build) the contract source to Wasm # - deploy the contract to the `network` specified above @@ -20,13 +25,9 @@ default = true # Optional. Whether to use this account as the `--source` for com # You can only use `client = true` when: # - the contract source must be part of the local Cargo workspace (in the # PROJECT_ROOT/contracts folder) -# - The specified name here ("stellar_hello_world_contract") must match the +# - The specified name here ("guess_the_number") must match the # underscored-version of the `name` in the contract's Cargo.toml. # - The environment is `development` or `test` -stellar_hello_world_contract = { client = true } - -# Rather than in one list, TOML allows specifying contracts in their own "sections" -[development.contracts.fungible_token_interface_example] client = true # If your contract has a `__constructor`, specify your arguments to it here. @@ -34,13 +35,7 @@ client = true # `stellar contract deploy` # Only available in `development` and `test` environments constructor_args = """ ---owner me --initial_supply "1000000000000000000000000" -""" - -[development.contracts.nft_enumerable_example] -client = true -constructor_args = """ ---owner me +--admin me """ # Calls to the contract to make after it's deployed and initialized with @@ -59,9 +54,9 @@ constructor_args = """ # `STELLAR_ACCOUNT=other-account`. # # Only supported in `development` and `test` environments. -# after_deploy = """ -# mint --amount 2000000 --to me" -# """ +after_deploy = """ +reset +""" # Coming Soon: Specify live contracts to bind & import in this project using the given name. # During initialization, these contracts will also be "spooned" into the development network, diff --git a/package-lock.json b/package-lock.json index 857620a..1165237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9596,6 +9596,10 @@ "dev": true, "license": "MIT" }, + "node_modules/guess_the_number": { + "resolved": "packages/guess_the_number", + "link": true + }, "node_modules/h3": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz", @@ -10995,6 +10999,10 @@ } } }, + "node_modules/nft_enumerable_example": { + "resolved": "packages/nft_enumerable_example", + "link": true + }, "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -12862,10 +12870,6 @@ "atomic-sleep": "^1.0.0" } }, - "node_modules/soroban_hello_world_contract": { - "resolved": "packages/soroban_hello_world_contract", - "link": true - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -14405,7 +14409,7 @@ "packages/fungible_token_interface_example": { "version": "0.0.0", "dependencies": { - "@stellar/stellar-sdk": "13.0.0", + "@stellar/stellar-sdk": "^13.x", "buffer": "6.0.3" }, "devDependencies": { @@ -14457,9 +14461,8 @@ "is-retry-allowed": "^3.0.0" } }, - "packages/nft_enumerable_example": { + "packages/guess_the_number": { "version": "0.0.0", - "extraneous": true, "dependencies": { "@stellar/stellar-sdk": "^13.x", "buffer": "6.0.3" @@ -14468,7 +14471,46 @@ "typescript": "^5.6.2" } }, - "packages/soroban_hello_world_contract": { + "packages/guess_the_number/node_modules/@stellar/stellar-base": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.1.0.tgz", + "integrity": "sha512-90EArG+eCCEzDGj3OJNoCtwpWDwxjv+rs/RNPhvg4bulpjN/CSRj+Ys/SalRcfM4/WRC5/qAfjzmJBAuquWhkA==", + "license": "Apache-2.0", + "dependencies": { + "@stellar/js-xdr": "^3.1.2", + "base32.js": "^0.1.0", + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "sha.js": "^2.3.6", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "sodium-native": "^4.3.3" + } + }, + "packages/guess_the_number/node_modules/@stellar/stellar-sdk": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.3.0.tgz", + "integrity": "sha512-8+GHcZLp+mdin8gSjcgfb/Lb6sSMYRX6Nf/0LcSJxvjLQR0XHpjGzOiRbYb2jSXo51EnA6kAV5j+4Pzh5OUKUg==", + "license": "Apache-2.0", + "dependencies": { + "@stellar/stellar-base": "^13.1.0", + "axios": "^1.8.4", + "bignumber.js": "^9.3.0", + "eventsource": "^2.0.2", + "feaxios": "^0.0.23", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/nft_enumerable_example": { "version": "0.0.0", "dependencies": { "@stellar/stellar-sdk": "^13.x", @@ -14478,7 +14520,7 @@ "typescript": "^5.6.2" } }, - "packages/soroban_hello_world_contract/node_modules/@stellar/stellar-base": { + "packages/nft_enumerable_example/node_modules/@stellar/stellar-base": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.1.0.tgz", "integrity": "sha512-90EArG+eCCEzDGj3OJNoCtwpWDwxjv+rs/RNPhvg4bulpjN/CSRj+Ys/SalRcfM4/WRC5/qAfjzmJBAuquWhkA==", @@ -14498,7 +14540,7 @@ "sodium-native": "^4.3.3" } }, - "packages/soroban_hello_world_contract/node_modules/@stellar/stellar-sdk": { + "packages/nft_enumerable_example/node_modules/@stellar/stellar-sdk": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.3.0.tgz", "integrity": "sha512-8+GHcZLp+mdin8gSjcgfb/Lb6sSMYRX6Nf/0LcSJxvjLQR0XHpjGzOiRbYb2jSXo51EnA6kAV5j+4Pzh5OUKUg==", @@ -14517,6 +14559,17 @@ "node": ">=18.0.0" } }, + "packages/soroban_hello_world_contract": { + "version": "0.0.0", + "extraneous": true, + "dependencies": { + "@stellar/stellar-sdk": "13.0.0", + "buffer": "6.0.3" + }, + "devDependencies": { + "typescript": "^5.6.2" + } + }, "packages/soroban_token_contract": { "version": "0.0.0", "extraneous": true, diff --git a/src/components/GuessTheNumber.tsx b/src/components/GuessTheNumber.tsx new file mode 100644 index 0000000..c84795a --- /dev/null +++ b/src/components/GuessTheNumber.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; +import { Code, Input, Text } from "@stellar/design-system"; +import { useWallet } from "../hooks/useWallet"; +import game from "../contracts/guess_the_number"; + +export const GuessTheNumber = () => { + const [guessedIt, setGuessedIt] = useState(); + const [theGuess, setTheGuess] = useState(); + const { address } = useWallet(); + + if (!address) { + return ( + + Connect wallet to play the guessing game + + ); + } + + const submitGuess = async () => { + if (!theGuess) return; + const { result } = await game.guess({ a_number: BigInt(theGuess) }); + setGuessedIt(result); + }; + + return ( +
{ + e.preventDefault(); + void submitGuess(); + }} + > + {guessedIt ? ( + <> + + You got it! + + + Set a new number by calling reset from the + CLI as the admin. + + + ) : ( + { + setGuessedIt(undefined); + setTheGuess(Number(e.target.value)); + }} + /> + )} + +   {/* Not sure the SDS way to add consistent spacing at the end */} + +
+ ); +}; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index e0547a6..21f4b47 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,61 +1,86 @@ import React from "react"; -import { Code, Layout } from "@stellar/design-system"; +import { Code, Layout, Text } from "@stellar/design-system"; +import { GuessTheNumber } from "../components/GuessTheNumber"; const Home: React.FC = () => ( -

Welcome to your app!

-

+ + Welcome to your app! + + This is a basic template to get your dapp started with the Stellar Design System and Stellar contracts. You can customize it further by adding your own contracts, components, and styles. -

-

Developing your contracts

-

- Your contracts are located in the contracts/ directory, and you can - modify them to suit your needs. -

-

- As you update them, the stellar scaffold watch{" "} - command will automatically recompile them and update the dapp with the - latest changes. -

-

Interacting with contracts from the frontend

- Scaffold stellar automatically builds your contract packages, and you can - import them in your frontend code like this: -
-        {`import stellar_hello_world_contract from "./contracts/stellar_hello_world_contract.ts";`}
-      
-

And then you can call the contract methods like this:

+ + + + Develop your contracts + + + Take a look in the contracts/ directory. Compare + that to what you see in the npm run dev output + (which itself is running stellar scaffold watch). + Also compare it to what you see when you click{" "} + </> Debugger up in the top right. See? + + + As you update your contracts,{" "} + stellar scaffold watch command will automatically + recompile them and update the dapp with the latest changes. + + + + Interact with contracts from the frontend + + + Scaffold stellar automatically builds, deploys, and generates frontend + packages (sometimes called "TypeScript bindings") for each of your + contracts. You can adjust how it does this in the{" "} + environments.toml file. Import these frontend + packages like this: +
-        {`const statusMessage = await stellar_hello_world_contract.hello({"to": "world"});`}
+        import game from "./contracts/guess_the_number";
       
-

- By doing this, you can use the contract methods in your components. If - your contract emits events, check out the{" "} - useSubscription hook in the hooks/ folder to - listen to them. -

-

Interacting with wallets

-

- This project is already integrated with Stellar Wallet Kit, and the - {` useWallet `} hook is available for you to use in your components. You - can use it to connect to get connected account information. -

-

Deploying your app

-

+ + If your contract emits events, check out the{" "} + useSubscription hook in the{" "} + hooks/ folder to listen to them. + + + As an example, here's the GuessTheNumber{" "} + component. Make changes to the contract and the component and see how + things change! + + + <GuessTheNumber /> + + + + Interact with wallets + + + This project is already integrated with Stellar Wallet Kit, and the{" "} + useWallet hook is available for you to use in + your components. You can use it to connect to get connected account + information. + + + Deploy your app + + To deploy your contracts, use the{" "} - stellar contract deploy command ( - - docs - - ) to deploy to the appropriate Stellar network. -

-

+ stellar registry publish and + stellar registry deploy commands ( use{" "} + stellar registry --help for more info ) to deploy + to the appropriate Stellar network. + + Build your frontend application code with{" "} - npm run build and deploying the output in the + npm run build and deploy the output in the dist/ directory. -

+
); From 4e75d15813f8d02cc836277b9be4966e136622dd Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:43:58 -0400 Subject: [PATCH 2/3] feat: add `guesser` arg --- contracts/guess-the-number/src/lib.rs | 3 ++- contracts/guess-the-number/src/test.rs | 8 +++++++- src/components/GuessTheNumber.tsx | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/contracts/guess-the-number/src/lib.rs b/contracts/guess-the-number/src/lib.rs index 7680039..52794f0 100644 --- a/contracts/guess-the-number/src/lib.rs +++ b/contracts/guess-the-number/src/lib.rs @@ -27,7 +27,8 @@ impl GuessTheNumber { } /// Guess a number between 1 and 10 - pub fn guess(env: &Env, a_number: u64) -> bool { + pub fn guess(env: &Env, guesser: Address, a_number: u64) -> bool { + guesser.require_auth(); a_number == env.storage().instance().get::<_, u64>(&THE_NUMBER).unwrap() } } diff --git a/contracts/guess-the-number/src/test.rs b/contracts/guess-the-number/src/test.rs index 8ceb909..4f3b2b2 100644 --- a/contracts/guess-the-number/src/test.rs +++ b/contracts/guess-the-number/src/test.rs @@ -28,6 +28,7 @@ fn one_guess_in_ten_returns_true() { let env = &Env::default(); let admin = Address::generate(env); + let user = Address::generate(env); let client = generate_client(env, &admin); @@ -35,7 +36,12 @@ fn one_guess_in_ten_returns_true() { set_caller(&client, "reset", &admin, ()); client.reset(); - let trues: std::vec::Vec<_> = (1u64..=10).filter(|number| client.guess(number)).collect(); + let trues: std::vec::Vec<_> = (1u64..=10) + .filter(|number| { + set_caller(&client, "guess", &user, (&user, number)); + client.guess(&user, number) + }) + .collect(); assert_eq!(trues.len(), 1); } diff --git a/src/components/GuessTheNumber.tsx b/src/components/GuessTheNumber.tsx index c84795a..1d87371 100644 --- a/src/components/GuessTheNumber.tsx +++ b/src/components/GuessTheNumber.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { Code, Input, Text } from "@stellar/design-system"; import { useWallet } from "../hooks/useWallet"; import game from "../contracts/guess_the_number"; +import { wallet } from "../util/wallet"; export const GuessTheNumber = () => { const [guessedIt, setGuessedIt] = useState(); @@ -18,7 +19,13 @@ export const GuessTheNumber = () => { const submitGuess = async () => { if (!theGuess) return; - const { result } = await game.guess({ a_number: BigInt(theGuess) }); + const tx = await game.guess({ + guesser: address, + a_number: BigInt(theGuess), + }); + const { result } = await tx.signAndSend({ + signTransaction: wallet.signTransaction.bind(game), + }); setGuessedIt(result); }; From 92b68db2ad02d08100732e21cf9f82cc38782961 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:45:16 -0400 Subject: [PATCH 3/3] Update src/components/GuessTheNumber.tsx --- src/components/GuessTheNumber.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/GuessTheNumber.tsx b/src/components/GuessTheNumber.tsx index 1d87371..d2663b2 100644 --- a/src/components/GuessTheNumber.tsx +++ b/src/components/GuessTheNumber.tsx @@ -19,10 +19,11 @@ export const GuessTheNumber = () => { const submitGuess = async () => { if (!theGuess) return; - const tx = await game.guess({ - guesser: address, - a_number: BigInt(theGuess), - }); + const tx = await game.guess( + { guesser: address, a_number: BigInt(theGuess) }, + // @ts-expect-error stellar-sdk-js has bad typings: publickey is, in fact, allowed + { publicKey: address } + ); const { result } = await tx.signAndSend({ signTransaction: wallet.signTransaction.bind(game), });