@novasamatech/host-api-wrapper 0.9.4 → 0.10.1

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.
package/README.md CHANGED
@@ -402,6 +402,17 @@ const config = await storage.readJSON('config');
402
402
 
403
403
  // Clear a key
404
404
  await storage.clear('key');
405
+
406
+ // Subscribe to a key. The callback fires with the current value right away,
407
+ // then on every later write or clear. `undefined` means the key is absent.
408
+ const sub = storage.subscribeBytes('key', (bytes) => {
409
+ console.log('key changed:', bytes);
410
+ });
411
+ storage.subscribeString('greeting', (text) => console.log(text));
412
+ storage.subscribeJSON('config', (value) => console.log(value));
413
+
414
+ // Stop receiving updates
415
+ sub.unsubscribe();
405
416
  ```
406
417
 
407
418
  ### Derive Entropy
@@ -509,3 +520,37 @@ const statusSub = payments.subscribePaymentStatus(receipt.id, status => {
509
520
  if (status.type === 'failed') console.log('Payment failed:', status.reason);
510
521
  });
511
522
  ```
523
+
524
+ ### Coin payment (RFC 0017)
525
+
526
+ Firewalled purses, cheques, and receivables. The long-running operations
527
+ (rebalance, delete, deposit, refund, listen) stream clearing status through a
528
+ callback and return a `Subscription`; `onInterrupt` fires with a
529
+ `CoinPaymentErr` if the host tears the stream down.
530
+
531
+ ```ts
532
+ import { createCoinPayment } from '@novasamatech/host-api-wrapper';
533
+
534
+ const coinPayment = createCoinPayment();
535
+
536
+ // Create a purse and read it back
537
+ const purse = await coinPayment.createPurse('Terminal purse');
538
+ const info = await coinPayment.queryPurse(purse);
539
+ console.log('balance:', info.balance);
540
+
541
+ // Pay a receivable with a cheque
542
+ const receivable = await coinPayment.createReceivable(purse);
543
+ const cheque = await coinPayment.createCheque(purse, receivable, 1000);
544
+
545
+ // Claim the cheque, watching the coins clear
546
+ const sub = coinPayment.deposit(cheque, status => {
547
+ if (status.tag === 'Done') console.log('cleared:', status.value.cleared);
548
+ if (status.tag === 'Failed') console.log('failed:', status.value.error);
549
+ });
550
+ sub.onInterrupt(err => console.log('deposit interrupted:', err));
551
+
552
+ // Wait for a cheque delivered over a standard channel
553
+ coinPayment.listenForPayment(receivable, item => {
554
+ if (item.tag === 'Cheque') console.log('received cheque:', item.value.amount);
555
+ });
556
+ ```
@@ -1,6 +1,6 @@
1
1
  import type { AccountConnectionStatus as AccountConnectionStatusCodec, AccountSelector, CodecType, LegacyAccount as LegacyAccountCodec, ProductAccountId as ProductAccountIdCodec, RegisteredRingVrfKey as RegisteredRingVrfKeyCodec, RingVrfKeyDisclosure as RingVrfKeyDisclosureCodec, RingVrfKeyHandle as RingVrfKeyHandleCodec, Subscription, Transport, VrfTranscriptItem as VrfTranscriptItemCodec } from '@novasamatech/host-api';
2
2
  import { RingLocation } from '@novasamatech/host-api';
3
- import type { PolkadotSigner } from 'polkadot-api';
3
+ import type { SignerTxCreator } from 'polkadot-api/tx-creator';
4
4
  export type { AccountSelector } from '@novasamatech/host-api';
5
5
  export type ProductAccountId = CodecType<typeof ProductAccountIdCodec>;
