Skip to content

Commit

Permalink
fix: merge main
Browse files Browse the repository at this point in the history
fix conflicts
  • Loading branch information
lucasportella committed Mar 4, 2024
2 parents 071cc5c + 5c99d15 commit 4fa16e9
Show file tree
Hide file tree
Showing 18 changed files with 303 additions and 19 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web3-hub",
"version": "0.6.7",
"version": "0.7.10",
"description": "Make web3 more easily with agnostic library",
"source": "src/index.ts",
"exports": {
Expand Down
8 changes: 8 additions & 0 deletions src/cardano/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ export class CardanoProvider implements ProviderEntity {
async joinPool(address: string, poolId: number, amount: number): Promise<string> {
throw new Error('Method not implemented.');
}

bondExtra(address: string, amount: number): Promise<string> {
throw new Error('Method not implemented.');
}

claim(address: string): Promise<string> {
throw new Error('Method not implemented.');
}
}
10 changes: 6 additions & 4 deletions src/entities/provider-entity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Account, Address, Balance } from '@/types'
import type { Account, Address, Balance, Hash } from '@/types'

export interface ProviderEntity {
connect(): Promise<Account[]>
getBalance(address: string): Promise<Balance>
signMessage(address: string, message: string): Promise<string>
getBalance(address: Address): Promise<Balance>
signMessage(address: Address, message: string): Promise<string>
signatureVerify(message: string, signature: string, address: Address): boolean
joinPool(address: string, poolId: number, amount: number): Promise<string>
joinPool(address: Address, poolId: number, amount: number): Promise<Hash>
bondExtra(address: Address, amount: number): Promise<Hash>
claim(address: Address): Promise<Hash>
}
46 changes: 46 additions & 0 deletions src/icp/connect.spec.ts
Original file line number Diff line number Diff line change
@@ -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: {
plug: {
requestConnect: vi.fn(),
principalId: 'wil-123',
},
},
},
}))

const principalId = 'wil-123';

describe('Connect case', () => {
it('should successfully connects and retrieves an account', async () => {
web3Window.ic.plug.requestConnect.mockResolvedValue(true)
const account = await connect()

expect(account).toEqual(principalId)
})

it('should throws error when no provider is available', async () => {
web3Window.ic.plug.requestConnect.mockResolvedValue(false)

await expect(connect()).rejects.toThrow(NoProviderAvailableError)
})

it('should throws error when no accounts are available', async () => {
web3Window.ic.plug.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.plug.requestConnect.mockRejectedValue(new Error('The agent creation was rejected'))

await expect(connect()).rejects.toThrow(UserRejectedError)
})
})
24 changes: 24 additions & 0 deletions src/icp/connect.ts
Original file line number Diff line number Diff line change
@@ -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<PrincipalAddress> {
try {
const hasConnected = await web3Window.ic.plug.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
}
}
35 changes: 35 additions & 0 deletions src/icp/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, Balance } from '..';
import { connect } from './connect';

