Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into pre-mainnet-restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
ElliotFriend committed Dec 4, 2023
2 parents 31fc76f + 36cc17f commit b05b2d9
Show file tree
Hide file tree
Showing 22 changed files with 1,332 additions and 567 deletions.
557 changes: 557 additions & 0 deletions dapps/dapp-challenges/challenge-3-oracle.mdx

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions docs/getting-started/create-an-app.mdx

Large diffs are not rendered by default.

221 changes: 16 additions & 205 deletions docs/getting-started/deploy-incrementor.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,221 +53,32 @@ You can check that it saved the contract ID correctly:
cat .soroban/incrementor-id
```

## Call the incrementor contract from the frontend

While `hello` is a simple view-only/read method, `increment` changes on-chain state. This means that someone needs to sign the transaction. So we'll need to add transaction-signing capabilities to the frontend.

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.

Right now, the wallet that best supports Soroban is [Freighter](../resources/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.

Go to Settings (the gear icon) → Preferences and toggle the switch to Enable Experimental Mode. Then go back to its home screen and select "Test Net" from the top-right dropdown. Finally, if it shows the message that your Stellar address is not funded, go ahead and click the "Fund with Friendbot" button.

Now you're all set up to use Freighter as a user, and you can use it in your app.

### Use Freighter

We're going to add a "Connect" button to the page that opens Freighter and prompts the user to give your web page permission to use Freighter. Once they grant this permission, the "Connect" button will be replaced with a message saying, "Signed in as G...".

First, double check that [@stellar/freighter-api](https://www.npmjs.com/package/@stellar/freighter-api) is in package.json as a dependency.

Take a look at the `ConnectFreighter` component in the `src/components` directory:

```html title="src/components/ConnectFreighter.astro"
<div id="freighter-wrap" class="wrap" aria-live="polite">
<small class="ifSignedIn ellipsis" data-freighter-ellipsis></small>
<div class="ifSignedOut">
<small style="display: none" data-freighter-error></small>
<button
style="display: none"
data-freighter-connect
aria-controls="freighter-wrap"
>
Connect
</button>
</div>
</div>

<style>
.wrap {
text-align: center;
}
.ellipsis {
line-height: 2.7rem;
margin: auto;
max-width: 12rem;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
white-space: nowrap;
}
</style>

<script>
import * as wallet from "../wallet";
const button = document.querySelector("[data-freighter-connect]") as HTMLInputElement;
const ellipsis = document.querySelector("[data-freighter-ellipsis]") as HTMLElement;
const errorMsg = document.querySelector("[data-freighter-error]") as HTMLElement;
button.addEventListener("click", async () => {
button.disabled = true;
await wallet.enable();
button.disabled = false;
});
wallet.onChange(async ({ account }) => {
if (wallet.isSignedIn()) {
ellipsis.innerHTML = `Signed in as ${account}`;
ellipsis.title = account;
} else if (wallet.isSignedInToWrongNetwork()) {
errorMsg.innerHTML = "Please switch to Testnet";
errorMsg.style.display = "block";
button.style.display = "none";
} else if (wallet.freighterIsLocked()) {
errorMsg.innerHTML = "Please unlock Freighter";
errorMsg.style.display = "block";
button.style.display = "none";
} else {
errorMsg.style.display = "none";
button.style.display = "block";
}
});
</script>
```

Some of this may look surprising. `<style>` and `<script>` tags in the middle of the page? Uncreative class names like `wrap`? `import` statements in a `<script>`? Top-level `await`? What's going on here?

Astro automatically scopes the styles within a component to that component, so there's no reason for us to come up with a clever names for our classes.

And all the `script` declarations get bundled together and included intelligently in the page. Even if you use the same component multiple times, the script will only be included once. And yes, you can use top-level `await`.

You can read more about this in [Astro's page about client-side scripts](https://docs.astro.build/en/guides/client-side-scripts/).

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.

Now we can import the component in the frontmatter of `pages/index.astro`:

```diff title="src/pages/index.astro"
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
+import ConnectFreighter from '../components/ConnectFreighter.astro';
import { Contract, networks } from 'hello-soroban-client';
...
```

And add it right below the `<h1>`:

```diff title="src/pages/index.astro"
<h1><span class="text-gradient">{greeting.join(' ')}</span></h1>
+<ConnectFreighter />
```

If you're no longer running your dev server, go ahead and restart it:
Now you can interact with it over RPC like you did with the Hello World contract:

```bash
npm run dev
```

Then open the page and click the "Connect" button. You should see Freighter pop up and ask you to sign in. Once you do, the button should be replaced with a message saying, "Signed in as G...".

Now you're ready to sign the call to `increment`!

### Call `increment`

We're going to generate a client for the incrementor contract with a similar command to the one we used before. Let's add a script for generating a client for the incrementor to `package.json`, and call them both from `postinstall` using a double ampersand ([&&](https://stackoverflow.com/a/25669618/249801)):

```diff title="package.json"
"bindings:hello": "soroban contract bindings typescript --network testnet --contract-id $(cat .soroban/hello-id) --output-dir node_modules/hello-soroban-client",
+"bindings:incrementor": "soroban contract bindings typescript --network testnet --contract-id $(cat .soroban/incrementor-id) --output-dir node_modules/incrementor-client",
-"postinstall": "if [ ! -d .soroban ]; then npm run setup; fi && npm run bindings:hello"
+"postinstall": "if [ ! -d .soroban ]; then npm run setup; fi && npm run bindings:hello && npm run bindings:incrementor"
soroban contract invoke \
--id $(cat .soroban/incrementor-id) \
--source alice \
--network testnet \
-- \
increment
```

Let's reinstall the dependencies. Remember that `npm i` will also run our `postinstall` script, which re-generates our contract client libraries.
You should see the following output:

```bash
npm i
1
```

Now let's create a new Astro component called `Counter.astro`, where we'll import and use our newly generated `incrementor-client`. Create a new file at `src/components/Counter.astro` with the following contents:

```html title="src/components/Counter.astro"
<strong>Incrementor</strong><br />
Current value: <strong id="current-value" aria-live="polite">???</strong><br />
<br />
<button data-increment aria-controls="current-value">Increment</button>

