diff --git a/packages/host-chat/README.md b/packages/host-chat/README.md
index 25ca90b2..b027cab7 100644
--- a/packages/host-chat/README.md
+++ b/packages/host-chat/README.md
@@ -1,297 +1,102 @@
-# @novasamatech/host-container
+# @novasamatech/host-chat
-A robust solution for hosting and managing decentralized applications (dapps) within the Polkadot ecosystem.
+Account lookup and chat-message codecs for host applications integrating with the Polkadot People chain.
## Overview
-Host container provides the infrastructure layer for securely embedding and communicating with third-party dapps.
-It handles the isolation boundary, message routing, lifecycle management, and security concerns inherent in hosting untrusted web content.
+`@novasamatech/host-chat` exposes the read side of the chat domain: discovering Polkadot
+accounts by username and resolving their on-chain identity from `Resources.Consumers`. It
+also publishes the SCALE codecs used by the chat wire protocol (messages, attachments,
+local-message envelopes) so host applications can decode statements they receive over the
+statement store.
+
+The package is UI-framework agnostic. The main entry point returns plain async functions
+backed by [`neverthrow`](https://github.com/supermacro/neverthrow) `ResultAsync`, and the
+codec exports are pure SCALE codecs with no runtime side effects.
## Installation
```shell
-npm install @novasamatech/host-container --save -E
-```
-
-### Basic Container Setup
-
-```ts
-import { createContainer, createIframeProvider } from '@novasamatech/host-container';
-
-const iframe = document.createElement('iframe');
-
-const provider = createIframeProvider({
- iframe,
- url: 'https://dapp.example.com'
-});
-const container = createContainer(provider);
-
-document.body.appendChild(iframe);
-```
-
-## API reference
-
-### handleFeature
-
-```ts
-container.handleFeature((params, { ok, err }) => {
- if (params.tag === 'Chat') {
- return ok(supportedChains.has(params.value));
- }
- return ok(false);
-});
-```
-
-### handlePermissionRequest
-
-```ts
-container.handlePermissionRequest(async (params, { ok, err }) => {
- if (params.tag === 'ChainConnect') {
- // Show permission dialog to user
- const approved = await showPermissionDialog(params.value);
- return approved ? ok(undefined) : err({ tag: 'Rejected' });
- }
- return err({ tag: 'Unknown', value: { reason: 'Unsupported permission type' } });
-});
-```
-
-### handleStorageRead
-
-```ts
-container.handleStorageRead(async (key, { ok, err }) => {
- const value = await storage.get(key);
- return ok(value ?? null);
-});
-```
-
-### handleStorageWrite
-
-```ts
-container.handleStorageWrite(async ([key, value], { ok, err }) => {
- try {
- await storage.set(key, value);
- return ok(undefined);
- } catch (e) {
- return err({ tag: 'Full' });
- }
-});
-```
-
-### handleStorageClear
-
-```ts
-container.handleStorageClear(async (key, { ok, err }) => {
- await storage.delete(key);
- return ok(undefined);
-});
-```
-
-### handleAccountGet
-
-```ts
-container.handleAccountGet(async ([dotnsId, derivationIndex], { ok, err }) => {
- const account = await getProductAccount(dotnsId, derivationIndex);
- if (account) {
- return ok({ publicKey: account.publicKey, name: account.name ?? null });
- }
- return err({ tag: 'NotConnected' });
-});
-```
-
-### handleAccountGetAlias
-
-```ts
-container.handleAccountGetAlias(async ([dotnsId, derivationIndex], { ok, err }) => {
- const alias = await getAccountAlias(dotnsId, derivationIndex);
- if (alias) {
- return ok({ context: alias.context, alias: alias.alias });
- }
- return err(new RequestCredentialsErr.NotConnected());
-});
-```
-
-### handleAccountCreateProof
-
-```ts
-container.handleAccountCreateProof(async ([[dotnsId, derivationIndex], ringLocation, message], { ok, err }) => {
- try {
- const proof = await createRingProof(dotnsId, derivationIndex, ringLocation, message);
- return ok(proof);
- } catch (e) {
- return err({ tag: 'RingNotFound' });
- }
-});
-```
-
-### handleGetLegacyAccounts
-
-```ts
-container.handleGetLegacyAccounts(async (_, { ok, err }) => {
- const accounts = await getLegacyAccounts();
- return ok(accounts);
-});
-```
-
-### handleCreateTransaction
-
-```ts
-container.handleCreateTransaction(async ([productAccountId, payload], { ok, err }) => {
- try {
- const signedTx = await createTransaction(productAccountId, payload);
- return ok(signedTx);
- } catch (e) {
- return err({ tag: 'Rejected' });
- }
-});
-```
-
-### handleCreateTransactionWithLegacyAccount
-
-```ts
-container.handleCreateTransactionWithLegacyAccount(async (payload, { ok, err }) => {
- try {
- const signedTx = await createTransactionWithLegacyAccount(payload);
- return ok(signedTx);
- } catch (e) {
- return err({ tag: 'Rejected' });
- }
-});
-```
-
-### handleSignRaw
-
-```ts
-container.handleSignRaw(async (payload, { ok, err }) => {
- try {
- const result = await signRaw(payload);
- return ok({ signature: result.signature, signedTransaction: result.signedTransaction });
- } catch (e) {
- return err({ tag: 'Rejected' });
- }
-});
-```
-
-### handleSignPayload
-
-```ts
-container.handleSignPayload(async (payload, { ok, err }) => {
- try {
- const result = await signPayload(payload);
- return ok({ signature: result.signature, signedTransaction: result.signedTransaction ?? null });
- } catch (e) {
- return err({ tag: 'Rejected' });
- }
-});
-```
-
-### handleChatCreateContact
-
-```ts
-container.handleChatCreateContact(async (contact, { ok, err }) => {
- await chatService.registerContact(contact);
- return ok(undefined);
-});
+npm install @novasamatech/host-chat --save -E
```
-### handleChatPostMessage
+## Getting started
```ts
-container.handleChatPostMessage(async (message, { ok, err }) => {
- const messageId = await chatService.postMessage(message);
- return ok({ messageId });
-});
-```
-
-### handleChatActionSubscribe
+import { createAccountService } from '@novasamatech/host-chat';
+import { createLazyClient } from '@novasamatech/statement-store';
-```ts
-container.handleChatActionSubscribe((_, send, interrupt) => {
- const listener = (action) => send(action);
- chatService.on('action', listener);
- return () => chatService.off('action', listener);
-});
-```
+const lazyClient = createLazyClient(/* chain provider */);
+const accounts = createAccountService('paseo-next-v2', lazyClient);
-### handleStatementStoreCreateProof
-
-```ts
-container.handleStatementStoreCreateProof(async ([[dotnsId, derivationIndex], statement], { ok, err }) => {
- try {
- const proof = await createStatementProof(dotnsId, derivationIndex, statement);
- return ok(proof);
- } catch (e) {
- return err({ tag: 'UnableToSign' });
+// Search the off-chain username index for accounts whose username starts with `alice`.
+const search = await accounts.search('alice', 'ASSIGNED');
+if (search.isOk()) {
+ for (const hit of search.value) {
+ console.log(hit.candidateAccountId, hit.username);
}
-});
-```
-
-### handleJsonRpcMessageSubscribe
-
-```ts
-import { getWsProvider } from 'polkadot-api/ws-provider';
-
-const provider = getWsProvider('wss://rpc.polkadot.io');
-container.handleJsonRpcMessageSubscribe(
- { genesisHash: '0x...' },
- provider
-);
-```
-
-### isReady
+}
-```ts
-const ready = await container.isReady();
-if (ready) {
- console.log('Container is ready');
+// Resolve a specific account's on-chain identity.
+const identity = await accounts.getConsumerInfo('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
+if (identity.isOk() && identity.value) {
+ console.log(identity.value.fullUsername, identity.value.credibility);
}
```
-### dispose
+### Networks
-```ts
-container.dispose();
-```
+`createAccountService` accepts one of:
-### subscribeConnectionStatus
+| Network | People chain endpoint |
+| ---------------- | -------------------------------------- |
+| `stable` | Polkadot People |
+| `preview` | Westend People |
+| `paseo-next` | Paseo People (V1) |
+| `paseo-next-v2` | Paseo People (V2 multi-device) |
-```ts
-const unsubscribe = container.subscribeConnectionStatus((status) => {
- console.log('Connection status:', status);
-});
-```
+Each network entry pins both the People chain WebSocket URL (used via `lazyClient`) and
+the off-chain identity-backend REST endpoint that `search` queries.
-## PAPI provider support
+## API
-Host container supports [PAPI](https://papi.how/) request redirection from product to host container.
-It can be useful to deduplicate socket connections or light client instances between multiple dapps.
+### `createAccountService(network, lazyClient)`
-To support this feature, you should add two additional handlers to the container:
+Returns an object with two methods:
-### Chain support check
-```ts
-const genesisHash = '0x...';
+- **`search(query, status)`** — query the off-chain username index. `status` is
+ `'ASSIGNED' | 'PENDING'`. Resolves to a list of `{ candidateAccountId, username, status,
+ onchainData, createdAt, updatedAt }` rows.
+- **`getConsumerInfo(address)`** — resolve a single SS58 address to an `Identity`
+ (`{ accountId, fullUsername, liteUsername, credibility }`) by reading
+ `Resources.Consumers` from the People chain. Returns `null` if the account has no
+ consumer entry. Tolerates both snake_case (V1) and camelCase (V2) runtime field names.
-container.handleFeature(async (feature) => {
- return feature.tag === 'Chain' && feature.value === genesisHash;
-});
-```
+Both methods return `ResultAsync<…, Error>`; call `.isOk()` / `.isErr()` to discriminate.
+
+## Codec subpath exports
-### Provider implementation
+The chat wire codecs are exposed under explicit subpaths so they can be tree-shaken
+independently of the main entry point:
```ts
-import { getWsProvider } from 'polkadot-api/ws-provider';
+import {
+ ChatMessage,
+ TextContent,
+ RichTextContent,
+ ChatAcceptedContent,
+ DeviceAddedContent,
+ DeviceRemovedContent,
+} from '@novasamatech/host-chat/codec/message';
-const genesisHash = '0x...';
-const provider = getWsProvider('wss://...');
+import {
+ FileMeta,
+ FileVariant,
+ P2PMixnetFile,
+} from '@novasamatech/host-chat/codec/attachment';
-container.connectToPapiProvider(genesisHash, provider);
+import type { ChatSession } from '@novasamatech/host-chat/session';
```
-## Known pitfalls
-
-### CSP error on iframe loading
-If a dapp is hosted on a different domain than the container and uses HTTPS, you should add this meta tag to your host application HTML:
-
-```html
-
-```
+These are byte-compatible with the Android / iOS Polkadot Mobile clients — modify with
+care, the indices are pinned by the protocol.
diff --git a/packages/host-chat/package.json b/packages/host-chat/package.json
index c815de8a..0f56dac2 100644
--- a/packages/host-chat/package.json
+++ b/packages/host-chat/package.json
@@ -2,7 +2,6 @@
"name": "@novasamatech/host-chat",
"type": "module",
"version": "0.8.0-0",
- "private": "true",
"description": "Host statement store chat integration",
"license": "Apache-2.0",
"repository": {
diff --git a/packages/host-chat/src/accountService.ts b/packages/host-chat/src/accountService.ts
index c9c4c66a..1e4d5e04 100644
--- a/packages/host-chat/src/accountService.ts
+++ b/packages/host-chat/src/accountService.ts
@@ -23,7 +23,7 @@ type AccountService = {
getConsumerInfo(address: string): ResultAsync;
};
-type Network = 'paseo-next' | 'preview' | 'stable';
+type Network = 'paseo-next' | 'paseo-next-v2' | 'preview' | 'stable';
type SearchResponse = {
candidateAccountId: string;
@@ -146,4 +146,10 @@ const NETWORK_CONFIGS: Record = {
wsUrl: 'wss://paseo-people-next-rpc.polkadot.io',
apiUrl: 'https://identity-backend.parity-testnet.parity.io/api/v1',
},
+ 'paseo-next-v2': {
+ id: 'paseo-next-v2',
+ name: 'Paseo Next V2',
+ wsUrl: 'wss://paseo-people-next-system-rpc.polkadot.io',
+ apiUrl: 'https://identity-backend-next.parity-testnet.parity.io/api/v1',
+ },
};