Skip to main content

Initialization


Installation

You can use npm or yarn to install the Liquality SDK

npm install @liquality/wallet-sdk

Setup API keys

Liquality SDK relies on API keys from different providers we use to fetch blockchain data. Before you can utilize all of the different functions Liquality SDK has to offer, you should export the sdk.setup()function which you can easily use in all of your files and components.

Example of a file where you export the setup function

import { setup } from "@liquality/wallet-sdk";

export function setupSDK() {
setup({
alchemyApiKey: "YOUR_API_KEY_HERE",
etherscanApiKey: "YOUR_API_KEY_HERE",
infuraProjectId: "-",
pocketNetworkApplicationID: "-",
quorum: 1,
slowGasPriceMultiplier: 1,
averageGasPriceMultiplier: 1.5,
fastGasPriceMultiplier: 2,
gasLimitMargin: 2000,
gelatoApiKey: "", //needed for gasless transactions
});
}

Example of how to use it in a React component

import { setupSDK } from "../setupSDK";
import { NftService } from "@liquality/wallet-sdk";
import React, { useState } from "react";

export default function Collectibles() {
setupSDK();
const [nfts, setNfts] = useState([]);
const fetchNfts = async (address: string, chainId: string) => {
const nfts: any = await NftService.getNfts(address, +chainId);
console.log(JSON.stringify(nfts));
setNfts(nfts);
}...
info

The setup function is only needed for some of the SDK functions.

Service Worker

  • The seamless login experience with social single-sign-on is dependent on the service worker which need to be registered in the project.

A service worker is a script that runs in the browser and operates separately from the DOM. It comes with several built-in network features. The Liquality SDK requires a service worker specific to the baseUrl to capture authentication redirects for the social single sign on.

For instance, if the baseUrl is set to http://localhost:3000/serviceworker, then the user will be redirected to the http://localhost:3000/serviceworker/redirect page after logging in, where the service worker will capture the results and send them back to the original window where the login was initiated.

info

The service worker setup is only needed for the create and login wallet functions of the SDK.

Service Worker Setup

To set up a service worker with React, you need to create a sw.js file in the public folder and register it in the index.html file. There is more information on how to do this in these outside resources.

The serviceworker code:

/public/serviceworker/sw.js
/* eslint-disable */
function getScope() {
return self.registration.scope;
}

self.addEventListener("message", function (event) {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});

self.addEventListener("fetch", function (event) {
try {
const url = new URL(event.request.url);
if (url.pathname.includes("redirect") && url.href.includes(getScope())) {
event.respondWith(
new Response(
new Blob(
[
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Redirect</title>
<style>
* {
box-sizing: border-box;
}

html,
body {
background: #fcfcfc;
height: 100%;
padding: 0;
margin: 0;
}

.container {
width: 100%;
height: 100%;

display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}

h1.title {
font-size: 14px;
color: #0f1222;
font-family: "Roboto", sans-serif !important;
margin: 0;
text-align: center;
}

.spinner .beat {
background-color: #0364ff;
height: 12px;
width: 12px;
margin: 24px 2px 10px;
border-radius: 100%;
-webkit-animation: beatStretchDelay 0.7s infinite linear;
animation: beatStretchDelay 0.7s infinite linear;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
display: inline-block;
}

.spinner .beat-odd {
animation-delay: 0s;
}

.spinner .beat-even {
animation-delay: 0.35s;
}

@-webkit-keyframes beatStretchDelay {
50% {
-webkit-transform: scale(0.75);
transform: scale(0.75);
-webkit-opacity: 0.2;
opacity: 0.2;
}

100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-opacity: 1;
opacity: 1;
}
}

@keyframes beatStretchDelay {
50% {
-webkit-transform: scale(0.75);
transform: scale(0.75);
-webkit-opacity: 0.2;
opacity: 0.2;
}

100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-opacity: 1;
opacity: 1;
}
}

@media (min-width: 768px) {
h1.title {
font-size: 14px;
}
p.info {
font-size: 28px;
}

.spinner .beat {
height: 12px;
width: 12px;
}
}
</style>
</head>

<body>
<div id="message" class="container">
<div class="spinner content" id="spinner">
<div class="beat beat-odd"></div>
<div class="beat beat-even"></div>
<div class="beat beat-odd"></div>
</div>
<h1 class="title content" id="closeText" style="display: none;">You can close this window now</h1>
</div>
<script
src="https://scripts.toruswallet.io/broadcastChannel_5_0_2.js"
integrity="Bu0bRAeSlh2jpBuUxKk5ivkdotaHH37cQ2XiV20EmFJmghb41D0f8xME/M1WZxFC"
></script>
<script>
function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return (
e &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" ||
// Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
// acknowledge QuotaExceededError only if there's something already stored
storage &&
storage.length !== 0
);
}
}
function showCloseText() {
var closeText = document.getElementById("closeText");
var spinner = document.getElementById("spinner");
if (closeText) {
closeText.style.display = "block";
}
if (spinner) {
spinner.style.display = "none";
}
}
var isLocalStorageAvailable = storageAvailable("localStorage");
var isSessionStorageAvailable = storageAvailable("sessionStorage");
// set theme
let theme = "light";
if (isLocalStorageAvailable) {
var torusTheme = localStorage.getItem("torus-theme");
if (torusTheme) {
theme = torusTheme.split("-")[0];
}
}

if (theme === "dark") {
document.querySelector("body").style.backgroundColor = "#24252A";
}
var bc;
var broadcastChannelOptions = {
// type: 'localstorage', // (optional) enforce a type, oneOf['native', 'idb', 'localstorage', 'node'
webWorkerSupport: false, // (optional) set this to false if you know that your channel will never be used in a WebWorker (increase performance)
};
var instanceParams = {};
var preopenInstanceId = new URL(window.location.href).searchParams.get("preopenInstanceId");
if (!preopenInstanceId) {
document.getElementById("message").style.visibility = "visible";
// in general oauth redirect
try {
var url = new URL(location.href);
var hash = url.hash.substr(1);
var hashParams = {};
if (hash) {
hashParams = hash.split("&").reduce(function (result, item) {
var parts = item.split("=");
result[parts[0]] = parts[1];
return result;
}, {});
}
var queryParams = {};
for (var key of url.searchParams.keys()) {
queryParams[key] = url.searchParams.get(key);
}
var error = "";
try {
if (Object.keys(hashParams).length > 0 && hashParams.state) {
instanceParams = JSON.parse(window.atob(decodeURIComponent(decodeURIComponent(hashParams.state)))) || {};
if (hashParams.error) error = hashParams.error;
} else if (Object.keys(queryParams).length > 0 && queryParams.state) {
instanceParams = JSON.parse(window.atob(decodeURIComponent(decodeURIComponent(queryParams.state)))) || {};
if (queryParams.error) error = queryParams.error;
}
} catch (e) {
console.error(e);
}
if (instanceParams.redirectToOpener) {
// communicate to window.opener
window.opener.postMessage(
{
channel: "redirect_channel_" + instanceParams.instanceId,
data: {
instanceParams: instanceParams,
hashParams: hashParams,
queryParams: queryParams,
},
error: error,
},
"http://localhost:3000"
);
} else {
// communicate via broadcast channel
bc = new broadcastChannelLib.BroadcastChannel("redirect_channel_" + instanceParams.instanceId, broadcastChannelOptions);
bc.addEventListener("message", function (ev) {
if (ev.success) {
bc.close();
console.log("posted", {
queryParams,
instanceParams,
hashParams,
});
} else {
window.close();
showCloseText();
}
});
bc.postMessage({
data: {
instanceParams: instanceParams,
hashParams: hashParams,
queryParams: queryParams,
},
error: error,
}).then(function () {
setTimeout(function () {
window.location.href = url.origin + location.search + location.hash;
}, 5000);
});
}
} catch (err) {
console.error(err, "service worker error in redirect");
bc && bc.close();
window.close();
showCloseText();
}
} else {
// in preopen, awaiting redirect
try {
bc = new broadcastChannelLib.BroadcastChannel("preopen_channel_" + preopenInstanceId, broadcastChannelOptions);
bc.onmessage = function (ev) {
var { preopenInstanceId: oldId, payload, message } = ev.data;
if (oldId === preopenInstanceId && payload && payload.url) {
window.location.href = payload.url;
} else if (oldId === preopenInstanceId && message === "setup_complete") {
bc.postMessage({
data: {
preopenInstanceId: preopenInstanceId,
message: "popup_loaded",
},
});
}
if (ev.error && ev.error !== "") {
console.error(ev.error);
bc.close();
}
};
} catch (err) {
console.error(err, "service worker error in preopen");
bc && bc.close();
window.close();
showCloseText();
}
}
</script>
</body>
</html>

${""}
`,
],
{ type: "text/html" }
)
)
);
}
} catch (error) {
console.log("Error caught in sw.js", error);
}
});

Production Environment

If you are hosting your application in production enviornment, you also need to create a environment variable called PUBLIC_URL at the web hosting platform you use. See below example of how it would look like in Vercel:

Webpack and Dependency Issues

If you are setting up a new web3 project from scratch, chances are likely you will have some dependency issues that need polyfilling. This is because web3.js and ether.js that work with the Liquality SDK under the hood have certain dependencies which are not present within the browser environment by webpack 5. That is why you need to polyfill certain modules in your project to override and enable their usage.


React Polyfill Solution

If you are using create-react-app version >= 5 you may run into issues building and the project complaining about certain modules. You can resolve it by polyfilling.

  • Install react-app-rewired and the required dependencies in your project
npm install --save-dev react-app-rewired crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer process

Then create a file called config-overrides.js in the root folder with the following content:

const webpack = require("webpack");

module.exports = function override(config) {
const fallback = config.resolve.fallback || {};
Object.assign(fallback, {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
assert: require.resolve("assert"),
http: require.resolve("stream-http"),
https: require.resolve("https-browserify"),
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;
};
info

If you're using craco or similar config files then similar changes need to be done to the craco.config.js file

  • Within the package.json file in the root project, you also need to change the scripts field for start, build and test. Instead of react-scripts replace it with react-app-rewired
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},

Vue Polyfill Solution

  • Install required dependencies in your project
npm install --save node-polyfill-webpack-plugin

Then add the following lines to vue.config.js

const { defineConfig } = require("@vue/cli-service");
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
configureWebpack: {
plugins: [new NodePolyfillPlugin()],
optimization: {
splitChunks: {
chunks: "all",
},
},
},
});

Angular Polyfill Solution

  • Install required dependencies in your project
npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify process buffer url
  • Then add the following paths in the tsconfig.json in compilerOptions. That way webpack can access the correct dependencies.
{
"compilerOptions": {
"paths" : {
"crypto": ["./node_modules/crypto-browserify"],
"stream": ["./node_modules/stream-browserify"],
"assert": ["./node_modules/assert"],
"http": ["./node_modules/stream-http"],
"https": ["./node_modules/https-browserify"],
"os": ["./node_modules/os-browserify"],
"process": ["./node_modules/process"],
}
}
}
  • Add the following lines to polyfills.ts file:
import { Buffer } from "buffer";

(window as any).global = window;
global.Buffer = Buffer;
global.process = {
env: { DEBUG: undefined },
version: "",
nextTick: require("next-tick"),
} as any;