メインコンテンツへスキップする

Banana SDK

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 react with name banana-sdk-demo and now let's move into to the 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: 0xCC497f137C3A5036C043EBd62c36F1b8C8A636C0

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.

For more information about building the frontend please refer to this guide.

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 = "0xCB8a3Ca479aa171aE895A5D2215A9115D261A566";

Step 6: Initializing Banana SDK instance and creating methods

// calling it in useEffect

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

const getBananaInstance = () => {
const bananaInstance = new Banana(Chains.shibuyaTestnet);
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

Learn more

To learn more about Banana Wallet head over to banana docs

Full tutorial code is available here

If your dApp already uses Rainbowkit then you can use Banana Wallet directly on Shibuya testnet. Please refer here for more information.