From 42faf1dfa77e2f02bf5a95a9cf8e285fac585994 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:14:21 -0400 Subject: [PATCH] Use `soroban contract init` command in getting started tutorial (#730) * Updates to setup.mdx * Rework hello-world to use the soroban contract init command * Rework deploy-to-testnet.mdx to use init command * Rework storing-data.mdx * Rework storing-data.mdx * Rework deploy-increment-contract.mdx * Rework create-an-app * Udpate deploy increment file name * Apply suggestions from code review Co-authored-by: Elliot Voris * Update re-installing to enable opt to not include version Co-authored-by: Elliot Voris * Update docs/getting-started/setup.mdx Co-authored-by: Elliot Voris * Address PR feedback * Simplfy generate and fund command calls * Update .env.example code block to match the fe template * Small updates toe create-an-app.mdx --------- Co-authored-by: Elliot Voris --- docs/getting-started/create-an-app.mdx | 331 +++++++----------- ...ntor.mdx => deploy-increment-contract.mdx} | 22 +- docs/getting-started/deploy-to-testnet.mdx | 22 +- docs/getting-started/hello-world.mdx | 212 +++++------ docs/getting-started/setup.mdx | 39 +-- docs/getting-started/storing-data.mdx | 227 ++++-------- docs/guides/testing/basic-contract-tests.mdx | 2 +- 7 files changed, 331 insertions(+), 524 deletions(-) rename docs/getting-started/{deploy-incrementor.mdx => deploy-increment-contract.mdx} (65%) diff --git a/docs/getting-started/create-an-app.mdx b/docs/getting-started/create-an-app.mdx index 9c809ec3..bd6fa9f9 100644 --- a/docs/getting-started/create-an-app.mdx +++ b/docs/getting-started/create-an-app.mdx @@ -16,84 +16,146 @@ Let's get started. You're going to need [Node.js](https://nodejs.org/en/download/package-manager/) v18.14.1 or greater. If you haven't yet, install it now. -Then we want to initialize the current directory, `soroban-tutorial`, as an Astro project, but Astro doesn't like that. It wants to create a new directory. So let's go ahead and do that, then move all the contents of the new directory into its parent directory. From the original `soroban-tutorial` directory, run: +We want to initialize our current project as an Astro project. To do this, we can again turn to the `soroban contract init` command, which has a `--frontend-template` flag that allows us to pass the url of a frontend template repository. As we learned in [Storing Data](storing-data.mdx#adding-the-increment-contract), `soroban contract init` will not overwrite existing files, and is safe to use to add to an existing project. -```bash -npm create astro@4.0.1 soroban-tutorial -- --template basics --install --no-git --typescript strictest -``` - -This will take a little while, as the `--install` option automatically installs the dependencies. Once it's done, let's move the contents of the new nested folder into the project root. Other project organization strategies are possible, but we find that it causes no problems to have the Node-specific web app stuff live right alongside the Rust-specific smart contract stuff and that keeping it all in the root just makes things simpler. +From our `getting-started-tutorial` directory, run the following command to add the Astro template files. -```bash -mv soroban-tutorial/* . -mv soroban-tutorial/.vscode . -cat soroban-tutorial/.gitignore >> .gitignore -rm soroban-tutorial/.gitignore -rmdir soroban-tutorial +```sh +soroban contract init ./ \ + --frontend-template https://github.com/AhaLabs/soroban-astro-template ``` -This is a good time to commit your changes so that later on, you can clearly see the differences between what came from Astro's `basics` template and the Soroban-specific stuff we're going to add. +This will add the following to your project, which we'll go over in more detail below. ```bash -git add . -git commit -m "Initialize Astro project" +├── CONTRIBUTING.md +├── initialize.js +├── package-lock.json +├── package.json +├── packages +├── public +│   └── favicon.svg +├── src +│   ├── components +│   │   └── Card.astro +│   ├── env.d.ts +│   ├── layouts +│   │   └── Layout.astro +│   └── pages +│   └── index.astro +└── tsconfig.json ``` ## Generate an NPM package for the Hello World contract -Before we even open the new frontend files, let's generate an NPM package for the Hello World contract. This is our suggested way to interact with contracts from frontends. These generated libraries work with any JavaScript project (not a specific UI like React), and make it easy to work with some of the trickiest bits of Soroban, like encoding [XDR](https://soroban.stellar.org/docs/fundamentals-and-concepts/fully-typed-contracts). +Before we open the new frontend files, let's generate an NPM package for the Hello World contract. This is our suggested way to interact with contracts from frontends. These generated libraries work with any JavaScript project (not a specific UI like React), and make it easy to work with some of the trickiest bits of Soroban, like encoding [XDR](https://soroban.stellar.org/docs/fundamentals-and-concepts/fully-typed-contracts). This is going to use the CLI command `soroban contract bindings typescript`: ```bash soroban contract bindings typescript \ --network testnet \ - --contract-id $(cat .soroban/hello-id) \ - --output-dir node_modules/hello-soroban-client + --contract-id $(cat .soroban/contract-ids/soroban_hello_world_contract.txt) \ + --output-dir packages/hello_world ``` -We attempt to keep the code in these generated libraries readable, so go ahead and look around. Open up the new `hello-soroban-client` directory in your editor. If you've built or contributed to Node projects, it will all look familiar. You'll see a `package.json` file, a `src` directory, a `tsconfig.json`, and even a README. The README is a great place to start. Go ahead and give it a read. +This project is set up as an NPM Workspace, and so the `hello_world` client library was generated in the `packages` directory at `packages/hello_world`. -As it says, when using local libraries, we've had the [most success](https://github.com/stellar/soroban-example-dapp/pull/117#discussion_r1232873560) when generating them directly into the `node_modules` folder, and leaving them out of the `dependencies` section. Yes, this is surprising, but it works the best. +We attempt to keep the code in these generated libraries readable, so go ahead and look around. Open up the new `packages/hello_world` directory in your editor. If you've built or contributed to Node projects, it will all look familiar. You'll see a `package.json` file, a `src` directory, a `tsconfig.json`, and even a README. -Let's update the `package.json` in your `soroban-tutorial` project with a `postinstall` script to make sure the generated library stays up-to-date: +## Generate an NPM package for the Increment contract -```diff title="package.json" - "scripts": { - ... -- "astro": "astro" -+ "astro": "astro", -+ "postinstall": "soroban contract bindings typescript --network testnet --contract-id $(cat .soroban/hello-id) --output-dir node_modules/hello-soroban-client" - } +Though we can run `soroban contract bindings typescript` for each of our contracts individually, the [soroban-astro-template](https://github.com/AhaLabs/soroban-astro-template) that we used as our template includes a very handy `initialize.js` script that will handle this for all of the contracts in our `contracts` directory. + +In addition to generating the NPM packages, `initialize.js` will also: + +- Generate and fund our Stellar account +- Build all of the contracts in the `contracts` dir +- Deploy our contracts +- Create handy contract clients for each contract + +We have already taken care of the first three bullet points in earlier steps of this tutorial, so those tasks will be noops when we run `initialize.js`. + +### Configure initialize.js + +We need to make sure that `initialize.js` has all of the environment variables it needs before we do anything else. Copy the `.env.example` file over to `.env`. The environment variables set in `.env` are used by the `initialize.js` script. + +```bash +cp .env.example .env ``` -### Call the contract from the frontend +Let's take a look at the contents of the `.env` file: -Now let's open up `src/pages/index.astro` and add some code to call the contract. We'll start by importing the generated library: +``` +# Prefix with "PUBLIC_" to make available in Astro frontend files +PUBLIC_SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017" +PUBLIC_SOROBAN_RPC_URL="http://localhost:8000/soroban/rpc" -```diff title="src/pages/index.astro" - --- - import Layout from '../layouts/Layout.astro'; - import Card from '../components/Card.astro'; -+import { Contract, networks } from 'hello-soroban-client'; -+ -+const greeter = new Contract({ -+ ...networks.testnet, -+ rpcUrl: 'https://soroban-testnet.stellar.org', // from https://soroban.stellar.org/docs/reference/rpc#public-rpc-providers -+}); -+ -+const { result } = await greeter.hello({ to: 'Soroban' }); - --- +SOROBAN_ACCOUNT="me" +SOROBAN_NETWORK="standalone" + +# env vars that begin with PUBLIC_ will be available to the client +PUBLIC_SOROBAN_RPC_URL=$SOROBAN_RPC_URL +``` + +This `.env` file defaults to connecting to a locally running network, but we want to configure our project to communicate with Testnet, since that is where we deployed our contracts. To do that, let's update the `.env` file to look like this: + +```diff +# Prefix with "PUBLIC_" to make available in Astro frontend files +-PUBLIC_SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017" ++PUBLIC_SOROBAN_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" +-PUBLIC_SOROBAN_RPC_URL="http://localhost:8000/soroban/rpc" ++PUBLIC_SOROBAN_RPC_URL="https://soroban-testnet.stellar.org:443" + +-SOROBAN_ACCOUNT="me" ++SOROBAN_ACCOUNT="alice" +-SOROBAN_NETWORK="standalone" ++SOROBAN_NETWORK="testnet" +``` + +:::info + +This `.env` file is used in the `initialize.js` script. When using the CLI, we can still use the network configuration we set up in the [Setup](./setup.mdx) step, or by passing the `--rpc-url` and `--network-passphrase` flags. + +::: + +### Run `initialize.js` + +First let's install the Javascript dependencies: + +```bash +npm install ``` -Then find the `

` tag and replace its contents with the greeting: +And then let's run `initialize.js`: + +```bash +npm run init +``` + +As mentioned above, this script attempts to build and deploy our contracts, which we have already done. The script is smart enough to check if a step has already been taken care of, and is a no-op in that case, so it is safe to run more than once. + +### Call the contract from the frontend + +Now let's open up `src/pages/index.astro` and take a look at how the frontend code integrates with the NPM package we created for our contracts. + +Here we can see that we're importing our generated `helloWorld` client from `../contracts/soroban_hello_world_contract`. We're then invoking the `hello` method and adding the result to the page. + +```ts title="src/pages/index.astro" +--- +import Layout from "../layouts/Layout.astro"; +import Card from "../components/Card.astro"; +import helloWorld from "../contracts/soroban_hello_world_contract"; +const { result } = await helloWorld.hello({ to: "you" }); +const greeting = result.join(" "); +--- + + ... -```diff title="src/pages/index.astro" --

Welcome to Astro

-+

{result.join(' ')}

+

{greeting}

``` -Now start the dev server: +Let's see it in action! Start the dev server: ```bash npm run dev @@ -103,7 +165,13 @@ And open [http://localhost:4321](http://localhost:4321) in your browser. You sho You can try updating the `{ to: 'Soroban' }` argument. When you save the file, the page will automatically update. -### What's happening here? +:::info + +When you start up the dev server with `npm run dev`, you will see similar output in your terminal as when you ran `npm run init`. This is because the `dev` script in package.json is set up to run `npm run init` and `astro dev`, so that you can ensure that your deployed contract and your generated NPM pacakage are always in sync. If you want to just start the dev server without the initialize.js script, you can run `npm run astro dev`. + +::: + + ## Call the incrementor contract from the frontend @@ -130,6 +198,8 @@ While `hello` is a simple view-only/read method, `increment` changes on-chain st The way signing works in a browser is with a _wallet_. Wallets can be web apps, browser extensions, standalone apps, or even separate hardware devices. +### Install Freighter Extension + Right now, the wallet that best supports Soroban is [Freighter](../reference/freighter.mdx). It is available as a Firefox Add-on, as well as extensions for Chrome and Brave. Go ahead and [install it now](https://freighter.app). Once it's installed, open it up by clicking the extension icon. If this is your first time using Freighter, you will need to create a new wallet. Go through the prompts to create a password and save your recovery passphrase. @@ -148,12 +218,6 @@ First, add [@stellar/freighter-api](https://www.npmjs.com/package/@stellar/freig npm install @stellar/freighter-api ``` -Then we need to work around a bug in NPM—adding a new dependency with `npm install [new dependency]` doesn't run the `postinstall` hook, the way that `npm install` does. But it does run the cleanup logic that removes "incorrect" folders like `node_modules/hello-soroban-client`. So you either need to run `npm i` (a shortcut for `npm install`), or `postinstall` directly: - -```bash -npm run postinstall -``` - Now let's add a new component to the `src/components` directory called `ConnectFreighter.astro` with the following contents: ```html title="src/components/ConnectFreighter.astro" @@ -220,55 +284,21 @@ You can read more about this in [Astro's page about client-side scripts](https:/ The code itself here is pretty self-explanatory. We import a few methods from `@stellar/freighter-api` to check if the user is logged in. If they already are, then `isAllowed` returns `true`. If it's been more than a day since they've used the Freighter extension, then the `publicKey` will be blank, so we tell them to unlock Freighter and refresh the page. If `isAllowed` and the `publicKey` both look good, we replace the contents of the `div` with the signed-in message, replacing the button. Otherwise, we add a click handler to the button to prompt the user to connect Freighter with `setAllowed`. Once they do, we again replace the contents of the `div` with the signed-in message. The [`aria` stuff](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) ensures that screen readers will read the new contents when they're updated. -Before we add this to our index page, let's make the buttons look better. Open `layouts/Layout.astro` and look for the `