6
6
  export type ProductAccount = {
@@ -182,14 +182,16 @@ export declare const createAccountsProvider: (transport?: Transport) => {
182
182
  reason: string;
183
183
  }, "SignVrfErr::Unknown">>;
184
184
  /**
185
- * Builds a `PolkadotSigner` that delegates to the host via `host_create_transaction`.
185
+ * Builds a `TxCreator` (polkadot-api v3) that delegates to the host via
186
+ * `host_create_transaction`. The host keeps ownership of extension inference
187
+ * and signing; the creator maps the v3 `TxPayloadV1` onto the host codec.
186
188
  *
187
- * The factory is async because `PolkadotSigner.publicKey` must be a synchronous
188
- * `Uint8Array` on the returned object — it is fetched up front via `host_account_get`.
189
+ * `publicKey` is exposed synchronously on the returned object and is taken
190
+ * from `account`, so no up-front `host_account_get` is needed.
189
191
  */
190
- getProductAccountSigner(account: ProductAccount, signerType?: "signPayload" | "createTransaction"): PolkadotSigner;
192
+ getProductAccountSigner(account: ProductAccount, signerType?: "signPayload" | "createTransaction"): SignerTxCreator;
191
193
  subscribeAccountConnectionStatus(callback: (status: AccountConnectionStatus) => void): Subscription<void>;
192
- getLegacyAccountSigner(account: LegacyAccount): PolkadotSigner;
194
+ getLegacyAccountSigner(account: LegacyAccount): SignerTxCreator;
193
195
  };
