Skip to content

Tutorial on developing a calculator web app

This is an example of how to use SingularityNET Web JS SDK to using a calculator service.

Description

It is assumed that there is an application provider (developer), who pays for all the transactions and service calls.

Development

Install package

Before the beginning you need to install snet-sdk-web package:

sh
npm install snet-sdk-web

And additional packages

sh
npm install google-protobuf @improbable-eng/grpc-web snet-sdk-web web3
npm install --save-dev react-app-rewired buffer process os-browserify url

Make sure that you have Node.js version >= 18 and react-scripts version >= 5.0.1

Create config-overrides.js in the root of your project with the content:

javascript
const webpack = require('webpack');

module.exports = function override(config) {
    const fallback = config.resolve.fallback || {};
    Object.assign(fallback, {
        os: require.resolve('os-browserify'),
        url: require.resolve('url'),
    });
    config.resolve.fallback = fallback;
    config.plugins = (config.plugins || []).concat([
        new webpack.ProvidePlugin({
            process: 'process/browser',
            Buffer: ['buffer', 'Buffer'],
        }),
    ]);
    config.ignoreWarnings = [/Failed to parse source map/];
    config.module.rules.push({
        test: /\.(js|mjs|jsx)$/,
        enforce: 'pre',
        loader: require.resolve('source-map-loader'),
        resolve: {
            fullySpecified: false,
        },
    });
    return config;
};

Update your package.json scripts to use react-app-rewired instead of react-scripts.

json
{
    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test",
        "eject": "react-scripts eject"
    }
}

Configuration

Firstly, we need to configure sdk and service client. So we create a config dict and then an sdk instance with that config.

js
// sdkConfig.js file
import SnetSDK from 'snet-sdk-web';

const config = {
    web3Provider: window.ethereum,
    networkId: '11155111',
    defaultGasPrice: '4700000',
    defaultGasLimit: '210000',
};

const initSDK = async () => {
    try {
        const web3Provider = window.ethereum;
        sdk = new SnetSDK(config);
        await sdk.setupAccount();
        return sdk;
    } catch (error) {
        throw error;
    }
};

Calculator service is deployed on the sepolia network. To create a client of this service we need to pass orgId, serviceId and not required groupName, paymentChannelManagementStrategy, options

js
const sdk = await initSdk();
const calculatorClient = sdk.createServiceСlient(
    '26072b8b6a0e448180f8c0e702ab6d2f',
    'Exampleservice'
);

User input

Secondly, we need to write a functionality for user input. On input: two numbers and action with it. The values entered by the user must be validated before sending.

js
import React, { useState } from 'react';

const ACTIONS = [
    { value: 'add', title: '+' },
    { value: 'sub', title: '-' },
    { value: 'mul', title: '*' },
    { value: 'div', title: ':' },
];

const ExampleService = () => {
    const [firstValue, setFirstValue] = useState('');
    const [secondValue, setSecondValue] = useState('');
    const [selectedAction, setSelectedAction] = useState(ACTIONS[0]);
    const [response, setResponse] = useState();

    return (
        <div className='service-container'>
            <input onChange={(event) => setFirstValue(event.target.value)} />
            <select onChange={(event) => setSelectedAction(event.target.value)}>
                {ACTIONS.map((action) => (
                    <option value={action.value} key={action.value}>
                        {action.title}
                    </option>
                ))}
            </select>
            <input onChange={(event) => setSecondValue(event.target.value)} />
            <button>Submit</button>
            <div>{response}</div>
        </div>
    );
};

Interaction with the service

For submitting results you need generate stubs files from proto file of your service.

Calculator service proto:

proto
syntax = "proto3";

package example_service;

message Numbers {
    float a = 1;
    float b = 2;
}

message Result {
    float value = 1;
}

service Calculator {
    rpc add(Numbers) returns (Result) {}
    rpc sub(Numbers) returns (Result) {}
    rpc mul(Numbers) returns (Result) {}
    rpc div(Numbers) returns (Result) {}
}

Now we need to generate stub files.

sh
protoc -I="." --js_out=import_style=commonjs,binary:. <file_name>.proto
protoc-gen-grpc -I="." --grpc_out=grpc_js:. <file_name>.proto

For more details please check the Generating stubs for JS tutorial

After that comands you will get four files. Transfer the js files to your project. In our example, this is the stubs folder. Now we can use the functions of our service

js
import { Calculator } from "./stubs/example_pb_service";
import { initSDK } from "./sdkConfig";
<...>
const SUCCESS_CODE = 0;

const ExampleService = () => {
    <...>
    const getServiceClient = async () => {
        const sdk = await initSDK();
        const client = await sdk.createServiceClient(
            serviceConfig.orgID,
            serviceConfig.serviceID
        );
        return client;
    };

    const submitAction = async () => {
        const methodDescriptor = Calculator[selectedAction];
        const request = new methodDescriptor.requestType();

        request.setA(firstValue);
        request.setB(secondValue);

        const props = {
        request,
        preventCloseServiceOnEnd: false,
        onEnd: onActionEnd,
        };

        const serviceClient = await getServiceClient();
        serviceClient.unary(methodDescriptor, props);
    }

    const onActionEnd = (response) => {
        const { message, status, statusMessage } = response;
        if (status !== SUCCESS_CODE) {
            throw new Error(statusMessage);
        }

        setResponse(message.getValue())
    }
    <...>
}

Functions for input and output (in our case setA, setB, getValue) you can find in the example_pb.js file.

The entire application code can be viewed at the link to GitHub.