@parity/product-sdk 0.1.0
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/LICENSE +201 -0
- package/dist/address/index.d.ts +8 -0
- package/dist/address/index.js +3 -0
- package/dist/address/index.js.map +1 -0
- package/dist/bulletin/index.d.ts +1 -0
- package/dist/bulletin/index.js +3 -0
- package/dist/bulletin/index.js.map +1 -0
- package/dist/chain/index.d.ts +1 -0
- package/dist/chain/index.js +3 -0
- package/dist/chain/index.js.map +1 -0
- package/dist/chunk-NVGSGXGH.js +177 -0
- package/dist/chunk-NVGSGXGH.js.map +1 -0
- package/dist/chunk-XSKBA5SR.js +8 -0
- package/dist/chunk-XSKBA5SR.js.map +1 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +3 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/index.js +3 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +3 -0
- package/dist/host/index.js.map +1 -0
- package/dist/identity/index.d.ts +186 -0
- package/dist/identity/index.js +109 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +177 -0
- package/dist/react/index.js +213 -0
- package/dist/react/index.js.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-CZQDzQ53.d.ts +142 -0
- package/dist/wallet/index.d.ts +1 -0
- package/dist/wallet/index.js +3 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +105 -0
- package/src/address/index.ts +6 -0
- package/src/bulletin/index.ts +6 -0
- package/src/chain/index.ts +6 -0
- package/src/contracts/index.ts +6 -0
- package/src/core/createApp.ts +284 -0
- package/src/core/index.ts +10 -0
- package/src/core/logger.ts +13 -0
- package/src/core/types.ts +155 -0
- package/src/crypto/index.ts +6 -0
- package/src/host/index.ts +6 -0
- package/src/identity/dotns.ts +96 -0
- package/src/identity/index.ts +34 -0
- package/src/identity/product-account.ts +158 -0
- package/src/identity/types.ts +75 -0
- package/src/index.ts +49 -0
- package/src/react/context.ts +33 -0
- package/src/react/index.ts +41 -0
- package/src/react/provider.tsx +76 -0
- package/src/react/useChain.ts +33 -0
- package/src/react/useStorage.ts +125 -0
- package/src/react/useWallet.ts +138 -0
- package/src/storage/index.ts +6 -0
- package/src/wallet/index.ts +9 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product account derivation
|
|
3
|
+
*
|
|
4
|
+
* Derives product-scoped accounts from a parent account
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
8
|
+
import { blake2b256 } from "@parity/product-sdk-crypto";
|
|
9
|
+
import { ss58Encode, ss58Decode, deriveH160 } from "@parity/product-sdk-address";
|
|
10
|
+
import type { ProductAccountInfo, AnonymousAliasInfo, RingLocation } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const log = createLogger("identity");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Derive a product-scoped account from a parent account
|
|
16
|
+
*
|
|
17
|
+
* The product account is deterministically derived using:
|
|
18
|
+
* productPublicKey = hash(parentPublicKey || productName)
|
|
19
|
+
*
|
|
20
|
+
* @param parentAddress - Parent account SS58 address
|
|
21
|
+
* @param productName - Product name for derivation
|
|
22
|
+
* @param ss58Prefix - SS58 prefix (default: 42)
|
|
23
|
+
* @returns Product account info
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const productAccount = deriveProductAccount(
|
|
28
|
+
* '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
|
29
|
+
* 'my-app'
|
|
30
|
+
* );
|
|
31
|
+
* console.log('Product address:', productAccount.address);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function deriveProductAccount(
|
|
35
|
+
parentAddress: string,
|
|
36
|
+
productName: string,
|
|
37
|
+
ss58Prefix = 42,
|
|
38
|
+
): ProductAccountInfo {
|
|
39
|
+
const { publicKey: parentPublicKey } = ss58Decode(parentAddress);
|
|
40
|
+
|
|
41
|
+
// Derive product public key: blake2b-256(parentPublicKey || productName)
|
|
42
|
+
const productNameBytes = new TextEncoder().encode(productName);
|
|
43
|
+
const combined = new Uint8Array(parentPublicKey.length + productNameBytes.length);
|
|
44
|
+
combined.set(parentPublicKey, 0);
|
|
45
|
+
combined.set(productNameBytes, parentPublicKey.length);
|
|
46
|
+
|
|
47
|
+
const productPublicKey = blake2b256(combined);
|
|
48
|
+
const address = ss58Encode(productPublicKey, ss58Prefix);
|
|
49
|
+
const h160Address = deriveH160(productPublicKey);
|
|
50
|
+
|
|
51
|
+
log.debug("Derived product account", {
|
|
52
|
+
parentAddress,
|
|
53
|
+
productName,
|
|
54
|
+
address,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
address,
|
|
59
|
+
h160Address,
|
|
60
|
+
parentAddress,
|
|
61
|
+
productName,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Verify that a product account was derived from a parent account
|
|
67
|
+
*
|
|
68
|
+
* @param productAddress - Product account address
|
|
69
|
+
* @param parentAddress - Claimed parent address
|
|
70
|
+
* @param productName - Product name
|
|
71
|
+
* @returns True if derivation is valid
|
|
72
|
+
*/
|
|
73
|
+
export function verifyProductAccount(
|
|
74
|
+
productAddress: string,
|
|
75
|
+
parentAddress: string,
|
|
76
|
+
productName: string,
|
|
77
|
+
): boolean {
|
|
78
|
+
try {
|
|
79
|
+
const derived = deriveProductAccount(parentAddress, productName);
|
|
80
|
+
const { publicKey: productKey } = ss58Decode(productAddress);
|
|
81
|
+
const { publicKey: derivedKey } = ss58Decode(derived.address);
|
|
82
|
+
|
|
83
|
+
// Compare public keys
|
|
84
|
+
if (productKey.length !== derivedKey.length) return false;
|
|
85
|
+
for (let i = 0; i < productKey.length; i++) {
|
|
86
|
+
if (productKey[i] !== derivedKey[i]) return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Derive an anonymous alias using Ring VRF
|
|
96
|
+
*
|
|
97
|
+
* This creates a context-specific alias that cannot be linked
|
|
98
|
+
* back to the original identity without the ring proof.
|
|
99
|
+
*
|
|
100
|
+
* @param context - Context for alias derivation (e.g., "voting-round-1")
|
|
101
|
+
* @param ringLocation - Ring location for proof generation
|
|
102
|
+
* @returns Anonymous alias info
|
|
103
|
+
*/
|
|
104
|
+
export function deriveAnonymousAlias(
|
|
105
|
+
context: string,
|
|
106
|
+
ringLocation: RingLocation,
|
|
107
|
+
): AnonymousAliasInfo {
|
|
108
|
+
log.debug("Deriving anonymous alias", { context, ringLocation });
|
|
109
|
+
|
|
110
|
+
// TODO: Implement Ring VRF alias derivation
|
|
111
|
+
// This requires the Ring VRF implementation from TruAPI
|
|
112
|
+
throw new Error(
|
|
113
|
+
"deriveAnonymousAlias() is not yet implemented. " +
|
|
114
|
+
"This requires container mode with Ring VRF support.",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create a Ring VRF proof for a message
|
|
120
|
+
*
|
|
121
|
+
* @param message - Message to prove
|
|
122
|
+
* @param ringLocation - Ring location
|
|
123
|
+
* @returns Proof bytes
|
|
124
|
+
*/
|
|
125
|
+
export async function createRingProof(
|
|
126
|
+
message: Uint8Array,
|
|
127
|
+
ringLocation: RingLocation,
|
|
128
|
+
): Promise<Uint8Array> {
|
|
129
|
+
log.debug("Creating ring proof", { ringLocation });
|
|
130
|
+
|
|
131
|
+
// TODO: Implement Ring VRF proof creation via TruAPI
|
|
132
|
+
throw new Error(
|
|
133
|
+
"createRingProof() is not yet implemented. " +
|
|
134
|
+
"This requires container mode with Ring VRF support.",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Verify a Ring VRF proof
|
|
140
|
+
*
|
|
141
|
+
* @param message - Original message
|
|
142
|
+
* @param proof - Proof bytes
|
|
143
|
+
* @param alias - Expected alias
|
|
144
|
+
* @returns True if proof is valid
|
|
145
|
+
*/
|
|
146
|
+
export async function verifyRingProof(
|
|
147
|
+
message: Uint8Array,
|
|
148
|
+
proof: Uint8Array,
|
|
149
|
+
alias: string,
|
|
150
|
+
): Promise<boolean> {
|
|
151
|
+
log.debug("Verifying ring proof");
|
|
152
|
+
|
|
153
|
+
// TODO: Implement Ring VRF proof verification
|
|
154
|
+
throw new Error(
|
|
155
|
+
"verifyRingProof() is not yet implemented. " +
|
|
156
|
+
"This requires container mode with Ring VRF support.",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity module types
|
|
3
|
+
*
|
|
4
|
+
* Types for DotNS name resolution and product account derivation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** DotNS name resolution result */
|
|
8
|
+
export interface DotNsRecord {
|
|
9
|
+
/** Resolved SS58 address */
|
|
10
|
+
address: string;
|
|
11
|
+
/** Name that was resolved */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Owner address */
|
|
14
|
+
owner: string;
|
|
15
|
+
/** Expiration timestamp (if applicable) */
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Product account info */
|
|
20
|
+
export interface ProductAccountInfo {
|
|
21
|
+
/** Product-scoped SS58 address */
|
|
22
|
+
address: string;
|
|
23
|
+
/** H160 EVM address */
|
|
24
|
+
h160Address: `0x${string}`;
|
|
25
|
+
/** Parent account address */
|
|
26
|
+
parentAddress: string;
|
|
27
|
+
/** Product name used for derivation */
|
|
28
|
+
productName: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Ring VRF alias info */
|
|
32
|
+
export interface AnonymousAliasInfo {
|
|
33
|
+
/** Anonymous alias identifier */
|
|
34
|
+
alias: string;
|
|
35
|
+
/** Ring location for proof generation */
|
|
36
|
+
ringLocation: RingLocation;
|
|
37
|
+
/** Context used for alias derivation */
|
|
38
|
+
context: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Ring location for VRF proofs */
|
|
42
|
+
export interface RingLocation {
|
|
43
|
+
/** Ring index */
|
|
44
|
+
ringIndex: number;
|
|
45
|
+
/** Member index within ring */
|
|
46
|
+
memberIndex: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Identity verification result */
|
|
50
|
+
export interface VerificationResult {
|
|
51
|
+
/** Whether identity is verified */
|
|
52
|
+
verified: boolean;
|
|
53
|
+
/** Verification method used */
|
|
54
|
+
method: "on-chain" | "judgement" | "social";
|
|
55
|
+
/** Verification details */
|
|
56
|
+
details?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** On-chain identity info */
|
|
60
|
+
export interface OnChainIdentity {
|
|
61
|
+
/** Display name */
|
|
62
|
+
display?: string;
|
|
63
|
+
/** Legal name */
|
|
64
|
+
legal?: string;
|
|
65
|
+
/** Web URL */
|
|
66
|
+
web?: string;
|
|
67
|
+
/** Email */
|
|
68
|
+
email?: string;
|
|
69
|
+
/** Twitter handle */
|
|
70
|
+
twitter?: string;
|
|
71
|
+
/** Riot/Matrix handle */
|
|
72
|
+
riot?: string;
|
|
73
|
+
/** Additional fields */
|
|
74
|
+
additional: Array<[string, string]>;
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @parity/product-sdk
|
|
3
|
+
*
|
|
4
|
+
* Unified SDK for building products on the Polkadot ecosystem.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createApp } from '@parity/product-sdk';
|
|
9
|
+
*
|
|
10
|
+
* const app = createApp({
|
|
11
|
+
* name: 'my-app',
|
|
12
|
+
* logLevel: 'info',
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // Connect wallet
|
|
16
|
+
* const { accounts } = await app.wallet.connect();
|
|
17
|
+
*
|
|
18
|
+
* // Use storage
|
|
19
|
+
* await app.storage.set('key', 'value');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Core exports
|
|
26
|
+
export { createApp } from "./core/createApp.js";
|
|
27
|
+
export { configure, createLogger } from "./core/logger.js";
|
|
28
|
+
export type {
|
|
29
|
+
App,
|
|
30
|
+
AppConfig,
|
|
31
|
+
LogLevel,
|
|
32
|
+
WalletApi,
|
|
33
|
+
StorageApi,
|
|
34
|
+
ChainApi,
|
|
35
|
+
BulletinApi,
|
|
36
|
+
Account,
|
|
37
|
+
ChainClient,
|
|
38
|
+
ChainDefinition,
|
|
39
|
+
TypedApi,
|
|
40
|
+
PolkadotClient,
|
|
41
|
+
} from "./core/types.js";
|
|
42
|
+
export type { LogEntry, LogHandler, LoggerConfig, Logger } from "./core/logger.js";
|
|
43
|
+
|
|
44
|
+
// Re-export common utilities from leaf packages
|
|
45
|
+
export { isInsideContainer, isInsideContainerSync } from "@parity/product-sdk-host";
|
|
46
|
+
export { createChainClient } from "@parity/product-sdk-chain-client";
|
|
47
|
+
export { SignerManager } from "@parity/product-sdk-signer";
|
|
48
|
+
export { createKvStore } from "@parity/product-sdk-storage";
|
|
49
|
+
export { BulletinClient, computeCid } from "@parity/product-sdk-bulletin";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React context for Product SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createContext, useContext } from "react";
|
|
6
|
+
import type { App } from "../core/types.js";
|
|
7
|
+
|
|
8
|
+
/** Context for the Product SDK app instance */
|
|
9
|
+
export const ProductSDKContext = createContext<App | null>(null);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook to access the Product SDK app instance
|
|
13
|
+
*
|
|
14
|
+
* @throws If used outside of ProductSDKProvider
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function MyComponent() {
|
|
19
|
+
* const app = useProductSDK();
|
|
20
|
+
* // Use app.wallet, app.storage, etc.
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function useProductSDK(): App {
|
|
25
|
+
const app = useContext(ProductSDKContext);
|
|
26
|
+
if (!app) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"useProductSDK must be used within a ProductSDKProvider. " +
|
|
29
|
+
'Wrap your app with <ProductSDKProvider name="your-app">.',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return app;
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @parity/product-sdk/react
|
|
3
|
+
*
|
|
4
|
+
* React bindings for the Product SDK.
|
|
5
|
+
* Provides hooks and components for wallet connection, storage, and chain interaction.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { ProductSDKProvider, useWallet, useStorage } from '@parity/product-sdk/react';
|
|
10
|
+
*
|
|
11
|
+
* function App() {
|
|
12
|
+
* return (
|
|
13
|
+
* <ProductSDKProvider name="my-app">
|
|
14
|
+
* <MyApp />
|
|
15
|
+
* </ProductSDKProvider>
|
|
16
|
+
* );
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* function MyApp() {
|
|
20
|
+
* const { isConnected, connect, accounts } = useWallet();
|
|
21
|
+
* const [theme, setTheme] = useStorage('theme', 'light');
|
|
22
|
+
*
|
|
23
|
+
* // ...
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Provider
|
|
29
|
+
export { ProductSDKProvider } from "./provider.js";
|
|
30
|
+
export type { ProductSDKProviderProps } from "./provider.js";
|
|
31
|
+
|
|
32
|
+
// Context
|
|
33
|
+
export { ProductSDKContext, useProductSDK } from "./context.js";
|
|
34
|
+
|
|
35
|
+
// Hooks
|
|
36
|
+
export { useWallet } from "./useWallet.js";
|
|
37
|
+
export type { UseWalletState, UseWalletActions, UseWalletReturn } from "./useWallet.js";
|
|
38
|
+
|
|
39
|
+
export { useStorage, useStorageString } from "./useStorage.js";
|
|
40
|
+
|
|
41
|
+
export { useChain } from "./useChain.js";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProductSDKProvider component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useState, type ReactNode } from "react";
|
|
6
|
+
import { ProductSDKContext } from "./context.js";
|
|
7
|
+
import { createApp } from "../core/createApp.js";
|
|
8
|
+
import type { App, LogLevel } from "../core/types.js";
|
|
9
|
+
|
|
10
|
+
/** Props for ProductSDKProvider */
|
|
11
|
+
export interface ProductSDKProviderProps {
|
|
12
|
+
/** Application name - used for storage namespacing and product account derivation */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Log level (default: 'info') */
|
|
15
|
+
logLevel?: LogLevel;
|
|
16
|
+
/** Child components */
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
/** Fallback to show while loading */
|
|
19
|
+
fallback?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Provider component that initializes the Product SDK
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { ProductSDKProvider } from '@parity/product-sdk/react';
|
|
28
|
+
*
|
|
29
|
+
* function App() {
|
|
30
|
+
* return (
|
|
31
|
+
* <ProductSDKProvider name="my-app" fallback={<Loading />}>
|
|
32
|
+
* <MyApp />
|
|
33
|
+
* </ProductSDKProvider>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function ProductSDKProvider({
|
|
39
|
+
name,
|
|
40
|
+
logLevel = "info",
|
|
41
|
+
children,
|
|
42
|
+
fallback = null,
|
|
43
|
+
}: ProductSDKProviderProps) {
|
|
44
|
+
const [app, setApp] = useState<App | null>(null);
|
|
45
|
+
const [error, setError] = useState<Error | null>(null);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let mounted = true;
|
|
49
|
+
|
|
50
|
+
createApp({ name, logLevel })
|
|
51
|
+
.then((createdApp) => {
|
|
52
|
+
if (mounted) {
|
|
53
|
+
setApp(createdApp);
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch((e) => {
|
|
57
|
+
if (mounted) {
|
|
58
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
mounted = false;
|
|
64
|
+
};
|
|
65
|
+
}, [name, logLevel]);
|
|
66
|
+
|
|
67
|
+
if (error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!app) {
|
|
72
|
+
return <>{fallback}</>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return <ProductSDKContext.Provider value={app}>{children}</ProductSDKContext.Provider>;
|
|
76
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChain hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { useProductSDK } from "./context.js";
|
|
7
|
+
import type { ChainDefinition, TypedApi } from "../core/types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to get a typed chain client
|
|
11
|
+
*
|
|
12
|
+
* @param chain - Chain descriptor (PAPI ChainDefinition)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { paseo_asset_hub } from '@parity/product-sdk-descriptors/paseo-asset-hub';
|
|
17
|
+
* import { useChain } from '@parity/product-sdk/react';
|
|
18
|
+
*
|
|
19
|
+
* function AssetHubBalance() {
|
|
20
|
+
* const assetHub = useChain(paseo_asset_hub);
|
|
21
|
+
*
|
|
22
|
+
* // assetHub is typed for Asset Hub queries
|
|
23
|
+
* const balance = await assetHub.query.System.Account.getValue(address);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function useChain<T extends ChainDefinition>(chain: T): TypedApi<T> {
|
|
28
|
+
const app = useProductSDK();
|
|
29
|
+
|
|
30
|
+
return useMemo(() => {
|
|
31
|
+
return app.chain.getClient(chain);
|
|
32
|
+
}, [app, chain]);
|
|
33
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStorage hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useEffect, useCallback } from "react";
|
|
6
|
+
import { useProductSDK } from "./context.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for key-value storage operations
|
|
10
|
+
*
|
|
11
|
+
* @param key - Storage key
|
|
12
|
+
* @param defaultValue - Default value if key doesn't exist
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* function ThemeToggle() {
|
|
17
|
+
* const [theme, setTheme, { loading }] = useStorage('theme', 'light');
|
|
18
|
+
*
|
|
19
|
+
* if (loading) return <div>Loading...</div>;
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
|
23
|
+
* Current: {theme}
|
|
24
|
+
* </button>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function useStorage<T = string>(
|
|
30
|
+
key: string,
|
|
31
|
+
defaultValue?: T,
|
|
32
|
+
): [T | null, (value: T) => Promise<void>, { loading: boolean; error: Error | null }] {
|
|
33
|
+
const app = useProductSDK();
|
|
34
|
+
|
|
35
|
+
const [value, setValue] = useState<T | null>(null);
|
|
36
|
+
const [loading, setLoading] = useState(true);
|
|
37
|
+
const [error, setError] = useState<Error | null>(null);
|
|
38
|
+
|
|
39
|
+
// Load initial value
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
let mounted = true;
|
|
42
|
+
|
|
43
|
+
const loadValue = async () => {
|
|
44
|
+
try {
|
|
45
|
+
setLoading(true);
|
|
46
|
+
const stored = await app.storage.getJSON<T>(key);
|
|
47
|
+
if (mounted) {
|
|
48
|
+
setValue(stored ?? defaultValue ?? null);
|
|
49
|
+
setLoading(false);
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
if (mounted) {
|
|
53
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
loadValue();
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
mounted = false;
|
|
63
|
+
};
|
|
64
|
+
}, [app, key, defaultValue]);
|
|
65
|
+
|
|
66
|
+
const setStoredValue = useCallback(
|
|
67
|
+
async (newValue: T) => {
|
|
68
|
+
try {
|
|
69
|
+
setError(null);
|
|
70
|
+
await app.storage.setJSON(key, newValue);
|
|
71
|
+
setValue(newValue);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[app, key],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return [value, setStoredValue, { loading, error }];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hook for string storage (simpler API)
|
|
85
|
+
*
|
|
86
|
+
* @param key - Storage key
|
|
87
|
+
* @param defaultValue - Default value
|
|
88
|
+
*/
|
|
89
|
+
export function useStorageString(
|
|
90
|
+
key: string,
|
|
91
|
+
defaultValue?: string,
|
|
92
|
+
): [string | null, (value: string) => Promise<void>, { loading: boolean }] {
|
|
93
|
+
const app = useProductSDK();
|
|
94
|
+
|
|
95
|
+
const [value, setValue] = useState<string | null>(null);
|
|
96
|
+
const [loading, setLoading] = useState(true);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
let mounted = true;
|
|
100
|
+
|
|
101
|
+
const loadValue = async () => {
|
|
102
|
+
const stored = await app.storage.get(key);
|
|
103
|
+
if (mounted) {
|
|
104
|
+
setValue(stored ?? defaultValue ?? null);
|
|
105
|
+
setLoading(false);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
loadValue();
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
mounted = false;
|
|
113
|
+
};
|
|
114
|
+
}, [app, key, defaultValue]);
|
|
115
|
+
|
|
116
|
+
const setStoredValue = useCallback(
|
|
117
|
+
async (newValue: string) => {
|
|
118
|
+
await app.storage.set(key, newValue);
|
|
119
|
+
setValue(newValue);
|
|
120
|
+
},
|
|
121
|
+
[app, key],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return [value, setStoredValue, { loading }];
|
|
125
|
+
}
|