194
196
  export declare const accounts: {
195
197
  getUserId(): import("neverthrow").ResultAsync<{
@@ -328,12 +330,14 @@ export declare const accounts: {
328
330
  reason: string;
329
331
  }, "SignVrfErr::Unknown">>;
330
332
  /**
331
- * Builds a `PolkadotSigner` that delegates to the host via `host_create_transaction`.
333
+ * Builds a `TxCreator` (polkadot-api v3) that delegates to the host via
334
+ * `host_create_transaction`. The host keeps ownership of extension inference
335
+ * and signing; the creator maps the v3 `TxPayloadV1` onto the host codec.
332
336
  *
333
- * The factory is async because `PolkadotSigner.publicKey` must be a synchronous
334
- * `Uint8Array` on the returned object — it is fetched up front via `host_account_get`.
337
+ * `publicKey` is exposed synchronously on the returned object and is taken
338
+ * from `account`, so no up-front `host_account_get` is needed.
335
339
  */
336
- getProductAccountSigner(account: ProductAccount, signerType?: "signPayload" | "createTransaction"): PolkadotSigner;
340
+ getProductAccountSigner(account: ProductAccount, signerType?: "signPayload" | "createTransaction"): SignerTxCreator;
337
341
  subscribeAccountConnectionStatus(callback: (status: AccountConnectionStatus) => void): Subscription<void>;
338
- getLegacyAccountSigner(account: LegacyAccount): PolkadotSigner;
342
+ getLegacyAccountSigner(account: LegacyAccount): SignerTxCreator;
339
343
  };
package/dist/accounts.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { CreateProofErr, GetAliasErr, GetUserIdErr, ListRingVrfKeysErr, LoginErr, ProductProofContext, RegisterRingVrfKeyErr, RequestCredentialsErr, RingLocation, RingVrfSignErr, SignVrfErr, SigningPayload, SigningPayloadWithoutAccount, SigningRawPayload, SigningRawPayloadWithoutAccount, assertEnumVariant, createHostApi, derivationIndexOf, enumValue, fromHex, isEnumVariant, toHex, } from '@novasamatech/host-api';
2
+ import { createV4Tx } from '@polkadot-api/signers-common';
2
3
  import { decAnyMetadata, unifyMetadata } from '@polkadot-api/substrate-bindings';
3
4
  import { err, ok } from 'neverthrow';
4
5
  import { AccountId } from 'polkadot-api';
5
- import { getPolkadotSignerFromPjs } from 'polkadot-api/pjs-signer';
6
+ import { getTxCreatorFromPjs } from 'polkadot-api/pjs-signer';
6
7
  import { sandboxTransport } from './sandboxTransport.js';
7
8
  /**
8
9
  * Builds a {@link RingVrfKeyHandle} for a key inside `owner`'s ring VRF domain.
@@ -206,10 +207,12 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
206
207
  });
207
208
  },
208
209
  /**
209
- * Builds a `PolkadotSigner` that delegates to the host via `host_create_transaction`.
210
+ * Builds a `TxCreator` (polkadot-api v3) that delegates to the host via
211
+ * `host_create_transaction`. The host keeps ownership of extension inference
212
+ * and signing; the creator maps the v3 `TxPayloadV1` onto the host codec.
210
213
  *
211
- * The factory is async because `PolkadotSigner.publicKey` must be a synchronous
212
- * `Uint8Array` on the returned object — it is fetched up front via `host_account_get`.
214
+ * `publicKey` is exposed synchronously on the returned object and is taken
215
+ * from `account`, so no up-front `host_account_get` is needed.
213
216
  */
214
217
  getProductAccountSigner(account, signerType = 'createTransaction') {
215
218
  const hostApi = createHostApi(transport);
@@ -218,7 +221,7 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
218
221
  * @deprecated added for backward compatibility
219
222
  */
220
223
  if (signerType === 'signPayload') {
221
- return getPolkadotSignerFromPjs(toHex(account.publicKey), async (payload) => {
224
+ return getTxCreatorFromPjs(toHex(account.publicKey), async (payload) => {
222
225
  const codecPayload = {
223
226
  account: productAccountId,
224
227
  payload: buildSigningPayloadFields(payload),
@@ -262,51 +265,66 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
262
265
  });
263
266
  });
264
267
  }
265
- return {
266
- publicKey: account.publicKey,
267
- async signTx(callData, signedExtensions, metadata) {
268
- const decMeta = unifyMetadata(decAnyMetadata(metadata));
269
- const { version: versions } = decMeta.extrinsic;
270
- const latestVersion = versions.reduce((acc, v) => Math.max(acc, v), 0);
271
- const txExtVersion = latestVersion === 4 ? 0 : latestVersion;
272
- const checkGenesis = signedExtensions['CheckGenesis'];
273
- if (!checkGenesis) {
274
- throw new Error("Can't find genesis hash on transaction");
268
+ // v3 `TxCreator`: a callable that turns a `TxPayloadV1` into a
269
+ // SCALE-encoded extrinsic (hex). A real signature is produced by the host
270
+ // via `host_create_transaction`; a mocked signature (fee estimation) is
271
+ // built locally with a zero-filled signature and no host round-trip,
272
+ // mirroring polkadot-api's own `getTxCreator`.
273
+ const createTx = async (payload, _opts, _bindings, mockedSignature) => {
274
+ // Fee estimation on a v4 extrinsic: build a zero-signed extrinsic
275
+ // locally, with no host round-trip and no real signature. polkadot-api
276
+ // has no local builder for other versions (e.g. v5), so those fall
277
+ // through to the host path below, which returns a well-formed extrinsic
278
+ // of the right size for estimation.
279
+ if (mockedSignature && (payload.txExtVersion ?? 0) === 0) {
280
+ const decMeta = unifyMetadata(decAnyMetadata(payload.context.metadata));
281
+ const v4Extensions = decMeta.extrinsic.extensionsByVersion[0];
282
+ if (v4Extensions) {
283
+ const extra = v4Extensions.map(({ identifier }) => {
284
+ const extension = payload.extensions.find(({ id }) => id === identifier);
285
+ if (!extension) {
286
+ throw new Error(`Missing ${identifier} signed extension`);
287
+ }
288
+ return fromHex(extension.extra);
289
+ });
290
+ const mockSr25519Signature = new Uint8Array(64);
291
+ return toHex(createV4Tx(decMeta, account.publicKey, mockSr25519Signature, extra, fromHex(payload.callData), 'Sr25519'));
275
292
  }
276
- const txPayload = {
277
- signer: productAccountId,
278
- genesisHash: toHex(checkGenesis.additionalSigned),
279
- callData,
280
- extensions: Object.values(signedExtensions).map(({ identifier, value, additionalSigned }) => ({
281
- id: identifier,
282
- extra: value,
283
- additionalSigned: additionalSigned,
284
- })),
285
- txExtVersion,
286
- };
287
- const response = await hostApi.createTransaction(enumValue('v1', txPayload));
288
- return response.match(response => {
289
- assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
290
- return response.value;
291
- }, err => {
292
- assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
293
- throw err.value;
294
- });
295
- },
296
- async signBytes(data) {
297
- const response = await hostApi.signRaw(enumValue('v1', {
298
- account: productAccountId,
299
- payload: { tag: 'Bytes', value: data },
300
- }));
301
- return response.match(response => {
302
- assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
303
- return fromHex(response.value.signature);
304
- }, err => {
305
- assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
306
- throw err.value;
307
- });
308
- },
293
+ }
294
+ const txPayload = {
295
+ signer: productAccountId,
296
+ genesisHash: payload.context.genesisHash,
297
+ callData: fromHex(payload.callData),
298
+ extensions: payload.extensions.map(({ id, extra, additionalSigned }) => ({
299
+ id,
300
+ extra: fromHex(extra),
301
+ additionalSigned: fromHex(additionalSigned),
302
+ })),
303
+ txExtVersion: payload.txExtVersion ?? deriveDefaultTxExtVersion(payload.context.metadata),
304
+ };
305
+ const response = await hostApi.createTransaction(enumValue('v1', txPayload));
306
+ return response.match(response => {
307
+ assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
308
+ return toHex(response.value);
309
+ }, err => {
310
+ assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
311
+ throw err.value;
312
+ });
309
313
  };
314
+ const signBytes = async (data) => {
315
+ const response = await hostApi.signRaw(enumValue('v1', {
316
+ account: productAccountId,
317
+ payload: { tag: 'Bytes', value: data },
318
+ }));
319
+ return response.match(response => {
320
+ assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
321
+ return fromHex(response.value.signature);
322
+ }, err => {
323
+ assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
324
+ throw err.value;
325
+ });
326
+ };
327
+ return Object.assign(createTx, { publicKey: account.publicKey, signBytes });
310
328
  },
311
329
  subscribeAccountConnectionStatus(callback) {
312
330
  const subscriber = hostApi.accountConnectionStatusSubscribe(enumValue('v1', undefined), status => {
@@ -325,7 +343,7 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
325
343
  // must be an SS58 address the wallet can match — not a raw hex public
326
344
  // key. Mirrors the injected-extension path, which uses accountId.dec.
327
345
  const accountId = AccountId();
328
- return getPolkadotSignerFromPjs(accountId.dec(account.publicKey), async (payload) => {
346
+ return getTxCreatorFromPjs(accountId.dec(account.publicKey), async (payload) => {
329
347
  const codecPayload = {
330
348
  signer: payload.address,
331
349
  payload: buildSigningPayloadFields(payload),
@@ -372,6 +390,13 @@ function asHex(v) {
372
390
  return v;
373
391
  return `0x${v}`;
374
392
  }
393
+ // Default transaction-extension version to request when the caller leaves it
394
+ // unset: 0 for extrinsic v4, the version itself otherwise (e.g. v5).
395
+ function deriveDefaultTxExtVersion(metadata) {
396
+ const { version: versions } = unifyMetadata(decAnyMetadata(metadata)).extrinsic;
397
+ const latestVersion = versions.reduce((acc, v) => Math.max(acc, v), 0);
398
+ return latestVersion === 4 ? 0 : latestVersion;
399
+ }
375
400
  function buildSigningPayloadFields(payload) {
376
401
  return {
377
402
  blockHash: asHex(payload.blockHash),
@@ -0,0 +1,35 @@
1
+ import type { CodecType, CoinPaymentCheque, CoinPaymentErr, CoinPaymentListenForItem, CoinPaymentPurseInfo, CoinPaymentStatus, HexString, Subscription, Transport } from '@novasamatech/host-api';
2
+ /** Purse identifier (RFC 0017). `0xffffffff` is the well-known main purse. */
3
+ export type PurseId = number;
4
+ /** Receivable public key, as a 0x-prefixed 32-byte hex string. */
5
+ export type Receivable = HexString;
6
+ export type PurseInfo = CodecType<typeof CoinPaymentPurseInfo>;
7
+ export type Cheque = CodecType<typeof CoinPaymentCheque>;
8
+ /** Clearing status streamed by rebalance, delete, deposit, and refund. */
9
+ export type ClearingStatus = CodecType<typeof CoinPaymentStatus>;
10
+ /** Item streamed by `listenForPayment`: a delivery channel, then the cheque. */
11
+ export type PaymentDelivery = CodecType<typeof CoinPaymentListenForItem>;
12
+ type CoinPaymentInterrupt = CodecType<typeof CoinPaymentErr>;
13
+ export declare const createCoinPayment: (transport?: Transport) => {
14
+ createPurse(name: string): Promise<PurseId>;
15
+ queryPurse(purse: PurseId): Promise<PurseInfo>;
16
+ createReceivable(into: PurseId): Promise<Receivable>;
17
+ createCheque(from: PurseId, to: Receivable, amount: number): Promise<Cheque>;
18
+ rebalancePurse(from: PurseId, to: PurseId, amount: number, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
19
+ deletePurse(target: PurseId, drainInto: PurseId, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
20
+ deposit(cheque: Cheque, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
21
+ refund(receivable: Receivable, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
22
+ listenForPayment(receivable: Receivable, onDelivery: (item: PaymentDelivery) => void): Subscription<CoinPaymentInterrupt>;
23
+ };
24
+ export declare const hostCoinPayment: {
25
+ createPurse(name: string): Promise<PurseId>;
26
+ queryPurse(purse: PurseId): Promise<PurseInfo>;
27
+ createReceivable(into: PurseId): Promise<Receivable>;
28
+ createCheque(from: PurseId, to: Receivable, amount: number): Promise<Cheque>;
29
+ rebalancePurse(from: PurseId, to: PurseId, amount: number, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
30
+ deletePurse(target: PurseId, drainInto: PurseId, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
31
+ deposit(cheque: Cheque, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
32
+ refund(receivable: Receivable, onStatus: (status: ClearingStatus) => void): Subscription<CoinPaymentInterrupt>;
33
+ listenForPayment(receivable: Receivable, onDelivery: (item: PaymentDelivery) => void): Subscription<CoinPaymentInterrupt>;
34
+ };
35
+ export {};
@@ -0,0 +1,67 @@
1
+ import { createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { resultToPromise, unwrapVersionedResult } from './helpers.js';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export const createCoinPayment = (transport = sandboxTransport) => {
5
+ const hostApi = createHostApi(transport);
6
+ const version = 'v1';
7
+ function stream(subscriber) {
8
+ return {
9
+ unsubscribe: subscriber.unsubscribe,
10
+ onInterrupt: cb => subscriber.onInterrupt(v => cb(v.value)),
11
+ };
12
+ }
13
+ return {
14
+ // Create a new firewalled purse and resolve with its assigned id.
15
+ createPurse(name) {
16
+ return resultToPromise(unwrapVersionedResult(version, hostApi.coinPaymentCreatePurse(enumValue(version, { name })))).then(({ purse }) => purse);
17
+ },
18
+ // Query product-visible metadata and balance for a purse.
19
+ queryPurse(purse) {
20
+ return resultToPromise(unwrapVersionedResult(version, hostApi.coinPaymentQueryPurse(enumValue(version, { purse })))).then(({ info }) => info);
21
+ },
22
+ // Create a fresh receivable public key for depositing into a purse.
23
+ createReceivable(into) {
24
+ return resultToPromise(unwrapVersionedResult(version, hostApi.coinPaymentCreateReceivable(enumValue(version, { into })))).then(({ receivable }) => receivable);
25
+ },
26
+ // Create a cheque paying from a local purse to a receivable.
27
+ createCheque(from, to, amount) {
28
+ return resultToPromise(unwrapVersionedResult(version, hostApi.coinPaymentCreateCheque(enumValue(version, { from, to, amount })))).then(({ cheque }) => cheque);
29
+ },
30
+ // Transfer balance between local purses, streaming clearing status.
31
+ rebalancePurse(from, to, amount, onStatus) {
32
+ return stream(hostApi.coinPaymentRebalancePurse(enumValue(version, { from, to, amount }), payload => {
33
+ if (payload.tag === version)
34
+ onStatus(payload.value);
35
+ }));
36
+ },
37
+ // Delete a purse after draining its balance into another local purse.
38
+ deletePurse(target, drainInto, onStatus) {
39
+ return stream(hostApi.coinPaymentDeletePurse(enumValue(version, { target, drainInto }), payload => {
40
+ if (payload.tag === version)
41
+ onStatus(payload.value);
42
+ }));
43
+ },
44
+ // Claim coins from a cheque into the receivable's purse.
45
+ deposit(cheque, onStatus) {
46
+ return stream(hostApi.coinPaymentDeposit(enumValue(version, { cheque }), payload => {
47
+ if (payload.tag === version)
48
+ onStatus(payload.value);
49
+ }));
50
+ },
51
+ // Attempt to return coins associated with a receivable.
52
+ refund(receivable, onStatus) {
53
+ return stream(hostApi.coinPaymentRefund(enumValue(version, { receivable }), payload => {
54
+ if (payload.tag === version)
55
+ onStatus(payload.value);
56
+ }));
57
+ },
58
+ // Listen for a cheque delivered through a standard transmission channel.
59
+ listenForPayment(receivable, onDelivery) {
60
+ return stream(hostApi.coinPaymentListenForPayment(enumValue(version, { receivable }), payload => {
61
+ if (payload.tag === version)
62
+ onDelivery(payload.value);
63
+ }));
64
+ },
65
+ };
66
+ };
67
+ export const hostCoinPayment = createCoinPayment();
package/dist/index.d.ts CHANGED
@@ -10,9 +10,16 @@ export type { ProductAccountId, ProductAccountRef, SignedStatement, Statement, S
10
10
  export { createStatementStore } from './statementStore.js';
11
11
  export type { AccountConnectionStatus, AccountSelector, LegacyAccount, ProductAccount, ProofContext, RegisteredRingVrfKey, RingVrfKeyDisclosure, RingVrfKeyHandle, VrfTranscriptItem, } from './accounts.js';
12
12
  export { accounts, createAccountsProvider, ringVrfKeyHandle } from './accounts.js';
13
+ export type { Locale } from './locale.js';
14
+ export { createLocaleProvider } from './locale.js';
13
15
  export type { ThemeMode } from './theme.js';
14
16
  export { createThemeProvider } from './theme.js';
15
17
  export { createLocalStorage, hostLocalStorage } from './localStorage.js';
18
+ export { createWorker, hostWorker } from './worker.js';
19
+ export type { HostInfo, HostPlatform, ProductContext } from './system.js';
20
+ export { createSystem, hostSystem } from './system.js';
21
+ export type { Cheque, ClearingStatus, PaymentDelivery, PurseInfo, Receivable } from './coinPayment.js';
22
+ export { createCoinPayment, hostCoinPayment } from './coinPayment.js';
16
23
  export type { NotificationId, PushNotificationInput } from './notification.js';
17
24
  export { createNotificationManager, notificationManager } from './notification.js';
18
25
  export { createPreimageManager, preimageManager } from './preimage.js';
package/dist/index.js CHANGED
@@ -7,8 +7,12 @@ export { createPapiProvider } from './papiProvider.js';
7
7
  export { createProductChatManager, matchChatCustomRenderers } from './chat.js';
8
8
  export { createStatementStore } from './statementStore.js';
9
9
  export { accounts, createAccountsProvider, ringVrfKeyHandle } from './accounts.js';
10
+ export { createLocaleProvider } from './locale.js';
10
11
  export { createThemeProvider } from './theme.js';
11
12
  export { createLocalStorage, hostLocalStorage } from './localStorage.js';
13
+ export { createWorker, hostWorker } from './worker.js';
14
+ export { createSystem, hostSystem } from './system.js';
15
+ export { createCoinPayment, hostCoinPayment } from './coinPayment.js';
12
16
  export { createNotificationManager, notificationManager } from './notification.js';
13
17
  export { createPreimageManager, preimageManager } from './preimage.js';
14
18
  export { createPaymentManager, paymentManager } from './payments.js';
@@ -1,3 +1,4 @@
1
+ import type { Subscription } from '@novasamatech/host-api';
1
2
  export declare const createLocalStorage: (transport?: import("@novasamatech/host-api").Transport) => {
2
3
  clear(key: string): Promise<undefined>;
3
4
  readBytes(key: string): Promise<Uint8Array<ArrayBufferLike> | undefined>;
@@ -6,6 +7,9 @@ export declare const createLocalStorage: (transport?: import("@novasamatech/host
6
7
  writeString(key: string, value: string): Promise<undefined>;
7
8
  readJSON(key: string): Promise<any>;
8
9
  writeJSON(key: string, value: unknown): Promise<undefined>;
10
+ subscribeBytes(key: string, callback: (value: Uint8Array | undefined) => void): Subscription<void>;
11
+ subscribeString(key: string, callback: (value: string | undefined) => void): Subscription<void>;
12
+ subscribeJSON(key: string, callback: (value: unknown) => void): Subscription<void>;
9
13
  };
10
14
  export declare const hostLocalStorage: {
11
15
  clear(key: string): Promise<undefined>;
@@ -15,4 +19,7 @@ export declare const hostLocalStorage: {
15
19
  writeString(key: string, value: string): Promise<undefined>;
16
20
  readJSON(key: string): Promise<any>;
17
21
  writeJSON(key: string, value: unknown): Promise<undefined>;
22
+ subscribeBytes(key: string, callback: (value: Uint8Array | undefined) => void): Subscription<void>;
23
+ subscribeString(key: string, callback: (value: string | undefined) => void): Subscription<void>;
24
+ subscribeJSON(key: string, callback: (value: unknown) => void): Subscription<void>;
18
25
  };
@@ -15,6 +15,17 @@ export const createLocalStorage = (transport = sandboxTransport) => {
15
15
  function clearKey(key) {
16
16
  return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.localStorageClear(enumValue(supportedVersion, key))));
17
17
  }
18
+ function subscribeBytes(key, callback) {
19
+ const subscriber = hostApi.localStorageSubscribe(enumValue(supportedVersion, { key }), item => {
20
+ if (item.tag === supportedVersion) {
21
+ callback(item.value.value);
22
+ }
23
+ });
24
+ return {
25
+ unsubscribe: subscriber.unsubscribe,
26
+ onInterrupt: cb => subscriber.onInterrupt(v => cb(v.value)),
27
+ };
28
+ }
18
29
  return {
19
30
  async clear(key) {
20
31
  return clearKey(key);
@@ -40,6 +51,17 @@ export const createLocalStorage = (transport = sandboxTransport) => {
40
51
  async writeJSON(key, value) {
41
52
  return writeBytes(key, textEncoder.encode(JSON.stringify(value)));
42
53
  },
54
+ // Emits the current value immediately, then on every later write or clear
55
+ // of the key. `undefined` means the key was cleared or is absent.
56
+ subscribeBytes(key, callback) {
57
+ return subscribeBytes(key, callback);
58
+ },
59
+ subscribeString(key, callback) {
60
+ return subscribeBytes(key, bytes => callback(bytes === undefined ? undefined : textDecoder.decode(bytes)));
61
+ },
62
+ subscribeJSON(key, callback) {
63
+ return subscribeBytes(key, bytes => callback(bytes === undefined || bytes.length === 0 ? undefined : JSON.parse(textDecoder.decode(bytes))));
64
+ },
43
65
  };
44
66
  };
45
67
  export const hostLocalStorage = createLocalStorage();
@@ -0,0 +1,6 @@
1
+ import type { CodecType, Subscription, Transport } from '@novasamatech/host-api';
2
+ import { HostLocale } from '@novasamatech/host-api';
3
+ export type Locale = CodecType<typeof HostLocale>;
4
+ export declare function createLocaleProvider(transport?: Transport): {
5
+ subscribeLocale(callback: (locale: Locale) => void): Subscription<void>;
6
+ };
package/dist/locale.js ADDED
@@ -0,0 +1,18 @@
1
+ import { HostLocale, createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { sandboxTransport } from './sandboxTransport.js';
3
+ export function createLocaleProvider(transport = sandboxTransport) {
4
+ const hostApi = createHostApi(transport);
5
+ return {
6
+ subscribeLocale(callback) {
7
+ const subscriber = hostApi.localeSubscribe(enumValue('v1', undefined), value => {
8
+ if (value.tag === 'v1') {
9
+ callback(value.value);
10
+ }
11
+ });
12
+ return {
13
+ unsubscribe: subscriber.unsubscribe,
14
+ onInterrupt: cb => subscriber.onInterrupt(v => cb(v.value)),
15
+ };
16
+ },
17
+ };
18
+ }
@@ -1,4 +1,4 @@
1
- import { createHostApi, enumValue, unwrapResultOrThrow } from '@novasamatech/host-api';
1
+ import { createHostApi, enumValue, isCallErrorFailure, unwrapResultOrThrow } from '@novasamatech/host-api';
2
2
  import { getSyncProvider } from '@polkadot-api/json-rpc-provider-proxy';
3
3
  import { sandboxTransport } from './sandboxTransport.js';
4
4
  export function createPapiProvider(genesisHash,
@@ -311,6 +311,8 @@ __fallback, internal) {
311
311
  .then(payload => {
312
312
  switch (payload.tag) {
313
313
  case 'v1': {
314
+ if (isCallErrorFailure(payload.value))
315
+ return false;
314
316
  return unwrapResultOrThrow(payload.value, e => new Error(e.payload.reason));
315
317
  }
316
318
  default:
@@ -0,0 +1,13 @@
1
+ import type { CodecType, Transport } from '@novasamatech/host-api';
2
+ import { HostInfo, HostPlatform, ProductContext } from '@novasamatech/host-api';
3
+ export type HostPlatform = CodecType<typeof HostPlatform>;
4
+ export type HostInfo = CodecType<typeof HostInfo>;
5
+ export type ProductContext = CodecType<typeof ProductContext>;
6
+ export declare const createSystem: (transport?: Transport) => {
7
+ info(): Promise<HostInfo>;
8
+ getProductContext(): Promise<ProductContext>;
9
+ };
10
+ export declare const hostSystem: {
11
+ info(): Promise<HostInfo>;
12
+ getProductContext(): Promise<ProductContext>;
13
+ };
package/dist/system.js ADDED
@@ -0,0 +1,18 @@
1
+ import { HostInfo, HostPlatform, ProductContext, createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { resultToPromise, unwrapVersionedResult } from './helpers.js';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export const createSystem = (transport = sandboxTransport) => {
5
+ const supportedVersion = 'v1';
6
+ const hostApi = createHostApi(transport);
7
+ return {
8
+ // Identity and version of the host currently running the product.
9
+ async info() {
10
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.info(enumValue(supportedVersion, undefined))));
11
+ },
12
+ // Product context bound to the current host runtime.
13
+ async getProductContext() {
14
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.getProductContext(enumValue(supportedVersion, undefined))));
15
+ },
16
+ };
17
+ };
18
+ export const hostSystem = createSystem();
@@ -0,0 +1,9 @@
1
+ import type { Transport } from '@novasamatech/host-api';
2
+ export declare const createWorker: (transport?: Transport) => {
3
+ beginOperation(label?: string): Promise<number>;
4
+ endOperation(id: number): Promise<void>;
5
+ };
6
+ export declare const hostWorker: {
7
+ beginOperation(label?: string): Promise<number>;
8
+ endOperation(id: number): Promise<void>;
9
+ };
package/dist/worker.js ADDED
@@ -0,0 +1,22 @@
1
+ import { createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { resultToPromise, unwrapVersionedResult } from './helpers.js';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export const createWorker = (transport = sandboxTransport) => {
5
+ const supportedVersion = 'v1';
6
+ const hostApi = createHostApi(transport);
7
+ return {
8
+ // Begin a pending operation. The host keeps this worker alive while it has
9
+ // at least one open operation, so a background task runs to completion even
10
+ // after the product's surface goes away. Returns the operation id.
11
+ async beginOperation(label) {
12
+ const { id } = await resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.workerBeginOperation(enumValue(supportedVersion, { label }))));
13
+ return id;
14
+ },
15
+ // End a pending operation. Idempotent: an unknown or already-ended id
16
+ // still resolves.
17
+ async endOperation(id) {
18
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.workerEndOperation(enumValue(supportedVersion, { id }))));
19
+ },
20
+ };
21
+ };
22
+ export const hostWorker = createWorker();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-api-wrapper",
3
3
  "type": "module",
4
- "version": "0.9.4",
4
+ "version": "0.10.1",
5
5
  "description": "Host API wrapper: integrate and run your product inside Polkadot browser.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -27,9 +27,10 @@
27
27
  "dependencies": {
28
28
  "@polkadot/extension-inject": "^0.63.1",
29
29
  "@polkadot-api/json-rpc-provider-proxy": "^0.4.0",
30
- "@polkadot-api/substrate-bindings": "^0.20.3",
31
- "@novasamatech/host-api": "0.9.4",
32
- "polkadot-api": ">=2",
30
+ "@polkadot-api/signers-common": "^0.3.0",
31
+ "@polkadot-api/substrate-bindings": "^0.21.0",
32
+ "@novasamatech/host-api": "0.10.1",
33
+ "polkadot-api": ">=3",
33
34
  "neverthrow": "^8.2.0"
34
35
  },
35
36
  "publishConfig": {