@worldcoin/minikit-react
Version:
minikit-react is a set of hooks for building mini-apps
434 lines (429 loc) • 12.1 kB
JavaScript
// src/address-book/is-verified.tsx
import { getIsUserVerified } from "@worldcoin/minikit-js/address-book";
import { useEffect, useState } from "react";
var useIsUserVerified = (walletAddress, rpcUrl) => {
const [isUserVerified, setIsUserVerified] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(null);
useEffect(() => {
const fetchIsUserVerified = async () => {
try {
const data = await getIsUserVerified(walletAddress);
setIsUserVerified(data);
} catch (err) {
setIsError(err);
} finally {
setIsLoading(false);
}
};
fetchIsUserVerified();
}, [walletAddress]);
return { isUserVerified, isLoading, isError };
};
// src/components/username-search.tsx
import { jsx } from "react/jsx-runtime";
var createDebounce = () => {
let timeoutId;
return (fn, delay2) => {
return (...args) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fn(...args);
}, delay2);
};
};
};
var DEBOUNCE_DELAY_MS = 300;
var debounce = createDebounce();
var getSearchedUsername = async (username) => {
const response = await fetch(
`https://usernames.worldcoin.org/api/v1/search/${username}`
);
if (response.status === 200) {
const json = await response.json();
return { status: response.status, data: json };
}
return { status: response.status, error: "Error fetching data" };
};
var UsernameSearch = ({
value,
handleChange,
setSearchedUsernames,
className,
inputProps
}) => {
const debouncedSearch = debounce(
async (e) => {
const username = e.target.value;
const data = await getSearchedUsername(username);
setSearchedUsernames(data);
},
DEBOUNCE_DELAY_MS
);
const onChange = (e) => {
debouncedSearch(e);
handleChange(e);
};
return /* @__PURE__ */ jsx(
"input",
{
type: "text",
value,
onChange,
className: className || "rounded-md border-black border-2",
...inputProps
}
);
};
// src/transaction/hooks.ts
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
// src/transaction/index.ts
var DEFAULT_API_BASE_URL = "https://developer.world.org";
function resolveMiniKitApiBaseUrl(config = {}) {
if (config.apiBaseUrl) {
return config.apiBaseUrl.replace(/\/$/, "");
}
return DEFAULT_API_BASE_URL;
}
async function fetchJson(url, errorPrefix) {
const response = await fetch(url, { method: "GET" });
if (!response.ok) {
const error = await response.json();
throw new Error(`${errorPrefix}: ${error.message}`);
}
return await response.json();
}
function normalizeUserOperationStatus(data) {
return {
userOpHash: data.userOpHash,
transactionHash: data.transaction_hash ?? void 0,
transactionStatus: data.status === "success" ? "mined" : data.status === "failed" ? "failed" : "pending",
...data.sender ? { sender: data.sender } : {},
...data.nonce ? { nonce: data.nonce } : {}
};
}
async function fetchTransactionHash(appConfig, transactionId) {
try {
const baseUrl = resolveMiniKitApiBaseUrl();
return await fetchJson(
`${baseUrl}/api/v2/minikit/transaction/${transactionId}?app_id=${appConfig.app_id}&type=transaction`,
"Failed to fetch transaction status"
);
} catch (error) {
console.log("Error fetching transaction status", error);
throw error instanceof Error ? error : new Error("Failed to fetch transaction status");
}
}
async function fetchUserOperationStatus(config, userOpHash) {
try {
const baseUrl = resolveMiniKitApiBaseUrl(config);
const data = await fetchJson(
`${baseUrl}/api/v2/minikit/userop/${userOpHash}`,
"Failed to fetch userOp status"
);
return normalizeUserOperationStatus(data);
} catch (error) {
console.log("Error fetching userOp status", error);
throw error instanceof Error ? error : new Error("Failed to fetch userOp status");
}
}
// src/transaction/hooks.ts
var DEFAULT_POLLING_INTERVAL_MS = 1e3;
var USER_OPERATION_INITIAL_POLLING_INTERVAL_MS = 300;
var USER_OPERATION_MAX_POLLING_INTERVAL_MS = 5e3;
function createFixedPollingStrategy(intervalMs) {
return {
getPollDelayMs: () => intervalMs
};
}
function createExponentialBackoffStrategy(initialDelayMs, maxDelayMs) {
return {
getPollDelayMs: (attempt) => Math.min(initialDelayMs * 2 ** attempt, maxDelayMs)
};
}
function useReceiptPolling(options) {
const {
client,
id,
fetchStatus,
strategy,
confirmations = 1,
timeout
} = options;
const [transactionHash, setTransactionHash] = useState2(void 0);
const [receipt, setReceipt] = useState2();
const [transactionStatus, setTransactionStatus] = useState2();
const [isLoading, setIsLoading] = useState2(false);
const [isError, setIsError] = useState2(false);
const [error, setError] = useState2();
const [retryKey, setRetryKey] = useState2(0);
const resetState = useCallback(() => {
setTransactionHash(void 0);
setReceipt(void 0);
setTransactionStatus(void 0);
setIsLoading(false);
setIsError(false);
setError(void 0);
}, []);
const retrigger = useCallback(() => {
resetState();
setRetryKey((value) => value + 1);
}, [resetState]);
useEffect2(() => {
if (!id) {
resetState();
return;
}
const abortController = new AbortController();
let timeoutId = null;
let attempt = 0;
setIsLoading(true);
setIsError(false);
setError(void 0);
const waitForReceipt = async (hash) => {
if (abortController.signal.aborted) return;
try {
const nextReceipt = await client.waitForTransactionReceipt({
hash,
confirmations,
timeout
});
if (abortController.signal.aborted) return;
setReceipt(nextReceipt);
setIsLoading(false);
} catch (nextError) {
if (abortController.signal.aborted) return;
setIsError(true);
setError(
nextError instanceof Error ? nextError : new Error(String(nextError))
);
setIsLoading(false);
}
};
const poll = async () => {
if (abortController.signal.aborted) {
return;
}
try {
const nextStatus = await fetchStatus();
if (abortController.signal.aborted) return;
setTransactionStatus(nextStatus);
if (nextStatus.transactionStatus === "failed") {
setIsError(true);
setError(new Error("Transaction failed"));
setIsLoading(false);
return;
}
if (nextStatus.transactionHash) {
setTransactionHash(nextStatus.transactionHash);
await waitForReceipt(nextStatus.transactionHash);
return;
}
timeoutId = setTimeout(poll, strategy.getPollDelayMs(attempt));
attempt += 1;
} catch (nextError) {
if (abortController.signal.aborted) return;
setIsError(true);
setError(
nextError instanceof Error ? nextError : new Error(String(nextError))
);
setIsLoading(false);
}
};
poll();
return () => {
abortController.abort();
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [
id,
retryKey,
fetchStatus,
strategy,
client,
confirmations,
timeout,
resetState
]);
return {
transactionHash,
receipt,
isError,
isLoading,
isSuccess: receipt?.status === "success" || transactionStatus?.transactionStatus === "mined",
error,
retrigger
};
}
function useWaitForTransactionReceipt(options) {
const {
appConfig,
pollingInterval = DEFAULT_POLLING_INTERVAL_MS,
transactionId
} = options;
const fetchStatus = useCallback(async () => {
return await fetchTransactionHash(appConfig, transactionId);
}, [appConfig.app_id, transactionId]);
const strategy = useMemo(
() => createFixedPollingStrategy(pollingInterval),
[pollingInterval]
);
return useReceiptPolling({
...options,
id: transactionId,
fetchStatus,
strategy
});
}
function useWaitForUserOperationReceipt(options) {
const {
apiBaseUrl,
pollingInterval = USER_OPERATION_INITIAL_POLLING_INTERVAL_MS,
userOpHash
} = options;
const fetchStatus = useCallback(async () => {
return await fetchUserOperationStatus({ apiBaseUrl }, userOpHash);
}, [apiBaseUrl, userOpHash]);
const strategy = useMemo(
() => createExponentialBackoffStrategy(
pollingInterval,
USER_OPERATION_MAX_POLLING_INTERVAL_MS
),
[pollingInterval]
);
return useReceiptPolling({
...options,
id: userOpHash,
fetchStatus,
strategy
});
}
function delay(ms, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, ms);
signal.addEventListener(
"abort",
() => {
clearTimeout(timer);
reject(new DOMException("Aborted", "AbortError"));
},
{ once: true }
);
});
}
async function pollForReceipt(params) {
const { fetchStatus, strategy, client, confirmations, timeout, signal } = params;
let attempt = 0;
const throwIfAborted = () => {
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
};
while (true) {
throwIfAborted();
const status = await fetchStatus();
throwIfAborted();
if (status.transactionStatus === "failed") {
throw new Error("Transaction failed");
}
if (status.transactionHash) {
const receipt = await client.waitForTransactionReceipt({
hash: status.transactionHash,
confirmations,
timeout
});
throwIfAborted();
return { transactionHash: status.transactionHash, receipt };
}
await delay(strategy.getPollDelayMs(attempt), signal);
attempt += 1;
}
}
function usePolling(options) {
const { execute } = options;
const [isLoading, setIsLoading] = useState2(false);
const abortRef = useRef(null);
const executeRef = useRef(execute);
executeRef.current = execute;
const reset = useCallback(() => {
abortRef.current?.abort();
abortRef.current = null;
setIsLoading(false);
}, []);
useEffect2(() => {
return () => {
abortRef.current?.abort();
};
}, []);
const poll = useCallback((id) => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setIsLoading(true);
return executeRef.current(id, controller.signal).finally(() => {
if (abortRef.current === controller) {
setIsLoading(false);
}
});
}, []);
return { poll, isLoading, reset };
}
function useUserOperationReceipt(options) {
const {
client,
apiBaseUrl,
pollingInterval = USER_OPERATION_INITIAL_POLLING_INTERVAL_MS,
confirmations = 1,
timeout
} = options;
const strategy = useMemo(
() => createExponentialBackoffStrategy(
pollingInterval,
USER_OPERATION_MAX_POLLING_INTERVAL_MS
),
[pollingInterval]
);
return usePolling({
execute: (userOpHash, signal) => pollForReceipt({
fetchStatus: () => fetchUserOperationStatus({ apiBaseUrl }, userOpHash),
strategy,
client,
confirmations,
timeout,
signal
})
});
}
function useTransactionReceipt(options) {
const {
client,
appConfig,
pollingInterval = DEFAULT_POLLING_INTERVAL_MS,
confirmations = 1,
timeout
} = options;
const strategy = useMemo(
() => createFixedPollingStrategy(pollingInterval),
[pollingInterval]
);
return usePolling({
execute: (transactionId, signal) => pollForReceipt({
fetchStatus: () => fetchTransactionHash(appConfig, transactionId),
strategy,
client,
confirmations,
timeout,
signal
})
});
}
export {
UsernameSearch,
useIsUserVerified,
useTransactionReceipt,
useUserOperationReceipt,
useWaitForTransactionReceipt,
useWaitForUserOperationReceipt
};