Skip to content

Commit

Permalink
feat: Custom wallet (#106)
Browse files Browse the repository at this point in the history
* Initial implementation of custom client

* Allowed build from Windows

* Fixed problem with instance metadata not being displayed in the interface

* Fixing problem with unreliable clipboard copy

* Hiding clients that failed to initialise from the UI

* Added VS code auto-formatting

* Pull request feedback
Also made the clipboard code more robust
  • Loading branch information
robdmoore authored Oct 5, 2023
1 parent b0cc46f commit f1bb0f1
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ dist

# Stores VSCode versions used for testing VSCode extensions
.vscode-test
.vscode
#.vscode

# yarn v2
.yarn/cache
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ dist

# Stores VSCode versions used for testing VSCode extensions
.vscode-test
.vscode
#.vscode

# yarn v2
.yarn/cache
Expand Down
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"description": "React hooks for using Algorand compatible wallets in dApps.",
"scripts": {
"dev": "yarn storybook",
"build": "rm -rf dist && rollup -c",
"build": "rimraf dist && rollup -c",
"test": "jest",
"lint": "eslint '**/*.{js,ts,tsx}'",
"format": "prettier --check '**/*.{js,ts,tsx}'",
Expand Down Expand Up @@ -60,6 +60,7 @@
"algosdk": "^2.1.0",
"babel-jest": "^29.1.2",
"babel-loader": "^9.0.0",
"buffer": "^6.0.3",
"commitizen": "4.3.0",
"css-loader": "^6.5.1",
"cz-conventional-changelog": "3.3.0",
Expand All @@ -80,6 +81,7 @@
"react-dom": "^18.2.0",
"release-it": "^16.1.0",
"require-from-string": "^2.0.2",
"rimraf": "^5.0.1",
"rollup": "^3.3.0",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-dts": "^5.0.0",
Expand Down
104 changes: 104 additions & 0 deletions src/clients/custom/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import Algod, { getAlgodClient } from '../../algod'
import BaseClient from '../base'
import { DEFAULT_NETWORK, PROVIDER_ID } from '../../constants'
import { debugLog } from '../../utils/debugLog'
import { ICON } from './constants'
import type _algosdk from 'algosdk'
import type { Network } from '../../types/node'
import type { InitParams } from '../../types/providers'
import type { Metadata, Wallet } from '../../types/wallet'
import type { CustomProvider, CustomWalletClientConstructor } from './types'

class CustomWalletClient extends BaseClient {
network: Network
providerProxy: CustomProvider

static metadata: Metadata = {
id: PROVIDER_ID.CUSTOM,
icon: ICON,
isWalletConnect: false,
name: 'Custom'
}

constructor({
providerProxy,
metadata,
algosdk,
algodClient,
network
}: CustomWalletClientConstructor) {
super(metadata, algosdk, algodClient)

this.providerProxy = providerProxy
this.network = network
}

static async init({
clientOptions,
algodOptions,
algosdkStatic,
network = DEFAULT_NETWORK
}: InitParams<PROVIDER_ID.CUSTOM>): Promise<BaseClient | null> {
try {
debugLog(`${PROVIDER_ID.CUSTOM.toUpperCase()} initializing...`)

if (!clientOptions) {
throw new Error(`Attempt to create custom wallet with no provider specified.`)
}

const algosdk = algosdkStatic || (await Algod.init(algodOptions)).algosdk
const algodClient = getAlgodClient(algosdk, algodOptions)

try {
return new CustomWalletClient({
providerProxy: clientOptions.getProvider({
algod: algodClient,
algosdkStatic: algosdk,
network
}),
metadata: {
...CustomWalletClient.metadata,
name: clientOptions.name,
icon: clientOptions.icon ?? CustomWalletClient.metadata.icon
},
algodClient,
algosdk,
network
})
} finally {
debugLog(`${PROVIDER_ID.CUSTOM.toUpperCase()} initialized`, '✅')
}
} catch (e) {
console.error('Error initializing...', e)
return null
}
}

async connect(): Promise<Wallet> {
return await this.providerProxy.connect(this.metadata)
}

async disconnect() {
await this.providerProxy.disconnect()
}

async reconnect(): Promise<Wallet | null> {
return await this.providerProxy.reconnect(this.metadata)
}

async signTransactions(
connectedAccounts: string[],
txnGroups: Uint8Array[] | Uint8Array[][],
indexesToSign?: number[],
returnGroup = true
) {
return await this.providerProxy.signTransactions(
connectedAccounts,
txnGroups,
indexesToSign,
returnGroup
)
}
}

export default CustomWalletClient
2 changes: 2 additions & 0 deletions src/clients/custom/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ICON =
"data:image/svg+xml,%3Csvg fill='%23000000' width='800px' height='800px' viewBox='0 0 24 24' id='wallet' data-name='Flat Line' xmlns='http://www.w3.org/2000/svg' class='icon flat-line'%3E%3Cpath id='secondary' d='M16,12h5V8H5A2,2,0,0,1,3,6V19a1,1,0,0,0,1,1H20a1,1,0,0,0,1-1V16H16a1,1,0,0,1-1-1V13A1,1,0,0,1,16,12Z' style='fill: rgb(44, 169, 188); stroke-width: 2;'%3E%3C/path%3E%3Cpath id='primary' d='M19,4H5A2,2,0,0,0,3,6H3A2,2,0,0,0,5,8H21' style='fill: none; stroke: rgb(0, 0, 0); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;'%3E%3C/path%3E%3Cpath id='primary-2' data-name='primary' d='M21,8V19a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V6A2,2,0,0,0,5,8Zm0,4H16a1,1,0,0,0-1,1v2a1,1,0,0,0,1,1h5Z' style='fill: none; stroke: rgb(0, 0, 0); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;'%3E%3C/path%3E%3C/svg%3E"
3 changes: 3 additions & 0 deletions src/clients/custom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import custom from './client'

export default custom
33 changes: 33 additions & 0 deletions src/clients/custom/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type algosdk from 'algosdk'
import type { Network } from '../../types/node'
import type { Metadata, Wallet } from '../../types/wallet'

export type CustomOptions = {
name: string
icon?: string
getProvider: (params: {
network?: Network
algod?: algosdk.Algodv2
algosdkStatic?: typeof algosdk
}) => CustomProvider
}

export type CustomProvider = {
connect(metadata: Metadata): Promise<Wallet>
disconnect(): Promise<void>
reconnect(metadata: Metadata): Promise<Wallet | null>
signTransactions(
connectedAccounts: string[],
txnGroups: Uint8Array[] | Uint8Array[][],
indexesToSign?: number[],
returnGroup?: boolean
): Promise<Uint8Array[]>
}

export type CustomWalletClientConstructor = {
providerProxy: CustomProvider
metadata: Metadata
algosdk: typeof algosdk
algodClient: algosdk.Algodv2
network: Network
}
18 changes: 16 additions & 2 deletions src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@ import algosigner from './algosigner'
import walletconnect from './walletconnect2'
import kmd from './kmd'
import mnemonic from './mnemonic'
import { CustomProvider } from './custom/types'
import custom from './custom'

export { pera, myalgo, defly, exodus, algosigner, walletconnect, kmd, mnemonic }
export {
pera,
myalgo,
defly,
exodus,
algosigner,
walletconnect,
kmd,
mnemonic,
custom,
CustomProvider
}

export default {
[pera.metadata.id]: pera,
Expand All @@ -19,5 +32,6 @@ export default {
[algosigner.metadata.id]: algosigner,
[walletconnect.metadata.id]: walletconnect,
[kmd.metadata.id]: kmd,
[mnemonic.metadata.id]: mnemonic
[mnemonic.metadata.id]: mnemonic,
[custom.metadata.id]: custom
}
19 changes: 17 additions & 2 deletions src/components/Example/Example.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react'
import { DeflyWalletConnect } from '@blockshake/defly-connect'
import { DaffiWalletConnect } from '@daffiwallet/connect'
import { WalletProvider, PROVIDER_ID, useInitializeProviders } from '../../index'
import { WalletProvider, PROVIDER_ID, useInitializeProviders, Network } from '../../index'
import Account from './Account'
import Connect from './Connect'
import Transact from './Transact'
import algosdk from 'algosdk'
import { ManualGoalSigningAlertPromptProvider } from './TestManualProvider'

const getDynamicPeraWalletConnect = async () => {
const PeraWalletConnect = (await import('@perawallet/connect')).PeraWalletConnect
Expand All @@ -17,7 +19,20 @@ export default function ConnectWallet() {
{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
{ id: PROVIDER_ID.PERA, getDynamicClient: getDynamicPeraWalletConnect },
{ id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
{ id: PROVIDER_ID.EXODUS }
{ id: PROVIDER_ID.EXODUS },
{
id: PROVIDER_ID.CUSTOM,
clientOptions: {
name: 'Manual',
getProvider: (params: {
network?: Network
algod?: algosdk.Algodv2
algosdkStatic?: typeof algosdk
}) => {
return new ManualGoalSigningAlertPromptProvider(params.algosdkStatic ?? algosdk)
}
}
}
]
})

Expand Down
Loading

0 comments on commit f1bb0f1

Please sign in to comment.