diff --git a/src/icp/connect.spec.ts b/src/icp/connect.spec.ts new file mode 100644 index 0000000..8046a19 --- /dev/null +++ b/src/icp/connect.spec.ts @@ -0,0 +1,46 @@ +import { NoAvailableAccountsError, NoProviderAvailableError } from '@/errors'; +import { UserRejectedError } from '@/errors/user-rejected-error'; +import { describe, expect, it, vi } from 'vitest'; +import { web3Window } from '..'; +import { connect } from './connect'; + +vi.mock('@/types', () => ({ + web3Window: { + ic: { + requestConnect: vi.fn(), + plug: { + principalId: 'wil-123', + }, + }, + }, +})) + +const principalId = 'wil-123'; + +describe('Connect case', () => { + it('should successfully connects and retrieves an account', async () => { + web3Window.ic.requestConnect.mockResolvedValue(true) + const account = await connect() + + expect(account).toEqual(principalId) + }) + + it('should throws error when no provider is available', async () => { + web3Window.ic.requestConnect.mockResolvedValue(false) + + await expect(connect()).rejects.toThrow(NoProviderAvailableError) + }) + + it('should throws error when no accounts are available', async () => { + web3Window.ic.requestConnect.mockResolvedValue(true) + web3Window.ic.plug.principalId = undefined; + + await expect(connect()).rejects.toThrow(NoAvailableAccountsError) + }) + + it('should throws error when the user rejects the connection', async () => { + web3Window.ic.requestConnect.mockRejectedValue(new Error('The agent creation was rejected')) + + await expect(connect()).rejects.toThrow(UserRejectedError) + }) +}) diff --git a/src/icp/connect.ts b/src/icp/connect.ts new file mode 100644 index 0000000..aaad15a --- /dev/null +++ b/src/icp/connect.ts @@ -0,0 +1,24 @@ +import { NoAvailableAccountsError, NoProviderAvailableError } from '@/errors'; +import { UserRejectedError } from '@/errors/user-rejected-error'; +import { web3Window } from '@/types'; +import type { PrincipalAddress } from './types'; + +export async function connect(): Promise { + try { + const hasConnected = await web3Window.ic.requestConnect(); + if (!hasConnected) + throw new NoProviderAvailableError() + + const account: PrincipalAddress | undefined = web3Window.ic.plug.principalId; + if (!account || account.length === 0) + throw new NoAvailableAccountsError() + + return account + } + catch (error) { + if ((error as Error).message.toLowerCase().includes('the agent creation was rejected')) + throw new UserRejectedError() + + throw error + } +} diff --git a/src/icp/index.ts b/src/icp/index.ts new file mode 100644 index 0000000..d3f66f6 --- /dev/null +++ b/src/icp/index.ts @@ -0,0 +1,27 @@ +import type { ProviderEntity } from '@/entities/provider-entity'; +import type { Account, Balance } from '..'; +import { connect } from './connect'; + +export class InternetComputerProvider implements ProviderEntity { + async connect(): Promise { + const principalAddress = await connect() + + return [{ name: 'Principal Account', address: principalAddress }] + } + + getBalance(address: string): Promise { + throw new Error('Method not implemented.'); + } + + signMessage(address: string, message: string): Promise { + throw new Error('Method not implemented.'); + } + + signatureVerify(message: string, signature: string, address: string): boolean { + throw new Error('Method not implemented.'); + } + + joinPool(address: string, poolId: number, amount: number): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/src/icp/types.ts b/src/icp/types.ts new file mode 100644 index 0000000..a0ebd06 --- /dev/null +++ b/src/icp/types.ts @@ -0,0 +1 @@ +export type PrincipalAddress = string diff --git a/src/index.ts b/src/index.ts index 8fa2d24..f6b8194 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './ada/index' +export * from './icp/index' export * from './networks' export * from './substrate/dot/index' export * from './substrate/ksm/index' diff --git a/src/networks.ts b/src/networks.ts index ab2afe6..d5371a6 100644 --- a/src/networks.ts +++ b/src/networks.ts @@ -19,12 +19,19 @@ export const Networks = { decimals: 5, minStakeAmount: 1, // TODO: Validate min stake amount }, + icp: { + id: 4, + name: 'Internet Computer', + decimals: 5, // TODO: Validate decimals + minStakeAmount: 1, // TODO: Validate min stake amount + }, } export enum Network { POLKADOT = 'dot', KUSAMA = 'ksm', CARDANO = 'ada', + INTERNET_COMPUTER = 'icp', } export type NetworkKey = keyof typeof Networks diff --git a/src/types.ts b/src/types.ts index 2c4bbb1..9752346 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,7 @@ export type ProviderBuilderProps = T extends 'dot' | 'ksm' export type Web3Window = { injectedWeb3: any cardano: any + ic: any } & Window & typeof globalThis export const web3Window = (window as Web3Window) diff --git a/src/web3-provider.ts b/src/web3-provider.ts index 7ea3a8a..6cae52e 100644 --- a/src/web3-provider.ts +++ b/src/web3-provider.ts @@ -2,6 +2,7 @@ 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 { InternetComputerProvider } from './icp'; import type { NetworkData, NetworkKey } from './networks'; import { getNetworkKeyById, isValidNetwork } from './networks'; import { PolkadotProvider } from './substrate/dot'; @@ -33,6 +34,8 @@ export class Web3Provider { return new KusamaProvider(props as SubstrateProviderProps) case 'ada': return new CardanoProvider(props as CardanoProviderProps) + case 'icp': + return new InternetComputerProvider() default: throw new InvalidNetworkError() }