Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guide for Stellar smart contract init templates #806

Merged
merged 13 commits into from
Aug 12, 2024
339 changes: 339 additions & 0 deletions docs/build/guides/conventions/soroban-contract-init-template.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
---
title: Develop Soroban initialization templates
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
hide_table_of_contents: true
description: Create frontend templates that can be used to work with Stellar smart contracts in frontend applications.
---

<head>
<title>Develop Soroban contract Initialization Template</title>
<meta charSet="utf-8" />
<meta
property="og:title"
content="Develop Soroban contract Initialization Template"
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
/>
<meta
property="og:description"
content="This guide will walk you through the process of developing soroban contract templates for working with soroban smart contracts in frontend applications. This guide will cover the process of creating or developing the templates itself as well as how to work with the template to build, deploy, and generate smart contract bindings for the template"
/>
</head>

This guide will walk you through the process of developing contract templates for working with Stellar smart contracts in frontend applications. This guide will cover the process of creating or developing the templates itself as well as how to work with the template to build, deploy, and generate smart contract bindings for the template.

## Prerequisites

Before you begin, ensure you have the following installed:

1. [Rust](https://www.rust-lang.org/) and Cargo (for compiling smart contracts)
2. [Node.js](https://nodejs.org/en) and npm (for running JavaScript deployment scripts)
3. [Stellar CLI](../../smart-contracts/getting-started/setup.mdx#install-the-stellar-cli)

## Anatomy of contract initialization (init) template

Stellar smart contracts are written in Rust, but suppose we want our frontend web application to work with these contracts: this is where templates come into the equation. Templates allow us to work with smart contracts in our frontend application. At its core a template consists of a frontend library and a script for interacting with the Stellar CLI.

Keep in mind you can use any frontend library to work with Stellar smart contracts. In this guide we'll use React to generate the template but it is worth noting that you can use whichever library you like or no library at all.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

We'll start off by installing the React library and then walking through creating the initialization script and finally wrap up with using the template to build, deploy, and generate contract bindings.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

## Creating the frontend

The following code initializes a new Vite React project and installs the necessary dependencies:
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

```bash
npm create vite@latest soroban-react-template --template react
cd soroban-react-template
npm install
npm run dev
```

## Creating the initialize script

The initialization script is the heart of the contract template. This little but highly crucial script enables us to execute Stellar CLI commands which, in turn, enables us to build, deploy, and generate smart contract bindings.

Usually, the initialization script is written in bash script but JavaScript can be used as well. This guide uses the latter but the same principles are involved when using bash scripts.
ElliotFriend marked this conversation as resolved.
Show resolved Hide resolved

We start off by creating `initialize.js` files in our frontend project directory.

```bash
touch initialize.js
```

This code creates the `initailze.js` file. Now let's open the file to begin writing the necessary code.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

### Installing the dependencies

The following dependencies will simplify the process of interacting with files and directories which the `initialize.js` file does together with other functionalities.

1. [glob](https://npmjs.com/package/glob)
2. [dotenv](https:npmjs.com/package/dotenv)

Install the above packages as dev dependencies by running the code below:
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

```bash
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
npm install dotenv glob --dev
```

### Writing the initialization code

As a general overview, the following script will perform the below tasks:

1. Create network profiles for contract project
2. Generate and fund account for interaction with the Stellar network
3. Build smart contract .wasm files
4. Deploy smart contract to the Stellar network
5. Generate smart contract Typescript bindings

#### Importing the modules

The following modules allow us to work with environment variables, execute Stellar CLI commands, search files and directories, work with paths, and create and delete files, in that order.

```javascript
import "dotenv/config";
import { execSync } from "child_process";
import { globSync } from "glob";
import path from "path";
import { writeFileSync, rmSync } from "fs";
```

### Creating the wrapper function

Instead of calling the `execSync` function and making the error handling process multiple times, we create a wrapper function to encapsulate this function.

Furthermore, when deploying the contract, the command returns the deployed contract ID, which is required when generating contract Typescript bindings. Wrapping the `execSync` function will enable to return Stellar CLI command output while handling errors in one go.
ElliotFriend marked this conversation as resolved.
Show resolved Hide resolved

```javascript
const run = (command) => {
try {
const output = execSync(command, { stdio: ["inherit", "pipe", "pipe"] });
console.log("Here comes the output\n");
return output.toString();
} catch (error) {
console.error("Error executing command: \n", error.message);
return null; // or throw error if you want to propagate it
}
};
```

### Creating network profiles

This code snippet executes a Stellar CLI command to add a network profile. It uses environment variables (process.env.NETWORK, process.env.RPC_URL, process.env.NETWORK_PASSPHRASE) to specify the network details and RPC URL.

You can clearly see the benefit of wrapping the `execSync` function as it results in a much cleaner code and short snippet while retaining same functionality.

```javascript
run(
`stellar network add ${process.env.NETWORK} --rpc-url ${process.env.RPC_URL} --network-passphrase ${process.env.NETWORK_PASSPHRASE}`,
);
```

### Creating account profiles

This code generates Stellar key pairs for an account (process.env.ACCOUNT) and funds them accordingly on a specific network (process.env.NETWORK).

```javascript
run(
`stellar keys generate ${process.env.ACCOUNT} --network ${process.env.NETWORK}`,
);
```

### Building the contract

This snippet builds Stellar smart contracts in the project directory.

```javascript
run("stellar contract build");
```

### Deploying the contract

This code deploys one or more Stellar smart contracts. It uses files found in target/wasm32-unknown-unknown/release/\*.wasm, deploys each with specified parameters, and stores contract details (alias and contractId) in contractsObject.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

The code iterates through the smart contract files found in the project directory and deploys all of them.

```javascript
const files = globSync("target/wasm32-unknown-unknown/release/*.wasm");
let alias, contractId;
console.log(files);
files.forEach((file) => {
alias = path.basename(file).split(".")[0];
console.log(`Deploying Contract ${alias}`);
contractId = run(
`stellar contract deploy --network ${process.env.NETWORK} --source ${process.env.ACCOUNT} --wasm ${file} --alias ${alias}`,
);
//save contractId and alias to contracts Object
contractsObject[alias] = {
alias,
id: contractId,
};
});
```

### Generating TypeScript bindings

This snippet generates TypeScript bindings for Stellar smart contracts deployed earlier. It iterates over files and uses `contractsObject` to obtain contract details (alias and `contractId`).
ElliotFriend marked this conversation as resolved.
Show resolved Hide resolved

```javascript
let filename, output_dir;
files.forEach((file) => {
filename = path.basename(file).split(".")[0];
output_dir = `./packages/${filename}`;
run(
`stellar contract bindings typescript --output-dir ${output_dir} --network ${process.env.NETWORK} --contract-id ${contractsObject[alias]["id"]} --wasm ${file} --overwrite`,
);
});
```

### Complete initialize.js script
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

The following snippet is the complete aggregated version of the snippets shown earlier. I've added some functionalities like deleting pre-existing contract .wasm files and creating contract.json file, which are all optional steps.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

```javascript
import "dotenv/config";
import { execSync } from "child_process";
import { globSync } from "glob";
import path from "path";
import { writeFileSync, rmSync } from "fs";

//Create An object to hold the Contract IDs and Alias
const contractsObject = {};

//Create a wrapper around execSync function to handle errors and makes its return easily available withing the script
const run = (command) => {
try {
const output = execSync(command, { stdio: ["inherit", "pipe", "pipe"] });
console.log("Here comes the output\n");
return output.toString();
} catch (error) {
console.error("Error executing command: \n", error.message);
return null; // or throw error if you want to propagate it
}
};

//Configure the network for the project
console.log("Configuring Network");
run(
`stellar network add ${process.env.NETWORK} --rpc-url ${process.env.RPC_URL} --network-passphrase ${process.env.NETWORK_PASSPHRASE}`,
);

//Generate and fund the Wallet Profile for the project
run(
`stellar keys generate ${process.env.ACCOUNT} --network ${process.env.NETWORK}`,
);

//Delete Remove all pre-existing Wasm Files
const existingFiles = globSync("./target/**.wasm");

console.log(`Existing Files ${existingFiles}`);

existingFiles.forEach((filePath, index) => {
try {
console.log(
`Deleting ${path.basename(filePath)} ${index + 1} out of ${existingFiles.length}`,
);
rmSync(filePath);
} catch (err) {
console.error(`Error while deleting Files \n ${err}`);
}
});

//Build Wasm Contract Files
run("stellar contract build");

//Deploy Contract Wasm and save the contact Ids and Aliases
const files = globSync("target/wasm32-unknown-unknown/release/*.wasm");
let alias, contractId;
console.log(files);
files.forEach((file) => {
alias = path.basename(file).split(".")[0];
console.log(`Deploying Contract ${alias}`);
contractId = run(
`stellar contract deploy --network ${process.env.NETWORK} --source ${process.env.ACCOUNT} --wasm ${file} --alias ${alias}`,
);
//save contractId and alias to contracts Object
contractsObject[alias] = {
alias,
id: contractId,
};
});

//Create a json File to store the contract name as key and contract id and contract alias contained in an object as value
const contractsJSONPath = "./contracts.json";
const contractsJSONData = JSON.stringify(contractsObject);

try {
writeFileSync(contractsJSONPath, contractsJSONData);
console.log(`JSON file has been saved to ${contractsJSONPath}`);
} catch (err) {
console.error("Error writing JSON file: ", err);
}

//Generate Contract Typescript Bindings
let filename, output_dir;
files.forEach((file) => {
filename = path.basename(file).split(".")[0];
output_dir = `./packages/${filename}`;
run(
`stellar contract bindings typescript --output-dir ${output_dir} --network ${process.env.NETWORK} --contract-id ${contractsObject[alias]["id"]} --wasm ${file} --overwrite`,
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
);
});
```

## Modifying `package.json` file

After saving the `initialize.js` script, let's modify our `package.json` file to create a new entry under scripts to run the code to work on the contract files.

Let's also modify the file to create a new workspace in the `package.json` to point to the path where the the TypeScript bindings are generated

```json
"scripts": {
"setup": "node initialize.js",
},

"workspace": ["./packages/*"],
```

## Running the script

Now that we have a complete script and a frontend, we should try to run the script. However, because the script is dependent on contract files we need to have a contract project ready, otherwise the script will fail and result in an error. To solve this issue we have to either copy the whole project contents and paste them to an existing Stellar contract project directory or upload the template file to a repo then create a new project and setting the 'f' parameter to point to the hosted repository of the template.

### Setting up the project

I have hosted a [public repository of the React template](https://github.com/CaptainPrinz/soroban-react-template). The following snippet implements the second option:
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

```bash
stellar contract init token_contract -w token -f https://github.com/CaptainPrinz/soroban-react-template.git
```

The snippet above initializes a new Soroban project, `token_contract` that uses a prebuild token contract and adds the frontend template we deployed to a remote repository.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

### Setting the environment variables

Our templates offer a great deal of customization via environment variables. These variables need to be set by the user by creating a `.env` file otherwise the process will fail as these variables will be undefined.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

### Starting the script

On your terminal run the following code:
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

```bash
npm run setup
ElliotFriend marked this conversation as resolved.
Show resolved Hide resolved
```

This runs the `initialize.js` script and first sets the network profile, account identities, builds the contract `.wasm` file, deploys the contracts, and generates the bindings.

If successful, there should be the following changes in the directory:

1. A new `.soroban` directory, which contains the network profile files, account identity files, and hosted smart contract files in json format
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
2. A `package` directory containing the generated TypeScript bindings
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
3. A `contracts.json` file

You are now finally able to import these generated bindings in your frontend application, create their clients, and use them to interact with the contracts deployed on the network.

## Customization

With the `initialize.js` script being the heart of the template file, you can go through the file and modify the paths in which files are saved in if necessary.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved

Also a great deal of customization is enabled by using environment variables, giving flexibility to change network profiles and account identity without limits.

## Conclusion

This section concludes the guide. Check out the documentation on how bindings can be used in dappa as this is a template tutorial is limited to areas concerned with templates as such the processes of connecting these generated bindings and using them in frontend appications is not covered.
hydroxylase marked this conversation as resolved.
Show resolved Hide resolved
Loading