Integration Tutorial Astar

Introduction​

In this tutorial we will show how you can integrate Banana Wallet to your JavaScript or TypeScript-based frontend. We will demonstrate how to create a new wallet or connect an existing Banana Wallet on any dApp on Astar Network.

Prerequisites​

  • Basic JavaScript/Typescript knowledge.

  • Enthusiasm to build an amazing dApp on Astar.

Getting started​

Step 1: Create a new repo with create-react-app​

Create a new react project with name banana-sdk-demo and now let's move into to it.

npx create-react-app banana-sdk-demo
cd banana-sdk-demo

Step 2: Installing banana sdk package​

Install @rize-labs/banana-wallet-sdk package with

npm install @rize-labs/banana-wallet-sdk
or
yarn add @rize-labs/banana-wallet-sdk

Step 3: Smart Contract deployment​

For this demo we will be using a very basic smart contract with only two functionalities:

  • Make a transaction to the blockchain by making a state variable change its value.

  • Fetch value of state variable.

Code for smart contract

pragma solidity ^0.8.12;

contract Sample {

    uint public stakedAmount = 0;
    
    function stake() external payable {
        stakedAmount = stakedAmount + msg.value;
    }

    function returnStake() external {
        payable(0x48701dF467Ba0efC8D8f34B2686Dc3b0A0b1cab5).transfer(stakedAmount);
    }
}

You can deploy the contract on Shibuya Testnet using remix or something of your own choice.

For this demo we had already deployed it here:

0x59DC70db4A6914Bece003Eff48C8fa4166aaC05a

Step 4: Building the front end​

We will have a simple front end with some buttons to interact with the blockchain. Although Banana SDK provides you with a smart contract wallet you don't need worry about its deployment. Everything is handled by us in the SDK so you can concentrate on building your dApp.

Step 5: Imports​

import "./App.css";
import { Banana, Chains } from '@rize-labs/banana-wallet-sdk';
import { useEffect, useState } from "react";
import { ethers } from "ethers";
import { SampleAbi } from "./SampleAbi";

Download app.css and SampleAbi.js from here App.css and SampleAbi.js

Initializing some states for demo

const [walletAddress, setWalletAddress] = useState("");
const [bananaSdkInstance, setBananSdkInstance] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [walletInstance, setWalletInstance] = useState(null);
const [output, setOutput] = useState("Welcome to Banana Demo");
const SampleContractAddress = "0x59DC70db4A6914Bece003Eff48C8fa4166aaC05a";

Step 6: Initializing Banana SDK instance and creating methods​

// calling it in useEffect

useEffect(() => {
    getBananaInstance();
}, []);

  const getBananaInstance = () => {
    const bananaInstance = new Banana(Chains.astar);
    setBananSdkInstance(bananaInstance);
  };

For simplicity in this example we are creating an SDK instance for Shibuya testnet.

Creating Wallet

const createWallet = async () => {
    // starts loading
    setIsLoading(true);
    
    // creating wallet
    const wallet = await bananaSdkInstance.createWallet();
    setWalletInstance(wallet);
    
    // getting address for wallet created
    const address = await wallet.getAddress();
    setWalletAddress(address);
    setOutput("Wallet Address: " + address);
    setIsLoading(false);
 };
 

Developers need to call the createWallet method which will inherently ask the user for a wallet name. Once username is provided, the wallet is initialized for the user, and the method returns an instance of the wallet.

Connecting wallet

const connectWallet = async () => {
    
    // checking does wallet name is cached in cookie 
    const walletName = bananaSdkInstance.getWalletName();
    
    // if cached we will use it 
    if (walletName) {
      setIsLoading(true);

     // connect wallet with cached wallet name
      const wallet = await bananaSdkInstance.connectWallet(walletName);
      setWalletInstance(wallet);

      // extracting wallet address for display purpose
      const address = await wallet.getAddress();
      setWalletAddress(address);
      setOutput("Wallet Address: " + address);
      setIsLoading(false);
    } else {
      setIsLoading(false);
      alert("You don't have wallet created!");
    }
  };
  

When the user wallet is created the wallet's public data is cached in the user's cookie. Once the getWalletName function fetches walletName from the cookie, we pass walletName into connectWallet which initializes and configures some wallet parameters internally, and returns a wallet instance.

Get ChainId

  const getChainId = async () => {
    setIsLoading(true);
    const signer = walletInstance.getSigner();
    const chainid = await signer.getChainId();
    setOutput(JSON.stringify(chainid));
    setIsLoading(false);
  };

