@novasamatech/statement-store 0.5.0-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +11 -0
  2. package/dist/adapter/lazyClient.d.ts +7 -0
  3. package/dist/adapter/lazyClient.js +17 -0
  4. package/dist/adapter/rpc.d.ts +3 -0
  5. package/dist/adapter/rpc.js +93 -0
  6. package/dist/adapter/types.d.ts +7 -0
  7. package/dist/adapter/types.js +1 -0
  8. package/dist/crypto.d.ts +4 -0
  9. package/dist/crypto.js +13 -0
  10. package/dist/helpers.d.ts +2 -0
  11. package/dist/helpers.js +12 -0
  12. package/dist/index.d.ts +15 -0
  13. package/dist/index.js +7 -0
  14. package/dist/model/session.d.ts +5 -0
  15. package/dist/model/session.js +18 -0
  16. package/dist/model/sessionAccount.d.ts +23 -0
  17. package/dist/model/sessionAccount.js +28 -0
  18. package/dist/session/encyption.d.ts +6 -0
  19. package/dist/session/encyption.js +25 -0
  20. package/dist/session/messageMapper.d.ts +4 -0
  21. package/dist/session/messageMapper.js +18 -0
  22. package/dist/session/scale/statementData.d.ts +24 -0
  23. package/dist/session/scale/statementData.js +36 -0
  24. package/dist/session/session.d.ts +13 -0
  25. package/dist/session/session.js +93 -0
  26. package/dist/session/statementProver.d.ts +6 -0
  27. package/dist/session/statementProver.js +1 -0
  28. package/dist/session/types.d.ts +23 -0
  29. package/dist/session/types.js +1 -0
  30. package/dist/transport/impl.d.ts +32 -0
  31. package/dist/transport/impl.js +59 -0
  32. package/dist/transport/scale/opaque.d.ts +1 -0
  33. package/dist/transport/scale/opaque.js +1 -0
  34. package/dist/transport/scale/statementData.d.ts +24 -0
  35. package/dist/transport/scale/statementData.js +36 -0
  36. package/dist/transport/transport.d.ts +31 -0
  37. package/dist/transport/transport.js +56 -0
  38. package/dist/types.d.ts +6 -0
  39. package/dist/types.js +1 -0
  40. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @novasamatech/statement-store
