diff --git a/README.md b/README.md
index e064bc0e964ea66b1597861fbaaa5e17eb24ff87..86f73fa1fe9425c34f3b12b14363e0ac507c8c27 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Priority 1
 - [ ] Network scan ?
 
 Priority 2
-- [ ] Login process (use a unique modal + a login method popover)
+- [x] Login process should use a unique modal, and a method selector - issue #26
 - [ ] Directory (aka wot) search using Data Pod (see [duniter-panel](https://duniter--vue-coinduf-eu.ipns.pagu.re/))
 - [ ] Submit profile to Data pod (see ddd-ui)
 - [ ] TX comments
diff --git a/codegen.yml b/codegen.yml
deleted file mode 100644
index d1b476992cc4d16caac41ee527435365978bc693..0000000000000000000000000000000000000000
--- a/codegen.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-overwrite: true
-schema: "src/app/network/indexer-schema.graphql"
-documents: "src/app/**/*!(.generated).{ts,graphql}"
-generates:
-  src/app/network/indexer-types.generated.ts:
-    plugins:
-      - "add"
-      - "typescript"
-      - "typescript-operations"
-      - "typescript-apollo-angular"
-      - "fragment-matcher"
-    config:
-      content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
-      nameSuffix: "Document"
-      sdkClass: true
-      serviceName: "IndexerGraphqlService"
-      namedClient: 'indexer'
-  src/app/network/indexer-helpers.generated.ts:
-    plugins:
-      - "add"
-      - "typescript-apollo-client-helpers"
-    config:
-      content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
diff --git a/graphql.config.yml b/graphql.config.yml
index 90912ebe961b5d7f438f2766e000359808bf3478..5d96ef556aabd8e78d69fb49fda33cf6e7d74799 100644
--- a/graphql.config.yml
+++ b/graphql.config.yml
@@ -1,8 +1,65 @@
-schema: src/app/network/indexer-schema.graphql
-extensions:
-  endpoints:
-    Gdev GraphQL Endpoint:
-      url: https://gdev-squid.axiom-team.fr/v1beta1/relay
-      headers:
-        user-agent: JS GraphQL
-      introspect: false
+projects:
+  indexer:
+    schema: src/app/network/indexer/indexer-schema.graphql
+    documents: "src/app/network/indexer/indexer-*!(.generated).{ts,gql}"
+    extensions:
+      endpoints:
+        Gdev Indexer GraphQL Endpoint:
+          url: https://gdev-squid.axiom-team.fr/v1beta1/relay
+          headers:
+            user-agent: JS GraphQL
+          introspect: false
+      codegen:
+        generates:
+          src/app/network/indexer/indexer-types.generated.ts:
+            plugins:
+              - "add"
+              - "typescript"
+              - "typescript-operations"
+              - "typescript-apollo-angular"
+              - "fragment-matcher"
+            config:
+              content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
+              nameSuffix: "Document"
+              sdkClass: true
+              serviceName: "IndexerGraphqlService"
+              namedClient: 'indexer'
+          src/app/network/indexer/indexer-helpers.generated.ts:
+            plugins:
+              - "add"
+              - "typescript-apollo-client-helpers"
+            config:
+              content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
+
+  pod:
+    schema: src/app/network/pod/pod-schema.graphql
+    documents: "src/app/network/pod/pod-*!(.generated).{ts,gql}"
+    extensions:
+      endpoints:
+        Gdev Pod GraphQL Endpoint:
+          url: https://datapod.coinduf.eu/v1/graphql
+          headers:
+            user-agent: JS GraphQL
+          introspect: false
+      codegen:
+        generates:
+          src/app/network/pod/pod-types.generated.ts:
+            plugins:
+              - "add"
+              - "typescript"
+              - "typescript-operations"
+              - "typescript-apollo-angular"
+              - "fragment-matcher"
+            config:
+              content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
+              nameSuffix: "Document"
+              sdkClass: true
+              serviceName: "PodGraphqlService"
+              namedClient: 'pod'
+          src/app/network/pod/pod-helpers.generated.ts:
+            plugins:
+              - "add"
+              - "typescript-apollo-client-helpers"
+            config:
+              content: "// Auto-generated via `npx graphql-codegen`, do not edit\n/* eslint-disable */"
+
diff --git a/package.json b/package.json
index 1c51128f2b46086ffaa78f10310142c8f3e2ea80..46976bbb71325f6482e66409c8401d954e93cf29 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,9 @@
     "generate": "npm run generate:defs && npm run generate:meta && npm run generate:graphql",
     "generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package @duniter/interfaces --input src/interfaces --endpoint src/interfaces/types.json",
     "generate:meta": "ts-node --skip-project node_modules/.bin/polkadot-types-from-chain --package @duniter/interfaces  --output src/interfaces --endpoint src/interfaces/types.json",
-    "generate:graphql": "graphql-codegen",
+    "generate:graphql": "graphql-codegen --project indexer && graphql-codegen --project pod",
+    "generate:graphql:indexer": "graphql-codegen --project indexer",
+    "generate:graphql:pod": "graphql-codegen --project pod",
     "prepare": "husky install",
     "version:get": "node scripts/node/version.js",
     "version:set": "node scripts/node/version.js --set",
diff --git a/src/app/account/account.converter.ts b/src/app/account/account.converter.ts
index 77dfc8adac45cc93896517c7c325f3fa935661b5..34586a8224458365ae5d0bed4c04881ddbc8cba6 100644
--- a/src/app/account/account.converter.ts
+++ b/src/app/account/account.converter.ts
@@ -1,21 +1,22 @@
-import { LightAccountConnectionFragment, LightAccountFragment, LightIdentityFragment } from '@app/network/indexer-types.generated';
+import { LightAccountConnectionFragment, LightAccountFragment, LightIdentityFragment } from '@app/network/indexer/indexer-types.generated';
 import { Account, parseAddressSquid } from '@app/account/account.model';
+import { ProfileFragment } from '@app/network/pod/pod-types.generated';
 
 export class AccountConverter {
-  static connectionToAccounts(accountConnection: LightAccountConnectionFragment, debug?: boolean): Account[] {
+  static squidConnectionToAccounts(accountConnection: LightAccountConnectionFragment, debug?: boolean): Account[] {
     const inputs = accountConnection.edges?.map((edge) => edge.node) as LightAccountFragment[];
-    const results = (inputs || []).map(this.toAccount);
+    const results = (inputs || []).map(this.squidToAccount);
     if (debug) console.debug('Results:', results);
     return results;
   }
 
-  static toAccounts(inputs: LightAccountFragment[], debug?: boolean): Account[] {
-    const results = (inputs || []).map(this.toAccount);
+  static squidToAccounts(inputs: LightAccountFragment[], debug?: boolean): Account[] {
+    const results = (inputs || []).map(this.squidToAccount);
     if (debug) console.debug('Results:', results);
     return results;
   }
 
-  static toAccount(input: LightAccountFragment): Account {
+  static squidToAccount(input: LightAccountFragment): Account {
     if (!input) return undefined;
     const addressSquid = parseAddressSquid(input.id);
     const identity = input.identity;
@@ -31,6 +32,24 @@ export class AccountConverter {
       },
     };
   }
+
+  static profileToAccounts(inputs: ProfileFragment[], opts?: { debug?: boolean; ipfsGateway?: string }): Account[] {
+    const results = (inputs || []).map((input) => this.profileToAccount(input, opts));
+    if (opts?.debug) console.debug('Results:', results);
+    return results;
+  }
+
+  static profileToAccount(input: ProfileFragment, opts?: { ipfsGateway?: string }): Account {
+    if (!input) return undefined;
+    const avatar = input.avatar_cid && opts.ipfsGateway ? opts.ipfsGateway + input.avatar_cid : undefined;
+    return <Account>{
+      address: input.address,
+      meta: {
+        name: input.title,
+        avatar,
+      },
+    };
+  }
 }
 
 export class IdentityConverter {
diff --git a/src/app/account/account.model.ts b/src/app/account/account.model.ts
index d6df2325df55332ae07188335a9b62093eb00074..dde14f29ead85461cf1ed5af63c775a571bbc73d 100644
--- a/src/app/account/account.model.ts
+++ b/src/app/account/account.model.ts
@@ -1,7 +1,9 @@
 import { HexString } from '@polkadot/util/types';
 import { ListItem } from '@app/shared/popover/list.popover';
 import { formatAddress } from '@app/shared/currencies';
-import { IdentityStatusEnum } from '@app/network/indexer-types.generated';
+import { IdentityStatusEnum } from '@app/network/indexer/indexer-types.generated';
+import { isEmptyArray } from '@app/shared/functions';
+import { combineLoadResults, LoadResult } from '@app/shared/services/service.model';
 
 export interface AddressSquid {
   index: number;
@@ -73,12 +75,43 @@ export class AccountUtils {
   }
 
   static getDisplayName(account: Partial<Account>) {
-    return account?.meta?.name || account?.meta?.uid || formatAddress(account?.address) || '';
+    if (!account) return '';
+    return account.meta?.name || account.meta?.uid || formatAddress(account.address) || '';
   }
 
   static isEquals(a1: Account, a2: Account) {
     return a1 === a2 || (a1 && a1.address && a1.address === a2?.address);
   }
+
+  static mergeAll(accounts: Account[]): Account[] {
+    if (isEmptyArray(accounts)) return accounts; // Nothing to merge
+
+    const mapByAddress = {};
+    return accounts.reduce((res, item) => {
+      const existingItem = mapByAddress[item.address];
+      if (existingItem) {
+        AccountUtils.merge(existingItem, item);
+        return res;
+      }
+      mapByAddress[item.address] = item;
+      return res.concat(item);
+    }, []);
+  }
+
+  static merge(target: Account, source: Account): Account {
+    if (target.address !== source.address) throw new Error('Both account should have same address!');
+    if (source.meta) {
+      target.meta = { ...source.meta, ...target.meta };
+    }
+    if (source.data) {
+      target.data = { ...source.data, ...target.data };
+    }
+    return target;
+  }
+
+  static combineAccountLoadResults(results: LoadResult<Account>[]): LoadResult<Account> {
+    return combineLoadResults(results, { reduce: AccountUtils.mergeAll });
+  }
 }
 
 export interface UnlockOptions {
diff --git a/src/app/account/accounts.service.ts b/src/app/account/accounts.service.ts
index 1ac3c20c73ee47178d6717d681fbf884c31bc434..cc840a14fae497fd55b6fd080f964b2a82a3db60 100644
--- a/src/app/account/accounts.service.ts
+++ b/src/app/account/accounts.service.ts
@@ -28,7 +28,7 @@ import { RxStartableService } from '@app/shared/services/rx-startable-service.cl
 import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
 import { ED25519_SEED_LENGTH, SCRYPT_PARAMS } from '@app/account/crypto.utils';
 import { KeyringPair } from '@polkadot/keyring/types';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { AppEvent } from '@app/shared/types';
 import { APP_AUTH_CONTROLLER, AuthData, IAuthController } from '@app/account/auth/auth.model';
 import { ExtrinsicError, ExtrinsicUtils } from '@app/shared/substrate/extrinsic.utils';
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 306a2bce7f49070a19f1fd42dc4e62294dc1f03e..35c322af862815db97501561e7a99528b58f1a8b 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -23,7 +23,8 @@ import { AccountsService } from '@app/account/accounts.service';
 import { AppAccountModule } from '@app/account/account.module';
 import { AppTransferModule } from '@app/transfer/send/transfer.module';
 import { APP_GRAPHQL_TYPE_POLICIES } from '@app/shared/services/network/graphql/graphql.service';
-import { INDEXER_GRAPHQL_TYPE_POLICIES } from '@app/network/indexer.config';
+import { INDEXER_GRAPHQL_TYPE_POLICIES } from '@app/network/indexer/indexer.config';
+import { POD_GRAPHQL_TYPE_POLICIES } from '@app/network/pod/pod.config';
 
 export function createTranslateLoader(http: HttpClient) {
   if (environment.production) {
@@ -69,6 +70,7 @@ export function createTranslateLoader(http: HttpClient) {
       provide: APP_GRAPHQL_TYPE_POLICIES,
       useValue: {
         ...INDEXER_GRAPHQL_TYPE_POLICIES,
+        ...POD_GRAPHQL_TYPE_POLICIES,
       },
     },
     { provide: APP_STORAGE, useExisting: StorageService },
diff --git a/src/app/block/block.model.ts b/src/app/block/block.model.ts
index 79987e59672e00034a51f060bd773ce5f3103585..80784b47eeba0aeeee8741df9693a33ded2b9163 100644
--- a/src/app/block/block.model.ts
+++ b/src/app/block/block.model.ts
@@ -1,6 +1,6 @@
 import { Moment } from 'moment/moment';
 import { equals, isNil, isNilOrBlank } from '@app/shared/functions';
-import { BlockBoolExp, BlockEdge } from '@app/network/indexer-types.generated';
+import { BlockBoolExp, BlockEdge } from '@app/network/indexer/indexer-types.generated';
 import { fromDateISOString } from '@app/shared/dates';
 
 export interface Block {
diff --git a/src/app/block/block.page.ts b/src/app/block/block.page.ts
index 1ad36fcbe88e5c098a70bd15d62a7ef886a13b92..342d46836eec64687d13f956bf3aa990b6635895 100644
--- a/src/app/block/block.page.ts
+++ b/src/app/block/block.page.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 import { AppPage, AppPageState } from '@app/shared/pages/base-page.class';
 import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
 import { Promise } from '@rx-angular/cdk/zone-less/browser';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { Block } from '@app/block/block.model';
 import { firstValueFrom, Observable } from 'rxjs';
 import { isNotNil, isNotNilOrBlank } from '@app/shared/functions';
diff --git a/src/app/certification/history/cert-history.model.ts b/src/app/certification/history/cert-history.model.ts
index 06969c90a2e761b7b8c148974f25e9bb96fb78e8..df12f4ccb2a312c05106538989d41fb12a3c190b 100644
--- a/src/app/certification/history/cert-history.model.ts
+++ b/src/app/certification/history/cert-history.model.ts
@@ -1,6 +1,6 @@
 import { equals, isNilOrBlank } from '@app/shared/functions';
 import { Account } from '@app/account/account.model';
-import { CertConnection, CertFragment } from '@app/network/indexer-types.generated';
+import { CertConnection, CertFragment } from '@app/network/indexer/indexer-types.generated';
 import { IdentityConverter } from '@app/account/account.converter';
 
 export interface Certification {
diff --git a/src/app/certification/history/cert-history.page.ts b/src/app/certification/history/cert-history.page.ts
index d2f931b1d4f572890d896f11c1636c950f89b4b8..7e6fea820b723342b1a01abb44ba5c7925f061d4 100644
--- a/src/app/certification/history/cert-history.page.ts
+++ b/src/app/certification/history/cert-history.page.ts
@@ -11,7 +11,7 @@ import { AccountsService } from '@app/account/accounts.service';
 import { firstValueFrom, merge, Observable } from 'rxjs';
 import { RxState } from '@rx-angular/state';
 import { APP_TRANSFER_CONTROLLER, ITransferController } from '@app/transfer/transfer.model';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { FetchMoreFn, LoadResult } from '@app/shared/services/service.model';
 import { Certification, CertificationSearchFilter, CertificationSearchFilterUtils } from './cert-history.model';
 import { ListItems } from '@app/shared/types';
diff --git a/src/app/account/account.queries.graphql b/src/app/network/indexer/indexer-account.gql
similarity index 100%
rename from src/app/account/account.queries.graphql
rename to src/app/network/indexer/indexer-account.gql
diff --git a/src/app/block/block.queries.graphql b/src/app/network/indexer/indexer-block.gql
similarity index 100%
rename from src/app/block/block.queries.graphql
rename to src/app/network/indexer/indexer-block.gql
diff --git a/src/app/certification/history/cert-history.queries.graphql b/src/app/network/indexer/indexer-certification.gql
similarity index 100%
rename from src/app/certification/history/cert-history.queries.graphql
rename to src/app/network/indexer/indexer-certification.gql
diff --git a/src/app/network/indexer-helpers.generated.ts b/src/app/network/indexer/indexer-helpers.generated.ts
similarity index 100%
rename from src/app/network/indexer-helpers.generated.ts
rename to src/app/network/indexer/indexer-helpers.generated.ts
diff --git a/src/app/network/indexer-schema.graphql b/src/app/network/indexer/indexer-schema.graphql
similarity index 100%
rename from src/app/network/indexer-schema.graphql
rename to src/app/network/indexer/indexer-schema.graphql
diff --git a/src/app/transfer/history/transfer.queries.graphql b/src/app/network/indexer/indexer-transfer.gql
similarity index 100%
rename from src/app/transfer/history/transfer.queries.graphql
rename to src/app/network/indexer/indexer-transfer.gql
diff --git a/src/app/network/indexer-types.generated.ts b/src/app/network/indexer/indexer-types.generated.ts
similarity index 100%
rename from src/app/network/indexer-types.generated.ts
rename to src/app/network/indexer/indexer-types.generated.ts
diff --git a/src/app/wot/wot.queries.graphql b/src/app/network/indexer/indexer-wot.gql
similarity index 100%
rename from src/app/wot/wot.queries.graphql
rename to src/app/network/indexer/indexer-wot.gql
diff --git a/src/app/network/indexer.config.ts b/src/app/network/indexer/indexer.config.ts
similarity index 100%
rename from src/app/network/indexer.config.ts
rename to src/app/network/indexer/indexer.config.ts
diff --git a/src/app/network/indexer.service.ts b/src/app/network/indexer/indexer.service.ts
similarity index 86%
rename from src/app/network/indexer.service.ts
rename to src/app/network/indexer/indexer.service.ts
index bf842e819d21724af87b2c65d4adc14df9e9fca2..92f6ac997c4b6aefd0bb9aa944871b95b8743469 100644
--- a/src/app/network/indexer.service.ts
+++ b/src/app/network/indexer/indexer.service.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable, Optional } from '@angular/core';
-import { Peer, Peers } from '@app/shared/services/network/peer.model';
+import { Peers } from '@app/shared/services/network/peer.model';
 import { Promise } from '@rx-angular/cdk/zone-less/browser';
 import { SettingsService } from '@app/settings/settings.service';
 import { arrayRandomPick, firstArrayValue, isNil, isNotNil, isNotNilOrBlank, toBoolean, toNumber } from '@app/shared/functions';
@@ -56,7 +56,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
   constructor(
     storage: StorageService,
     private settings: SettingsService,
-    private indexerGraphqlService: IndexerGraphqlService,
+    private graphqlService: IndexerGraphqlService,
     @Optional() @Inject(APP_GRAPHQL_TYPE_POLICIES) typePolicies: TypePolicies,
     @Optional() @Inject(APP_GRAPHQL_FRAGMENTS) fragments: DocumentNode[]
   ) {
@@ -95,7 +95,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
 
     let data$: Observable<LightAccountConnectionFragment>;
     if (isNotNilOrBlank(filter.address)) {
-      data$ = this.indexerGraphqlService
+      data$ = this.graphqlService
         .wotSearchByAddress(
           {
             address: filter.address,
@@ -109,7 +109,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
         )
         .pipe(map(({ data }) => data.accountConnection as LightAccountConnectionFragment));
     } else if (isNotNilOrBlank(filter.searchText)) {
-      data$ = this.indexerGraphqlService
+      data$ = this.graphqlService
         .wotSearchByText(
           {
             searchText: `%${filter.searchText}%`,
@@ -123,8 +123,8 @@ export class IndexerService extends GraphqlService<IndexerState> {
         )
         .pipe(map(({ data }) => data.accountConnection as LightAccountConnectionFragment));
     } else {
-      data$ = this.indexerGraphqlService
-        .wotSearchLastWatch(
+      data$ = this.graphqlService
+        .wotSearchLast(
           {
             after: options.after,
             first: options.first,
@@ -135,12 +135,12 @@ export class IndexerService extends GraphqlService<IndexerState> {
             fetchPolicy: options.fetchPolicy || 'cache-first',
           }
         )
-        .valueChanges.pipe(map(({ data }) => data.accountConnection as LightAccountConnectionFragment));
+        .pipe(map(({ data }) => data.accountConnection as LightAccountConnectionFragment));
     }
 
     return data$.pipe(
       map((connection: LightAccountConnectionFragment) => {
-        const data = AccountConverter.connectionToAccounts(connection);
+        const data = AccountConverter.squidConnectionToAccounts(connection);
         const result: LoadResult<Account> = { data };
         if (connection.pageInfo.hasNextPage) {
           const endCursor = connection.pageInfo.endCursor;
@@ -166,7 +166,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
     };
 
     if (filter?.address) {
-      return this.indexerGraphqlService
+      return this.graphqlService
         .transferConnectionByAddress(
           {
             address: filter.address,
@@ -224,14 +224,14 @@ export class IndexerService extends GraphqlService<IndexerState> {
       return result;
     };
     if (isNotNilOrBlank(filter.issuer)) {
-      return this.indexerGraphqlService.certsConnectionByIssuer(variables, fetchOptions).pipe(
+      return this.graphqlService.certsConnectionByIssuer(variables, fetchOptions).pipe(
         map(({ data }) => {
           const res = data.identityConnection.edges[0]?.node;
           return toEntities(res?.connection as CertConnection, res?.aggregate.aggregate.count || 0);
         })
       );
     } else {
-      return this.indexerGraphqlService.certsConnectionByReceiver(variables, fetchOptions).pipe(
+      return this.graphqlService.certsConnectionByReceiver(variables, fetchOptions).pipe(
         map(({ data }) => {
           const res = data.identityConnection.edges[0]?.node;
           return toEntities(res?.connection as CertConnection, res?.aggregate.aggregate.count || 0);
@@ -255,7 +255,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
     }
 
     if (isNotNilOrBlank(filter?.height)) {
-      return this.indexerGraphqlService
+      return this.graphqlService
         .blocks({
           ...options,
           after: null,
@@ -265,7 +265,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
         .pipe(map(({ data: { blockConnection } }) => BlockConverter.toBlocks(blockConnection.edges as BlockEdge[], true)));
     }
 
-    return this.indexerGraphqlService
+    return this.graphqlService
       .blocks({
         ...options,
         where: filter?.where,
@@ -275,7 +275,7 @@ export class IndexerService extends GraphqlService<IndexerState> {
 
   blockById(id: string): Observable<Block> {
     console.info(`${this._logPrefix}Loading block #${id}`);
-    return this.indexerGraphqlService
+    return this.graphqlService
       .blockById({ id })
       .pipe(map(({ data: { blockConnection } }) => BlockConverter.toBlock(blockConnection.edges[0] as BlockEdge)));
   }
@@ -308,40 +308,4 @@ export class IndexerService extends GraphqlService<IndexerState> {
       minBlockHeight: currency?.minBlockHeight,
     };
   }
-
-  protected async ngOnStop(): Promise<void> {
-    super.ngOnStop();
-  }
-
-  protected async filterAlivePeers(
-    peers: string[],
-    opts?: {
-      timeout?: number;
-    }
-  ): Promise<Peer[]> {
-    const result: Peer[] = [];
-    await Promise.all(
-      peers
-        .map((peer) => Peers.fromUri(peer))
-        .map((peer) =>
-          this.isPeerAlive(peer, opts).then((alive) => {
-            if (!alive) return;
-            result.push(peer);
-          })
-        )
-    );
-    return result;
-  }
-
-  protected async isPeerAlive(
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    peer: Peer,
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    opts?: {
-      timeout?: number;
-    }
-  ): Promise<boolean> {
-    // TODO
-    return Promise.resolve(true);
-  }
 }
diff --git a/src/app/network/ipfs/ipfs.service.ts b/src/app/network/ipfs/ipfs.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dbcfb3b1993f75fec4a0683bf6df51d6c1515cb1
--- /dev/null
+++ b/src/app/network/ipfs/ipfs.service.ts
@@ -0,0 +1,86 @@
+import { Injectable } from '@angular/core';
+import { Peer, Peers } from '@app/shared/services/network/peer.model';
+import { Promise } from '@rx-angular/cdk/zone-less/browser';
+import { SettingsService } from '@app/settings/settings.service';
+import { arrayRandomPick, isNotNil } from '@app/shared/functions';
+import { StorageService } from '@app/shared/services/storage/storage.service';
+import { Observable } from 'rxjs';
+import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
+import { RxStartableService } from '@app/shared/services/rx-startable-service.class';
+
+export interface IpfsState {
+  peer: Peer;
+  gatewayBaseUrl: string;
+  offline: boolean;
+}
+
+@Injectable({ providedIn: 'root' })
+export class IpfsService extends RxStartableService<IpfsState> {
+  @RxStateSelect() peer$: Observable<Peer>;
+  @RxStateProperty() peer: Peer;
+  @RxStateProperty() gatewayBaseUrl: string;
+
+  constructor(
+    storage: StorageService,
+    private settings: SettingsService
+  ) {
+    super(storage, {
+      name: 'ipfs-service',
+      startByReadyFunction: false, // Need an explicit call to start()
+    });
+  }
+
+  getGatewayUrl(cid: string): string {
+    return this.gatewayBaseUrl + cid;
+  }
+
+  /* -- protected functions -- */
+
+  protected async ngOnStart(): Promise<IpfsState> {
+    // Wait settings and storage
+    const settings = await this.settings.ready();
+
+    let peer = Peers.fromUri(settings.ipfsGateway);
+    if (!peer) {
+      const peers = await this.filterAlivePeers(settings.preferredIpfsGateways || []);
+      if (!peers.length) {
+        throw { message: 'ERROR.CHECK_NETWORK_CONNECTION' };
+      }
+      peer = arrayRandomPick(peers);
+    }
+
+    const gatewayBaseUrl = Peers.getHttpUri(peer) + '/ipfs/';
+
+    return {
+      peer,
+      gatewayBaseUrl,
+      offline: false,
+    };
+  }
+
+  protected async filterAlivePeers(
+    peers: string[],
+    opts?: {
+      timeout?: number;
+    }
+  ): Promise<Peer[]> {
+    return (
+      await Promise.all(
+        peers.map((peer) => Peers.fromUri(peer)).map((peer) => this.isPeerAlive(peer, opts).then((alive) => (alive ? peer : undefined)))
+      )
+    ).filter(isNotNil);
+  }
+
+  protected async isPeerAlive(
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    peer: Peer,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    opts?: {
+      timeout?: number;
+    }
+  ): Promise<boolean> {
+    // TODO
+    console.log(`${this._logPrefix}TODO: implement ${this.constructor.name}.isPeerAlive()`, peer);
+    return Promise.resolve(true);
+  }
+}
diff --git a/src/app/network/network.service.ts b/src/app/network/network.service.ts
index 1b026570f9fe2b6869a6c117e8de859094795f29..dc844e40f6b7974397b9e8d8921aa7a103614c4d 100644
--- a/src/app/network/network.service.ts
+++ b/src/app/network/network.service.ts
@@ -8,10 +8,11 @@ import { RxStartableService } from '@app/shared/services/rx-startable-service.cl
 import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
 import { mergeMap, Observable, tap } from 'rxjs';
 import { filter, map } from 'rxjs/operators';
-import { arrayRandomPick, isNotNilOrBlank, toNumber } from '@app/shared/functions';
-import { IndexerService } from './indexer.service';
+import { arrayRandomPick, isNotNil, isNotNilOrBlank, toNumber } from '@app/shared/functions';
+import { IndexerService } from './indexer/indexer.service';
 import { fromDateISOString } from '@app/shared/dates';
 import { ContextService } from '@app/shared/services/storage/context.service';
+import { PodService } from '@app/network/pod/pod.service';
 
 export interface NetworkState {
   peer: Peer;
@@ -24,6 +25,7 @@ export interface NetworkState {
 @Injectable({ providedIn: 'root' })
 export class NetworkService extends RxStartableService<NetworkState> {
   indexer = inject(IndexerService);
+  pod = inject(PodService);
 
   @RxStateProperty() peer: Peer;
   @RxStateProperty() currency: Currency;
@@ -147,8 +149,10 @@ export class NetworkService extends RxStartableService<NetworkState> {
 
     const ud0 = toNumber(api.consts.universalDividend.unitsPerUd) / currency.powBase;
 
+    // Configure and start indexer and pod
     this.indexer.currency = currency;
-    await this.indexer.start();
+    this.pod.currency = currency;
+    await Promise.all([this.indexer.start(), this.pod.start()]);
 
     return {
       api,
@@ -172,18 +176,11 @@ export class NetworkService extends RxStartableService<NetworkState> {
       timeout?: number;
     }
   ): Promise<Peer[]> {
-    const result: Peer[] = [];
-    await Promise.all(
-      peers
-        .map((peer) => Peers.fromUri(peer))
-        .map((peer) =>
-          this.isPeerAlive(peer).then((alive) => {
-            if (!alive) return;
-            result.push(peer);
-          })
-        )
-    );
-    return result;
+    return (
+      await Promise.all(
+        peers.map((peer) => Peers.fromUri(peer)).map((peer) => this.isPeerAlive(peer, opts).then((alive) => (alive ? peer : undefined)))
+      )
+    ).filter(isNotNil);
   }
 
   protected async isPeerAlive(
diff --git a/src/app/network/pod/pod-helpers.generated.ts b/src/app/network/pod/pod-helpers.generated.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04cd4a4992a0f5e23cf7ef3c80b0a326427e53a5
--- /dev/null
+++ b/src/app/network/pod/pod-helpers.generated.ts
@@ -0,0 +1,140 @@
+// Auto-generated via `npx graphql-codegen`, do not edit
+/* eslint-disable */
+import { FieldPolicy, FieldReadFunction, TypePolicies, TypePolicy } from '@apollo/client/cache';
+export type AddTransactionResponseKeySpecifier = ('message' | 'success' | AddTransactionResponseKeySpecifier)[];
+export type AddTransactionResponseFieldPolicy = {
+	message?: FieldPolicy<any> | FieldReadFunction<any>,
+	success?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type DeleteProfileResponseKeySpecifier = ('message' | 'success' | DeleteProfileResponseKeySpecifier)[];
+export type DeleteProfileResponseFieldPolicy = {
+	message?: FieldPolicy<any> | FieldReadFunction<any>,
+	success?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type MigrateProfileResponseKeySpecifier = ('message' | 'success' | MigrateProfileResponseKeySpecifier)[];
+export type MigrateProfileResponseFieldPolicy = {
+	message?: FieldPolicy<any> | FieldReadFunction<any>,
+	success?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type UpdateProfileResponseKeySpecifier = ('message' | 'success' | UpdateProfileResponseKeySpecifier)[];
+export type UpdateProfileResponseFieldPolicy = {
+	message?: FieldPolicy<any> | FieldReadFunction<any>,
+	success?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type mutation_rootKeySpecifier = ('addTransaction' | 'deleteProfile' | 'migrateProfile' | 'updateProfile' | mutation_rootKeySpecifier)[];
+export type mutation_rootFieldPolicy = {
+	addTransaction?: FieldPolicy<any> | FieldReadFunction<any>,
+	deleteProfile?: FieldPolicy<any> | FieldReadFunction<any>,
+	migrateProfile?: FieldPolicy<any> | FieldReadFunction<any>,
+	updateProfile?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type profilesKeySpecifier = ('avatar' | 'city' | 'data_cid' | 'description' | 'geoloc' | 'index_request_cid' | 'pubkey' | 'socials' | 'time' | 'title' | profilesKeySpecifier)[];
+export type profilesFieldPolicy = {
+	avatar?: FieldPolicy<any> | FieldReadFunction<any>,
+	city?: FieldPolicy<any> | FieldReadFunction<any>,
+	data_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	description?: FieldPolicy<any> | FieldReadFunction<any>,
+	geoloc?: FieldPolicy<any> | FieldReadFunction<any>,
+	index_request_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	pubkey?: FieldPolicy<any> | FieldReadFunction<any>,
+	socials?: FieldPolicy<any> | FieldReadFunction<any>,
+	time?: FieldPolicy<any> | FieldReadFunction<any>,
+	title?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type profiles_aggregateKeySpecifier = ('aggregate' | 'nodes' | profiles_aggregateKeySpecifier)[];
+export type profiles_aggregateFieldPolicy = {
+	aggregate?: FieldPolicy<any> | FieldReadFunction<any>,
+	nodes?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type profiles_aggregate_fieldsKeySpecifier = ('count' | 'max' | 'min' | profiles_aggregate_fieldsKeySpecifier)[];
+export type profiles_aggregate_fieldsFieldPolicy = {
+	count?: FieldPolicy<any> | FieldReadFunction<any>,
+	max?: FieldPolicy<any> | FieldReadFunction<any>,
+	min?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type profiles_max_fieldsKeySpecifier = ('avatar' | 'city' | 'data_cid' | 'description' | 'index_request_cid' | 'pubkey' | 'time' | 'title' | profiles_max_fieldsKeySpecifier)[];
+export type profiles_max_fieldsFieldPolicy = {
+	avatar?: FieldPolicy<any> | FieldReadFunction<any>,
+	city?: FieldPolicy<any> | FieldReadFunction<any>,
+	data_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	description?: FieldPolicy<any> | FieldReadFunction<any>,
+	index_request_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	pubkey?: FieldPolicy<any> | FieldReadFunction<any>,
+	time?: FieldPolicy<any> | FieldReadFunction<any>,
+	title?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type profiles_min_fieldsKeySpecifier = ('avatar' | 'city' | 'data_cid' | 'description' | 'index_request_cid' | 'pubkey' | 'time' | 'title' | profiles_min_fieldsKeySpecifier)[];
+export type profiles_min_fieldsFieldPolicy = {
+	avatar?: FieldPolicy<any> | FieldReadFunction<any>,
+	city?: FieldPolicy<any> | FieldReadFunction<any>,
+	data_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	description?: FieldPolicy<any> | FieldReadFunction<any>,
+	index_request_cid?: FieldPolicy<any> | FieldReadFunction<any>,
+	pubkey?: FieldPolicy<any> | FieldReadFunction<any>,
+	time?: FieldPolicy<any> | FieldReadFunction<any>,
+	title?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type query_rootKeySpecifier = ('profiles' | 'profiles_aggregate' | 'profiles_by_pk' | query_rootKeySpecifier)[];
+export type query_rootFieldPolicy = {
+	profiles?: FieldPolicy<any> | FieldReadFunction<any>,
+	profiles_aggregate?: FieldPolicy<any> | FieldReadFunction<any>,
+	profiles_by_pk?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type subscription_rootKeySpecifier = ('profiles' | 'profiles_aggregate' | 'profiles_by_pk' | 'profiles_stream' | subscription_rootKeySpecifier)[];
+export type subscription_rootFieldPolicy = {
+	profiles?: FieldPolicy<any> | FieldReadFunction<any>,
+	profiles_aggregate?: FieldPolicy<any> | FieldReadFunction<any>,
+	profiles_by_pk?: FieldPolicy<any> | FieldReadFunction<any>,
+	profiles_stream?: FieldPolicy<any> | FieldReadFunction<any>
+};
+export type StrictTypedTypePolicies = {
+	AddTransactionResponse?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | AddTransactionResponseKeySpecifier | (() => undefined | AddTransactionResponseKeySpecifier),
+		fields?: AddTransactionResponseFieldPolicy,
+	},
+	DeleteProfileResponse?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | DeleteProfileResponseKeySpecifier | (() => undefined | DeleteProfileResponseKeySpecifier),
+		fields?: DeleteProfileResponseFieldPolicy,
+	},
+	MigrateProfileResponse?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | MigrateProfileResponseKeySpecifier | (() => undefined | MigrateProfileResponseKeySpecifier),
+		fields?: MigrateProfileResponseFieldPolicy,
+	},
+	UpdateProfileResponse?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | UpdateProfileResponseKeySpecifier | (() => undefined | UpdateProfileResponseKeySpecifier),
+		fields?: UpdateProfileResponseFieldPolicy,
+	},
+	mutation_root?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | mutation_rootKeySpecifier | (() => undefined | mutation_rootKeySpecifier),
+		fields?: mutation_rootFieldPolicy,
+	},
+	profiles?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | profilesKeySpecifier | (() => undefined | profilesKeySpecifier),
+		fields?: profilesFieldPolicy,
+	},
+	profiles_aggregate?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | profiles_aggregateKeySpecifier | (() => undefined | profiles_aggregateKeySpecifier),
+		fields?: profiles_aggregateFieldPolicy,
+	},
+	profiles_aggregate_fields?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | profiles_aggregate_fieldsKeySpecifier | (() => undefined | profiles_aggregate_fieldsKeySpecifier),
+		fields?: profiles_aggregate_fieldsFieldPolicy,
+	},
+	profiles_max_fields?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | profiles_max_fieldsKeySpecifier | (() => undefined | profiles_max_fieldsKeySpecifier),
+		fields?: profiles_max_fieldsFieldPolicy,
+	},
+	profiles_min_fields?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | profiles_min_fieldsKeySpecifier | (() => undefined | profiles_min_fieldsKeySpecifier),
+		fields?: profiles_min_fieldsFieldPolicy,
+	},
+	query_root?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | query_rootKeySpecifier | (() => undefined | query_rootKeySpecifier),
+		fields?: query_rootFieldPolicy,
+	},
+	subscription_root?: Omit<TypePolicy, "fields" | "keyFields"> & {
+		keyFields?: false | subscription_rootKeySpecifier | (() => undefined | subscription_rootKeySpecifier),
+		fields?: subscription_rootFieldPolicy,
+	}
+};
+export type TypedTypePolicies = StrictTypedTypePolicies & TypePolicies;
\ No newline at end of file
diff --git a/src/app/network/pod/pod-profile.gql b/src/app/network/pod/pod-profile.gql
new file mode 100644
index 0000000000000000000000000000000000000000..8fa6369250d854880f4d7c8b86729ae126ab5062
--- /dev/null
+++ b/src/app/network/pod/pod-profile.gql
@@ -0,0 +1,52 @@
+fragment LightProfile on profiles {
+  id: data_cid
+  __typename
+  address: pubkey
+  title
+  avatar_cid: avatar
+  time
+}
+
+fragment Profile on profiles {
+  ...LightProfile
+  description
+  city
+  geoloc
+  socials
+  index_request_cid
+}
+
+query ProfileSearchByText(
+  $searchText: String!,
+  $limit: Int!,
+  $offset: Int!,
+  $orderBy: [profiles_order_by!],
+  $withTotal: Boolean!
+) {
+  profiles(
+    offset: $offset,
+    limit: $limit,
+    where: { title: { _ilike: $searchText } },
+    order_by: $orderBy
+  ) {
+    ...LightProfile
+  }
+
+  profiles_aggregate(where: { title: { _ilike: $searchText } }) @include (if: $withTotal) {
+    aggregate {
+      count
+    }
+  }
+}
+
+query ProfileByAddress($address: String!) {
+  profiles_by_pk(pubkey: $address) {
+    ...Profile
+  }
+}
+
+query ProfileSearchByAddresses($addresses: [String!]!) {
+  profiles(where: {pubkey: { _in: $addresses}}) {
+    ...LightProfile
+  }
+}
diff --git a/src/app/network/pod/pod-schema.graphql b/src/app/network/pod/pod-schema.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..8273276d3ca437130e48ed19b0e410b3594f5e28
--- /dev/null
+++ b/src/app/network/pod/pod-schema.graphql
@@ -0,0 +1,406 @@
+# This file was generated. Do not edit manually.
+
+schema {
+  query: query_root
+  mutation: mutation_root
+  subscription: subscription_root
+}
+
+"whether this query should be cached (Hasura Cloud only)"
+directive @cached(
+  "refresh the cache entry"
+  refresh: Boolean! = false,
+  "measured in seconds"
+  ttl: Int! = 60
+) on QUERY
+
+type AddTransactionResponse {
+  message: String!
+  success: Boolean!
+}
+
+type DeleteProfileResponse {
+  message: String!
+  success: Boolean!
+}
+
+type MigrateProfileResponse {
+  message: String!
+  success: Boolean!
+}
+
+type UpdateProfileResponse {
+  message: String!
+  success: Boolean!
+}
+
+"mutation root"
+type mutation_root {
+  "addTransaction"
+  addTransaction(address: String!, comment: String!, hash: String!, id: String!, signature: String!): AddTransactionResponse
+  "deleteProfile"
+  deleteProfile(address: String!, hash: String!, signature: String!): DeleteProfileResponse
+  "migrateProfile"
+  migrateProfile(addressNew: String!, addressOld: String!, hash: String!, signature: String!): MigrateProfileResponse
+  "updateProfile"
+  updateProfile(address: String!, avatarBase64: String, city: String, description: String, geoloc: GeolocInput, hash: String!, signature: String!, socials: [SocialInput!], title: String): UpdateProfileResponse
+}
+
+"columns and relationships of \"profiles\""
+type profiles {
+  "cid of avatar"
+  avatar: String
+  city: String
+  "CID of the latest data from which this document comes from"
+  data_cid: String
+  description: String
+  geoloc: point
+  "CID of the latest index request that modified this document"
+  index_request_cid: String!
+  "base58 pubkey of profile owner"
+  pubkey: String!
+  socials(
+    "JSON select path"
+    path: String
+  ): jsonb
+  "timestamp of the latest index request that modified this document"
+  time: timestamptz!
+  "title of c+ profile"
+  title: String
+}
+
+"aggregated selection of \"profiles\""
+type profiles_aggregate {
+  aggregate: profiles_aggregate_fields
+  nodes: [profiles!]!
+}
+
+"aggregate fields of \"profiles\""
+type profiles_aggregate_fields {
+  count(columns: [profiles_select_column!], distinct: Boolean): Int!
+  max: profiles_max_fields
+  min: profiles_min_fields
+}
+
+"aggregate max on columns"
+type profiles_max_fields {
+  "cid of avatar"
+  avatar: String
+  city: String
+  "CID of the latest data from which this document comes from"
+  data_cid: String
+  description: String
+  "CID of the latest index request that modified this document"
+  index_request_cid: String
+  "base58 pubkey of profile owner"
+  pubkey: String
+  "timestamp of the latest index request that modified this document"
+  time: timestamptz
+  "title of c+ profile"
+  title: String
+}
+
+"aggregate min on columns"
+type profiles_min_fields {
+  "cid of avatar"
+  avatar: String
+  city: String
+  "CID of the latest data from which this document comes from"
+  data_cid: String
+  description: String
+  "CID of the latest index request that modified this document"
+  index_request_cid: String
+  "base58 pubkey of profile owner"
+  pubkey: String
+  "timestamp of the latest index request that modified this document"
+  time: timestamptz
+  "title of c+ profile"
+  title: String
+}
+
+type query_root {
+  "fetch data from the table: \"profiles\""
+  profiles(
+    "distinct select on columns"
+    distinct_on: [profiles_select_column!],
+    "limit the number of rows returned"
+    limit: Int,
+    "skip the first n rows. Use only with order_by"
+    offset: Int,
+    "sort the rows by one or more columns"
+    order_by: [profiles_order_by!],
+    "filter the rows returned"
+    where: profiles_bool_exp
+  ): [profiles!]!
+  "fetch aggregated fields from the table: \"profiles\""
+  profiles_aggregate(
+    "distinct select on columns"
+    distinct_on: [profiles_select_column!],
+    "limit the number of rows returned"
+    limit: Int,
+    "skip the first n rows. Use only with order_by"
+    offset: Int,
+    "sort the rows by one or more columns"
+    order_by: [profiles_order_by!],
+    "filter the rows returned"
+    where: profiles_bool_exp
+  ): profiles_aggregate!
+  "fetch data from the table: \"profiles\" using primary key columns"
+  profiles_by_pk(
+    "base58 pubkey of profile owner"
+    pubkey: String!
+  ): profiles
+}
+
+type subscription_root {
+  "fetch data from the table: \"profiles\""
+  profiles(
+    "distinct select on columns"
+    distinct_on: [profiles_select_column!],
+    "limit the number of rows returned"
+    limit: Int,
+    "skip the first n rows. Use only with order_by"
+    offset: Int,
+    "sort the rows by one or more columns"
+    order_by: [profiles_order_by!],
+    "filter the rows returned"
+    where: profiles_bool_exp
+  ): [profiles!]!
+  "fetch aggregated fields from the table: \"profiles\""
+  profiles_aggregate(
+    "distinct select on columns"
+    distinct_on: [profiles_select_column!],
+    "limit the number of rows returned"
+    limit: Int,
+    "skip the first n rows. Use only with order_by"
+    offset: Int,
+    "sort the rows by one or more columns"
+    order_by: [profiles_order_by!],
+    "filter the rows returned"
+    where: profiles_bool_exp
+  ): profiles_aggregate!
+  "fetch data from the table: \"profiles\" using primary key columns"
+  profiles_by_pk(
+    "base58 pubkey of profile owner"
+    pubkey: String!
+  ): profiles
+  "fetch data from the table in a streaming manner: \"profiles\""
+  profiles_stream(
+    "maximum number of rows returned in a single batch"
+    batch_size: Int!,
+    "cursor to stream the results returned by the query"
+    cursor: [profiles_stream_cursor_input]!,
+    "filter the rows returned"
+    where: profiles_bool_exp
+  ): [profiles!]!
+}
+
+"ordering argument of a cursor"
+enum cursor_ordering {
+  "ascending ordering of the cursor"
+  ASC
+  "descending ordering of the cursor"
+  DESC
+}
+
+"column ordering options"
+enum order_by {
+  "in ascending order, nulls last"
+  asc
+  "in ascending order, nulls first"
+  asc_nulls_first
+  "in ascending order, nulls last"
+  asc_nulls_last
+  "in descending order, nulls first"
+  desc
+  "in descending order, nulls first"
+  desc_nulls_first
+  "in descending order, nulls last"
+  desc_nulls_last
+}
+
+"select columns of table \"profiles\""
+enum profiles_select_column {
+  "column name"
+  avatar
+  "column name"
+  city
+  "column name"
+  data_cid
+  "column name"
+  description
+  "column name"
+  geoloc
+  "column name"
+  index_request_cid
+  "column name"
+  pubkey
+  "column name"
+  socials
+  "column name"
+  time
+  "column name"
+  title
+}
+
+scalar jsonb
+
+scalar point
+
+scalar timestamptz
+
+input GeolocInput {
+  latitude: Float!
+  longitude: Float!
+}
+
+input SocialInput {
+  type: String
+  url: String!
+}
+
+"Boolean expression to compare columns of type \"String\". All fields are combined with logical 'AND'."
+input String_comparison_exp {
+  _eq: String
+  _gt: String
+  _gte: String
+  "does the column match the given case-insensitive pattern"
+  _ilike: String
+  _in: [String!]
+  "does the column match the given POSIX regular expression, case insensitive"
+  _iregex: String
+  _is_null: Boolean
+  "does the column match the given pattern"
+  _like: String
+  _lt: String
+  _lte: String
+  _neq: String
+  "does the column NOT match the given case-insensitive pattern"
+  _nilike: String
+  _nin: [String!]
+  "does the column NOT match the given POSIX regular expression, case insensitive"
+  _niregex: String
+  "does the column NOT match the given pattern"
+  _nlike: String
+  "does the column NOT match the given POSIX regular expression, case sensitive"
+  _nregex: String
+  "does the column NOT match the given SQL regular expression"
+  _nsimilar: String
+  "does the column match the given POSIX regular expression, case sensitive"
+  _regex: String
+  "does the column match the given SQL regular expression"
+  _similar: String
+}
+
+input jsonb_cast_exp {
+  String: String_comparison_exp
+}
+
+"Boolean expression to compare columns of type \"jsonb\". All fields are combined with logical 'AND'."
+input jsonb_comparison_exp {
+  _cast: jsonb_cast_exp
+  "is the column contained in the given json value"
+  _contained_in: jsonb
+  "does the column contain the given json value at the top level"
+  _contains: jsonb
+  _eq: jsonb
+  _gt: jsonb
+  _gte: jsonb
+  "does the string exist as a top-level key in the column"
+  _has_key: String
+  "do all of these strings exist as top-level keys in the column"
+  _has_keys_all: [String!]
+  "do any of these strings exist as top-level keys in the column"
+  _has_keys_any: [String!]
+  _in: [jsonb!]
+  _is_null: Boolean
+  _lt: jsonb
+  _lte: jsonb
+  _neq: jsonb
+  _nin: [jsonb!]
+}
+
+"Boolean expression to compare columns of type \"point\". All fields are combined with logical 'AND'."
+input point_comparison_exp {
+  _eq: point
+  _gt: point
+  _gte: point
+  _in: [point!]
+  _is_null: Boolean
+  _lt: point
+  _lte: point
+  _neq: point
+  _nin: [point!]
+}
+
+"Boolean expression to filter rows from the table \"profiles\". All fields are combined with a logical 'AND'."
+input profiles_bool_exp {
+  _and: [profiles_bool_exp!]
+  _not: profiles_bool_exp
+  _or: [profiles_bool_exp!]
+  avatar: String_comparison_exp
+  city: String_comparison_exp
+  data_cid: String_comparison_exp
+  description: String_comparison_exp
+  geoloc: point_comparison_exp
+  index_request_cid: String_comparison_exp
+  pubkey: String_comparison_exp
+  socials: jsonb_comparison_exp
+  time: timestamptz_comparison_exp
+  title: String_comparison_exp
+}
+
+"Ordering options when selecting data from \"profiles\"."
+input profiles_order_by {
+  avatar: order_by
+  city: order_by
+  data_cid: order_by
+  description: order_by
+  geoloc: order_by
+  index_request_cid: order_by
+  pubkey: order_by
+  socials: order_by
+  time: order_by
+  title: order_by
+}
+
+"Streaming cursor of the table \"profiles\""
+input profiles_stream_cursor_input {
+  "Stream column input with initial value"
+  initial_value: profiles_stream_cursor_value_input!
+  "cursor ordering"
+  ordering: cursor_ordering
+}
+
+"Initial value of the column from where the streaming should start"
+input profiles_stream_cursor_value_input {
+  "cid of avatar"
+  avatar: String
+  city: String
+  "CID of the latest data from which this document comes from"
+  data_cid: String
+  description: String
+  geoloc: point
+  "CID of the latest index request that modified this document"
+  index_request_cid: String
+  "base58 pubkey of profile owner"
+  pubkey: String
+  socials: jsonb
+  "timestamp of the latest index request that modified this document"
+  time: timestamptz
+  "title of c+ profile"
+  title: String
+}
+
+"Boolean expression to compare columns of type \"timestamptz\". All fields are combined with logical 'AND'."
+input timestamptz_comparison_exp {
+  _eq: timestamptz
+  _gt: timestamptz
+  _gte: timestamptz
+  _in: [timestamptz!]
+  _is_null: Boolean
+  _lt: timestamptz
+  _lte: timestamptz
+  _neq: timestamptz
+  _nin: [timestamptz!]
+}
diff --git a/src/app/network/pod/pod-types.generated.ts b/src/app/network/pod/pod-types.generated.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67cf3a2c058f1b4bf4fae30d895815712b8b6ec9
--- /dev/null
+++ b/src/app/network/pod/pod-types.generated.ts
@@ -0,0 +1,734 @@
+// Auto-generated via `npx graphql-codegen`, do not edit
+/* eslint-disable */
+import { gql } from 'apollo-angular';
+import { Injectable } from '@angular/core';
+import * as Apollo from 'apollo-angular';
+import * as ApolloCore from '@apollo/client/core';
+export type Maybe<T> = T | null;
+export type InputMaybe<T> = Maybe<T>;
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
+export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
+export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
+export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
+export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+  ID: { input: string; output: string; }
+  String: { input: string; output: string; }
+  Boolean: { input: boolean; output: boolean; }
+  Int: { input: number; output: number; }
+  Float: { input: number; output: number; }
+  jsonb: { input: any; output: any; }
+  point: { input: any; output: any; }
+  timestamptz: { input: any; output: any; }
+};
+
+export type AddTransactionResponse = {
+  __typename?: 'AddTransactionResponse';
+  message: Scalars['String']['output'];
+  success: Scalars['Boolean']['output'];
+};
+
+export type DeleteProfileResponse = {
+  __typename?: 'DeleteProfileResponse';
+  message: Scalars['String']['output'];
+  success: Scalars['Boolean']['output'];
+};
+
+export type GeolocInput = {
+  latitude: Scalars['Float']['input'];
+  longitude: Scalars['Float']['input'];
+};
+
+export type MigrateProfileResponse = {
+  __typename?: 'MigrateProfileResponse';
+  message: Scalars['String']['output'];
+  success: Scalars['Boolean']['output'];
+};
+
+export type SocialInput = {
+  type?: InputMaybe<Scalars['String']['input']>;
+  url: Scalars['String']['input'];
+};
+
+/** Boolean expression to compare columns of type "String". All fields are combined with logical 'AND'. */
+export type String_Comparison_Exp = {
+  _eq?: InputMaybe<Scalars['String']['input']>;
+  _gt?: InputMaybe<Scalars['String']['input']>;
+  _gte?: InputMaybe<Scalars['String']['input']>;
+  /** does the column match the given case-insensitive pattern */
+  _ilike?: InputMaybe<Scalars['String']['input']>;
+  _in?: InputMaybe<Array<Scalars['String']['input']>>;
+  /** does the column match the given POSIX regular expression, case insensitive */
+  _iregex?: InputMaybe<Scalars['String']['input']>;
+  _is_null?: InputMaybe<Scalars['Boolean']['input']>;
+  /** does the column match the given pattern */
+  _like?: InputMaybe<Scalars['String']['input']>;
+  _lt?: InputMaybe<Scalars['String']['input']>;
+  _lte?: InputMaybe<Scalars['String']['input']>;
+  _neq?: InputMaybe<Scalars['String']['input']>;
+  /** does the column NOT match the given case-insensitive pattern */
+  _nilike?: InputMaybe<Scalars['String']['input']>;
+  _nin?: InputMaybe<Array<Scalars['String']['input']>>;
+  /** does the column NOT match the given POSIX regular expression, case insensitive */
+  _niregex?: InputMaybe<Scalars['String']['input']>;
+  /** does the column NOT match the given pattern */
+  _nlike?: InputMaybe<Scalars['String']['input']>;
+  /** does the column NOT match the given POSIX regular expression, case sensitive */
+  _nregex?: InputMaybe<Scalars['String']['input']>;
+  /** does the column NOT match the given SQL regular expression */
+  _nsimilar?: InputMaybe<Scalars['String']['input']>;
+  /** does the column match the given POSIX regular expression, case sensitive */
+  _regex?: InputMaybe<Scalars['String']['input']>;
+  /** does the column match the given SQL regular expression */
+  _similar?: InputMaybe<Scalars['String']['input']>;
+};
+
+export type UpdateProfileResponse = {
+  __typename?: 'UpdateProfileResponse';
+  message: Scalars['String']['output'];
+  success: Scalars['Boolean']['output'];
+};
+
+/** ordering argument of a cursor */
+export enum Cursor_Ordering {
+  /** ascending ordering of the cursor */
+  Asc = 'ASC',
+  /** descending ordering of the cursor */
+  Desc = 'DESC'
+}
+
+export type Jsonb_Cast_Exp = {
+  String?: InputMaybe<String_Comparison_Exp>;
+};
+
+/** Boolean expression to compare columns of type "jsonb". All fields are combined with logical 'AND'. */
+export type Jsonb_Comparison_Exp = {
+  _cast?: InputMaybe<Jsonb_Cast_Exp>;
+  /** is the column contained in the given json value */
+  _contained_in?: InputMaybe<Scalars['jsonb']['input']>;
+  /** does the column contain the given json value at the top level */
+  _contains?: InputMaybe<Scalars['jsonb']['input']>;
+  _eq?: InputMaybe<Scalars['jsonb']['input']>;
+  _gt?: InputMaybe<Scalars['jsonb']['input']>;
+  _gte?: InputMaybe<Scalars['jsonb']['input']>;
+  /** does the string exist as a top-level key in the column */
+  _has_key?: InputMaybe<Scalars['String']['input']>;
+  /** do all of these strings exist as top-level keys in the column */
+  _has_keys_all?: InputMaybe<Array<Scalars['String']['input']>>;
+  /** do any of these strings exist as top-level keys in the column */
+  _has_keys_any?: InputMaybe<Array<Scalars['String']['input']>>;
+  _in?: InputMaybe<Array<Scalars['jsonb']['input']>>;
+  _is_null?: InputMaybe<Scalars['Boolean']['input']>;
+  _lt?: InputMaybe<Scalars['jsonb']['input']>;
+  _lte?: InputMaybe<Scalars['jsonb']['input']>;
+  _neq?: InputMaybe<Scalars['jsonb']['input']>;
+  _nin?: InputMaybe<Array<Scalars['jsonb']['input']>>;
+};
+
+/** mutation root */
+export type Mutation_Root = {
+  __typename?: 'mutation_root';
+  /** addTransaction */
+  addTransaction?: Maybe<AddTransactionResponse>;
+  /** deleteProfile */
+  deleteProfile?: Maybe<DeleteProfileResponse>;
+  /** migrateProfile */
+  migrateProfile?: Maybe<MigrateProfileResponse>;
+  /** updateProfile */
+  updateProfile?: Maybe<UpdateProfileResponse>;
+};
+
+
+/** mutation root */
+export type Mutation_RootAddTransactionArgs = {
+  address: Scalars['String']['input'];
+  comment: Scalars['String']['input'];
+  hash: Scalars['String']['input'];
+  id: Scalars['String']['input'];
+  signature: Scalars['String']['input'];
+};
+
+
+/** mutation root */
+export type Mutation_RootDeleteProfileArgs = {
+  address: Scalars['String']['input'];
+  hash: Scalars['String']['input'];
+  signature: Scalars['String']['input'];
+};
+
+
+/** mutation root */
+export type Mutation_RootMigrateProfileArgs = {
+  addressNew: Scalars['String']['input'];
+  addressOld: Scalars['String']['input'];
+  hash: Scalars['String']['input'];
+  signature: Scalars['String']['input'];
+};
+
+
+/** mutation root */
+export type Mutation_RootUpdateProfileArgs = {
+  address: Scalars['String']['input'];
+  avatarBase64?: InputMaybe<Scalars['String']['input']>;
+  city?: InputMaybe<Scalars['String']['input']>;
+  description?: InputMaybe<Scalars['String']['input']>;
+  geoloc?: InputMaybe<GeolocInput>;
+  hash: Scalars['String']['input'];
+  signature: Scalars['String']['input'];
+  socials?: InputMaybe<Array<SocialInput>>;
+  title?: InputMaybe<Scalars['String']['input']>;
+};
+
+/** column ordering options */
+export enum Order_By {
+  /** in ascending order, nulls last */
+  Asc = 'asc',
+  /** in ascending order, nulls first */
+  AscNullsFirst = 'asc_nulls_first',
+  /** in ascending order, nulls last */
+  AscNullsLast = 'asc_nulls_last',
+  /** in descending order, nulls first */
+  Desc = 'desc',
+  /** in descending order, nulls first */
+  DescNullsFirst = 'desc_nulls_first',
+  /** in descending order, nulls last */
+  DescNullsLast = 'desc_nulls_last'
+}
+
+/** Boolean expression to compare columns of type "point". All fields are combined with logical 'AND'. */
+export type Point_Comparison_Exp = {
+  _eq?: InputMaybe<Scalars['point']['input']>;
+  _gt?: InputMaybe<Scalars['point']['input']>;
+  _gte?: InputMaybe<Scalars['point']['input']>;
+  _in?: InputMaybe<Array<Scalars['point']['input']>>;
+  _is_null?: InputMaybe<Scalars['Boolean']['input']>;
+  _lt?: InputMaybe<Scalars['point']['input']>;
+  _lte?: InputMaybe<Scalars['point']['input']>;
+  _neq?: InputMaybe<Scalars['point']['input']>;
+  _nin?: InputMaybe<Array<Scalars['point']['input']>>;
+};
+
+/** columns and relationships of "profiles" */
+export type Profiles = {
+  __typename?: 'profiles';
+  /** cid of avatar */
+  avatar?: Maybe<Scalars['String']['output']>;
+  city?: Maybe<Scalars['String']['output']>;
+  /** CID of the latest data from which this document comes from */
+  data_cid?: Maybe<Scalars['String']['output']>;
+  description?: Maybe<Scalars['String']['output']>;
+  geoloc?: Maybe<Scalars['point']['output']>;
+  /** CID of the latest index request that modified this document */
+  index_request_cid: Scalars['String']['output'];
+  /** base58 pubkey of profile owner */
+  pubkey: Scalars['String']['output'];
+  socials?: Maybe<Scalars['jsonb']['output']>;
+  /** timestamp of the latest index request that modified this document */
+  time: Scalars['timestamptz']['output'];
+  /** title of c+ profile */
+  title?: Maybe<Scalars['String']['output']>;
+};
+
+
+/** columns and relationships of "profiles" */
+export type ProfilesSocialsArgs = {
+  path?: InputMaybe<Scalars['String']['input']>;
+};
+
+/** aggregated selection of "profiles" */
+export type Profiles_Aggregate = {
+  __typename?: 'profiles_aggregate';
+  aggregate?: Maybe<Profiles_Aggregate_Fields>;
+  nodes: Array<Profiles>;
+};
+
+/** aggregate fields of "profiles" */
+export type Profiles_Aggregate_Fields = {
+  __typename?: 'profiles_aggregate_fields';
+  count: Scalars['Int']['output'];
+  max?: Maybe<Profiles_Max_Fields>;
+  min?: Maybe<Profiles_Min_Fields>;
+};
+
+
+/** aggregate fields of "profiles" */
+export type Profiles_Aggregate_FieldsCountArgs = {
+  columns?: InputMaybe<Array<Profiles_Select_Column>>;
+  distinct?: InputMaybe<Scalars['Boolean']['input']>;
+};
+
+/** Boolean expression to filter rows from the table "profiles". All fields are combined with a logical 'AND'. */
+export type Profiles_Bool_Exp = {
+  _and?: InputMaybe<Array<Profiles_Bool_Exp>>;
+  _not?: InputMaybe<Profiles_Bool_Exp>;
+  _or?: InputMaybe<Array<Profiles_Bool_Exp>>;
+  avatar?: InputMaybe<String_Comparison_Exp>;
+  city?: InputMaybe<String_Comparison_Exp>;
+  data_cid?: InputMaybe<String_Comparison_Exp>;
+  description?: InputMaybe<String_Comparison_Exp>;
+  geoloc?: InputMaybe<Point_Comparison_Exp>;
+  index_request_cid?: InputMaybe<String_Comparison_Exp>;
+  pubkey?: InputMaybe<String_Comparison_Exp>;
+  socials?: InputMaybe<Jsonb_Comparison_Exp>;
+  time?: InputMaybe<Timestamptz_Comparison_Exp>;
+  title?: InputMaybe<String_Comparison_Exp>;
+};
+
+/** aggregate max on columns */
+export type Profiles_Max_Fields = {
+  __typename?: 'profiles_max_fields';
+  /** cid of avatar */
+  avatar?: Maybe<Scalars['String']['output']>;
+  city?: Maybe<Scalars['String']['output']>;
+  /** CID of the latest data from which this document comes from */
+  data_cid?: Maybe<Scalars['String']['output']>;
+  description?: Maybe<Scalars['String']['output']>;
+  /** CID of the latest index request that modified this document */
+  index_request_cid?: Maybe<Scalars['String']['output']>;
+  /** base58 pubkey of profile owner */
+  pubkey?: Maybe<Scalars['String']['output']>;
+  /** timestamp of the latest index request that modified this document */
+  time?: Maybe<Scalars['timestamptz']['output']>;
+  /** title of c+ profile */
+  title?: Maybe<Scalars['String']['output']>;
+};
+
+/** aggregate min on columns */
+export type Profiles_Min_Fields = {
+  __typename?: 'profiles_min_fields';
+  /** cid of avatar */
+  avatar?: Maybe<Scalars['String']['output']>;
+  city?: Maybe<Scalars['String']['output']>;
+  /** CID of the latest data from which this document comes from */
+  data_cid?: Maybe<Scalars['String']['output']>;
+  description?: Maybe<Scalars['String']['output']>;
+  /** CID of the latest index request that modified this document */
+  index_request_cid?: Maybe<Scalars['String']['output']>;
+  /** base58 pubkey of profile owner */
+  pubkey?: Maybe<Scalars['String']['output']>;
+  /** timestamp of the latest index request that modified this document */
+  time?: Maybe<Scalars['timestamptz']['output']>;
+  /** title of c+ profile */
+  title?: Maybe<Scalars['String']['output']>;
+};
+
+/** Ordering options when selecting data from "profiles". */
+export type Profiles_Order_By = {
+  avatar?: InputMaybe<Order_By>;
+  city?: InputMaybe<Order_By>;
+  data_cid?: InputMaybe<Order_By>;
+  description?: InputMaybe<Order_By>;
+  geoloc?: InputMaybe<Order_By>;
+  index_request_cid?: InputMaybe<Order_By>;
+  pubkey?: InputMaybe<Order_By>;
+  socials?: InputMaybe<Order_By>;
+  time?: InputMaybe<Order_By>;
+  title?: InputMaybe<Order_By>;
+};
+
+/** select columns of table "profiles" */
+export enum Profiles_Select_Column {
+  /** column name */
+  Avatar = 'avatar',
+  /** column name */
+  City = 'city',
+  /** column name */
+  DataCid = 'data_cid',
+  /** column name */
+  Description = 'description',
+  /** column name */
+  Geoloc = 'geoloc',
+  /** column name */
+  IndexRequestCid = 'index_request_cid',
+  /** column name */
+  Pubkey = 'pubkey',
+  /** column name */
+  Socials = 'socials',
+  /** column name */
+  Time = 'time',
+  /** column name */
+  Title = 'title'
+}
+
+/** Streaming cursor of the table "profiles" */
+export type Profiles_Stream_Cursor_Input = {
+  /** Stream column input with initial value */
+  initial_value: Profiles_Stream_Cursor_Value_Input;
+  /** cursor ordering */
+  ordering?: InputMaybe<Cursor_Ordering>;
+};
+
+/** Initial value of the column from where the streaming should start */
+export type Profiles_Stream_Cursor_Value_Input = {
+  /** cid of avatar */
+  avatar?: InputMaybe<Scalars['String']['input']>;
+  city?: InputMaybe<Scalars['String']['input']>;
+  /** CID of the latest data from which this document comes from */
+  data_cid?: InputMaybe<Scalars['String']['input']>;
+  description?: InputMaybe<Scalars['String']['input']>;
+  geoloc?: InputMaybe<Scalars['point']['input']>;
+  /** CID of the latest index request that modified this document */
+  index_request_cid?: InputMaybe<Scalars['String']['input']>;
+  /** base58 pubkey of profile owner */
+  pubkey?: InputMaybe<Scalars['String']['input']>;
+  socials?: InputMaybe<Scalars['jsonb']['input']>;
+  /** timestamp of the latest index request that modified this document */
+  time?: InputMaybe<Scalars['timestamptz']['input']>;
+  /** title of c+ profile */
+  title?: InputMaybe<Scalars['String']['input']>;
+};
+
+export type Query_Root = {
+  __typename?: 'query_root';
+  /** fetch data from the table: "profiles" */
+  profiles: Array<Profiles>;
+  /** fetch aggregated fields from the table: "profiles" */
+  profiles_aggregate: Profiles_Aggregate;
+  /** fetch data from the table: "profiles" using primary key columns */
+  profiles_by_pk?: Maybe<Profiles>;
+};
+
+
+export type Query_RootProfilesArgs = {
+  distinct_on?: InputMaybe<Array<Profiles_Select_Column>>;
+  limit?: InputMaybe<Scalars['Int']['input']>;
+  offset?: InputMaybe<Scalars['Int']['input']>;
+  order_by?: InputMaybe<Array<Profiles_Order_By>>;
+  where?: InputMaybe<Profiles_Bool_Exp>;
+};
+
+
+export type Query_RootProfiles_AggregateArgs = {
+  distinct_on?: InputMaybe<Array<Profiles_Select_Column>>;
+  limit?: InputMaybe<Scalars['Int']['input']>;
+  offset?: InputMaybe<Scalars['Int']['input']>;
+  order_by?: InputMaybe<Array<Profiles_Order_By>>;
+  where?: InputMaybe<Profiles_Bool_Exp>;
+};
+
+
+export type Query_RootProfiles_By_PkArgs = {
+  pubkey: Scalars['String']['input'];
+};
+
+export type Subscription_Root = {
+  __typename?: 'subscription_root';
+  /** fetch data from the table: "profiles" */
+  profiles: Array<Profiles>;
+  /** fetch aggregated fields from the table: "profiles" */
+  profiles_aggregate: Profiles_Aggregate;
+  /** fetch data from the table: "profiles" using primary key columns */
+  profiles_by_pk?: Maybe<Profiles>;
+  /** fetch data from the table in a streaming manner: "profiles" */
+  profiles_stream: Array<Profiles>;
+};
+
+
+export type Subscription_RootProfilesArgs = {
+  distinct_on?: InputMaybe<Array<Profiles_Select_Column>>;
+  limit?: InputMaybe<Scalars['Int']['input']>;
+  offset?: InputMaybe<Scalars['Int']['input']>;
+  order_by?: InputMaybe<Array<Profiles_Order_By>>;
+  where?: InputMaybe<Profiles_Bool_Exp>;
+};
+
+
+export type Subscription_RootProfiles_AggregateArgs = {
+  distinct_on?: InputMaybe<Array<Profiles_Select_Column>>;
+  limit?: InputMaybe<Scalars['Int']['input']>;
+  offset?: InputMaybe<Scalars['Int']['input']>;
+  order_by?: InputMaybe<Array<Profiles_Order_By>>;
+  where?: InputMaybe<Profiles_Bool_Exp>;
+};
+
+
+export type Subscription_RootProfiles_By_PkArgs = {
+  pubkey: Scalars['String']['input'];
+};
+
+
+export type Subscription_RootProfiles_StreamArgs = {
+  batch_size: Scalars['Int']['input'];
+  cursor: Array<InputMaybe<Profiles_Stream_Cursor_Input>>;
+  where?: InputMaybe<Profiles_Bool_Exp>;
+};
+
+/** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */
+export type Timestamptz_Comparison_Exp = {
+  _eq?: InputMaybe<Scalars['timestamptz']['input']>;
+  _gt?: InputMaybe<Scalars['timestamptz']['input']>;
+  _gte?: InputMaybe<Scalars['timestamptz']['input']>;
+  _in?: InputMaybe<Array<Scalars['timestamptz']['input']>>;
+  _is_null?: InputMaybe<Scalars['Boolean']['input']>;
+  _lt?: InputMaybe<Scalars['timestamptz']['input']>;
+  _lte?: InputMaybe<Scalars['timestamptz']['input']>;
+  _neq?: InputMaybe<Scalars['timestamptz']['input']>;
+  _nin?: InputMaybe<Array<Scalars['timestamptz']['input']>>;
+};
+
+export type LightProfileFragment = { __typename: 'profiles', title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null };
+
+export type ProfileFragment = { __typename: 'profiles', description?: string | null, city?: string | null, geoloc?: any | null, socials?: any | null, index_request_cid: string, title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null };
+
+export type ProfileSearchByTextQueryVariables = Exact<{
+  searchText: Scalars['String']['input'];
+  limit: Scalars['Int']['input'];
+  offset: Scalars['Int']['input'];
+  orderBy?: InputMaybe<Array<Profiles_Order_By> | Profiles_Order_By>;
+  withTotal: Scalars['Boolean']['input'];
+}>;
+
+
+export type ProfileSearchByTextQuery = { __typename?: 'query_root', profiles: Array<{ __typename: 'profiles', title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null }>, profiles_aggregate?: { __typename?: 'profiles_aggregate', aggregate?: { __typename?: 'profiles_aggregate_fields', count: number } | null } };
+
+export type ProfileByAddressQueryVariables = Exact<{
+  address: Scalars['String']['input'];
+}>;
+
+
+export type ProfileByAddressQuery = { __typename?: 'query_root', profiles_by_pk?: { __typename: 'profiles', description?: string | null, city?: string | null, geoloc?: any | null, socials?: any | null, index_request_cid: string, title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null } | null };
+
+export type ProfileSearchByAddressesQueryVariables = Exact<{
+  addresses: Array<Scalars['String']['input']> | Scalars['String']['input'];
+}>;
+
+
+export type ProfileSearchByAddressesQuery = { __typename?: 'query_root', profiles: Array<{ __typename: 'profiles', title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null }> };
+
+export type ProfileSearchByAddressQueryVariables = Exact<{
+  address: Scalars['String']['input'];
+}>;
+
+
+export type ProfileSearchByAddressQuery = { __typename?: 'query_root', profiles_by_pk?: { __typename: 'profiles', description?: string | null, city?: string | null, geoloc?: any | null, socials?: any | null, index_request_cid: string, title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null } | null };
+
+export type LightProfileByAddressesQueryVariables = Exact<{
+  addresses: Array<Scalars['String']['input']> | Scalars['String']['input'];
+}>;
+
+
+export type LightProfileByAddressesQuery = { __typename?: 'query_root', profiles: Array<{ __typename: 'profiles', title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null }> };
+
+export type LightProfileByAddressQueryVariables = Exact<{
+  address: Scalars['String']['input'];
+}>;
+
+
+export type LightProfileByAddressQuery = { __typename?: 'query_root', profiles_by_pk?: { __typename: 'profiles', title?: string | null, time: any, id?: string | null, address: string, avatar_cid?: string | null } | null };
+
+export const LightProfileFragmentDoc = gql`
+    fragment LightProfile on profiles {
+  id: data_cid
+  __typename
+  address: pubkey
+  title
+  avatar_cid: avatar
+  time
+}
+    `;
+export const ProfileFragmentDoc = gql`
+    fragment Profile on profiles {
+  ...LightProfile
+  description
+  city
+  geoloc
+  socials
+  index_request_cid
+}
+    ${LightProfileFragmentDoc}`;
+export const ProfileSearchByTextDocument = gql`
+    query ProfileSearchByText($searchText: String!, $limit: Int!, $offset: Int!, $orderBy: [profiles_order_by!], $withTotal: Boolean!) {
+  profiles(
+    offset: $offset
+    limit: $limit
+    where: {title: {_ilike: $searchText}}
+    order_by: $orderBy
+  ) {
+    ...LightProfile
+  }
+  profiles_aggregate(where: {title: {_ilike: $searchText}}) @include(if: $withTotal) {
+    aggregate {
+      count
+    }
+  }
+}
+    ${LightProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class ProfileSearchByTextGQL extends Apollo.Query<ProfileSearchByTextQuery, ProfileSearchByTextQueryVariables> {
+    document = ProfileSearchByTextDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+export const ProfileByAddressDocument = gql`
+    query ProfileByAddress($address: String!) {
+  profiles_by_pk(pubkey: $address) {
+    ...Profile
+  }
+}
+    ${ProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class ProfileByAddressGQL extends Apollo.Query<ProfileByAddressQuery, ProfileByAddressQueryVariables> {
+    document = ProfileByAddressDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+export const ProfileSearchByAddressesDocument = gql`
+    query ProfileSearchByAddresses($addresses: [String!]!) {
+  profiles(where: {pubkey: {_in: $addresses}}) {
+    ...LightProfile
+  }
+}
+    ${LightProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class ProfileSearchByAddressesGQL extends Apollo.Query<ProfileSearchByAddressesQuery, ProfileSearchByAddressesQueryVariables> {
+    document = ProfileSearchByAddressesDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+export const ProfileSearchByAddressDocument = gql`
+    query ProfileSearchByAddress($address: String!) {
+  profiles_by_pk(pubkey: $address) {
+    ...Profile
+  }
+}
+    ${ProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class ProfileSearchByAddressGQL extends Apollo.Query<ProfileSearchByAddressQuery, ProfileSearchByAddressQueryVariables> {
+    document = ProfileSearchByAddressDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+export const LightProfileByAddressesDocument = gql`
+    query LightProfileByAddresses($addresses: [String!]!) {
+  profiles(where: {pubkey: {_in: $addresses}}) {
+    ...LightProfile
+  }
+}
+    ${LightProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class LightProfileByAddressesGQL extends Apollo.Query<LightProfileByAddressesQuery, LightProfileByAddressesQueryVariables> {
+    document = LightProfileByAddressesDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+export const LightProfileByAddressDocument = gql`
+    query LightProfileByAddress($address: String!) {
+  profiles_by_pk(pubkey: $address) {
+    ...LightProfile
+  }
+}
+    ${LightProfileFragmentDoc}`;
+
+  @Injectable({
+    providedIn: 'root'
+  })
+  export class LightProfileByAddressGQL extends Apollo.Query<LightProfileByAddressQuery, LightProfileByAddressQueryVariables> {
+    document = LightProfileByAddressDocument;
+    client = 'pod';
+    constructor(apollo: Apollo.Apollo) {
+      super(apollo);
+    }
+  }
+
+  type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
+
+  interface WatchQueryOptionsAlone<V> extends Omit<ApolloCore.WatchQueryOptions<V>, 'query' | 'variables'> {}
+
+  interface QueryOptionsAlone<V> extends Omit<ApolloCore.QueryOptions<V>, 'query' | 'variables'> {}
+
+  @Injectable({ providedIn: 'root' })
+  export class PodGraphqlService {
+    constructor(
+      private profileSearchByTextGql: ProfileSearchByTextGQL,
+      private profileByAddressGql: ProfileByAddressGQL,
+      private profileSearchByAddressesGql: ProfileSearchByAddressesGQL,
+      private profileSearchByAddressGql: ProfileSearchByAddressGQL,
+      private lightProfileByAddressesGql: LightProfileByAddressesGQL,
+      private lightProfileByAddressGql: LightProfileByAddressGQL
+    ) {}
+      
+    profileSearchByText(variables: ProfileSearchByTextQueryVariables, options?: QueryOptionsAlone<ProfileSearchByTextQueryVariables>) {
+      return this.profileSearchByTextGql.fetch(variables, options)
+    }
+    
+    profileSearchByTextWatch(variables: ProfileSearchByTextQueryVariables, options?: WatchQueryOptionsAlone<ProfileSearchByTextQueryVariables>) {
+      return this.profileSearchByTextGql.watch(variables, options)
+    }
+    
+    profileByAddress(variables: ProfileByAddressQueryVariables, options?: QueryOptionsAlone<ProfileByAddressQueryVariables>) {
+      return this.profileByAddressGql.fetch(variables, options)
+    }
+    
+    profileByAddressWatch(variables: ProfileByAddressQueryVariables, options?: WatchQueryOptionsAlone<ProfileByAddressQueryVariables>) {
+      return this.profileByAddressGql.watch(variables, options)
+    }
+    
+    profileSearchByAddresses(variables: ProfileSearchByAddressesQueryVariables, options?: QueryOptionsAlone<ProfileSearchByAddressesQueryVariables>) {
+      return this.profileSearchByAddressesGql.fetch(variables, options)
+    }
+    
+    profileSearchByAddressesWatch(variables: ProfileSearchByAddressesQueryVariables, options?: WatchQueryOptionsAlone<ProfileSearchByAddressesQueryVariables>) {
+      return this.profileSearchByAddressesGql.watch(variables, options)
+    }
+    
+    profileSearchByAddress(variables: ProfileSearchByAddressQueryVariables, options?: QueryOptionsAlone<ProfileSearchByAddressQueryVariables>) {
+      return this.profileSearchByAddressGql.fetch(variables, options)
+    }
+    
+    profileSearchByAddressWatch(variables: ProfileSearchByAddressQueryVariables, options?: WatchQueryOptionsAlone<ProfileSearchByAddressQueryVariables>) {
+      return this.profileSearchByAddressGql.watch(variables, options)
+    }
+    
+    lightProfileByAddresses(variables: LightProfileByAddressesQueryVariables, options?: QueryOptionsAlone<LightProfileByAddressesQueryVariables>) {
+      return this.lightProfileByAddressesGql.fetch(variables, options)
+    }
+    
+    lightProfileByAddressesWatch(variables: LightProfileByAddressesQueryVariables, options?: WatchQueryOptionsAlone<LightProfileByAddressesQueryVariables>) {
+      return this.lightProfileByAddressesGql.watch(variables, options)
+    }
+    
+    lightProfileByAddress(variables: LightProfileByAddressQueryVariables, options?: QueryOptionsAlone<LightProfileByAddressQueryVariables>) {
+      return this.lightProfileByAddressGql.fetch(variables, options)
+    }
+    
+    lightProfileByAddressWatch(variables: LightProfileByAddressQueryVariables, options?: WatchQueryOptionsAlone<LightProfileByAddressQueryVariables>) {
+      return this.lightProfileByAddressGql.watch(variables, options)
+    }
+  }
+
+      export interface PossibleTypesResultData {
+        possibleTypes: {
+          [key: string]: string[]
+        }
+      }
+      const result: PossibleTypesResultData = {
+  "possibleTypes": {}
+};
+      export default result;
+    
\ No newline at end of file
diff --git a/src/app/network/pod/pod.config.ts b/src/app/network/pod/pod.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f69ba56ebcf8e774c92946ab0145b5b5ed992b8d
--- /dev/null
+++ b/src/app/network/pod/pod.config.ts
@@ -0,0 +1,3 @@
+import { StrictTypedTypePolicies } from './pod-helpers.generated';
+
+export const POD_GRAPHQL_TYPE_POLICIES = <StrictTypedTypePolicies>{};
diff --git a/src/app/network/pod/pod.service.ts b/src/app/network/pod/pod.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a27a24bd157bcac5ad1077ddc55a952db7d680a7
--- /dev/null
+++ b/src/app/network/pod/pod.service.ts
@@ -0,0 +1,165 @@
+import { Inject, Injectable, Optional } from '@angular/core';
+import { Peers } from '@app/shared/services/network/peer.model';
+import { Promise } from '@rx-angular/cdk/zone-less/browser';
+import { SettingsService } from '@app/settings/settings.service';
+import { arrayRandomPick, isNil, isNotEmptyArray, isNotNil, isNotNilOrBlank, toNumber } from '@app/shared/functions';
+import { TypePolicies } from '@apollo/client/core';
+import {
+  APP_GRAPHQL_FRAGMENTS,
+  APP_GRAPHQL_TYPE_POLICIES,
+  GraphqlService,
+  GraphqlServiceState,
+} from '@app/shared/services/network/graphql/graphql.service';
+import { DocumentNode } from 'graphql/index';
+import { StorageService } from '@app/shared/services/storage/storage.service';
+import { firstValueFrom, Observable, of } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { Currency } from '@app/currency/currency.model';
+import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
+import { firstNotNilPromise } from '@app/shared/observables';
+import { Order_By, PodGraphqlService, ProfileFragment } from './pod-types.generated';
+import { WotSearchFilter } from '@app/wot/wot.model';
+import { LoadResult } from '@app/shared/services/service.model';
+import { Account } from '@app/account/account.model';
+import { FetchPolicy } from '@apollo/client';
+import { AccountConverter } from '@app/account/account.converter';
+import { IpfsService } from '@app/network/ipfs/ipfs.service';
+
+export interface PodState extends GraphqlServiceState {
+  currency: Currency;
+}
+
+@Injectable({ providedIn: 'root' })
+export class PodService extends GraphqlService<PodState> {
+  @RxStateSelect() currency$: Observable<Currency>;
+  @RxStateProperty() currency: Currency;
+
+  @RxStateSelect() minBlockHeight$: Observable<number>;
+  @RxStateProperty() minBlockHeight: number;
+
+  constructor(
+    storage: StorageService,
+    private settings: SettingsService,
+    private graphqlService: PodGraphqlService,
+    private ipfsService: IpfsService,
+    @Optional() @Inject(APP_GRAPHQL_TYPE_POLICIES) typePolicies: TypePolicies,
+    @Optional() @Inject(APP_GRAPHQL_FRAGMENTS) fragments: DocumentNode[]
+  ) {
+    super(storage, typePolicies, fragments, {
+      name: 'pod-service',
+      startByReadyFunction: false, // Need an explicit call to start()
+    });
+  }
+
+  profileSearch(
+    filter: WotSearchFilter,
+    options: { after?: string; first?: number; fetchPolicy?: FetchPolicy; total?: number; withTotal?: boolean }
+  ): Observable<LoadResult<Account>> {
+    console.info(`${this._logPrefix}Searching profile by filter...`, filter);
+
+    const offset = toNumber(+options?.after, 0);
+    const limit = toNumber(+options?.first, this.fetchSize);
+    const withTotal = offset === 0 && options?.withTotal !== false;
+    let data$: Observable<LoadResult<ProfileFragment>>;
+
+    // Load by unique address
+    if (isNotNilOrBlank(filter.address)) {
+      data$ = this.graphqlService
+        .profileByAddress(
+          {
+            address: filter.address,
+          },
+          options
+        )
+        .pipe(
+          map(({ data }) => {
+            return {
+              data: [data.profiles_by_pk as ProfileFragment],
+              total: isNil(data.profiles_by_pk) ? 0 : 1,
+            };
+          })
+        );
+    }
+
+    // Load by adresses
+    else if (isNotEmptyArray(filter.addresses)) {
+      data$ = this.graphqlService
+        .profileSearchByAddresses(
+          {
+            addresses: filter.addresses,
+          },
+          options
+        )
+        .pipe(
+          map(({ data }) => {
+            return {
+              data: data.profiles as ProfileFragment[],
+            };
+          })
+        );
+    } else if (isNotNilOrBlank(filter.searchText)) {
+      data$ = this.graphqlService
+        .profileSearchByText({
+          offset,
+          limit,
+          searchText: `%${filter.searchText}%`,
+          orderBy: [{ time: Order_By.Asc }, { title: Order_By.Asc }],
+          withTotal,
+        })
+        .pipe(
+          map(({ data }) => {
+            return {
+              data: data.profiles as ProfileFragment[],
+              total: data.profiles_aggregate?.aggregate.count,
+            };
+          })
+        );
+    } else {
+      return of(<LoadResult<Account>>{});
+    }
+
+    return data$.pipe(
+      map((res) => {
+        const data = AccountConverter.profileToAccounts(res.data, { ipfsGateway: this.ipfsService.gatewayBaseUrl });
+        const total = toNumber(res?.total, options?.total);
+        const result: LoadResult<Account> = { data, total };
+        const nextOffset = offset + limit;
+        if (isNotNil(total) && nextOffset < total) {
+          result.fetchMore = (first) => {
+            console.debug(`${this._logPrefix}Fetching more profiles - offset: ${nextOffset}`);
+            return firstValueFrom(
+              this.profileSearch(filter, { ...options, after: nextOffset.toString(), first: toNumber(first, options.first), total })
+            );
+          };
+        }
+        return result;
+      })
+    );
+  }
+
+  protected async ngOnStart(): Promise<PodState> {
+    if (!this.ipfsService.started) this.ipfsService.start();
+
+    // Wait settings and ipfs service
+    const [settings, currency] = await Promise.all([this.settings.ready(), firstNotNilPromise(this.currency$)]);
+
+    let peer = Peers.fromUri(settings.pod);
+    if (!peer) {
+      const peers = await this.filterAlivePeers(settings.preferredPods);
+      if (!peers.length) {
+        throw { message: 'ERROR.CHECK_NETWORK_CONNECTION' };
+      }
+      peer = arrayRandomPick(peers);
+    }
+
+    const client = await super.createClient(peer, 'pod');
+
+    return {
+      peer,
+      client,
+      currency,
+      offline: false,
+      fetchSize: this.defaultFetchSize,
+    };
+  }
+}
diff --git a/src/app/settings/settings.model.ts b/src/app/settings/settings.model.ts
index 21e160ab6aad573c947f9b6bde9a832f8822cff4..2414d4cac4b772433c903847aa7ebdec2e347b17 100644
--- a/src/app/settings/settings.model.ts
+++ b/src/app/settings/settings.model.ts
@@ -15,6 +15,10 @@ export interface Settings {
   preferredPeers?: string[];
   indexer: string;
   preferredIndexers?: string[];
+  pod: string;
+  preferredPods?: string[];
+  ipfsGateway: string;
+  preferredIpfsGateways?: string[];
   pages?: any;
   locale?: string;
   mobile?: boolean;
diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html
index efbd40eebe5db312f5adbd64219d4342ab8e6af6..1f3f12b4de7ec3d6eceefa33073642a11628fa2f 100644
--- a/src/app/settings/settings.page.html
+++ b/src/app/settings/settings.page.html
@@ -60,6 +60,7 @@
         </ion-button>
       </ion-item>
 
+      <!-- indexer -->
       <ion-item>
         <ion-icon slot="start" name="cloud-done"></ion-icon>
         <ion-label>
@@ -74,6 +75,36 @@
         </ion-button>
       </ion-item>
 
+      <!-- pod -->
+      <ion-item>
+        <ion-icon slot="start" name="cloud-done"></ion-icon>
+        <ion-label>
+          <h2 color="dark" translate>SETTINGS.POD</h2>
+          <p>
+            {{ pod }}
+          </p>
+        </ion-label>
+
+        <ion-button slot="end" (click)="selectPodModal.present()" [title]="'SETTINGS.POPUP_PEER.BTN_SHOW_LIST' | translate">
+          <ion-label>...</ion-label>
+        </ion-button>
+      </ion-item>
+
+      <!-- ipfs -->
+      <ion-item>
+        <ion-icon slot="start" name="cloud-done"></ion-icon>
+        <ion-label>
+          <h2 color="dark" translate>SETTINGS.IPFS</h2>
+          <p>
+            {{ ipfsGateway }}
+          </p>
+        </ion-label>
+
+        <ion-button slot="end" (click)="selectIpfsGatewayModal.present()" [title]="'SETTINGS.POPUP_PEER.BTN_SHOW_LIST' | translate">
+          <ion-label>...</ion-label>
+        </ion-button>
+      </ion-item>
+
       <ion-item-divider translate>SETTINGS.AUTHENTICATION_SETTINGS</ion-item-divider>
 
       <ion-item>
@@ -112,47 +143,76 @@
 <!-- Select peers modal -->
 <ion-modal #selectPeerModal [backdropDismiss]="true">
   <ng-template>
-    <ion-header>
-      <ion-toolbar color="secondary">
-        <ion-buttons slot="start">
-          <ion-button (click)="selectPeerModal.dismiss()" *ngIf="mobile">
-            <ion-icon slot="icon-only" name="arrow-back"></ion-icon>
-          </ion-button>
-        </ion-buttons>
-
-        <ion-title translate>SETTINGS.POPUP_PEER.BTN_SHOW_LIST</ion-title>
-      </ion-toolbar>
-    </ion-header>
-    <ion-content>
-      <ion-list>
-        <ion-item *rxFor="let peer of preferredPeers$" tappable (click)="selectPeer(peer)">
-          <ion-label>{{ peer }}</ion-label>
-        </ion-item>
-      </ion-list>
-    </ion-content>
+    <ng-container *ngTemplateOutlet="peerList; context: { $implicit: preferredPeers$, modal: selectPeerModal, property: 'peer' }"></ng-container>
   </ng-template>
 </ion-modal>
 
 <!-- select indexer modal -->
 <ion-modal #selectIndexerModal [backdropDismiss]="true">
   <ng-template>
-    <ion-header>
-      <ion-toolbar color="secondary">
-        <ion-buttons slot="start">
-          <ion-button (click)="selectIndexerModal.dismiss()" *ngIf="mobile">
-            <ion-icon slot="icon-only" name="arrow-back"></ion-icon>
-          </ion-button>
-        </ion-buttons>
-
-        <ion-title translate>SETTINGS.POPUP_PEER.BTN_SHOW_LIST</ion-title>
-      </ion-toolbar>
-    </ion-header>
-    <ion-content>
-      <ion-list>
-        <ion-item *rxFor="let peer of preferredIndexers$" tappable (click)="selectIndexer(peer)">
-          <ion-label>{{ peer }}</ion-label>
-        </ion-item>
-      </ion-list>
-    </ion-content>
+    <ng-container
+      *ngTemplateOutlet="peerList; context: { $implicit: preferredIndexers$, modal: selectIndexerModal, property: 'indexer' }"
+    ></ng-container>
+  </ng-template>
+</ion-modal>
+
+<!-- select pod modal -->
+<ion-modal #selectPodModal [backdropDismiss]="true">
+  <ng-template>
+    <ng-container *ngTemplateOutlet="peerList; context: { $implicit: preferredPods$, modal: selectPodModal, property: 'pod' }"></ng-container>
+  </ng-template>
+</ion-modal>
+
+<!-- select IPFS modal -->
+<ion-modal #selectIpfsGatewayModal [backdropDismiss]="true">
+  <ng-template>
+    <ng-container
+      *ngTemplateOutlet="peerList; context: { $implicit: preferredIpfsGateways$, modal: selectIpfsGatewayModal, property: 'ipfsGateway' }"
+    ></ng-container>
   </ng-template>
 </ion-modal>
+
+<ng-template #peerList let-peers$ let-property="property" let-modal="modal">
+  <ion-header>
+    <ion-toolbar color="secondary">
+      <ion-buttons slot="start">
+        <ion-button (click)="modal.dismiss()" *ngIf="mobile">
+          <ion-icon slot="icon-only" name="arrow-back"></ion-icon>
+        </ion-button>
+      </ion-buttons>
+
+      <ion-title translate>SETTINGS.POPUP_PEER.BTN_SHOW_LIST</ion-title>
+    </ion-toolbar>
+  </ion-header>
+  <ion-content>
+    <ion-list>
+      <ion-item
+        *rxFor="let peer of peers$"
+        tappable
+        (click)="setStateValue(peer, property) && modal.dismiss()"
+        [class.selected]="peer === this[property]"
+      >
+        <ion-label>{{ peer }}</ion-label>
+        @if (property && peer === this[property]) {
+          <ion-icon slot="end" name="checkmark"></ion-icon>
+        }
+      </ion-item>
+    </ion-list>
+  </ion-content>
+  @if (!mobile) {
+    <ion-footer>
+      <ion-toolbar>
+        <ion-row class="ion-no-padding">
+          <ion-col></ion-col>
+
+          <!-- buttons -->
+          <ion-col size="auto">
+            <ion-button fill="clear" color="dark" (click)="modal.dismiss()">
+              <ion-label translate>COMMON.BTN_CANCEL</ion-label>
+            </ion-button>
+          </ion-col>
+        </ion-row>
+      </ion-toolbar>
+    </ion-footer>
+  }
+</ng-template>
diff --git a/src/app/settings/settings.page.scss b/src/app/settings/settings.page.scss
index 5fdf1824ec6e13554ebc39e3cb271dff7e57ea7b..77f11754471800b8d00ca8c3b3adbc6d34e0b919 100644
--- a/src/app/settings/settings.page.scss
+++ b/src/app/settings/settings.page.scss
@@ -1,2 +1,9 @@
 #container {
 }
+
+ion-list {
+  ion-item.selected {
+    --background: rgba(var(--ion-color-primary-rgb), 0.14);
+    --color: var(--ion-color-primary);
+  }
+}
diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts
index 0262e8214d1e7a8bdb6e89cf0ec5e2635b41f4c1..dc8ac6326b1bd1932c217011a2367d016bc2086e 100644
--- a/src/app/settings/settings.page.ts
+++ b/src/app/settings/settings.page.ts
@@ -55,6 +55,10 @@ export class SettingsPage extends AppPage<SettingsPageState> implements OnInit {
   @RxStateSelect() peer$: Observable<string>;
   @RxStateSelect() preferredIndexers$: Observable<string[]>;
   @RxStateSelect() indexer$: Observable<string>;
+  @RxStateSelect() preferredPods$: Observable<string[]>;
+  @RxStateSelect() pod$: Observable<string>;
+  @RxStateSelect() preferredIpfsGateways$: Observable<string[]>;
+  @RxStateSelect() ipfsGateway$: Observable<string>;
   @RxStateSelect() dirty$: Observable<boolean>;
 
   @RxStateProperty() darkMode: boolean;
@@ -62,11 +66,15 @@ export class SettingsPage extends AppPage<SettingsPageState> implements OnInit {
   @RxStateProperty() useRelativeUnit: boolean;
   @RxStateProperty() peer: string;
   @RxStateProperty() indexer: string;
+  @RxStateProperty() pod: string;
+  @RxStateProperty() ipfsGateway: string;
   @RxStateProperty() unAuthDelayMs: number;
   @RxStateProperty() dirty: boolean;
 
   @ViewChild('selectPeerModal') selectPeerModal: IonModal;
   @ViewChild('selectIndexerModal') selectIndexerModal: IonModal;
+  @ViewChild('selectPodModal') selectPodModal: IonModal;
+  @ViewChild('selectIpfsGatewayModal') selectIpfsGatewayModal: IonModal;
 
   constructor(
     protected networkService: NetworkService,
@@ -79,13 +87,16 @@ export class SettingsPage extends AppPage<SettingsPageState> implements OnInit {
     this._state.connect('useRelativeUnit', this._state.select('displayUnit').pipe(map((unit) => unit === 'du')));
 
     // Detect changes
-    this._state.hold(this._state.select(['locale', 'peer', 'indexer', 'unAuthDelayMs', 'displayUnit'], (s) => s).pipe(skip(1)), () => {
-      if (this.mobile) {
-        this.save();
-      } else {
-        this.markAsDirty();
+    this._state.hold(
+      this._state.select(['locale', 'peer', 'indexer', 'pod', 'ipfsGateway', 'unAuthDelayMs', 'displayUnit'], (s) => s).pipe(skip(1)),
+      () => {
+        if (this.mobile) {
+          this.save();
+        } else {
+          this.markAsDirty();
+        }
       }
-    });
+    );
   }
 
   protected async ngOnLoad() {
@@ -106,14 +117,9 @@ export class SettingsPage extends AppPage<SettingsPageState> implements OnInit {
     this.dirty = false;
   }
 
-  selectPeer(peer: string) {
-    this.peer = peer;
-    this.selectPeerModal.dismiss();
-  }
-
-  selectIndexer(peer: string) {
-    this.indexer = peer;
-    this.selectIndexerModal.dismiss();
+  setStateValue(value: string, property: keyof Settings) {
+    this._state.set(property, () => value);
+    return true;
   }
 
   markAsDirty() {
diff --git a/src/app/settings/settings.service.ts b/src/app/settings/settings.service.ts
index ae9578bacdeaf9fbad1fc6c761ce1950519e6f81..caced262851f9a5a5168f94f42ef9979d5c16011 100644
--- a/src/app/settings/settings.service.ts
+++ b/src/app/settings/settings.service.ts
@@ -81,9 +81,13 @@ export class SettingsService extends RxStartableService<SettingsState> {
       locale: environment.defaultLocale,
       peer: environment.dev?.peer || environment.defaultPeers?.[0],
       indexer: environment.dev?.indexer || environment.defaultIndexers?.[0],
+      pod: environment.dev?.pod || environment.defaultPods?.[0],
+      ipfsGateway: environment.dev?.ipfsGateway || environment.defaultIfpsGateways?.[0],
       ...this.get(),
       preferredPeers: arrayDistinct([...environment.defaultPeers, ...(data?.preferredPeers || [])]),
       preferredIndexers: arrayDistinct([...environment.defaultIndexers, ...(data?.preferredIndexers || [])]),
+      preferredPods: arrayDistinct([...environment.defaultPods, ...(data?.preferredPods || [])]),
+      preferredIpfsGateways: arrayDistinct([...environment.defaultIfpsGateways, ...(data?.preferredIpfsGateways || [])]),
     };
   }
 
@@ -98,6 +102,8 @@ export class SettingsService extends RxStartableService<SettingsState> {
       // Merge default and restored data
       preferredPeers: arrayDistinct([...environment.defaultPeers, ...(data?.preferredPeers || [])]),
       preferredIndexers: arrayDistinct([...environment.defaultIndexers, ...(data?.preferredIndexers || [])]),
+      preferredPods: arrayDistinct([...environment.defaultPods, ...(data?.preferredPods || [])]),
+      preferredIpfsGateways: arrayDistinct([...environment.defaultIfpsGateways, ...(data?.preferredIpfsGateways || [])]),
     };
   }
 
diff --git a/src/app/shared/pipes/account.pipes.ts b/src/app/shared/pipes/account.pipes.ts
index db2c22e1186562d175ba87baf82c1f555354a46b..3ab04af15ee050f48c089c0f47f4e0802058a4f9 100644
--- a/src/app/shared/pipes/account.pipes.ts
+++ b/src/app/shared/pipes/account.pipes.ts
@@ -4,12 +4,15 @@ import { equals, getPropertyByPath } from '@app/shared/functions';
 import { Subscription } from 'rxjs';
 import { AccountsService, LoadAccountDataOptions } from '@app/account/accounts.service';
 
+export interface AccountAbstractPipeOptions {
+  listenChanges?: boolean;
+}
 // @dynamic
 /**
  * A common pipe, that will subscribe to all account changes, to refresh its value
  */
 @Injectable()
-export abstract class AccountAbstractPipe<T, O> implements PipeTransform {
+export abstract class AccountAbstractPipe<T, O extends Object = AccountAbstractPipeOptions> implements PipeTransform {
   private value: T = null;
   private _lastAccount: Partial<Account> | null = null;
   private _lastOptions: O = null;
@@ -22,11 +25,11 @@ export abstract class AccountAbstractPipe<T, O> implements PipeTransform {
     private _watchOptions?: LoadAccountDataOptions
   ) {}
 
-  transform(account: Partial<Account>, opts: O): T {
+  transform(account: Partial<Account>, opts?: O): T {
     // Not a user account (e.g. any wot identity)
     if (!account?.address) {
       this._dispose();
-      return this._transform(account);
+      return this._transform(account, opts);
     }
 
     // if we ask another time for the same account and opts, return the last value
@@ -46,8 +49,8 @@ export abstract class AccountAbstractPipe<T, O> implements PipeTransform {
     // if there is a subscription to onLangChange, clean it
     this._dispose();
 
-    // subscribe to onTranslationChange event, in case the translations change
-    if (!this._changesSubscription) {
+    // subscribe to account changes
+    if (!this._changesSubscription && opts?.['listenChanges'] !== false) {
       this._changesSubscription = this._accountsService.watchByAddress(account.address, this._watchOptions).subscribe((updatedAccount) => {
         this.value = this._transform(updatedAccount, opts);
         this._cd.markForCheck();
@@ -76,7 +79,7 @@ export abstract class AccountAbstractPipe<T, O> implements PipeTransform {
   }
 }
 
-export declare type AccountPropertyPipeOptions<T> = string | { key?: string; defaultValue?: T };
+export declare type AccountPropertyPipeOptions<T> = string | (AccountAbstractPipeOptions & { key?: string; defaultValue?: T });
 
 @Pipe({
   name: 'accountProperty',
@@ -106,7 +109,7 @@ export class AccountPropertyPipe<T = never, O extends AccountPropertyPipeOptions
   name: 'balance',
   pure: false,
 })
-export class AccountBalancePipe extends AccountAbstractPipe<number, void> implements PipeTransform {
+export class AccountBalancePipe extends AccountAbstractPipe<number> implements PipeTransform {
   constructor(cd: ChangeDetectorRef) {
     super(cd, { withBalance: true });
   }
@@ -120,7 +123,7 @@ export class AccountBalancePipe extends AccountAbstractPipe<number, void> implem
   name: 'accountName',
   pure: false,
 })
-export class AccountNamePipe extends AccountAbstractPipe<string, void> implements PipeTransform {
+export class AccountNamePipe extends AccountAbstractPipe<string> implements PipeTransform {
   constructor(cd: ChangeDetectorRef) {
     super(cd, { withBalance: false });
   }
@@ -134,13 +137,13 @@ export class AccountNamePipe extends AccountAbstractPipe<string, void> implement
   name: 'isMemberAccount',
   pure: false,
 })
-export class IsMemberAccountPipe extends AccountAbstractPipe<boolean, void> implements PipeTransform {
+export class IsMemberAccountPipe extends AccountAbstractPipe<boolean> implements PipeTransform {
   constructor(cd: ChangeDetectorRef) {
     super(cd, { withBalance: false, withMembership: true });
   }
 
   protected _transform(account: Partial<Account>): boolean {
-    return (account && account.meta && account.meta.isMember === true) || false;
+    return (account?.meta && account.meta.isMember === true) || false;
   }
 }
 
diff --git a/src/app/shared/pipes/block-number.pipe.ts b/src/app/shared/pipes/block-number.pipe.ts
index 6d6cd1f40fa10e6f6ebf1649528dab965513c696..45d249b8dcbb0b1214c1c0b8e5671307a5641e0e 100644
--- a/src/app/shared/pipes/block-number.pipe.ts
+++ b/src/app/shared/pipes/block-number.pipe.ts
@@ -1,5 +1,5 @@
 import { Pipe, PipeTransform } from '@angular/core';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { isNil } from '@app/shared/functions';
 import { SettingsService } from '@app/settings/settings.service';
 
diff --git a/src/app/shared/pipes/block-timestamp.pipe.ts b/src/app/shared/pipes/block-timestamp.pipe.ts
index 5b4009245e537e6709400a30302301a372d31f86..1c2c5d42df246bd5a84c8cd71d850c78ec3507cb 100644
--- a/src/app/shared/pipes/block-timestamp.pipe.ts
+++ b/src/app/shared/pipes/block-timestamp.pipe.ts
@@ -3,7 +3,7 @@ import { Moment } from 'moment';
 import { DateUtils } from '@app/shared/dates';
 import { NetworkService } from '@app/network/network.service';
 import { isNil } from '@app/shared/functions';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 
 @Pipe({
   name: 'blockTime',
diff --git a/src/app/shared/services/network/graphql/graphql.service.ts b/src/app/shared/services/network/graphql/graphql.service.ts
index 2997ae12f6cc5732215f538e288ad3cfc8fd4529..3578345b8f43ce9b702b0659a5d13240a55baf17 100644
--- a/src/app/shared/services/network/graphql/graphql.service.ts
+++ b/src/app/shared/services/network/graphql/graphql.service.ts
@@ -52,6 +52,7 @@ import { environment } from '@environments/environment';
 import { RxStartableService, RxStartableServiceOptions } from '@app/shared/services/rx-startable-service.class';
 import { Peer, Peers } from '../peer.model';
 import { RxStateProperty, RxStateSelect } from '@app/shared/decorator/state.decorator';
+import { Promise } from '@rx-angular/cdk/zone-less/browser';
 // Workaround for issue https://github.com/ng-packagr/ng-packagr/issues/2215
 const QueueLink = unwrapESModule(queueLinkImported);
 const SerializingLink = unwrapESModule(serializingLinkImported);
@@ -929,4 +930,30 @@ export abstract class GraphqlService<
     }
     return undefined;
   }
+
+  protected async filterAlivePeers(
+    peers: string[],
+    opts?: {
+      timeout?: number;
+    }
+  ): Promise<Peer[]> {
+    return (
+      await Promise.all(
+        peers.map((peer) => Peers.fromUri(peer)).map((peer) => this.isPeerAlive(peer, opts).then((alive) => (alive ? peer : undefined)))
+      )
+    ).filter(isNotNil);
+  }
+
+  protected async isPeerAlive(
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    peer: Peer,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    opts?: {
+      timeout?: number;
+    }
+  ): Promise<boolean> {
+    // TODO
+    console.log(`${this._logPrefix}TODO: implement ${this.constructor.name}.isPeerAlive()`, peer);
+    return Promise.resolve(true);
+  }
 }
diff --git a/src/app/shared/services/service.model.ts b/src/app/shared/services/service.model.ts
index 185beeb51b2b69f3378937259f39162a0f05b150..5cd1fa151e38d56a5ddff2277e71c12bd4773d68 100644
--- a/src/app/shared/services/service.model.ts
+++ b/src/app/shared/services/service.model.ts
@@ -1,4 +1,5 @@
 import { getPropertyByPathAsString, isNotNil, isNotNilOrBlank, matchUpperCase, startsWithUpperCase } from '../functions';
+import { Promise } from '@rx-angular/cdk/zone-less/browser';
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 export declare type ReadyAsyncFunction<T = any> = () => Promise<T>;
@@ -64,6 +65,59 @@ export function suggestFromArray<T = any>(
   };
 }
 
+export function combineLoadResults<T>(
+  results: LoadResult<T>[],
+  options?: {
+    reduce: (value: T[]) => T[];
+    data?: T[];
+    total?: number;
+  }
+): LoadResult<T> {
+  let data = options?.data || [];
+  const offset = data?.length || 0;
+
+  // Compute data
+  data = data.concat(results.map((r) => r?.data || []).flat());
+
+  // Reduce (e.g. remove duplicated)
+  if (typeof options?.reduce === 'function') {
+    data = options.reduce(data);
+  }
+
+  // Truncate data
+  const newData = offset > 0 ? data.slice(offset) : data;
+
+  // Compute total
+  let total = isNotNil(options?.total)
+    ? options.total
+    : results
+        .map((r) => r?.total)
+        .filter(isNotNil)
+        .reduce((max, total) => Math.max(max || 0, total), -1);
+  if (total === -1) total = undefined;
+
+  // Compute fetch more
+  const fetchMoreFns = results.map((r) => r?.fetchMore).filter(isNotNil);
+  const fetchMore = combineFetchMore(fetchMoreFns, { ...options, data, total });
+
+  return { data: newData, total, fetchMore };
+}
+
+export function combineFetchMore<T>(
+  fetchMoreFns: FetchMoreFn<LoadResult<T>>[],
+  options?: {
+    reduce: (value: T[]) => T[];
+    data?: T[];
+    total?: number;
+  }
+): FetchMoreFn<LoadResult<T>> {
+  if (!fetchMoreFns?.length || (options?.data && !options.data.length)) return undefined;
+  return async (first) => {
+    const results = await Promise.all(fetchMoreFns.map((fetchMoreFn) => fetchMoreFn(first)));
+    return combineLoadResults(results, options);
+  };
+}
+
 export interface IStartableService<T> {
   started: boolean;
 
diff --git a/src/app/transfer/history/transfer-history.page.ts b/src/app/transfer/history/transfer-history.page.ts
index cc60c09aec194979f0fa7782c80b3f78312ac6eb..f4e421c66e766de7f1b6d7ebe81a0774a0a182a6 100644
--- a/src/app/transfer/history/transfer-history.page.ts
+++ b/src/app/transfer/history/transfer-history.page.ts
@@ -18,7 +18,7 @@ import {
   TransferSearchFilter,
   TransferSearchFilterUtils,
 } from '@app/transfer/transfer.model';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { FetchMoreFn, LoadResult } from '@app/shared/services/service.model';
 
 export interface TransferHistoryPageState extends AppPageState {
diff --git a/src/app/transfer/transfer.model.ts b/src/app/transfer/transfer.model.ts
index a00ad97e0649f4f64999898430d2d0e26b77cc95..b214e87da01c58fbb8f1992fc67055df1c26d463 100644
--- a/src/app/transfer/transfer.model.ts
+++ b/src/app/transfer/transfer.model.ts
@@ -2,7 +2,7 @@ import { InjectionToken } from '@angular/core';
 import { Account, parseAddressSquid } from '@app/account/account.model';
 import { Moment } from 'moment/moment';
 import { equals, isNil, isNilOrBlank } from '@app/shared/functions';
-import { TransferFragment } from '@app/network/indexer-types.generated';
+import { TransferFragment } from '@app/network/indexer/indexer-types.generated';
 import { fromDateISOString } from '@app/shared/dates';
 import { AccountConverter } from '@app/account/account.converter';
 
@@ -53,10 +53,10 @@ export class TransferConverter {
     const toAddress = parseAddressSquid(item.to?.id).address;
     // Account is the issuer
     if (fromAddress === accountAddress) {
-      to = AccountConverter.toAccount(item.to);
+      to = AccountConverter.squidToAccount(item.to);
       amount = -1 * item.amount;
     } else if (toAddress === accountAddress) {
-      from = AccountConverter.toAccount(item.from);
+      from = AccountConverter.squidToAccount(item.from);
       amount = item.amount;
     }
     return <Transfer>{
diff --git a/src/app/wot/wot-details.page.ts b/src/app/wot/wot-details.page.ts
index 023a2c625ad54c77bc80114bdce3ba220da0a915..5dd7ec3b281c3939f073dd1aa15f8b6c2d080bcc 100644
--- a/src/app/wot/wot-details.page.ts
+++ b/src/app/wot/wot-details.page.ts
@@ -10,7 +10,7 @@ import { RxState } from '@rx-angular/state';
 import { APP_TRANSFER_CONTROLLER, ITransferController } from '@app/transfer/transfer.model';
 import { filter, map } from 'rxjs/operators';
 import { firstArrayValue, isNotNilOrBlank } from '@app/shared/functions';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { address2PubkeyV1, pubkeyV1Checksum } from '@app/shared/currencies';
 
 export interface WotDetailsPageState extends AppPageState {
diff --git a/src/app/wot/wot-lookup.page.html b/src/app/wot/wot-lookup.page.html
index 0cdc591d1228e5728ebe58b24f47f1ebbbc93d59..68df96ce6a3f822281805046b1a6f42f86da3d93 100644
--- a/src/app/wot/wot-lookup.page.html
+++ b/src/app/wot/wot-lookup.page.html
@@ -112,18 +112,37 @@
             </ion-avatar>
             <ion-label>
               <h2>
-                <ion-text [color]="item.meta?.isMember ? 'primary' : 'dark'">
-                  <small><ion-icon name="person"></ion-icon></small>
+                <ion-text [color]="item.meta?.isMember && !item.meta.name ? 'primary' : 'dark'">
+                  @if (!item.meta?.name) {
+                    <ion-icon name="person"></ion-icon>
+                  }
                   @if (filter?.last) {
-                    {{ item.meta?.uid }}
+                    <span>{{ item | accountName: { listenChanges: false } }}</span>
                   } @else {
-                    <span [innerHTML]="item.meta?.uid | highlight: { search: searchText }"></span>
+                    <span [innerHTML]="item | accountName: { listenChanges: false } | highlight: { search: searchText }"></span>
                   }
                 </ion-text>
               </h2>
               <p>
-                <ion-icon name="key"></ion-icon>
-                {{ item.address | addressFormat }}
+                @if (item.meta.name && item.meta.uid) {
+                  <ion-text [color]="item.meta?.isMember ? 'primary' : 'dark'">
+                    <ion-icon name="person"></ion-icon>
+                    @if (filter?.last) {
+                      <span>{{ item.meta.uid }}</span>
+                    } @else {
+                      <span [innerHTML]="item.meta.uid | highlight: { search: searchText }"></span>
+                    }
+                  </ion-text>
+                }
+                <!-- address -->
+                <ion-text>
+                  <ion-icon name="key"></ion-icon>
+                  <span>{{ item.address | addressFormat }}</span>
+                </ion-text>
+                <!-- not member -->
+                @if (!item.meta?.isMember) {
+                  <ion-text color="danger" translate>WOT.NOT_MEMBER_PARENTHESIS</ion-text>
+                }
               </p>
             </ion-label>
             <ion-button
diff --git a/src/app/wot/wot-lookup.page.scss b/src/app/wot/wot-lookup.page.scss
index 3fa413da5fcdc18866f08a19a23a7d1dc876e549..068b4d1217b51e524c6c4c84c47123b94ac80ba8 100644
--- a/src/app/wot/wot-lookup.page.scss
+++ b/src/app/wot/wot-lookup.page.scss
@@ -1,3 +1,25 @@
-ion-list.list-md {
-  padding-top: 0;
+ion-list {
+  &.list-md {
+    padding-top: 0;
+  }
+
+  --text-padding: 4px;
+  p {
+    --text-padding: 2px;
+  }
+
+  ion-item {
+    ion-text {
+      ion-icon {
+        width: 0.9em;
+        height: 0.9em;
+        vertical-align: baseline;
+      }
+      padding-inline-end: calc(var(--text-padding) * 2);
+    }
+
+    ion-text:has(ion-icon) span {
+      margin-inline-start: var(--text-padding);
+    }
+  }
 }
diff --git a/src/app/wot/wot-lookup.page.ts b/src/app/wot/wot-lookup.page.ts
index 1cf4cea6b422ad49f0176850a3c9c35026d88c90..9debd33cb377488421282c1162f2c802cb90005b 100644
--- a/src/app/wot/wot-lookup.page.ts
+++ b/src/app/wot/wot-lookup.page.ts
@@ -13,9 +13,10 @@ import { RxState } from '@rx-angular/state';
 import { InfiniteScrollCustomEvent, IonPopover, ModalController } from '@ionic/angular';
 
 import { APP_TRANSFER_CONTROLLER, ITransferController } from '@app/transfer/transfer.model';
-import { IndexerService } from '@app/network/indexer.service';
+import { IndexerService } from '@app/network/indexer/indexer.service';
 import { FetchMoreFn, LoadResult } from '@app/shared/services/service.model';
 import { environment } from '@environments/environment';
+import { WotService } from '@app/wot/wot.service';
 
 export interface WotLookupState extends AppPageState {
   searchText: string;
@@ -70,6 +71,7 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit, Wo
 
   constructor(
     private indexerService: IndexerService,
+    private wotService: WotService,
     private modalCtrl: ModalController,
     @Inject(APP_TRANSFER_CONTROLLER) private transferController: ITransferController
   ) {
@@ -149,7 +151,7 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit, Wo
 
   search(searchFilter?: WotSearchFilter, options?: { first: number; after: string }): Observable<LoadResult<Account>> {
     try {
-      return this.indexerService.wotSearch(searchFilter, options).pipe(
+      return this.wotService.wotSearch(searchFilter, options).pipe(
         filter(() => WotSearchFilterUtils.isEquals(this.filter, searchFilter)),
         tap(() => this.markAsLoaded())
       );
@@ -202,7 +204,9 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit, Wo
     await this.waitIdle();
 
     if (this.canFetchMore) {
-      console.debug(this._logPrefix + 'Fetching more items, from offset: ' + this.count, event);
+      // DEBUG
+      //console.debug(this._logPrefix + 'Fetching more items, from offset: ' + this.count, event);
+
       const { data, fetchMore } = await this.fetchMoreFn();
 
       if (data?.length) {
diff --git a/src/app/wot/wot.model.ts b/src/app/wot/wot.model.ts
index 61a70e22df77d01a36ca1d52c6a29d07ba4f3bd6..2e1b5ebf5c364960ff3d8959b80e46654f8f6967 100644
--- a/src/app/wot/wot.model.ts
+++ b/src/app/wot/wot.model.ts
@@ -14,6 +14,7 @@ export interface WotLookupOptions {
 
 export interface WotSearchFilter {
   address?: string;
+  addresses?: string[];
   searchText?: string;
   last?: boolean;
   pending?: boolean;
diff --git a/src/app/wot/wot.service.ts b/src/app/wot/wot.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33cf2846b18f30893054112c24830332db4399b3
--- /dev/null
+++ b/src/app/wot/wot.service.ts
@@ -0,0 +1,65 @@
+import { Injectable } from '@angular/core';
+import { StartableService } from '@app/shared/services/startable-service.class';
+import { IndexerService } from '@app/network/indexer/indexer.service';
+import { PodService } from '@app/network/pod/pod.service';
+import { WotSearchFilter } from '@app/wot/wot.model';
+import { FetchPolicy } from '@apollo/client';
+import { Promise } from '@rx-angular/cdk/zone-less/browser';
+import { combineLatestAll, concat, firstValueFrom, mergeMap, Observable, toArray } from 'rxjs';
+import { LoadResult } from '@app/shared/services/service.model';
+import { Account, AccountUtils } from '@app/account/account.model';
+import { map } from 'rxjs/operators';
+import { isNotEmptyArray, isNotNil } from '@app/shared/functions';
+
+@Injectable({ providedIn: 'root' })
+export class WotService extends StartableService {
+  constructor(
+    private indexer: IndexerService,
+    private pod: PodService
+  ) {
+    super();
+  }
+
+  protected async ngOnStart(): Promise<void> {
+    await Promise.all([this.indexer.ready(), this.pod.ready()]);
+  }
+
+  wotSearch(filter: WotSearchFilter, options: { after?: string; first?: number; fetchPolicy?: FetchPolicy }): Observable<LoadResult<Account>> {
+    const search1$ = this.indexer.wotSearch(filter, options).pipe(toArray());
+    const search2$ = this.pod.profileSearch(filter, options).pipe(toArray());
+
+    return concat(search1$, search2$).pipe(
+      combineLatestAll(),
+      map(AccountUtils.combineAccountLoadResults),
+      mergeMap((res) => this.decorateWithProfiles(res))
+    );
+  }
+
+  async decorateWithProfiles(result: LoadResult<Account>): Promise<LoadResult<Account>> {
+    // Get addresses without profiles
+    const noProfilesAddresses = (result?.data || [])
+      .filter((account) => !account.meta?.name)
+      .map((account) => account.address)
+      .filter(isNotNil);
+
+    // Load profiles from addresses
+    if (isNotEmptyArray(noProfilesAddresses)) {
+      console.debug(`${this._logPrefix}Loading profiles from ${noProfilesAddresses.length} account's addresses...`);
+      const profiles = (await firstValueFrom(this.pod.profileSearch({ addresses: noProfilesAddresses }, { withTotal: false })))?.data;
+      if (isNotEmptyArray(profiles)) {
+        AccountUtils.mergeAll(result.data.concat(profiles));
+      }
+    }
+
+    // Decorate fetchMore
+    if (result.fetchMore) {
+      const inheritedFetchMore = result.fetchMore;
+      result.fetchMore = async (first) => {
+        const moreResult = await inheritedFetchMore(first);
+        return this.decorateWithProfiles(moreResult);
+      };
+    }
+
+    return result;
+  }
+}
diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json
index ed976ebe9e8282963efd3d9510506c2ff957cdf0..7a405c784ab26dc9d48e3be544ffba33b3fe1247 100644
--- a/src/assets/i18n/fr.json
+++ b/src/assets/i18n/fr.json
@@ -146,6 +146,8 @@
     "PEER": "Nœud Duniter",
     "PEER_SHORT": "Nœud Duniter",
     "INDEXER": "Indexeur de données",
+    "POD": "Serveur de données",
+    "IPFS": "Passerelle IPFS",
     "PEER_CHANGED_TEMPORARY": "Adresse utilisée temporairement",
     "PERSIST_CACHE": "Conserver les données de navigation (expérimental)",
     "PERSIST_CACHE_HELP": "Permet une navigation plus rapide, en conservant localement les données reçues, pour les utiliser d'une session à l'autre.",
diff --git a/src/environments/environment.class.ts b/src/environments/environment.class.ts
index 3e7b41824932984bfa4af1fcd9f6086d319e0022..bf0a8c400c656a12aeba6b12e837be3b60050f09 100644
--- a/src/environments/environment.class.ts
+++ b/src/environments/environment.class.ts
@@ -15,6 +15,8 @@ export interface Environment {
 
   defaultPeers: string[];
   defaultIndexers: string[];
+  defaultPods: string[];
+  defaultIfpsGateways: string[];
 
   // GraphQL
   graphql: {
@@ -37,6 +39,8 @@ export interface Environment {
     // Default peer
     peer?: string;
     indexer?: string;
+    pod?: string;
+    ipfsGateway?: string;
 
     // Load polkadot default account (alice, etc.)
     testingAccounts?: boolean;
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 49f97e23f36169184492d967e202e314361b0bc6..7b72aa070c0fbb7eed114f044212a8e992deaa0d 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -50,6 +50,7 @@ export const environment = <Environment>{
   },
 
   defaultPeers: [
+    /* Local endpoint */
     //'ws://127.0.0.1:9944',
     /* GDev endpoints */
     'wss://gdev.coinduf.eu/ws',
@@ -61,8 +62,25 @@ export const environment = <Environment>{
   ],
 
   defaultIndexers: [
+    /* Local endpoint */
+    //'http://localhost:8080/v1/graphql',
+    /* GDev endpoints */
     'https://gdev-squid.axiom-team.fr/v1beta1/relay',
     'https://squid.gdev.coinduf.eu/v1beta1/relay',
     //'https://gdev-squid.axiom-team.fr/graphql'
   ],
+
+  defaultPods: [
+    /* Local endpoint */
+    // 'http://localhost:8081/v1/graphql'
+    /* GDev endpoints */
+    'https://datapod.coinduf.eu/v1/graphql',
+  ],
+
+  defaultIfpsGateways: [
+    /* Local endpoint */
+    // 'http://localhost:8080'
+    /* GDev endpoints */
+    'https://pagu.re',
+  ],
 };
diff --git a/src/theme/_cesium.scss b/src/theme/_cesium.scss
index e397052be0b1fc9b290806489dbf6ad03cb40cff..a1f3690755e9c6f8884ae91de42f9e645add2aa6 100644
--- a/src/theme/_cesium.scss
+++ b/src/theme/_cesium.scss
@@ -57,6 +57,8 @@ ion-list {
       --border-radius: 5px !important;
       --border-width: 1px !important;
       --border-color: var(--ion-color-step-150) !important;
+      overflow: hidden;
+      border: var(--border-width, 1px) solid var(--border-color, grey);
     }
 
     a {