This commit is contained in:
jejolare 2024-07-24 19:37:25 +07:00
commit 99907ee706
8 changed files with 489 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

121
README.md Normal file
View file

@ -0,0 +1,121 @@
# ZanoWallet
`zano_web3` is a TypeScript library for interacting with the ZanoWallet extension in the browser. It allows you to connect to a user's ZanoWallet, handle authentication, and manage wallet credentials.
## Features
- **Easy Integration**: Simplifies the process of connecting to the ZanoWallet extension.
- **Local Storage Support**: Optionally store wallet credentials in local storage.
- **Customizable**: Offers hooks for various connection lifecycle events.
- **Error Handling**: Provides a structured way to handle errors during the connection process.
## Installation
To install `zano_web3`, use npm or yarn:
```bash
npm install zano_web3
```
or
```bash
yarn add zano_web3
```
## Usage
### Importing the Library
```typescript
import ZanoWallet from 'zano_web3';
```
### Creating a ZanoWallet Instance
To create a `ZanoWallet` instance, you need to provide configuration options via the `ZanoWalletParams` interface.
```typescript
const zanoWallet = new ZanoWallet({
authPath: '/api/auth', // Custom server path for authentication
useLocalStorage: true, // Store wallet credentials in local storage (default: true)
aliasRequired: false, // Whether an alias is required (optional)
customLocalStorageKey: 'myWalletKey', // Custom key for local storage (optional)
customNonce: 'customNonceValue', // Custom nonce for signing (optional)
disableServerRequest: false, // Disable server request after signing (optional)
onConnectStart: () => {
console.log('Connecting to ZanoWallet...');
},
onConnectEnd: (data) => {
console.log('Connected:', data);
},
onConnectError: (error) => {
console.error('Connection error:', error);
},
beforeConnect: async () => {
console.log('Preparing to connect...');
},
onLocalConnectEnd: (data) => {
console.log('Local connection established:', data);
}
});
```
### Connecting to ZanoWallet
To initiate the connection process, call the `connect` method:
```typescript
await zanoWallet.connect();
```
### Handling Wallet Credentials
You can manually manage wallet credentials using `getSavedWalletCredentials` and `setWalletCredentials` methods:
```typescript
const credentials = zanoWallet.getSavedWalletCredentials();
if (credentials) {
console.log('Stored credentials:', credentials);
}
zanoWallet.setWalletCredentials({
nonce: 'newNonce',
signature: 'newSignature',
publicKey: 'newPublicKey'
});
```
## Using the `useZanoWallet` Hook
The `useZanoWallet` hook is a custom React hook provided by the `zano_web3` library. It simplifies the process of interacting with the ZanoWallet extension in a React application.
This hook is designed to handle server-side rendering (SSR) limitations by ensuring that it only runs on the client-side. This means that any code using the `useZanoWallet` hook will not be executed during server-side rendering, but will work as expected once the application is running in the browser.
To use the `useZanoWallet` hook, you can import it from the `zano_web3` library and call it within a functional component:
```typescript
import { useZanoWallet } from 'zano_web3';
function MyComponent() {
const wallet = useZanoWallet({
// same params as for new ZanoWallet
});
return (
<div>Your component...</div>
);
}
```
## Requirements
- ZanoWallet browser extension must be installed.
## Contributing
If you find any issues or want to contribute, please create a pull request or submit an issue.

6
index.ts Normal file
View file

@ -0,0 +1,6 @@
import zanoWallet from "./src/zanoWallet";
import {useZanoWallet} from "./src/hooks";
export {useZanoWallet};
export default zanoWallet;

102
package-lock.json generated Normal file
View file

@ -0,0 +1,102 @@
{
"name": "zano_web3",
"version": "2.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zano_web3",
"version": "2.3.0",
"license": "ISC",
"dependencies": {
"react": "^18.3.1",
"uuid": "^10.0.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/uuid": "^10.0.0",
"typescript": "^5.5.4"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"dev": true
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
}
}
}

36
package.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "zano_web3",
"version": "2.3.0",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jejolare/zano_web3.git"
},
"keywords": [
"zano",
"web3",
"crypto",
"blockchain",
"wallet"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react": "^18.3.3",
"@types/uuid": "^10.0.0",
"typescript": "^5.5.4"
},
"dependencies": {
"react": "^18.3.1",
"uuid": "^10.0.0"
},
"bugs": {
"url": "https://github.com/jejolare/zano_web3/issues"
},
"homepage": "https://github.com/jejolare/zano_web3#readme"
}

18
src/hooks.ts Normal file
View file