2
+
3
+ Statement store integration layer.
4
+
5
+ ## Overview
6
+
7
+ ## Installation
8
+
9
+ ```shell
10
+ npm install @novasamatech/statement-store --save -E
11
+ ```
@@ -0,0 +1,7 @@
1
+ import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
2
+ import type { PolkadotClient } from 'polkadot-api';
3
+ export type LazyClient = ReturnType<typeof createLazyClient>;
4
+ export declare const createLazyClient: (provider: JsonRpcProvider) => {
5
+ getClient(): PolkadotClient;
6
+ disconnect(): void;
7
+ };
@@ -0,0 +1,17 @@
1
+ import { createClient } from 'polkadot-api';
2
+ export const createLazyClient = (provider) => {
3
+ let client = null;
4
+ return {
5
+ getClient() {
6
+ if (!client) {
7
+ client = createClient(provider);
8
+ }
9
+ return client;
10
+ },
11
+ disconnect() {
12
+ if (client) {
13
+ client.destroy();
14
+ }
15
+ },
16
+ };
17
+ };
@@ -0,0 +1,3 @@
1
+ import type { LazyClient } from './lazyClient.js';
2
+ import type { StatementStoreAdapter } from './types.js';
3
+ export declare function createPapiStatementStoreAdapter(lazyClient: LazyClient): StatementStoreAdapter;
@@ -0,0 +1,93 @@
1
+ import { createStatementSdk } from '@polkadot-api/sdk-statement';
2
+ import { Binary } from '@polkadot-api/substrate-bindings';
3
+ import { toHex } from '@polkadot-api/utils';
4
+ import { fromPromise } from 'neverthrow';
5
+ import { toError } from '../helpers.js';
6
+ const POLLING_INTERVAL = 1500;
7
+ function createKey(topics) {
8
+ return topics.map(toHex).sort().join('');
9
+ }
10
+ export function createPapiStatementStoreAdapter(lazyClient) {
11
+ const sdk = createStatementSdk((method, params) => {
12
+ const client = lazyClient.getClient();
13
+ return client._request(method, params);
14
+ });
15
+ const pollings = new Map();
16
+ const subscriptions = new Map();
17
+ function addSubscription(key, subscription) {
18
+ let subs = subscriptions.get(key);
19
+ if (!subs) {
20
+ subs = [];
21
+ subscriptions.set(key, subs);
22
+ }
23
+ subs.push(subscription);
24
+ return subs;
25
+ }
26
+ function removeSubscription(key, subscription) {
27
+ let subs = subscriptions.get(key);
28
+ if (!subs) {
29
+ return [];
30
+ }
31
+ subs = subs.filter(x => x !== subscription);
32
+ return subs;
33
+ }
34
+ const transportProvider = {
35
+ queryStatements(topics, destination) {
36
+ return fromPromise(sdk.getStatements({
37
+ topics: topics.map(t => Binary.fromBytes(t)),
38
+ dest: destination ? Binary.fromBytes(destination) : null,
39
+ }), toError);
40
+ },
41
+ subscribeStatements(topics, callback) {
42
+ const key = createKey(topics);
43
+ const callbacks = addSubscription(key, callback);
44
+ if (callbacks.length === 1) {
45
+ const unsub = polling(POLLING_INTERVAL, () => transportProvider.queryStatements(topics), statements => {
46
+ const list = subscriptions.get(key);
47
+ if (list) {
48
+ for (const fn of list) {
49
+ fn(statements);
50
+ }
51
+ }
52
+ });
53
+ pollings.set(key, unsub);
54
+ }
55
+ return () => {
56
+ const callbacks = removeSubscription(key, callback);
57
+ if (callbacks.length === 0) {
58
+ const stopPolling = pollings.get(key);
59
+ stopPolling?.();
60
+ pollings.delete(key);
61
+ }
62
+ };
63
+ },
64
+ submitStatement(statement) {
65
+ return fromPromise(sdk.submit(statement), toError);
66
+ },
67
+ };
68
+ return transportProvider;
69
+ }
70
+ function polling(interval, request, callback) {
71
+ let active = true;
72
+ let tm = null;
73
+ function createCycle() {
74
+ tm = setTimeout(() => {
75
+ if (!active) {
76
+ return;
77
+ }
78
+ request().match(data => {
79
+ callback(data);
80
+ createCycle();
81
+ }, () => {
82
+ createCycle();
83
+ });
84
+ }, interval);
85
+ }
86
+ createCycle();
87
+ return () => {
88
+ active = false;
89
+ if (tm !== null) {
90
+ clearTimeout(tm);
91
+ }
92
+ };
93
+ }
@@ -0,0 +1,7 @@
1
+ import type { SignedStatement, Statement } from '@polkadot-api/sdk-statement';
2
+ import type { ResultAsync } from 'neverthrow';
3
+ export type StatementStoreAdapter = {
4
+ queryStatements(topics: Uint8Array[], destination?: Uint8Array): ResultAsync<Statement[], Error>;
5
+ subscribeStatements(topics: Uint8Array[], callback: (statements: Statement[]) => unknown): VoidFunction;
6
+ submitStatement(statement: SignedStatement): ResultAsync<void, Error>;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { Codec } from 'scale-ts';
2
+ export declare function BrandedBytesCodec<T extends Uint8Array>(length?: number): Codec<T>;
3
+ export declare function stringToBytes(str: string): Uint8Array<ArrayBuffer>;
4
+ export declare function khash(secret: Uint8Array, message: Uint8Array): Uint8Array<ArrayBufferLike>;
package/dist/crypto.js ADDED
@@ -0,0 +1,13 @@
1
+ import { blake2b } from '@noble/hashes/blake2.js';
2
+ import { Bytes } from 'scale-ts';
3
+ export function BrandedBytesCodec(length) {
4
+ return Bytes(length);
5
+ }
6
+ // helpers
7
+ const textEncoder = new TextEncoder();
8
+ export function stringToBytes(str) {
9
+ return textEncoder.encode(str);
10
+ }
11
+ export function khash(secret, message) {
12
+ return blake2b(message, { dkLen: 32, key: secret });
13
+ }
@@ -0,0 +1,2 @@
1
+ export declare function toError(err: unknown): Error;
2
+ export declare function nonNullable<T>(value: T): value is Exclude<NonNullable<T>, void>;
@@ -0,0 +1,12 @@
1
+ export function toError(err) {
2
+ if (err instanceof Error) {
3
+ return err;
4
+ }
5
+ if (err) {
6
+ return new Error(err.toString());
7
+ }
8
+ return new Error('Unknown error occurred.');
9
+ }
10
+ export function nonNullable(value) {
11
+ return value !== null && value !== undefined;
12
+ }
@@ -0,0 +1,15 @@
1
+ export type { Statement } from '@polkadot-api/sdk-statement';
2
+ export type { SessionId } from './model/session.js';
3
+ export { SessionIdCodec, createSessionId } from './model/session.js';
4
+ export type { AccountId, LocalSessionAccount, RemoteSessionAccount, SessionAccount } from './model/sessionAccount.js';
5
+ export { AccountIdCodec, LocalSessionAccountCodec, RemoteSessionAccountCodec, createAccountId, createLocalSessionAccount, createRemoteSessionAccount, } from './model/sessionAccount.js';
6
+ export type { Session } from './session/types.js';
7
+ export { createSession } from './session/session.js';
8
+ export type { StatementProver } from './session/statementProver.js';
9
+ export type { Encryption } from './session/encyption.js';
10
+ export { createEncryption } from './session/encyption.js';
11
+ export type { LazyClient } from './adapter/lazyClient.js';
12
+ export { createLazyClient } from './adapter/lazyClient.js';
13
+ export type { StatementStoreAdapter } from './adapter/types.js';
14
+ export { createPapiStatementStoreAdapter } from './adapter/rpc.js';
15
+ export { khash } from './crypto.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { SessionIdCodec, createSessionId } from './model/session.js';
2
+ export { AccountIdCodec, LocalSessionAccountCodec, RemoteSessionAccountCodec, createAccountId, createLocalSessionAccount, createRemoteSessionAccount, } from './model/sessionAccount.js';
3
+ export { createSession } from './session/session.js';
4
+ export { createEncryption } from './session/encyption.js';
5
+ export { createLazyClient } from './adapter/lazyClient.js';
6
+ export { createPapiStatementStoreAdapter } from './adapter/rpc.js';
7
+ export { khash } from './crypto.js';
@@ -0,0 +1,5 @@
1
+ import type { Branded } from '../types.js';
2
+ import type { SessionAccount } from './sessionAccount.js';
3
+ export type SessionId = Branded<Uint8Array, 'SessionId'>;
4
+ export declare const SessionIdCodec: import("scale-ts").Codec<Uint8Array<ArrayBufferLike>>;
5
+ export declare function createSessionId(sharedSecret: Uint8Array, accountA: SessionAccount, accountB: SessionAccount): SessionId;
@@ -0,0 +1,18 @@
1
+ import { mergeUint8 } from '@polkadot-api/utils';
2
+ import { Bytes } from 'scale-ts';
3
+ import { khash, stringToBytes } from '../crypto.js';
4
+ export const SessionIdCodec = Bytes(32);
5
+ export function createSessionId(sharedSecret, accountA, accountB) {
6
+ const sessionPrefix = stringToBytes('session');
7
+ const pinSeparator = stringToBytes('/');
8
+ function makePin(pin) {
9
+ return pin ? mergeUint8([pinSeparator, stringToBytes(pin)]) : pinSeparator;
10
+ }
11
+ const accountSessionParams = mergeUint8([
12
+ accountA.accountId,
13
+ accountB.accountId,
14
+ makePin(accountA.pin),
15
+ makePin(accountB.pin),
16
+ ]);
17
+ return khash(sharedSecret, mergeUint8([sessionPrefix, accountSessionParams]));
18
+ }
@@ -0,0 +1,23 @@
1
+ import type { Branded } from '../types.js';
2
+ export type AccountId = Branded<Uint8Array, 'AccountId'>;
3
+ export declare const AccountIdCodec: import("scale-ts").Codec<AccountId>;
4
+ export declare function createAccountId(value: Uint8Array): AccountId;
5
+ export type SessionAccount = {
6
+ accountId: AccountId;
7
+ pin: string | undefined;
8
+ };
9
+ export type LocalSessionAccount = SessionAccount;
10
+ export declare const LocalSessionAccountCodec: import("scale-ts").Codec<{
11
+ accountId: AccountId;
12
+ pin: string | undefined;
13
+ }>;
14
+ export declare function createLocalSessionAccount(accountId: AccountId, pin?: string): LocalSessionAccount;
15
+ export type RemoteSessionAccount = SessionAccount & {
16
+ publicKey: Uint8Array;
17
+ };
18
+ export declare const RemoteSessionAccountCodec: import("scale-ts").Codec<{
19
+ accountId: AccountId;
20
+ publicKey: Uint8Array<ArrayBufferLike>;
21
+ pin: string | undefined;
22
+ }>;
23
+ export declare function createRemoteSessionAccount(accountId: AccountId, publicKey: Uint8Array, pin?: string): RemoteSessionAccount;
@@ -0,0 +1,28 @@
1
+ import { Bytes, Option, Struct, str } from 'scale-ts';
2
+ import { BrandedBytesCodec } from '../crypto.js';
3
+ export const AccountIdCodec = BrandedBytesCodec(32);
4
+ export function createAccountId(value) {
5
+ return value.slice(0, 32);
6
+ }
7
+ export const LocalSessionAccountCodec = Struct({
8
+ accountId: AccountIdCodec,
9
+ pin: Option(str),
10
+ });
11
+ export function createLocalSessionAccount(accountId, pin) {
12
+ return {
13
+ accountId,
14
+ pin,
15
+ };
16
+ }
17
+ export const RemoteSessionAccountCodec = Struct({
18
+ accountId: AccountIdCodec,
19
+ publicKey: Bytes(32),
20
+ pin: Option(str),
21
+ });
22
+ export function createRemoteSessionAccount(accountId, publicKey, pin) {
23
+ return {
24
+ accountId,
25
+ publicKey,
26
+ pin,
27
+ };
28
+ }
@@ -0,0 +1,6 @@
1
+ import { Result } from 'neverthrow';
2
+ export type Encryption = {
3
+ encrypt(cipherText: Uint8Array): Result<Uint8Array, Error>;
4
+ decrypt(encryptedMessage: Uint8Array): Result<Uint8Array, Error>;
5
+ };
6
+ export declare function createEncryption(sharedSecret: Uint8Array): Encryption;
@@ -0,0 +1,25 @@
1
+ import { gcm } from '@noble/ciphers/aes.js';
2
+ import { hkdf } from '@noble/hashes/hkdf.js';
3
+ import { sha256 } from '@noble/hashes/sha2.js';
4
+ import { randomBytes } from '@noble/hashes/utils.js';
5
+ import { mergeUint8 } from '@polkadot-api/utils';
6
+ import { Result, fromThrowable } from 'neverthrow';
7
+ export function createEncryption(sharedSecret) {
8
+ return {
9
+ encrypt: fromThrowable(cipherText => {
10
+ const nonce = randomBytes(12);
11
+ const salt = new Uint8Array(); // secure enough since P256 random keys provide enough entropy
12
+ const info = new Uint8Array(); // no need to introduce any context
13
+ const aesKey = hkdf(sha256, sharedSecret, salt, info, 32);
14
+ const aes = gcm(aesKey, nonce);
15
+ return mergeUint8([nonce, aes.encrypt(cipherText)]);
16
+ }),
17
+ decrypt: fromThrowable(encryptedMessage => {
18
+ const nonce = encryptedMessage.slice(0, 12);
19
+ const cipherText = encryptedMessage.slice(12);
20
+ const aesKey = hkdf(sha256, sharedSecret, new Uint8Array(), new Uint8Array(), 32);
21
+ const aes = gcm(aesKey, nonce);
22
+ return aes.decrypt(cipherText);
23
+ }),
24
+ };
25
+ }
@@ -0,0 +1,4 @@
1
+ import type { CodecType } from 'scale-ts';
2
+ import type { StatementData } from './scale/statementData.js';
3
+ import type { Message } from './types.js';
4
+ export declare function toMessage<T>(statementData: CodecType<ReturnType<typeof StatementData<T>>>): Message<T>[];
@@ -0,0 +1,18 @@
1
+ export function toMessage(statementData) {
2
+ switch (statementData.tag) {
3
+ case 'request':
4
+ return statementData.value.data.map((data, index) => ({
5
+ type: 'request',
6
+ requestId: `${statementData.value.requestId}-${index.toString()}`,
7
+ data,
8
+ }));
9
+ case 'response':
10
+ return [
11
+ {
12
+ type: 'response',
13
+ requestId: statementData.value.requestId,
14
+ code: statementData.value.responseCode,
15
+ },
16
+ ];
17
+ }
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { Codec } from 'scale-ts';
2
+ export type TransportError = 'decryptionFailed' | 'decodingFailed' | 'unknown';
3
+ export declare const TransportErrorCodec: Codec<TransportError>;
4
+ export declare const Request: <T>(data: Codec<T>) => Codec<{
5
+ requestId: string;
6
+ data: T[];
7
+ }>;
8
+ export declare const Response: Codec<{
9
+ requestId: string;
10
+ responseCode: TransportError;
11
+ }>;
12
+ export declare const StatementData: <T>(data: Codec<T>) => Codec<{
13
+ tag: "request";
14
+ value: {
15
+ requestId: string;
16
+ data: T[];
17
+ };
18
+ } | {
19
+ tag: "response";
20
+ value: {
21
+ requestId: string;
22
+ responseCode: TransportError;
23
+ };
24
+ }>;
@@ -0,0 +1,36 @@
1
+ import { Enum, Struct, Vector, enhanceCodec, str, u8 } from 'scale-ts';
2
+ export const TransportErrorCodec = enhanceCodec(u8, error => {
3
+ switch (error) {
4
+ case 'decryptionFailed':
5
+ return 1;
6
+ case 'decodingFailed':
7
+ return 2;
8
+ case 'unknown':
9
+ return 255;
10
+ }
11
+ }, code => {
12
+ switch (code) {
13
+ case 1:
14
+ return 'decryptionFailed';
15
+ case 2:
16
+ return 'decodingFailed';
17
+ default:
18
+ return 'unknown';
19
+ }
20
+ });
21
+ export const Request = (data) => {
22
+ return Struct({
23
+ requestId: str,
24
+ data: Vector(data),
25
+ });
26
+ };
27
+ export const Response = Struct({
28
+ requestId: str,
29
+ responseCode: TransportErrorCodec,
30
+ });
31
+ export const StatementData = (data) => {
32
+ return Enum({
33
+ request: Request(data),
34
+ response: Response,
35
+ });
36
+ };
@@ -0,0 +1,13 @@
1
+ import type { StatementStoreAdapter } from '../adapter/types.js';
2
+ import type { LocalSessionAccount, RemoteSessionAccount } from '../model/sessionAccount.js';
3
+ import type { Encryption } from './encyption.js';
4
+ import type { StatementProver } from './statementProver.js';
5
+ import type { Session } from './types.js';
6
+ export type SessionParams = {
7
+ localAccount: LocalSessionAccount;
8
+ remoteAccount: RemoteSessionAccount;
9
+ statementStore: StatementStoreAdapter;
10
+ encryption: Encryption;
11
+ prover: StatementProver;
12
+ };
13
+ export declare function createSession({ localAccount, remoteAccount, statementStore, encryption, prover, }: SessionParams): Session;
@@ -0,0 +1,93 @@
1
+ import { Binary } from '@polkadot-api/substrate-bindings';
2
+ import { nanoid } from 'nanoid';
3
+ import { ResultAsync, err, fromThrowable, ok, okAsync } from 'neverthrow';
4
+ import { Bytes } from 'scale-ts';
5
+ import { khash, stringToBytes } from '../crypto.js';
6
+ import { nonNullable, toError } from '../helpers.js';
7
+ import { createSessionId } from '../model/session.js';
8
+ import { toMessage } from './messageMapper.js';
9
+ import { StatementData } from './scale/statementData.js';
10
+ export function createSession({ localAccount, remoteAccount, statementStore, encryption, prover, }) {
11
+ let subscriptions = [];
12
+ function submit(sessionId, channel, data) {
13
+ return encryption
14
+ .encrypt(data)
15
+ .map(data => ({
16
+ priority: getPriority(now()),
17
+ channel: Binary.fromBytes(channel),
18
+ topics: [Binary.fromBytes(sessionId)],
19
+ data: Binary.fromBytes(data),
20
+ }))
21
+ .asyncAndThen(prover.generateMessageProof)
22
+ .andThen(statementStore.submitStatement);
23
+ }
24
+ return {
25
+ submitRequest(codec, message) {
26
+ const requestId = nanoid();
27
+ const sessionId = createSessionId(remoteAccount.publicKey, localAccount, remoteAccount);
28
+ const statementDataCodec = StatementData(codec);
29
+ const encode = fromThrowable(statementDataCodec.enc, toError);
30
+ const rawData = encode({
31
+ tag: 'request',
32
+ value: { requestId, data: [message] },
33
+ });
34
+ return rawData
35
+ .asyncAndThen(data => submit(sessionId, createRequestChannel(sessionId), data))
36
+ .map(() => ({ requestId }));
37
+ },
38
+ submitResponse(requestId, responseCode) {
39
+ const sessionId = createSessionId(remoteAccount.publicKey, localAccount, remoteAccount);
40
+ const statementDataCodec = StatementData(Bytes());
41
+ const encode = fromThrowable(statementDataCodec.enc, toError);
42
+ const rawData = encode({
43
+ tag: 'response',
44
+ value: { requestId, responseCode },
45
+ });
46
+ return rawData.asyncAndThen(data => submit(sessionId, createResponseChannel(sessionId), data));
47
+ },
48
+ subscribe(codec, callback) {
49
+ const statementDataCodec = StatementData(codec);
50
+ const sessionId = createSessionId(remoteAccount.publicKey, remoteAccount, localAccount);
51
+ return statementStore.subscribeStatements([sessionId], statements => {
52
+ ResultAsync.combine(statements.map(statement => {
53
+ if (!statement.data)
54
+ return okAsync(null);
55
+ const data = statement.data.asBytes();
56
+ return prover
57
+ .verifyMessageProof(statement)
58
+ .andThen(verified => (verified ? ok() : err(new Error('Statement proof is not valid'))))
59
+ .andThen(() => encryption.decrypt(data))
60
+ .map(statementDataCodec.dec)
61
+ .orElse(() => ok(null));
62
+ }))
63
+ .map(messages => messages.filter(nonNullable).flatMap(toMessage))
64
+ .andTee(messages => {
65
+ if (messages.length > 0) {
66
+ callback(messages);
67
+ }
68
+ });
69
+ });
70
+ },
71
+ dispose() {
72
+ for (const unsub of subscriptions) {
73
+ unsub();
74
+ }
75
+ subscriptions = [];
76
+ },
77
+ };
78
+ }
79
+ function now() {
80
+ const d1 = new Date();
81
+ const d2 = new Date(d1.getUTCFullYear(), d1.getUTCMonth(), d1.getUTCDate(), d1.getUTCHours(), d1.getUTCMinutes(), d1.getUTCSeconds());
82
+ return d2.getTime();
83
+ }
84
+ function getPriority(timestamp) {
85
+ // time - (November 15, 2025)
86
+ return Math.floor((timestamp - 1763154000000) / 1000);
87
+ }
88
+ function createRequestChannel(sessionId) {
89
+ return khash(sessionId, stringToBytes('request'));
90
+ }
91
+ function createResponseChannel(sessionId) {
92
+ return khash(sessionId, stringToBytes('response'));
93
+ }
@@ -0,0 +1,6 @@
1
+ import type { SignedStatement, Statement } from '@polkadot-api/sdk-statement';
2
+ import type { ResultAsync } from 'neverthrow';
3
+ export type StatementProver = {
4
+ generateMessageProof(statement: Statement): ResultAsync<SignedStatement, Error>;
5
+ verifyMessageProof(statement: Statement): ResultAsync<boolean, Error>;
6
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { Codec } from 'scale-ts';
3
+ import type { Callback } from '../types.js';
4
+ import type { TransportError } from './scale/statementData.js';
5
+ export type RequestMessage<T> = {
6
+ type: 'request';
7
+ requestId: string;
8
+ data: T;
9
+ };
10
+ export type ResponseMessage = {
11
+ type: 'response';
12
+ requestId: string;
13
+ code: TransportError;
14
+ };
15
+ export type Message<T> = RequestMessage<T> | ResponseMessage;
16
+ export type Session = {
17
+ submitRequest<T>(codec: Codec<T>, data: T): ResultAsync<{
18
+ requestId: string;
19
+ }, Error>;
20
+ submitResponse(requestId: string, responseCode: TransportError): ResultAsync<void, Error>;
21
+ subscribe<T>(codec: Codec<T>, callback: Callback<Message<T>[]>): VoidFunction;
22
+ dispose(): void;
23
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import type { SignedStatement, Statement } from '@polkadot-api/sdk-statement';
2
+ import type { ResultAsync } from 'neverthrow';
3
+ import type { Codec } from 'scale-ts';
4
+ import type { StatementAdapter } from '../adapter/types.js';
5
+ import type { SessionId } from '../model/session.js';
6
+ import type { Callback } from '../types.js';
7
+ import type { TransportError } from './scale/statementData.js';
8
+ export type Transport = ReturnType<typeof createTransport>;
9
+ type RequestMessage<T> = {
10
+ type: 'request';
11
+ requestId: string;
12
+ data: T;
13
+ };
14
+ type ResponseMessage = {
15
+ type: 'response';
16
+ requestId: string;
17
+ code: TransportError;
18
+ };
19
+ type Message<T> = RequestMessage<T> | ResponseMessage;
20
+ type Params = {
21
+ adapter: StatementAdapter;
22
+ };
23
+ export declare function createTransport({ adapter }: Params): {
24
+ subscribeStatements(topic: Uint8Array, callback: Callback<Statement[]>): VoidFunction;
25
+ subscribe<T>({ sessionId, sharedSecret, codec, }: {
26
+ sessionId: SessionId;
27
+ sharedSecret: Uint8Array;
28
+ codec: Codec<T>;
29
+ }, callback: Callback<Message<T>[]>): VoidFunction;
30
+ submitRequest(statement: SignedStatement): ResultAsync<void, Error>;
31
+ };
32
+ export {};
@@ -0,0 +1,59 @@
1
+ import { Result, fromThrowable, ok } from 'neverthrow';
2
+ import { decrypt } from '../crypto.js';
3
+ import { nonNullable, toError } from '../helpers.js';
4
+ import { StatementData } from './scale/statementData.js';
5
+ const decryptResults = fromThrowable(decrypt, toError);
6
+ function mapMessage(statementData) {
7
+ switch (statementData.tag) {
8
+ case 'request':
9
+ return statementData.value.data.map((data, index) => ({
10
+ type: 'request',
11
+ requestId: `${statementData.value.requestId}-${index.toString()}`,
12
+ data,
13
+ }));
14
+ case 'response':
15
+ return [
16
+ {
17
+ type: 'response',
18
+ requestId: statementData.value.requestId,
19
+ code: statementData.value.responseCode,
20
+ },
21
+ ];
22
+ }
23
+ }
24
+ // function createRequestChannel(session: Uint8Array) {
25
+ // return khash(session, stringToBytes('request'));
26
+ // }
27
+ //
28
+ // export function createResponseChannel(session: Uint8Array) {
29
+ // return khash(session, stringToBytes('response'));
30
+ // }
31
+ export function createTransport({ adapter }) {
32
+ const transport = {
33
+ subscribeStatements(topic, callback) {
34
+ return adapter.subscribeStatements([topic], callback);
35
+ },
36
+ subscribe({ sessionId, sharedSecret, codec, }, callback) {
37
+ const statementDataCodec = StatementData(codec);
38
+ return adapter.subscribeStatements([sessionId], statements => {
39
+ Result.combine(statements.map(statement => {
40
+ if (!statement.data)
41
+ return ok(null);
42
+ return decryptResults(sharedSecret, statement.data.asBytes())
43
+ .map(statementDataCodec.dec)
44
+ .orElse(() => ok(null));
45
+ }))
46
+ .map(messages => messages.filter(nonNullable).flatMap(mapMessage))
47
+ .andTee(messages => {
48
+ if (messages.length > 0) {
49
+ callback(messages);
50
+ }
51
+ });
52
+ });
53
+ },
54
+ submitRequest(statement) {
55
+ return adapter.submitStatement(statement);
56
+ },
57
+ };
58
+ return transport;
59
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { Codec } from 'scale-ts';
2
+ export type TransportError = 'decryptionFailed' | 'decodingFailed' | 'unknown';
3
+ export declare const TransportErrorCodec: Codec<TransportError>;
4
+ export declare const Request: <T>(data: Codec<T>) => Codec<{
5
+ requestId: string;
6
+ data: T[];
7
+ }>;
8
+ export declare const Response: Codec<{
9
+ requestId: string;
10
+ responseCode: TransportError;
11
+ }>;
12
+ export declare const StatementData: <T>(data: Codec<T>) => Codec<{
13
+ tag: "request";
14
+ value: {
15
+ requestId: string;
16
+ data: T[];
17
+ };
18
+ } | {
19
+ tag: "response";
20
+ value: {
21
+ requestId: string;
22
+ responseCode: TransportError;
23
+ };
24
+ }>;
@@ -0,0 +1,36 @@
1
+ import { Enum, Struct, Vector, enhanceCodec, str, u8 } from 'scale-ts';
2
+ export const TransportErrorCodec = enhanceCodec(u8, error => {
3
+ switch (error) {
4
+ case 'decryptionFailed':
5
+ return 1;
6
+ case 'decodingFailed':
7
+ return 2;
8
+ case 'unknown':
9
+ return 255;
10
+ }
11
+ }, code => {
12
+ switch (code) {
13
+ case 1:
14
+ return 'decryptionFailed';
15
+ case 2:
16
+ return 'decodingFailed';
17
+ default:
18
+ return 'unknown';
19
+ }
20
+ });
21
+ export const Request = (data) => {
22
+ return Struct({
23
+ requestId: str,
24
+ data: Vector(data),
25
+ });
26
+ };
27
+ export const Response = Struct({
28
+ requestId: str,
29
+ responseCode: TransportErrorCodec,
30
+ });
31
+ export const StatementData = (data) => {
32
+ return Enum({
33
+ request: Request(data),
34
+ response: Response,
35
+ });
36
+ };
@@ -0,0 +1,31 @@
1
+ import type { SignedStatement } from '@polkadot-api/sdk-statement';
2
+ import type { ResultAsync } from 'neverthrow';
3
+ import type { Codec } from 'scale-ts';
4
+ import type { StatementAdapter } from '../adapter/types.js';
5
+ import type { SessionId } from '../model/session.js';
6
+ import type { Callback } from '../types.js';
7
+ import type { TransportError } from './scale/statementData.js';
8
+ export type Transport = ReturnType<typeof createTransport>;
9
+ type RequestMessage<T> = {
10
+ type: 'request';
11
+ requestId: string;
12
+ data: T;
13
+ };
14
+ type ResponseMessage = {
15
+ type: 'response';
16
+ requestId: string;
17
+ code: TransportError;
18
+ };
19
+ type Message<T> = RequestMessage<T> | ResponseMessage;
20
+ type Params = {
21
+ adapter: StatementAdapter;
22
+ };
23
+ export declare function createTransport({ adapter }: Params): {
24
+ subscribe<T>({ sessionId, sharedSecret, codec, }: {
25
+ sessionId: SessionId;
26
+ sharedSecret: Uint8Array;
27
+ codec: Codec<T>;
28
+ }, callback: Callback<Message<T>[]>): VoidFunction;
29
+ submitRequest(statement: SignedStatement): ResultAsync<void, Error>;
30
+ };
31
+ export {};
@@ -0,0 +1,56 @@
1
+ import { Result, fromThrowable, ok } from 'neverthrow';
2
+ import { decrypt } from '../crypto.js';
3
+ import { nonNullable, toError } from '../helpers.js';
4
+ import { StatementData } from './scale/statementData.js';
5
+ const decryptResults = fromThrowable(decrypt, toError);
6
+ function mapMessage(statementData) {
7
+ switch (statementData.tag) {
8
+ case 'request':
9
+ return statementData.value.data.map((data, index) => ({
10
+ type: 'request',
11
+ requestId: `${statementData.value.requestId}-${index.toString()}`,
12
+ data,
13
+ }));
14
+ case 'response':
15
+ return [
16
+ {
17
+ type: 'response',
18
+ requestId: statementData.value.requestId,
19
+ code: statementData.value.responseCode,
20
+ },
21
+ ];
22
+ }
23
+ }
24
+ // function createRequestChannel(session: Uint8Array) {
25
+ // return khash(session, stringToBytes('request'));
26
+ // }
27
+ //
28
+ // export function createResponseChannel(session: Uint8Array) {
29
+ // return khash(session, stringToBytes('response'));
30
+ // }
31
+ export function createTransport({ adapter }) {
32
+ const transport = {
33
+ subscribe({ sessionId, sharedSecret, codec, }, callback) {
34
+ const statementDataCodec = StatementData(codec);
35
+ return adapter.subscribeStatements([sessionId], statements => {
36
+ Result.combine(statements.map(statement => {
37
+ if (!statement.data)
38
+ return ok(null);
39
+ return decryptResults(sharedSecret, statement.data.asBytes())
40
+ .map(statementDataCodec.dec)
41
+ .orElse(() => ok(null));
42
+ }))
43
+ .map(messages => messages.filter(nonNullable).flatMap(mapMessage))
44
+ .andTee(messages => {
45
+ if (messages.length > 0) {
46
+ callback(messages);
47
+ }
48
+ });
49
+ });
50
+ },
51
+ submitRequest(statement) {
52
+ return adapter.submitStatement(statement);
53
+ },
54
+ };
55
+ return transport;
56
+ }
@@ -0,0 +1,6 @@
1
+ declare const __brand: unique symbol;
2
+ export type Branded<T, K extends string> = T & {
3
+ [__brand]: K;
4
+ };
5
+ export type Callback<T, R = unknown> = (value: T) => R;
6
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@novasamatech/statement-store",
3
+ "type": "module",
4
+ "version": "0.5.0-9",
5
+ "description": "Statement store integration",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/novasamatech/spektr-sdk.git"
10
+ },
11
+ "keywords": [
12
+ "polkadot"
13
+ ],
14
+ "main": "dist/index.js",
15
+ "exports": {
16
+ "./package.json": "./package.json",
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "dependencies": {
27
+ "@polkadot-api/sdk-statement": "^0.2.0",
28
+ "@polkadot-api/substrate-bindings": "^0.16.5",
29
+ "@noble/hashes": "2.0.1",
30
+ "@noble/ciphers": "2.1.1",
31
+ "polkadot-api": "^1.23.1",
32
+ "neverthrow": "^8.2.0",
33
+ "scale-ts": "^1.6.1"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }