Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ Cardano base provider #1

Merged
merged 10 commits into from
Feb 16, 2024
9 changes: 9 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Task title

> Description

✨ Features:
- [ ]

🔨 Improvements:
- [ ]
8 changes: 8 additions & 0 deletions src/ada/available-wallets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// TODO: Add klever mobile app source name
export const availableWallets = ['nami'] as const

export type AvailableWallet = typeof availableWallets[number]

export enum CardanoWallet {
NAMI = 'nami',
}
30 changes: 30 additions & 0 deletions src/ada/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NotInjectedError } from '@/errors';
import { NoAvailableAccountsError } from '@/errors/no-accounts-available-error';
import { NoProviderAvailableError } from '@/errors/no-provider-available-error';
import { web3Window } from '@/types';
import { availableWallets } from './available-wallets';
import type { CardanoUsedAddress } from './types';

export async function connect(wallet?: string): Promise<CardanoUsedAddress[]> {
if (!web3Window.cardano)
throw new NotInjectedError()

let injectedWallet = wallet
if (typeof injectedWallet === 'undefined') {
for (const availableWallet of availableWallets) {
if (web3Window.cardano[availableWallet])
injectedWallet = availableWallet
}
}

if (typeof injectedWallet === 'undefined')
throw new NoProviderAvailableError()

await web3Window.cardano[injectedWallet].enable()

const usedAddresses: CardanoUsedAddress[] = await web3Window.cardano.getUsedAddresses()
if (usedAddresses.length === 0)
saviojks marked this conversation as resolved.
Show resolved Hide resolved
throw new NoAvailableAccountsError()

return usedAddresses
}
35 changes: 35 additions & 0 deletions src/ada/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ProviderEntity } from '@/entities/provider-entity';
import type { Account, Address, Balance } from '@/types';
import { CardanoWallet } from './available-wallets';
import { connect } from './connect';
import type { CardanoProviderProps } from './types';

export class CardanoProvider implements ProviderEntity {
wallet?: CardanoWallet

constructor({ wallet }: CardanoProviderProps) {
if (wallet)
this.wallet = CardanoWallet[wallet.toUpperCase() as keyof typeof CardanoWallet]
}

async connect(): Promise<Account[]> {
const accounts = await connect(this.wallet)

return accounts.map((account, index) => ({
saviojks marked this conversation as resolved.
Show resolved Hide resolved
name: `Account #${index}`,
address: account,
}))
}

async getBalance(address: Address): Promise<Balance> {
throw new Error('Not yet implemented.')
}

async signMessage(address: Address, message: string): Promise<string> {
throw new Error('Not yet implemented.')
}

signatureVerify(message: string, signature: string, address: string): boolean {
throw new Error('Not yet implemented.')
}
}
7 changes: 7 additions & 0 deletions src/ada/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { AvailableWallet } from './available-wallets';

export interface CardanoProviderProps {
wallet?: AvailableWallet
}

export type CardanoUsedAddress = string
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './ada/index'
export * from './networks'
export * from './substrate/dot/index'
export * from './substrate/ksm/index'
Expand Down
6 changes: 6 additions & 0 deletions src/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ export const Networks = {
name: 'Kusama',
decimals: 12,
},
ada: {
id: 3,
name: 'Cardano',
decimals: 15, // TODO: Check this value
},
}

export enum Network {
POLKADOT = 'dot',
KUSAMA = 'ksm',
CARDANO = 'ada',
}

export type NetworkKey = keyof typeof Networks
Expand Down
5 changes: 3 additions & 2 deletions src/substrate/connect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NotInjectedError } from '@/errors';
import { NoAvailableAccountsError } from '@/errors/no-accounts-available-error';
import { NoProviderAvailableError } from '@/errors/no-provider-available-error';
import { web3Window } from '@/types';
import * as extension from '@polkadot/extension-dapp';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { connect } from './connect';
Expand All @@ -17,11 +18,11 @@ describe('Connect wallet use case', () => {
vi.resetAllMocks()

appName = 'Web3 Hub';
(window as any).injectedWeb3 = {}
web3Window.injectedWeb3 = {}
})

it('should be able to throw error when window dont have web3 object', async () => {
delete (window as any).injectedWeb3
delete web3Window.injectedWeb3

await expect(connect(appName)).rejects.toThrow(NotInjectedError)
})
Expand Down
3 changes: 2 additions & 1 deletion src/substrate/connect.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NotInjectedError } from '@/errors';
import { NoAvailableAccountsError } from '@/errors/no-accounts-available-error';
import { NoProviderAvailableError } from '@/errors/no-provider-available-error';
import { web3Window } from '@/types';
import { web3Accounts, web3Enable } from '@polkadot/extension-dapp';
import type { SubstrateAccountWithMeta } from './types';

export async function connect(appName: string): Promise<SubstrateAccountWithMeta[]> {
if (!(window as any).injectedWeb3)
if (!web3Window.injectedWeb3)
throw new NotInjectedError()

const isInjected = await web3Enable(appName)
Expand Down
11 changes: 10 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { CardanoProviderProps } from './ada/types'
import type { NetworkKey } from './networks'
import type { SubstrateProviderProps } from './substrate/types'

export type Address = string
Expand All @@ -12,4 +14,11 @@ export interface Balance {
frozen: number | string
}

export type ProviderBuilderProps = SubstrateProviderProps
export type ProviderBuilderProps<T extends NetworkKey> = T extends 'dot' | 'ksm' ? SubstrateProviderProps : T extends 'ada' ? CardanoProviderProps : never;

export type Web3Window = {
injectedWeb3: any
cardano: any
} & Window & typeof globalThis

export const web3Window = (window as Web3Window)
24 changes: 14 additions & 10 deletions src/web3-provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { CardanoProvider } from './ada';
import type { CardanoProviderProps } from './ada/types';
import type { ProviderEntity } from './entities/provider-entity';
import { InvalidNetworkError } from './errors/invalid-network-error';
import type { NetworkData, NetworkKey } from './networks';
import { getNetworkKeyById, isValidNetwork } from './networks';
import { PolkadotProvider } from './substrate/dot';
import { KusamaProvider } from './substrate/ksm';
import type { SubstrateProviderProps } from './substrate/types';
import type { ProviderBuilderProps } from './types';

export class Web3Provider {
Expand All @@ -21,16 +24,17 @@ export class Web3Provider {
this.network = network
}

private getAvailableProviders() {
return {
dot: PolkadotProvider,
ksm: KusamaProvider,
// TODO: Improve props type to avoid generic for vanilla javascript users
build<T extends NetworkKey>(props: ProviderBuilderProps<T>): ProviderEntity {
switch (this.network) {
case 'dot':
return new PolkadotProvider(props as SubstrateProviderProps)
case 'ksm':
return new KusamaProvider(props as SubstrateProviderProps)
case 'ada':
return new CardanoProvider(props as CardanoProviderProps)
default:
throw new InvalidNetworkError()
}
}

build(props: ProviderBuilderProps): ProviderEntity {
const Provider = this.getAvailableProviders()[this.network]

return new Provider(props)
}
}
Loading