From 2acec109c8184728556e86b202a9c90e13cc0957 Mon Sep 17 00:00:00 2001 From: Jordi Parra Crespo Date: Wed, 14 Feb 2024 11:10:30 +0100 Subject: [PATCH 1/4] fix(app): improve accounts from private key fetching --- src/config/config.declarations.d.ts | 4 +++ src/config/configs/config.base.json | 2 ++ src/module/sdk/NearApiService/ApiService.ts | 17 +++++++-- src/module/sdk/NearApiService/FetchService.ts | 28 ++++++++++++--- .../NearApiService/NearApiService.types.ts | 18 ++++++++++ .../sdk/NearApiService/NearBlocksService.ts | 36 +++++++++++++++++++ .../sdk/NearSdkService/NearSdkService.ts | 16 +++++---- .../component/display/TokenCard/TokenCard.tsx | 4 +-- .../display/Balance/Balance.types.ts | 2 +- src/module/wallet/wallet.types.ts | 2 +- 10 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 src/module/sdk/NearApiService/NearBlocksService.ts diff --git a/src/config/config.declarations.d.ts b/src/config/config.declarations.d.ts index 9752e153b..801dfa5d7 100644 --- a/src/config/config.declarations.d.ts +++ b/src/config/config.declarations.d.ts @@ -42,7 +42,9 @@ declare module "@peersyst/react-native-components" { export interface Config { minimumTransactionAmount: number; indexerTestnetUrl: string; + nearblocksTesnetApiUrl: string; indexerMainnetUrl: string; + nearblocksMainnetApiUrl: string; mainnetExplorerLink: string; testnetExplorerLink: string; faucetUrl: string; @@ -74,7 +76,9 @@ declare module "@peersyst/react-native-components" { export interface CreateConfig { minimumTransactionAmount: number; indexerTestnetUrl: string; + nearblocksTesnetApiUrl: string; indexerMainnetUrl: string; + nearblocksMainnetApiUrl: string; mainnetExplorerLink: string; testnetExplorerLink: string; faucetUrl: string; diff --git a/src/config/configs/config.base.json b/src/config/configs/config.base.json index 14249fedb..0d171a69d 100644 --- a/src/config/configs/config.base.json +++ b/src/config/configs/config.base.json @@ -4,7 +4,9 @@ "mainnetExplorerLink": "https://explorer.mainnet.near.org", "testnetExplorerLink": "https://explorer.testnet.near.org", "indexerTestnetUrl": "https://testnet-api.kitwallet.app", + "nearblocksTesnetApiUrl": "https://api-testnet.nearblocks.io/v1", "indexerMainnetUrl": "https://api.kitwallet.app", + "nearblocksMainnetApiUrl": "https://api.nearblocks.io/v1", "faucetUrl": "https://faucet.nervos.org/", "mainnetNodeUrl": "https://rpc.mainnet.near.org", "testnetNodeUrl": "https://rpc.testnet.near.org", diff --git a/src/module/sdk/NearApiService/ApiService.ts b/src/module/sdk/NearApiService/ApiService.ts index e95e3760e..dfe2e0417 100644 --- a/src/module/sdk/NearApiService/ApiService.ts +++ b/src/module/sdk/NearApiService/ApiService.ts @@ -9,7 +9,7 @@ import { TransactionWithoutActions, } from "../NearSdkService"; import { convertYoctoToNear, parseBlockTimestamp } from "../utils"; -import { FetchService } from "./FetchService"; +import { FetchService, HttpError } from "./FetchService"; import { AccessKeyApiDto, LikelyResponseApiDto, @@ -19,12 +19,16 @@ import { StakingDepositApiDto, ActionApiDto, } from "./NearApiService.types"; +import { NearBlocksService } from "./NearBlocksService"; export class ApiService extends FetchService implements NearApiServiceInterface { public baseUrl: string; - constructor(endpoint: string) { + nearblocksService: NearBlocksService; + + constructor(endpoint: string, chain: Chains) { super(); this.baseUrl = endpoint; + this.nearblocksService = new NearBlocksService(chain); } /** @@ -107,7 +111,14 @@ export class ApiService extends FetchService implements NearApiServiceInterface */ async getAccountsFromPublicKey({ address }: NearApiServiceParams): Promise { - const accounts = await this.handleFetch(`${this.baseUrl}/publicKey/${address}/accounts`); + let accounts: string[] = []; + try { + accounts = await this.nearblocksService.getAccountsFromPublicKey({ address }); + } catch (error: unknown) { + if (error instanceof HttpError && error.code === 429) { + accounts = await this.handleFetch(`${this.baseUrl}/publicKey/${address}/accounts`); + } + } return this.parseNearAccounts(accounts); } diff --git a/src/module/sdk/NearApiService/FetchService.ts b/src/module/sdk/NearApiService/FetchService.ts index e25da9688..c886f6dfc 100644 --- a/src/module/sdk/NearApiService/FetchService.ts +++ b/src/module/sdk/NearApiService/FetchService.ts @@ -1,8 +1,28 @@ export class FetchService { protected async handleFetch(url: string): Promise { - const response: Response = await fetch(url); - if (response.status !== 200) throw new Error(`Error ${response.status} ${response.statusText}`); - const data = await response.json(); - return data as T; + try { + const response: Response = await fetch(url); + switch (response.status) { + case 200: + return (await response.json()) as Promise; + case 429: + throw new HttpError(429, "Too many requests"); + default: + throw new HttpError(response.status, response.statusText); + } + } catch (error: unknown) { + if (error instanceof Error) { + throw new HttpError(500, error.message); + } + throw new HttpError(500, "Unknown error"); + } + } +} + +export class HttpError extends Error { + code: number; + constructor(code: number, message: string) { + super(message); + this.code = code; } } diff --git a/src/module/sdk/NearApiService/NearApiService.types.ts b/src/module/sdk/NearApiService/NearApiService.types.ts index a2f32d04c..433674bff 100644 --- a/src/module/sdk/NearApiService/NearApiService.types.ts +++ b/src/module/sdk/NearApiService/NearApiService.types.ts @@ -105,3 +105,21 @@ export interface StakingDepositApiDto { validator_id: string; deposit: string; } + +export interface NearblocksAccessKeyResponseDto { + keys: NearblocksAccessKeyDto[]; +} + +export interface NearblocksAccessKeyDto { + public_key: string; + account_id: string; + permission_kind: "FULL_ACCESS" | "FUNCTION_CALL"; + created: { + transaction_hash: string; + block_timestamp: number; + }; + deleted: { + transaction_hash: null | string; + block_timestamp: null | number; + }; +} diff --git a/src/module/sdk/NearApiService/NearBlocksService.ts b/src/module/sdk/NearApiService/NearBlocksService.ts new file mode 100644 index 000000000..d6a083f1e --- /dev/null +++ b/src/module/sdk/NearApiService/NearBlocksService.ts @@ -0,0 +1,36 @@ +import { config } from "config"; +import { Chains } from "../NearSdkService"; +import { FetchService } from "./FetchService"; +import { NearApiServiceParams, NearblocksAccessKeyResponseDto } from "./NearApiService.types"; + +export class NearBlocksService extends FetchService { + public chain: Chains; + public testnetUrl = config.nearblocksTesnetApiUrl; + public mainnetUrl = config.nearblocksMainnetApiUrl; + + constructor(chain: Chains) { + super(); + this.chain = chain; + } + + private getNearblocksApiUrlFromChain(): string { + return this.chain === Chains.MAINNET ? this.mainnetUrl : this.testnetUrl; + } + + private fetch(path: string): Promise { + const nearBlocksApi = this.getNearblocksApiUrlFromChain(); + return this.handleFetch(`${nearBlocksApi}${path}`); + } + + async getAccountsFromPublicKey({ address }: NearApiServiceParams): Promise { + const accounts: string[] = []; + const keys = await this.fetch(`/keys/${address}`); + if (!keys?.keys || keys.keys.length === 0) return accounts; + for (const key of keys.keys) { + if (key.permission_kind === "FULL_ACCESS") { + accounts.push(key.account_id); + } + } + return accounts; + } +} diff --git a/src/module/sdk/NearSdkService/NearSdkService.ts b/src/module/sdk/NearSdkService/NearSdkService.ts index 327c4f92a..4dc13b96e 100644 --- a/src/module/sdk/NearSdkService/NearSdkService.ts +++ b/src/module/sdk/NearSdkService/NearSdkService.ts @@ -88,7 +88,7 @@ export class NearSDKService { this.nameId = nameId; this.mnemonic = mnemonic; this.baseApiUrl = baseApiUrl; - this.apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer); + this.apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer, chain); this.nearDecimals = nearDecimals; this.enableIndexer = enableIndexer; // Create KeyPairEd25519 @@ -111,8 +111,8 @@ export class NearSDKService { return decode(encode(publicKey.data)).toString("hex"); } - static createApiService(baseApiUrl: string, enableIndexer: boolean): NearApiServiceInterface { - return enableIndexer ? new IndexerService(baseApiUrl) : new ApiService(baseApiUrl); + static createApiService(baseApiUrl: string, enableIndexer: boolean, chain: Chains): NearApiServiceInterface { + return enableIndexer ? new IndexerService(baseApiUrl) : new ApiService(baseApiUrl, chain); } static async createAndConnect(params: BaseCreateNearSdkParams): Promise { @@ -127,16 +127,17 @@ export class NearSDKService { mnemonic, baseApiUrl, enableIndexer, + chain, ...rest }: CreateNearSdkWithMnemonicParams): Promise { const { secretKey, publicKey } = parseSeedPhrase(mnemonic); - const apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer); + const apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer, chain); const nameIds = await apiService.getAccountsFromPublicKey({ address: publicKey }); if (nameIds.length === 0) { nameIds.push(NearSDKService.getAddressFromPublicKey(PublicKey.fromString(publicKey))); } const services = nameIds.map(async (nameId) => { - const service = new NearSDKService({ baseApiUrl, secretKey, nameId, mnemonic, enableIndexer, ...rest }); + const service = new NearSDKService({ baseApiUrl, secretKey, nameId, mnemonic, enableIndexer, chain, ...rest }); await service.connect(); return service; }); @@ -147,17 +148,18 @@ export class NearSDKService { secretKey, baseApiUrl, enableIndexer, + chain, ...rest }: CreateNearSdkWithSecretKeyParams): Promise { const secret = secretKey.split(":").pop(); const publicKey = new KeyPairEd25519(secret!).getPublicKey().toString(); - const apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer); + const apiService = NearSDKService.createApiService(baseApiUrl, enableIndexer, chain); const nameIds = await apiService.getAccountsFromPublicKey({ address: publicKey }); if (nameIds.length === 0) { nameIds.push(NearSDKService.getAddressFromPublicKey(PublicKey.fromString(publicKey))); } const services = nameIds.map(async (nameId) => { - const service = new NearSDKService({ ...rest, baseApiUrl, secretKey, nameId, enableIndexer }); + const service = new NearSDKService({ ...rest, baseApiUrl, secretKey, nameId, enableIndexer, chain }); await service.connect(); return service; }); diff --git a/src/module/token/component/display/TokenCard/TokenCard.tsx b/src/module/token/component/display/TokenCard/TokenCard.tsx index c05e5d428..136320db5 100644 --- a/src/module/token/component/display/TokenCard/TokenCard.tsx +++ b/src/module/token/component/display/TokenCard/TokenCard.tsx @@ -21,8 +21,8 @@ const TokenCard = ({ token }: TokenCardProps): JSX.Element => { {name} - - + + diff --git a/src/module/wallet/component/display/Balance/Balance.types.ts b/src/module/wallet/component/display/Balance/Balance.types.ts index d83fad7fe..9cb5786eb 100644 --- a/src/module/wallet/component/display/Balance/Balance.types.ts +++ b/src/module/wallet/component/display/Balance/Balance.types.ts @@ -19,7 +19,7 @@ export interface BalanceThreshold { decimals: number; } -export interface BalanceProps extends Omit { +export interface BalanceProps extends Omit { balance: FullNumber; units?: AppCurrency | string; unitsPosition?: "left" | "right"; diff --git a/src/module/wallet/wallet.types.ts b/src/module/wallet/wallet.types.ts index 420d60875..b2b91cc14 100644 --- a/src/module/wallet/wallet.types.ts +++ b/src/module/wallet/wallet.types.ts @@ -25,7 +25,7 @@ export interface UnencryptedWalletInfo extends BaseWallet { } export interface UnsecureWalletStorageType { - testnet: []; + testnet: UnencryptedWalletInfo[]; mainnet: UnencryptedWalletInfo[]; } From 2aca82695d2ba69e70645551eed4df4a61d60b21 Mon Sep 17 00:00:00 2001 From: Jordi Parra Crespo Date: Wed, 14 Feb 2024 12:09:25 +0100 Subject: [PATCH 2/4] fix(app): fix cirular dependency --- src/module/sdk/NearApiService/NearBlocksService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/module/sdk/NearApiService/NearBlocksService.ts b/src/module/sdk/NearApiService/NearBlocksService.ts index d6a083f1e..06ad27b41 100644 --- a/src/module/sdk/NearApiService/NearBlocksService.ts +++ b/src/module/sdk/NearApiService/NearBlocksService.ts @@ -1,4 +1,8 @@ -import { config } from "config"; +/** + * Do not `import {config} from "config"` in this file + * because it will cause a circular dependency with SettingsState + */ +import config from "../../../config/config"; import { Chains } from "../NearSdkService"; import { FetchService } from "./FetchService"; import { NearApiServiceParams, NearblocksAccessKeyResponseDto } from "./NearApiService.types"; From f2eb845c2ab48428a4e66bfa2b9ee703a6d83bb0 Mon Sep 17 00:00:00 2001 From: Jordi Parra Crespo Date: Wed, 14 Feb 2024 12:37:01 +0100 Subject: [PATCH 3/4] fix(app): feedback and circular deps --- src/module/sdk/NearApiService/ApiService.ts | 4 ++-- src/module/sdk/NearApiService/FetchService.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/module/sdk/NearApiService/ApiService.ts b/src/module/sdk/NearApiService/ApiService.ts index dfe2e0417..8c63d14ef 100644 --- a/src/module/sdk/NearApiService/ApiService.ts +++ b/src/module/sdk/NearApiService/ApiService.ts @@ -9,7 +9,7 @@ import { TransactionWithoutActions, } from "../NearSdkService"; import { convertYoctoToNear, parseBlockTimestamp } from "../utils"; -import { FetchService, HttpError } from "./FetchService"; +import { FetchService, FetchError } from "./FetchService"; import { AccessKeyApiDto, LikelyResponseApiDto, @@ -115,7 +115,7 @@ export class ApiService extends FetchService implements NearApiServiceInterface try { accounts = await this.nearblocksService.getAccountsFromPublicKey({ address }); } catch (error: unknown) { - if (error instanceof HttpError && error.code === 429) { + if (error instanceof FetchError && error.code === 429) { accounts = await this.handleFetch(`${this.baseUrl}/publicKey/${address}/accounts`); } } diff --git a/src/module/sdk/NearApiService/FetchService.ts b/src/module/sdk/NearApiService/FetchService.ts index c886f6dfc..1003a4415 100644 --- a/src/module/sdk/NearApiService/FetchService.ts +++ b/src/module/sdk/NearApiService/FetchService.ts @@ -6,20 +6,20 @@ export class FetchService { case 200: return (await response.json()) as Promise; case 429: - throw new HttpError(429, "Too many requests"); + throw new FetchError(429, "Too many requests"); default: - throw new HttpError(response.status, response.statusText); + throw new FetchError(response.status, response.statusText); } } catch (error: unknown) { if (error instanceof Error) { - throw new HttpError(500, error.message); + throw new FetchError(500, error.message); } - throw new HttpError(500, "Unknown error"); + throw new FetchError(500, "Unknown error"); } } } -export class HttpError extends Error { +export class FetchError extends Error { code: number; constructor(code: number, message: string) { super(message); From 643d7964edbc437612fbc76a6e1ffba4b1469a53 Mon Sep 17 00:00:00 2001 From: Jordi Parra Crespo Date: Wed, 14 Feb 2024 12:41:26 +0100 Subject: [PATCH 4/4] style(app): format code --- src/module/sdk/NearApiService/NearBlocksService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module/sdk/NearApiService/NearBlocksService.ts b/src/module/sdk/NearApiService/NearBlocksService.ts index 06ad27b41..f73518fbd 100644 --- a/src/module/sdk/NearApiService/NearBlocksService.ts +++ b/src/module/sdk/NearApiService/NearBlocksService.ts @@ -1,8 +1,8 @@ /** - * Do not `import {config} from "config"` in this file + * Do not `import {config} from "config"` in this file * because it will cause a circular dependency with SettingsState */ -import config from "../../../config/config"; +import config from "../../../config/config"; import { Chains } from "../NearSdkService"; import { FetchService } from "./FetchService"; import { NearApiServiceParams, NearblocksAccessKeyResponseDto } from "./NearApiService.types";