<script>
import { Contract, networks } from "incrementor-client";
const incrementor = new Contract({
...networks.testnet,
rpcUrl: "https://soroban-testnet.stellar.org", // from https://soroban.stellar.org/docs/reference/rpc-list#sdf-futurenet-and-testnet-only
});
const button = document.querySelector("[data-increment]") as HTMLInputElement;
const currentValue = document.querySelector("#current-value") as HTMLElement;
button.addEventListener("click", async () => {
button.disabled = true;
button.classList.add("loading");
currentValue.innerHTML =
currentValue.innerHTML +
'<span class="visually-hidden"> – updating…</span>';
const newValue = await incrementor.increment();
// Only use `innerHTML` with contract values you trust!
// Blindly using values from an untrusted contract opens your users to script injection attacks!
currentValue.innerHTML = newValue;
button.disabled = false;
button.classList.remove("loading");
});
</script>
```

This should be somewhat familiar by now. We have a `script` that, thanks to Astro's build system, can `import` modules directly. We use `document.querySelector` to find the elements defined above. And we add a `click` handler to the button, which calls `increment` and updates the value on the page. It also sets the button to `disabled` and adds a `loading` class while the call is in progress to prevent the user from clicking it again and visually communicate that something is happening. For people using screen readers, the loading state is communicated with the [visually-hidden](https://www.a11yproject.com/posts/how-to-hide-content/) span, which will be announced to them thanks to the `aria` tags we saw before.

Also, notice that you don't need to manually specify Freighter as the wallet in the instantiation of `contract` or the call to `increment`. This may change in the future, but while Freighter is the only game in town, these generated libraries automatically use it. If you want to override this behavior, you can pass a `wallet` option; check the latest `Wallet` interface in [the template source](https://github.com/stellar/soroban-tools/blob/main/cmd/crates/soroban-spec-typescript/src/project_template/src/method-options.ts) for details.

Now let's use this component. In `pages/index.astro`, first import it:

```diff title="src/pages/index.astro"
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import ConnectFreighter from '../components/ConnectFreighter.astro';
+import Counter from '../components/Counter.astro';
import { Contract, networks } from "hello-soroban-client";
...
```

Then use it. Let's replace the contents of the `instructions` paragraph with it:

```diff title="src/pages/index.astro"
<p class="instructions">
- To get started, open the directory <code>src/pages</code> in your project.<br />
- <strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
+ <Counter />
</p>
```
Run it a few more times to watch the count change.

Check the page; if you're still running your dev server, it should have already updated. Click the "Increment" button; you should see a Freighter confirmation. Confirm, and... the value updates! 🎉
## Run your own network/node

## Summary
Sometimes you'll need to run your own node:

In this section:
- Production apps! Stellar maintains public test RPC nodes for Testnet and Futurenet, but not for Mainnet. Instead, you will need to run your own node, and point your app at that. If you want to use a software-as-a-service platform for this, [various providers](../resources/rpc-list.mdx) are available.
- When you need a network that differs from the version deployed to Testnet.

- we learned that deployment is a two-step process, and deployed our new incrementor contract to Testnet
- installed a Freighter wallet to our browser
- and set our project up to be able to connect to the Freighter browser extension through a "Connect" button
- created a script in package.json to generate the incrementor contract client
- created a Counter component to call the incrementor contract from the browser
The Soroban team maintains Docker containers that makes this as straightforward as possible. See the [RPC](../resources/rpc.mdx) reference for details.

In the next section, we'll take a closer look at the scripts in package.json, look at some common issues and how to troubleshoot, and wrap up the tutorial.
Up next, we'll use the deployed contracts to build a simple web app.
Loading

0 comments on commit b05b2d9

Please sign in to comment.