@sip-protocol/react 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/dist/index.mjs ADDED
@@ -0,0 +1,358 @@
1
+ // src/providers/sip-provider.tsx
2
+ import React, { createContext, useContext, useMemo } from "react";
3
+ import { SIP } from "@sip-protocol/sdk";
4
+ var SIPContext = createContext(void 0);
5
+ function SIPProvider({ config, children }) {
6
+ const client = useMemo(() => new SIP(config), [config]);
7
+ const value = useMemo(() => ({ client, config }), [client, config]);
8
+ return /* @__PURE__ */ React.createElement(SIPContext.Provider, { value }, children);
9
+ }
10
+ function useSIPContext() {
11
+ const context = useContext(SIPContext);
12
+ if (!context) {
13
+ throw new Error("useSIPContext must be used within SIPProvider");
14
+ }
15
+ return context;
16
+ }
17
+
18
+ // src/hooks/use-sip.ts
19
+ import { useState, useCallback } from "react";
20
+ import { SIP as SIP2 } from "@sip-protocol/sdk";
21
+ function useSIP() {
22
+ const [standaloneClient, setStandaloneClient] = useState(null);
23
+ const [standaloneReady, setStandaloneReady] = useState(false);
24
+ const [standaloneError, setStandaloneError] = useState(null);
25
+ let providerContext;
26
+ try {
27
+ providerContext = useSIPContext();
28
+ } catch {
29
+ providerContext = null;
30
+ }
31
+ const standaloneInitialize = useCallback(async (config) => {
32
+ if (standaloneClient && standaloneReady) {
33
+ console.warn("SIP client already initialized. Call will be ignored.");
34
+ return;
35
+ }
36
+ try {
37
+ setStandaloneError(null);
38
+ setStandaloneReady(false);
39
+ const newClient = new SIP2(config);
40
+ setStandaloneClient(newClient);
41
+ setStandaloneReady(true);
42
+ } catch (err) {
43
+ const error = err instanceof Error ? err : new Error(String(err));
44
+ setStandaloneError(error);
45
+ setStandaloneReady(false);
46
+ throw error;
47
+ }
48
+ }, [standaloneClient, standaloneReady]);
49
+ if (providerContext) {
50
+ return {
51
+ client: providerContext.client,
52
+ isReady: true,
53
+ // Provider always provides ready client
54
+ error: null,
55
+ // Provider throws on error, doesn't expose it
56
+ initialize: async () => {
57
+ console.warn("initialize() called but SIPProvider is already providing a client. This call will be ignored.");
58
+ }
59
+ };
60
+ }
61
+ return {
62
+ client: standaloneClient,
63
+ isReady: standaloneReady,
64
+ error: standaloneError,
65
+ initialize: standaloneInitialize
66
+ };
67
+ }
68
+
69
+ // src/hooks/use-stealth-address.ts
70
+ import { useState as useState2, useEffect, useCallback as useCallback2 } from "react";
71
+ import {
72
+ generateStealthMetaAddress,
73
+ generateStealthAddress,
74
+ generateEd25519StealthMetaAddress,
75
+ generateEd25519StealthAddress,
76
+ encodeStealthMetaAddress,
77
+ isEd25519Chain
78
+ } from "@sip-protocol/sdk";
79
+ function useStealthAddress(chain) {
80
+ const [metaAddress, setMetaAddress] = useState2(null);
81
+ const [stealthAddress, setStealthAddress] = useState2(null);
82
+ const [isGenerating, setIsGenerating] = useState2(false);
83
+ useEffect(() => {
84
+ let cancelled = false;
85
+ setIsGenerating(true);
86
+ const timer = setTimeout(() => {
87
+ if (cancelled) return;
88
+ try {
89
+ const isEd25519 = isEd25519Chain(chain);
90
+ const metaAddressData = isEd25519 ? generateEd25519StealthMetaAddress(chain) : generateStealthMetaAddress(chain);
91
+ const encoded = encodeStealthMetaAddress(metaAddressData.metaAddress);
92
+ if (cancelled) return;
93
+ setMetaAddress(encoded);
94
+ const stealthData = isEd25519 ? generateEd25519StealthAddress(metaAddressData.metaAddress) : generateStealthAddress(metaAddressData.metaAddress);
95
+ if (cancelled) return;
96
+ setStealthAddress(stealthData.stealthAddress.address);
97
+ } catch (error) {
98
+ console.error("Failed to generate stealth addresses:", error);
99
+ if (cancelled) return;
100
+ setMetaAddress(null);
101
+ setStealthAddress(null);
102
+ } finally {
103
+ if (!cancelled) {
104
+ setIsGenerating(false);
105
+ }
106
+ }
107
+ }, 0);
108
+ return () => {
109
+ cancelled = true;
110
+ clearTimeout(timer);
111
+ };
112
+ }, [chain]);
113
+ const regenerate = useCallback2(() => {
114
+ if (!metaAddress) {
115
+ return;
116
+ }
117
+ setIsGenerating(true);
118
+ setTimeout(() => {
119
+ try {
120
+ const parts = metaAddress.split(":");
121
+ if (parts.length < 4) {
122
+ throw new Error("Invalid meta-address format");
123
+ }
124
+ const [, chainId, spendingKey, viewingKey] = parts;
125
+ const metaAddressObj = {
126
+ chain: chainId,
127
+ spendingKey: spendingKey.startsWith("0x") ? spendingKey : `0x${spendingKey}`,
128
+ viewingKey: viewingKey.startsWith("0x") ? viewingKey : `0x${viewingKey}`
129
+ };
130
+ const isEd25519 = isEd25519Chain(chain);
131
+ const stealthData = isEd25519 ? generateEd25519StealthAddress(metaAddressObj) : generateStealthAddress(metaAddressObj);
132
+ setStealthAddress(stealthData.stealthAddress.address);
133
+ } catch (error) {
134
+ console.error("Failed to regenerate stealth address:", error);
135
+ } finally {
136
+ setIsGenerating(false);
137
+ }
138
+ }, 0);
139
+ }, [metaAddress, chain]);
140
+ const copyToClipboard = useCallback2(async () => {
141
+ if (!stealthAddress) {
142
+ return;
143
+ }
144
+ try {
145
+ await navigator.clipboard.writeText(stealthAddress);
146
+ } catch (error) {
147
+ console.error("Failed to copy to clipboard:", error);
148
+ const textArea = document.createElement("textarea");
149
+ textArea.value = stealthAddress;
150
+ textArea.style.position = "fixed";
151
+ textArea.style.left = "-999999px";
152
+ document.body.appendChild(textArea);
153
+ textArea.select();
154
+ try {
155
+ document.execCommand("copy");
156
+ } catch (err) {
157
+ console.error("Fallback copy failed:", err);
158
+ } finally {
159
+ document.body.removeChild(textArea);
160
+ }
161
+ }
162
+ }, [stealthAddress]);
163
+ return {
164
+ metaAddress,
165
+ stealthAddress,
166
+ isGenerating,
167
+ regenerate,
168
+ copyToClipboard
169
+ };
170
+ }
171
+
172
+ // src/hooks/use-private-swap.ts
173
+ import { useState as useState3, useCallback as useCallback3 } from "react";
174
+ function usePrivateSwap() {
175
+ const { client: sip } = useSIP();
176
+ const [quote, setQuote] = useState3(null);
177
+ const [status, setStatus] = useState3("idle");
178
+ const [isLoading, setIsLoading] = useState3(false);
179
+ const [error, setError] = useState3(null);
180
+ const fetchQuote = useCallback3(async (params) => {
181
+ if (!sip) {
182
+ throw new Error("SIP client not initialized. Wrap your app with SIPProvider or call initialize().");
183
+ }
184
+ try {
185
+ setStatus("fetching_quote");
186
+ setIsLoading(true);
187
+ setError(null);
188
+ const intentParams = {
189
+ input: {
190
+ asset: {
191
+ chain: params.inputChain,
192
+ symbol: params.inputToken,
193
+ address: null,
194
+ decimals: 9
195
+ // Default, should be configurable
196
+ },
197
+ amount: BigInt(params.inputAmount)
198
+ },
199
+ output: {
200
+ asset: {
201
+ chain: params.outputChain,
202
+ symbol: params.outputToken,
203
+ address: null,
204
+ decimals: 18
205
+ // Default, should be configurable
206
+ },
207
+ minAmount: 0n,
208
+ maxSlippage: params.maxSlippage ?? 0.01
209
+ },
210
+ privacy: params.privacyLevel ?? "shielded"
211
+ };
212
+ const quotes = await sip.getQuotes(intentParams);
213
+ if (quotes.length === 0) {
214
+ throw new Error("No quotes available");
215
+ }
216
+ setQuote(quotes[0]);
217
+ setStatus("idle");
218
+ } catch (err) {
219
+ setError(err instanceof Error ? err : new Error("Failed to fetch quote"));
220
+ setStatus("failed");
221
+ throw err;
222
+ } finally {
223
+ setIsLoading(false);
224
+ }
225
+ }, [sip]);
226
+ const swap = useCallback3(async (params) => {
227
+ if (!sip) {
228
+ throw new Error("SIP client not initialized. Wrap your app with SIPProvider or call initialize().");
229
+ }
230
+ try {
231
+ setStatus("pending");
232
+ setIsLoading(true);
233
+ setError(null);
234
+ const intentParams = {
235
+ input: {
236
+ asset: {
237
+ chain: params.input.chain,
238
+ symbol: params.input.token,
239
+ address: null,
240
+ decimals: 9
241
+ // Default, should be configurable
242
+ },
243
+ amount: params.input.amount
244
+ },
245
+ output: {
246
+ asset: {
247
+ chain: params.output.chain,
248
+ symbol: params.output.token,
249
+ address: null,
250
+ decimals: 18
251
+ // Default, should be configurable
252
+ },
253
+ minAmount: params.output.minAmount,
254
+ maxSlippage: params.maxSlippage ?? 0.01
255
+ },
256
+ privacy: params.privacyLevel
257
+ };
258
+ const intent = await sip.createIntent(intentParams);
259
+ let swapQuote = quote;
260
+ if (!swapQuote) {
261
+ const quotes = await sip.getQuotes(intentParams);
262
+ if (quotes.length === 0) {
263
+ throw new Error("No quotes available");
264
+ }
265
+ swapQuote = quotes[0];
266
+ }
267
+ setStatus("confirming");
268
+ const result = await sip.execute(intent, swapQuote);
269
+ setStatus("completed");
270
+ return {
271
+ txHash: result.txHash,
272
+ status: result.status,
273
+ outputAmount: result.outputAmount,
274
+ intentId: result.intentId
275
+ };
276
+ } catch (err) {
277
+ setError(err instanceof Error ? err : new Error("Swap failed"));
278
+ setStatus("failed");
279
+ throw err;
280
+ } finally {
281
+ setIsLoading(false);
282
+ }
283
+ }, [sip, quote]);
284
+ const reset = useCallback3(() => {
285
+ setQuote(null);
286
+ setStatus("idle");
287
+ setIsLoading(false);
288
+ setError(null);
289
+ }, []);
290
+ return {
291
+ quote,
292
+ fetchQuote,
293
+ swap,
294
+ status,
295
+ isLoading,
296
+ error,
297
+ reset
298
+ };
299
+ }
300
+
301
+ // src/hooks/use-viewing-key.ts
302
+ import { useState as useState4, useCallback as useCallback4 } from "react";
303
+ import {
304
+ generateViewingKey as sdkGenerateViewingKey,
305
+ decryptWithViewing
306
+ } from "@sip-protocol/sdk";
307
+ function useViewingKey() {
308
+ const [viewingKey, setViewingKey] = useState4(null);
309
+ const [sharedWith, setSharedWith] = useState4([]);
310
+ const generate = useCallback4((path) => {
311
+ const key = sdkGenerateViewingKey(path);
312
+ setViewingKey(key);
313
+ setSharedWith([]);
314
+ return key;
315
+ }, []);
316
+ const decrypt = useCallback4(
317
+ async (encrypted) => {
318
+ if (!viewingKey) {
319
+ throw new Error("No viewing key available. Call generate() first.");
320
+ }
321
+ return decryptWithViewing(encrypted, viewingKey);
322
+ },
323
+ [viewingKey]
324
+ );
325
+ const share = useCallback4(
326
+ async (auditorId) => {
327
+ if (!viewingKey) {
328
+ throw new Error("No viewing key available. Call generate() first.");
329
+ }
330
+ const shareEntry = {
331
+ auditorId,
332
+ viewingKeyHash: viewingKey.hash,
333
+ sharedAt: Date.now()
334
+ };
335
+ setSharedWith((prev) => [...prev, shareEntry]);
336
+ },
337
+ [viewingKey]
338
+ );
339
+ return {
340
+ /** Current viewing key (null if not generated) */
341
+ viewingKey,
342
+ /** List of auditors who have been given access */
343
+ sharedWith,
344
+ /** Generate a new viewing key */
345
+ generate,
346
+ /** Decrypt encrypted transaction data */
347
+ decrypt,
348
+ /** Share viewing key with an auditor */
349
+ share
350
+ };
351
+ }
352
+ export {
353
+ SIPProvider,
354
+ usePrivateSwap,
355
+ useSIP,
356
+ useStealthAddress,
357
+ useViewingKey
358
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@sip-protocol/react",
3
+ "version": "0.1.0",
4
+ "description": "React hooks for Shielded Intents Protocol",
5
+ "author": "SIP Protocol <hello@sip-protocol.org>",
6
+ "homepage": "https://sip-protocol.org",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/sip-protocol/sip-protocol.git",
10
+ "directory": "packages/react"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/sip-protocol/sip-protocol/issues"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src"
28
+ ],
29
+ "dependencies": {
30
+ "@sip-protocol/sdk": "^0.6.0",
31
+ "@sip-protocol/types": "^0.2.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^18.0.0",
35
+ "react-dom": "^18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@testing-library/react": "^16.3.0",
39
+ "@testing-library/react-hooks": "^8.0.1",
40
+ "@types/react": "^18.2.0",
41
+ "@types/react-dom": "^18.2.0",
42
+ "jsdom": "^27.2.0",
43
+ "tsup": "^8.0.0",
44
+ "typescript": "^5.3.0",
45
+ "vitest": "^1.1.0"
46
+ },
47
+ "keywords": [
48
+ "sip",
49
+ "privacy",
50
+ "intents",
51
+ "cross-chain",
52
+ "react",
53
+ "hooks"
54
+ ],
55
+ "license": "MIT",
56
+ "scripts": {
57
+ "build": "tsup src/index.ts --format cjs,esm --dts",
58
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
59
+ "lint": "eslint --ext .ts,.tsx src/",
60
+ "typecheck": "tsc --noEmit",
61
+ "clean": "rm -rf dist",
62
+ "test": "vitest"
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ export { useSIP, type UseSIPReturn } from './use-sip'
2
+ export { useStealthAddress } from './use-stealth-address'
3
+ export {
4
+ usePrivateSwap,
5
+ type SwapStatus,
6
+ type QuoteParams,
7
+ type SwapParams,
8
+ type SwapResult,
9
+ } from './use-private-swap'
10
+ export { useViewingKey } from './use-viewing-key'
@@ -0,0 +1,268 @@
1
+ import { useState, useCallback } from 'react'
2
+ import { useSIP } from './use-sip'
3
+ import type { Quote, PrivacyLevel, CreateIntentParams, TrackedIntent, FulfillmentResult } from '@sip-protocol/types'
4
+
5
+ /**
6
+ * Status of the swap lifecycle
7
+ */
8
+ export type SwapStatus =
9
+ | 'idle'
10
+ | 'fetching_quote'
11
+ | 'pending'
12
+ | 'confirming'
13
+ | 'completed'
14
+ | 'failed'
15
+
16
+ /**
17
+ * Parameters for fetching a quote
18
+ */
19
+ export interface QuoteParams {
20
+ /** Input chain */
21
+ inputChain: string
22
+ /** Output chain */
23
+ outputChain: string
24
+ /** Input token symbol */
25
+ inputToken: string
26
+ /** Output token symbol */
27
+ outputToken: string
28
+ /** Input amount (as string, in smallest unit) */
29
+ inputAmount: string
30
+ /** Privacy level (optional) */
31
+ privacyLevel?: PrivacyLevel
32
+ /** Maximum acceptable slippage (0-1, e.g. 0.01 = 1%) */
33
+ maxSlippage?: number
34
+ }
35
+
36
+ /**
37
+ * Parameters for executing a swap
38
+ */
39
+ export interface SwapParams {
40
+ /** Input asset details */
41
+ input: {
42
+ chain: string
43
+ token: string
44
+ amount: bigint
45
+ }
46
+ /** Output asset details */
47
+ output: {
48
+ chain: string
49
+ token: string
50
+ minAmount: bigint
51
+ }
52
+ /** Privacy level */
53
+ privacyLevel: PrivacyLevel
54
+ /** Maximum acceptable slippage (0-1, e.g. 0.01 = 1%) */
55
+ maxSlippage?: number
56
+ }
57
+
58
+ /**
59
+ * Result of a swap execution
60
+ */
61
+ export interface SwapResult {
62
+ /** Transaction hash (if available) */
63
+ txHash?: string
64
+ /** Status of the swap */
65
+ status: string
66
+ /** Output amount received */
67
+ outputAmount?: bigint
68
+ /** Intent ID */
69
+ intentId: string
70
+ }
71
+
72
+ /**
73
+ * usePrivateSwap - Execute private swaps with shielded intents
74
+ *
75
+ * @remarks
76
+ * Hook for managing the complete lifecycle of a private swap:
77
+ * - Fetch quotes from solvers
78
+ * - Execute swaps with privacy
79
+ * - Track swap status through completion
80
+ * - Handle errors gracefully
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * import { usePrivateSwap } from '@sip-protocol/react'
85
+ * import { PrivacyLevel } from '@sip-protocol/types'
86
+ *
87
+ * function MyComponent() {
88
+ * const { quote, fetchQuote, swap, status, isLoading, error, reset } = usePrivateSwap()
89
+ *
90
+ * // Fetch a quote
91
+ * const handleGetQuote = async () => {
92
+ * await fetchQuote({
93
+ * inputChain: 'solana',
94
+ * outputChain: 'ethereum',
95
+ * inputToken: 'SOL',
96
+ * outputToken: 'ETH',
97
+ * inputAmount: '1000000000', // 1 SOL
98
+ * })
99
+ * }
100
+ *
101
+ * // Execute the swap
102
+ * const handleSwap = async () => {
103
+ * const result = await swap({
104
+ * input: { chain: 'solana', token: 'SOL', amount: 1000000000n },
105
+ * output: { chain: 'ethereum', token: 'ETH', minAmount: 0n },
106
+ * privacyLevel: PrivacyLevel.SHIELDED,
107
+ * maxSlippage: 0.01,
108
+ * })
109
+ * }
110
+ * }
111
+ * ```
112
+ */
113
+ export function usePrivateSwap() {
114
+ const { client: sip } = useSIP()
115
+
116
+ // State
117
+ const [quote, setQuote] = useState<Quote | null>(null)
118
+ const [status, setStatus] = useState<SwapStatus>('idle')
119
+ const [isLoading, setIsLoading] = useState(false)
120
+ const [error, setError] = useState<Error | null>(null)
121
+
122
+ /**
123
+ * Fetch a quote for the given parameters
124
+ */
125
+ const fetchQuote = useCallback(async (params: QuoteParams): Promise<void> => {
126
+ if (!sip) {
127
+ throw new Error('SIP client not initialized. Wrap your app with SIPProvider or call initialize().')
128
+ }
129
+
130
+ try {
131
+ setStatus('fetching_quote')
132
+ setIsLoading(true)
133
+ setError(null)
134
+
135
+ // Build CreateIntentParams from QuoteParams
136
+ const intentParams: CreateIntentParams = {
137
+ input: {
138
+ asset: {
139
+ chain: params.inputChain as any,
140
+ symbol: params.inputToken,
141
+ address: null,
142
+ decimals: 9, // Default, should be configurable
143
+ },
144
+ amount: BigInt(params.inputAmount),
145
+ },
146
+ output: {
147
+ asset: {
148
+ chain: params.outputChain as any,
149
+ symbol: params.outputToken,
150
+ address: null,
151
+ decimals: 18, // Default, should be configurable
152
+ },
153
+ minAmount: 0n,
154
+ maxSlippage: params.maxSlippage ?? 0.01,
155
+ },
156
+ privacy: params.privacyLevel ?? 'shielded' as PrivacyLevel,
157
+ }
158
+
159
+ // Get quotes from SIP client
160
+ const quotes = await sip.getQuotes(intentParams)
161
+
162
+ if (quotes.length === 0) {
163
+ throw new Error('No quotes available')
164
+ }
165
+
166
+ // Use the best quote (first one, assuming they're sorted)
167
+ setQuote(quotes[0])
168
+ setStatus('idle')
169
+ } catch (err) {
170
+ setError(err instanceof Error ? err : new Error('Failed to fetch quote'))
171
+ setStatus('failed')
172
+ throw err
173
+ } finally {
174
+ setIsLoading(false)
175
+ }
176
+ }, [sip])
177
+
178
+ /**
179
+ * Execute a swap with the given parameters
180
+ */
181
+ const swap = useCallback(async (params: SwapParams): Promise<SwapResult> => {
182
+ if (!sip) {
183
+ throw new Error('SIP client not initialized. Wrap your app with SIPProvider or call initialize().')
184
+ }
185
+
186
+ try {
187
+ setStatus('pending')
188
+ setIsLoading(true)
189
+ setError(null)
190
+
191
+ // Create the shielded intent
192
+ const intentParams: CreateIntentParams = {
193
+ input: {
194
+ asset: {
195
+ chain: params.input.chain as any,
196
+ symbol: params.input.token,
197
+ address: null,
198
+ decimals: 9, // Default, should be configurable
199
+ },
200
+ amount: params.input.amount,
201
+ },
202
+ output: {
203
+ asset: {
204
+ chain: params.output.chain as any,
205
+ symbol: params.output.token,
206
+ address: null,
207
+ decimals: 18, // Default, should be configurable
208
+ },
209
+ minAmount: params.output.minAmount,
210
+ maxSlippage: params.maxSlippage ?? 0.01,
211
+ },
212
+ privacy: params.privacyLevel,
213
+ }
214
+
215
+ const intent: TrackedIntent = await sip.createIntent(intentParams)
216
+
217
+ // Get quotes if we don't have one cached
218
+ let swapQuote = quote
219
+ if (!swapQuote) {
220
+ const quotes = await sip.getQuotes(intentParams)
221
+ if (quotes.length === 0) {
222
+ throw new Error('No quotes available')
223
+ }
224
+ swapQuote = quotes[0]
225
+ }
226
+
227
+ setStatus('confirming')
228
+
229
+ // Execute the swap
230
+ const result: FulfillmentResult = await sip.execute(intent, swapQuote)
231
+
232
+ setStatus('completed')
233
+
234
+ return {
235
+ txHash: result.txHash,
236
+ status: result.status,
237
+ outputAmount: result.outputAmount,
238
+ intentId: result.intentId,
239
+ }
240
+ } catch (err) {
241
+ setError(err instanceof Error ? err : new Error('Swap failed'))
242
+ setStatus('failed')
243
+ throw err
244
+ } finally {
245
+ setIsLoading(false)
246
+ }
247
+ }, [sip, quote])
248
+
249
+ /**
250
+ * Reset all state to initial values
251
+ */
252
+ const reset = useCallback(() => {
253
+ setQuote(null)
254
+ setStatus('idle')
255
+ setIsLoading(false)
256
+ setError(null)
257
+ }, [])
258
+
259
+ return {
260
+ quote,
261
+ fetchQuote,
262
+ swap,
263
+ status,
264
+ isLoading,
265
+ error,
266
+ reset,
267
+ }
268
+ }