Getting chainId is pretty straight forward. Developers should extract the signer from the wallet and use getChainId to obtain the chainId of the current network.

Get Network

  const getNetwork = async () => {
    setIsLoading(true);
    const provider = walletInstance.getProvider();
    const network = await provider.getNetwork();
    setOutput(JSON.stringify(network));
    setIsLoading(false);
  };

Extracting the network is as easy as it looks. Developers should extract the provider from the wallet and use the getNetwork method to obtain the chain info.

Make transaction

   const makeTransaction = async () => {
    setIsLoading(true);
    
    // getting signer
    const signer = walletInstance.getSigner();
    const amount = "0.00001";
    const tx = {
      gasLimit: "0x55555",
      to: SampleContractAddress,
      value: ethers.utils.parseEther(amount),
      data: new ethers.utils.Interface(SampleAbi).encodeFunctionData(
        "stake",
        []
      ),
    };

    try {
      // sending txn object via signer
      const txn = signer.sendTransaction(tx);
      setOutput(JSON.stringify(txn));
    } catch (err) {
      console.log(err);
    }
    setIsLoading(false);
  };

To initiate a transaction you will create a transaction object. Extract signer from the wallet instance and initiate a transaction by passing the transaction object to the send transaction method. PS: Make sure your wallet is funded before you initiate transactions.

Signing message

  const signMessage = async () => {
    setIsLoading(true);
    const sampleMsg = "Hello World";
    const signer = walletInstance.getSigner();
    const signMessageResponse = await signer.signBananaMessage(sampleMsg);
    setOutput(JSON.stringify(signMessageResponse));
    setIsLoading(false);
  };

Signing a message is as simple as it looks. Pass a message that needs to be signed, and the method will return an object { messageSigned: "", signature: "" }

messageSigned: message that was signed.

signature: signature for the signed message.

Step 7: Building the frontend​

JSX code for frontend

<div className="App">
  <h1>Banana SDK Demo</h1>
  {walletAddress && <p> Wallet Address: {walletAddress}</p>}
  <button className="btn" onClick={() => createWallet()}>
    Create Wallet
  </button>
  <button className="btn" onClick={() => connectWallet()}>
    Connect Wallet
  </button>
  <button className="btn" onClick={() => getChainId()}>
    ChainId
  </button>
  <button className="btn" onClick={() => getNetwork()}>
    Network
  </button>
  <button className="btn" onClick={() => makeTransaction()}>
    Make transaction
  </button>
  <button className="btn" onClick={() => signMessage()}>
    Sign message
  </button>
  <h1> Output Panel</h1>
  <div className="output-div">
    <p>{isLoading ? "Loading.." : output}</p>
  </div>
</div>

Troubleshooting​

If you are facing a webpack 5 polyfill issue please try using react-app-rewired.

npm install react-app-rewired 

npm install stream-browserify constants-browserify crypto-browserify os-browserify path-browserify process stream-browserify buffer ethers@^5.7.2

create a file name config-overrides.js using the content below.

const { ProvidePlugin }= require("webpack")

module.exports = {
  webpack: function (config, env) {
    config.module.rules = config.module.rules.map(rule => {
      if (rule.oneOf instanceof Array) {
        rule.oneOf[rule.oneOf.length - 1].exclude = [/\.(js|mjs|jsx|cjs|ts|tsx)$/, /\.html$/, /\.json$/];
      }
      return rule;
    });
    config.resolve.fallback = {
      ...config.resolve.fallback,
      stream: require.resolve("stream-browserify"),
      buffer: require.resolve("buffer"),
      crypto: require.resolve("crypto-browserify"),
      process: require.resolve("process"),
      os: require.resolve("os-browserify"),
      path: require.resolve("path-browserify"),
      constants: require.resolve("constants-browserify"), 
      fs: false
    }
    config.resolve.extensions = [...config.resolve.extensions, ".ts", ".js"]
    config.ignoreWarnings = [/Failed to parse source map/];
    config.plugins = [
      ...config.plugins,
      new ProvidePlugin({
        Buffer: ["buffer", "Buffer"],
      }),
      new ProvidePlugin({
          process: ["process"]
      }),
    ]
    return config;
  },
}

Change package.json to start using react-app-rewired instead of react-scripts.

react-scripts start -> react-app-rewired start
react-scripts build -> react-app-rewired build
react-scripts test -> react-app-rewired test

If you are still unable to resolve the issue please post your query to Banana Discord here

If you have any questions please post them Banana SDK Discord forum.

Last updated