export class InternetComputerProvider implements ProviderEntity {
async connect(): Promise<Account[]> {
const principalAddress = await connect()

return [{ name: 'Principal Account', address: principalAddress }]
}

getBalance(address: string): Promise<Balance> {
throw new Error('Method not implemented.');
}

signMessage(address: string, message: string): Promise<string> {
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<string> {
throw new Error('Method not implemented.');
}

bondExtra(address: string, amount: number): Promise<string> {
throw new Error('Method not implemented.');
}

claim(address: string): Promise<string> {
throw new Error('Method not implemented.');
}
}
1 change: 1 addition & 0 deletions src/icp/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type PrincipalAddress = string
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './cardano/index'
export * from './icp/index'
export * from './networks'
export * from './substrate/dot/index'
export * from './substrate/ksm/index'
Expand Down
7 changes: 7 additions & 0 deletions src/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ export const Networks = {
decimals: 5,
minStakeAmount: 1, // TODO: Validate min stake amount
},
icp: {
id: 4,
name: 'Internet Computer',
decimals: 8,
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
Expand Down
50 changes: 50 additions & 0 deletions src/substrate/bond-extra.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { UserRejectedError } from '@/errors/user-rejected-error'
import { Networks } from '@/networks'
import { web3FromAddress } from '@polkadot/extension-dapp'
import { describe, expect, it, vi } from 'vitest'
import { bondExtra } from './bond-extra'

vi.mock('@/networks', () => ({
Networks: {
validNetwork: { decimals: 10 },
},
}))

vi.mock('@polkadot/extension-dapp', () => ({
web3FromAddress: vi.fn(),
}))

const address = 'wil123'
const amount = 100
const validHash = 'hash123'

describe('Bond Extra value case', () => {
it('should call bondExtra function with correct params', async () => {
const injector = { signer: 'signer' };
(web3FromAddress as any).mockResolvedValue(injector)

const bondExtraMock = vi.fn().mockImplementation(() => ({
signAndSend: vi.fn().mockResolvedValue({ toString: () => validHash }),
}))

const api: any = { tx: { nominationPools: { bondExtra: bondExtraMock } } }
const hash = await bondExtra('validNetwork' as any, api, address, amount)

expect(web3FromAddress).toHaveBeenCalledWith(address)
expect(bondExtraMock).toHaveBeenCalledWith({
FreeBalance: 100 * 10 ** (Networks as any).validNetwork.decimals,
})
expect(hash).toEqual(validHash)
})

it('should throw error when the tx is rejected by user', async () => {
(web3FromAddress as any).mockResolvedValue({ signer: 'signer' })

const bondExtraMock = vi.fn().mockImplementation(() => ({
signAndSend: vi.fn().mockRejectedValue(new Error('Rejected by user')),
}))
const api: any = { tx: { nominationPools: { bondExtra: bondExtraMock } } }

await expect(bondExtra('validNetwork' as any, api, address, amount)).rejects.toThrow(UserRejectedError)
})
})
26 changes: 26 additions & 0 deletions src/substrate/bond-extra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { UserRejectedError } from '@/errors/user-rejected-error';
import type { Network } from '@/networks';
import { Networks } from '@/networks';
import type { Address } from '@/types';
import type { ApiPromise } from '@polkadot/api';
import { web3FromAddress } from '@polkadot/extension-dapp';

export async function bondExtra(network: Network, api: ApiPromise, address: Address, amount: number) {
try {
const precision = Networks[network].decimals
const injector = await web3FromAddress(address)
const tx = api.tx.nominationPools.bondExtra({
FreeBalance: amount * 10 ** precision,
});
const signer = { signer: injector.signer }

const hash = await tx.signAndSend(address, signer)
return hash.toString()
}
catch (error) {
if ((error as Error).message.toLowerCase().includes('rejected by user'))
throw new UserRejectedError()

throw error
}
}
53 changes: 53 additions & 0 deletions src/substrate/claim.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { UserRejectedError } from '@/errors/user-rejected-error';
import { web3FromAddress } from '@polkadot/extension-dapp';
import { describe, expect, it, vi } from 'vitest';
import { claim } from './claim';

vi.mock('@polkadot/extension-dapp', () => ({
web3FromAddress: vi.fn(),
}))

const address = 'wil123'

describe('Claim case', () => {
it('should successfully submit claim tx and return hash', async () => {
const injector = { signer: 'signer' };
(web3FromAddress as any).mockResolvedValue(injector)

const signAndSendMock = vi.fn().mockResolvedValue({ toString: () => 'hash123' })
const claimPayoutMock = vi.fn().mockImplementation(() => ({
signAndSend: signAndSendMock,
}))

const api: any = {
tx: {
nominationPools: {
claimPayout: claimPayoutMock,
},
},
}

const hash = await claim(api, address)

expect(web3FromAddress).toHaveBeenCalledWith(address)
expect(claimPayoutMock).toHaveBeenCalled()
expect(signAndSendMock).toHaveBeenCalledWith(address, { signer: injector.signer })
expect(hash).toBe('hash123')
})

it('should throw error when the user reject tx', async () => {
(web3FromAddress as any).mockResolvedValue({ signer: 'signer' })

const api: any = {
tx: {
nominationPools: {
claimPayout: vi.fn().mockImplementation(() => ({
signAndSend: vi.fn().mockRejectedValue(new Error('Rejected by user')),
})),
},
},
}

await expect(claim(api, address)).rejects.toThrow(UserRejectedError)
})
})
21 changes: 21 additions & 0 deletions src/substrate/claim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { UserRejectedError } from '@/errors/user-rejected-error';
import type { Address } from '@/types';
import type { ApiPromise } from '@polkadot/api';
import { web3FromAddress } from '@polkadot/extension-dapp';

export async function claim(api: ApiPromise, address: Address) {
try {
const injector = await web3FromAddress(address)
const tx = api.tx.nominationPools.claimPayout()
const signer = { signer: injector.signer }

const hash = await tx.signAndSend(address, signer)
return hash.toString()
}
catch (error) {
if ((error as Error).message.toLowerCase().includes('rejected by user'))
throw new UserRejectedError()

throw error
}
}
19 changes: 19 additions & 0 deletions src/substrate/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { ProviderEntity } from '@/entities/provider-entity';
import { InvalidNetworkError } from '@/errors/invalid-network-error';
import type { Network } from '@/networks';
import { Networks } from '@/networks';
import type { Account, Address, Balance } from '@/types';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { bondExtra } from './bond-extra';
import { claim } from './claim';
import { connect } from './connect';
import { getBalance } from './get-balance';
import { joinPool } from './join-pool';
Expand All @@ -17,6 +21,9 @@ export class SubstrateProvider implements ProviderEntity {
rpcProvider: string

constructor(network: Network, appName: string, rpcProvider: string) {
if (!Networks[network])
throw new InvalidNetworkError()

this.network = network
this.appName = appName
this.rpcProvider = rpcProvider
Expand Down Expand Up @@ -61,4 +68,16 @@ export class SubstrateProvider implements ProviderEntity {

return joinPool(this.network, api, address, poolId, amount)
}

async bondExtra(address: string, amount: number): Promise<string> {
const api = await this.createProvider()

return bondExtra(this.network, api, address, amount)
}

async claim(address: string): Promise<string> {
const api = await this.createProvider()

return claim(api, address)
}
}
9 changes: 0 additions & 9 deletions src/substrate/join-pool.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { InvalidNetworkError } from '@/errors/invalid-network-error'
import { MinAmountError } from '@/errors/min-amount-error'
import { UserRejectedError } from '@/errors/user-rejected-error'
import { web3FromAddress } from '@polkadot/extension-dapp'
Expand Down Expand Up @@ -26,14 +25,6 @@ const address = 'wil123'
const network = Network.POLKADOT

describe('Join pool case', () => {
it('throws InvalidNetworkError for an unsupported network', async () => {
const poolId = 1
const amount = 10

await expect(joinPool('invalidNetwork' as any, {} as any, address, poolId, amount))
.rejects.toThrow(InvalidNetworkError);
});

it('throws MinAmountError if the amount is below the minimum stake amount', async () => {
const poolId = 1
const invalidAmount = 9
Expand Down
Loading

0 comments on commit 4fa16e9

Please sign in to comment.