@ -0,0 +1,18 @@
import ZanoWallet, { ZanoWalletParams } from './zanoWallet';
import { useEffect, useState } from 'react';
function useZanoWallet(params: ZanoWalletParams) {
const [zanoWallet, setZanoWallet] = useState<ZanoWallet | null>(null);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
setZanoWallet(new ZanoWallet(params));
}, []);
return zanoWallet;
}
export { useZanoWallet };

192
src/zanoWallet.ts Normal file
View file

@ -0,0 +1,192 @@
import { v4 as uuidv4 } from 'uuid';
export interface ZanoWalletParams {
authPath: string;
useLocalStorage?: boolean; // default: true
aliasRequired?: boolean;
customLocalStorageKey?: string;
customNonce?: string;
customServerPath?: string;
disableServerRequest?: boolean;
onConnectStart?: (...params: any) => any;
onConnectEnd?: (...params: any) => any;
onConnectError?: (...params: any) => any;
beforeConnect?: (...params: any) => any;
onLocalConnectEnd?: (...params: any) => any;
}
type GlobalWindow = Window & typeof globalThis;
interface ZanoWindowParams {
request: (str: string, params?: any, timeoutMs?: number | null) => Promise<any>;
}
type ZanoWindow = Omit<GlobalWindow, 'Infinity'> & {
zano: ZanoWindowParams
}
interface WalletCredentials {
nonce: string;
signature: string;
publicKey: string;
}
class ZanoWallet {
private DEFAULT_LOCAL_STORAGE_KEY = "wallet";
private localStorageKey: string;
private params: ZanoWalletParams;
private zanoWallet: ZanoWindowParams;
constructor(params: ZanoWalletParams) {
if (typeof window === 'undefined') {
throw new Error('ZanoWallet can only be used in the browser');
}
if (!((window as unknown) as ZanoWindow).zano) {
throw new Error('ZanoWallet requires the ZanoWallet extension to be installed');
}
this.params = params;
this.zanoWallet = ((window as unknown) as ZanoWindow).zano;
this.localStorageKey = params.customLocalStorageKey || this.DEFAULT_LOCAL_STORAGE_KEY;
}
private handleError({ message } : { message: string }) {
if (this.params.onConnectError) {
this.params.onConnectError(message);
} else {
console.error(message);
}
}
getSavedWalletCredentials() {
const savedWallet = localStorage.getItem(this.localStorageKey);
if (!savedWallet) return undefined;
try {
return JSON.parse(savedWallet) as WalletCredentials;
} catch {
return undefined;
}
}
setWalletCredentials(credentials: WalletCredentials | undefined) {
if (credentials) {
localStorage.setItem(this.localStorageKey, JSON.stringify(credentials));
} else {
localStorage.removeItem(this.localStorageKey);
}
}
async connect() {
if (this.params.beforeConnect) {
await this.params.beforeConnect();
}
if (this.params.onConnectStart) {
this.params.onConnectStart();
}
const walletData = (await ((window as unknown) as ZanoWindow).zano.request('GET_WALLET_DATA')).data;
if (!walletData?.address) {
return this.handleError({ message: 'Companion is offline' });
}
if (!walletData?.alias && this.params.aliasRequired) {
return this.handleError({ message: 'Alias not found' });
}
let nonce = "";
let signature = "";
let publicKey = "";
const existingWallet = this.params.useLocalStorage ? this.getSavedWalletCredentials() : undefined;
if (existingWallet) {
nonce = existingWallet.nonce;
signature = existingWallet.signature;
publicKey = existingWallet.publicKey;
} else {
const generatedNonce = this.params.customNonce || uuidv4();
const signResult = await this.zanoWallet.request(
'REQUEST_MESSAGE_SIGN',
{
message: generatedNonce
},
null
);
if (!signResult?.data?.result) {
return this.handleError({ message: 'Failed to sign message' });
}
nonce = generatedNonce;
signature = signResult.data.result.sig;
publicKey = signResult.data.result.pkey;
}
const serverData = {
alias: walletData.alias,
address: walletData.address,
signature,
publicKey,
message: nonce,
isSavedData: !!existingWallet
}
if (this.params.onLocalConnectEnd) {
this.params.onLocalConnectEnd(serverData);
}
if (!this.params.disableServerRequest) {
const result = await fetch( this.params.customServerPath || "/api/auth", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(
{
data: serverData
}
)
})
.then(res => res.json())
.catch((e) => ({
success: false,
error: e.message
}));
if (!result?.success || !result?.data) {
return this.handleError({ message: result.error });
}
if (!existingWallet && this.params.useLocalStorage) {
this.setWalletCredentials({
publicKey,
signature,
nonce
});
}
if (this.params.onConnectEnd) {
this.params.onConnectEnd({
...serverData,
token: result.data.token
});
}
}
}
}
export default ZanoWallet;

12
tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"lib": ["ES6", "DOM"]
},
"include": ["src/**/*", "index.ts"]
}