diff --git a/examples/hello/frontend/package.json b/examples/hello/frontend/package.json index 5757f5c0..9d1d5e80 100644 --- a/examples/hello/frontend/package.json +++ b/examples/hello/frontend/package.json @@ -10,7 +10,7 @@ "preview": "vite preview" }, "dependencies": { - "@zetachain/toolkit": "16.1.4", + "@zetachain/toolkit": "16.2.0", "@zetachain/wallet": "1.0.13", "clsx": "^2.1.1", "ethers": "^6.13.2", diff --git a/examples/hello/frontend/src/App.tsx b/examples/hello/frontend/src/App.tsx index a154812e..2ede58e2 100644 --- a/examples/hello/frontend/src/App.tsx +++ b/examples/hello/frontend/src/App.tsx @@ -3,21 +3,26 @@ import { UniversalSignInContextProvider } from '@zetachain/wallet/react'; import { AppContent } from './AppContent'; import { Header } from './components/Header'; import { USE_DYNAMIC_WALLET } from './constants/wallets'; +import { UnisatWalletProvider } from './context/UnisatWalletProvider'; import { useTheme } from './hooks/useTheme'; function App() { const { theme } = useTheme(); - return USE_DYNAMIC_WALLET ? ( - -
- - - ) : ( - <> -
- - + return ( + + {USE_DYNAMIC_WALLET ? ( + +
+ + + ) : ( + <> +
+ + + )} + ); } diff --git a/examples/hello/frontend/src/ConnectedContent.tsx b/examples/hello/frontend/src/ConnectedContent.tsx index 0338f5e1..07eeb73b 100644 --- a/examples/hello/frontend/src/ConnectedContent.tsx +++ b/examples/hello/frontend/src/ConnectedContent.tsx @@ -18,6 +18,7 @@ interface ConnectedContentProps { supportedChain: SupportedChain | undefined; primaryWallet?: PrimaryWallet | null; account?: string | null; + onChainSelect?: (chain: SupportedChain) => void; } const DynamicConnectedContent = ({ @@ -85,11 +86,15 @@ const Eip6963ConnectedContent = ({ selectedProvider, supportedChain, account, + onChainSelect, }: ConnectedContentProps) => { const { switchChain } = useSwitchChain(); const handleNetworkSelect = (chain: SupportedChain) => { - switchChain(chain.chainId); + onChainSelect?.(chain); + if (chain.chainType === 'EVM') { + switchChain(chain.chainId); + } }; return ( @@ -126,6 +131,7 @@ export function ConnectedContent({ supportedChain, primaryWallet, account, + onChainSelect, }: ConnectedContentProps) { return USE_DYNAMIC_WALLET ? ( ); } diff --git a/examples/hello/frontend/src/Eip6963AppContent.tsx b/examples/hello/frontend/src/Eip6963AppContent.tsx index b6b9035b..6b53f452 100644 --- a/examples/hello/frontend/src/Eip6963AppContent.tsx +++ b/examples/hello/frontend/src/Eip6963AppContent.tsx @@ -1,15 +1,30 @@ +import { useEffect, useState } from 'react'; + import { ConnectedContent } from './ConnectedContent'; -import { SUPPORTED_CHAINS } from './constants/chains'; +import { SUPPORTED_CHAINS, type SupportedChain } from './constants/chains'; import { DisconnectedContent } from './DisconnectedContent'; import { useEip6963Wallet } from './hooks/useEip6963Wallet'; export function Eip6963AppContent() { const { selectedProvider, decimalChainId, account } = useEip6963Wallet(); - const supportedChain = SUPPORTED_CHAINS.find( + // Find the EVM chain from wallet + const evmChain = SUPPORTED_CHAINS.find( (chain) => chain.chainId === decimalChainId ); + // Track selected chain separately to support non-EVM chains (SOL, BTC) + const [selectedChain, setSelectedChain] = useState< + SupportedChain | undefined + >(evmChain); + + // Sync with EVM wallet changes (when user switches chains in MetaMask, etc.) + useEffect(() => { + if (evmChain && evmChain.chainType === 'EVM') { + setSelectedChain(evmChain); + } + }, [evmChain]); + const isDisconnected = !selectedProvider; if (isDisconnected) { @@ -19,7 +34,8 @@ export function Eip6963AppContent() { return ( ); diff --git a/examples/hello/frontend/src/components/NetworkSelector.tsx b/examples/hello/frontend/src/components/NetworkSelector.tsx index 7b525cc7..5f3b04af 100644 --- a/examples/hello/frontend/src/components/NetworkSelector.tsx +++ b/examples/hello/frontend/src/components/NetworkSelector.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { SUPPORTED_CHAINS, type SupportedChain } from '../constants/chains'; import { USE_DYNAMIC_WALLET } from '../constants/wallets'; +import { useUnisatWallet } from '../context/UnisatWalletProvider'; import { Dropdown, type DropdownOption } from './Dropdown'; interface NetworkSelectorProps { @@ -19,12 +20,19 @@ export const NetworkSelector = ({ disabled = false, className = '', }: NetworkSelectorProps) => { + const { connect: connectUnisatWallet, switchChain: switchUnisatChain } = + useUnisatWallet(); + // Convert chains to dropdown options const options: DropdownOption[] = useMemo( () => - SUPPORTED_CHAINS.filter( - (chain) => USE_DYNAMIC_WALLET || chain.chainType === 'EVM' - ).map((chain) => ({ + SUPPORTED_CHAINS.filter((chain) => { + if (USE_DYNAMIC_WALLET) { + return chain.chainType === 'EVM' || chain.chainType === 'SOL'; + } else { + return chain.chainType === 'EVM' || chain.chainType === 'BTC'; + } + }).map((chain) => ({ id: chain.chainId, label: chain.name, value: chain, @@ -45,7 +53,17 @@ export const NetworkSelector = ({ [selectedChain, options] ); - const handleSelect = (option: DropdownOption) => { + const handleSelect = async (option: DropdownOption) => { + if (option.value.chainType === 'BTC') { + try { + await connectUnisatWallet(); + await switchUnisatChain('BITCOIN_SIGNET'); + } catch (error) { + console.error('Failed to connect/switch Unisat wallet:', error); + return; // Don't update selection if connection failed + } + } + onNetworkSelect?.(option.value); }; diff --git a/examples/hello/frontend/src/constants/chains.ts b/examples/hello/frontend/src/constants/chains.ts index 1cdafe0c..472f3200 100644 --- a/examples/hello/frontend/src/constants/chains.ts +++ b/examples/hello/frontend/src/constants/chains.ts @@ -2,7 +2,7 @@ export interface SupportedChain { explorerUrl: (txHash: string) => string; name: string; chainId: number; - chainType: 'EVM' | 'SOL'; + chainType: 'EVM' | 'SOL' | 'BTC'; icon: string; colorHex: string; } @@ -69,8 +69,20 @@ export const SUPPORTED_CHAINS: SupportedChain[] = [ icon: '/logos/solana-logo.svg', colorHex: '#9945FF', }, + { + explorerUrl: (txHash: string) => + `https://mempool.space/signet/tx/${txHash}`, + name: 'Bitcoin Signet', + chainId: 18333, + chainType: 'BTC', + icon: '/logos/bitcoin-logo.svg', + colorHex: '#F7931A', + }, ]; +export const BITCOIN_GATEWAY_ADDRESS_SIGNET = + 'tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur'; + export const SUPPORTED_CHAIN_IDS = SUPPORTED_CHAINS.map( (chain) => chain.chainId ); diff --git a/examples/hello/frontend/src/context/UnisatWalletProvider.tsx b/examples/hello/frontend/src/context/UnisatWalletProvider.tsx new file mode 100644 index 00000000..89479fcb --- /dev/null +++ b/examples/hello/frontend/src/context/UnisatWalletProvider.tsx @@ -0,0 +1,295 @@ +import { getBytes, hexlify } from 'ethers'; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; + +import { USE_DYNAMIC_WALLET } from '../constants/wallets'; + +interface UnisatBitcoinAccount { + address: string; + publicKey: string; + type: 'payment' | 'ordinals'; +} + +type UnisatChain = + | 'BITCOIN_MAINNET' + | 'BITCOIN_TESTNET' + | 'BITCOIN_TESTNET4' + | 'BITCOIN_SIGNET' + | 'FRACTAL_BITCOIN_MAINNET' + | 'FRACTAL_BITCOIN_TESTNET'; + +export interface UnisatChainDetail { + enum: UnisatChain; + name: string; + network: string; +} + +export interface UnisatWalletContextType { + connected: boolean; + connecting: boolean; + accounts: UnisatBitcoinAccount[]; + rdns: string; + paymentAccount: UnisatBitcoinAccount | null; + connect: () => Promise; + disconnect: () => void; + signPSBT: ( + psbt: string, + inputsToSign: { address: string; signingIndexes: number[] }[] + ) => Promise; + getChain: () => Promise; + switchChain: (chain: UnisatChain) => Promise; +} + +const UnisatWalletContext = createContext( + undefined +); + +interface UnisatProvider { + requestAccounts: () => Promise; + getAccounts: () => Promise; + getPublicKey: () => Promise; + signPsbt: ( + psbtHex: string, + options?: { + autoFinalized?: boolean; + toSignInputs?: { index: number; address?: string }[]; + } + ) => Promise; + switchChain: (chain: UnisatChain) => Promise; + getChain: () => Promise; + on: (event: string, handler: (...args: unknown[]) => void) => void; + removeListener: ( + event: string, + handler: (...args: unknown[]) => void + ) => void; +} + +const getUnisat = () => + (window as unknown as { unisat?: UnisatProvider }).unisat; + +// Stub provider for when USE_DYNAMIC_WALLET is true +const StubUnisatWalletProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const stubValue: UnisatWalletContextType = { + connected: false, + connecting: false, + accounts: [], + rdns: '', + paymentAccount: null, + connect: async () => { + throw new Error('Unisat wallet not available with Dynamic wallet'); + }, + disconnect: () => {}, + signPSBT: async () => { + throw new Error('Unisat wallet not available with Dynamic wallet'); + }, + getChain: async () => { + throw new Error('Unisat wallet not available with Dynamic wallet'); + }, + switchChain: async () => { + throw new Error('Unisat wallet not available with Dynamic wallet'); + }, + }; + + return ( + + {children} + + ); +}; + +const ActualUnisatWalletProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [connected, setConnected] = useState(false); + const [connecting, setConnecting] = useState(false); + const [accounts, setAccounts] = useState([]); + + const handleAccountUpdate = async (address: string) => { + const unisat = getUnisat(); + if (!unisat) return; + + try { + const publicKey = await unisat.getPublicKey(); + const formattedAccounts: UnisatBitcoinAccount[] = [ + { + address, + publicKey, + type: 'payment', + }, + ]; + setAccounts(formattedAccounts); + setConnected(true); + } catch (error) { + console.error('Failed to get account details:', error); + } + }; + + const connect = async () => { + const unisat = getUnisat(); + if (!unisat) { + alert( + 'Unisat wallet not found! Please install Unisat: https://unisat.io/' + ); + window.open('https://unisat.io/', '_blank'); + return; + } + + try { + setConnecting(true); + const accounts = await unisat.requestAccounts(); + + if (accounts.length === 0) { + throw new Error('No accounts returned from Unisat'); + } + + await handleAccountUpdate(accounts[0]); + } catch (error) { + console.error('Failed to connect to Unisat:', error); + alert('Failed to connect to Unisat wallet'); + } finally { + setConnecting(false); + } + }; + + const disconnect = useCallback(() => { + setAccounts([]); + setConnected(false); + }, []); + + useEffect(() => { + const unisat = getUnisat(); + if (!unisat) return; + + const handleAccountsChanged = (accounts: unknown) => { + const accountsArray = accounts as string[]; + if (accountsArray.length === 0) { + disconnect(); + } else { + handleAccountUpdate(accountsArray[0]); + } + }; + + unisat.on('accountsChanged', handleAccountsChanged); + + return () => { + unisat.removeListener('accountsChanged', handleAccountsChanged); + }; + }, [disconnect]); + + const signPSBT = async ( + psbtBase64: string, + inputsToSign: { address: string; signingIndexes: number[] }[] + ): Promise => { + const unisat = getUnisat(); + if (!unisat) { + throw new Error('Unisat wallet not connected'); + } + + try { + // Convert base64 PSBT to hex for Unisat + const psbtHex = hexlify( + Uint8Array.from(atob(psbtBase64), (c) => c.charCodeAt(0)) + ).slice(2); + + // Convert inputsToSign to Unisat's format + const toSignInputs = inputsToSign.flatMap(({ address, signingIndexes }) => + signingIndexes.map((index) => ({ index, address })) + ); + + // Unisat returns hex-encoded signed PSBT + const signedPsbtHex = await unisat.signPsbt(psbtHex, { + autoFinalized: false, + toSignInputs, + }); + + // Convert hex back to base64 for our functions + const signedPsbtBase64 = btoa( + String.fromCharCode(...getBytes('0x' + signedPsbtHex)) + ); + + return signedPsbtBase64; + } catch (error) { + console.error('Failed to sign PSBT:', error); + throw error; + } + }; + + const getChain = async (): Promise => { + const unisat = getUnisat(); + if (!unisat) { + throw new Error('Unisat wallet not connected'); + } + + try { + return await unisat.getChain(); + } catch (error) { + console.error('Failed to get network:', error); + throw error; + } + }; + + const switchChain = async (chain: UnisatChain): Promise => { + const unisat = getUnisat(); + if (!unisat) { + throw new Error('Unisat wallet not connected'); + } + + try { + await unisat.switchChain(chain); + } catch (error) { + console.error('Failed to switch network:', error); + throw error; + } + }; + + const paymentAccount = accounts.find((acc) => acc.type === 'payment') || null; + + const rdns = 'io.unisat'; + + return ( + + {children} + + ); +}; + +export const UnisatWalletProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + return USE_DYNAMIC_WALLET ? ( + {children} + ) : ( + {children} + ); +}; + +// eslint-disable-next-line react-refresh/only-export-components +export const useUnisatWallet = () => { + const context = useContext(UnisatWalletContext); + if (context === undefined) { + throw new Error( + 'useUnisatWallet must be used within a UnisatWalletProvider' + ); + } + return context; +}; diff --git a/examples/hello/frontend/src/hooks/useHandleCall.ts b/examples/hello/frontend/src/hooks/useHandleCall.ts index c1fc744f..f032a0fd 100644 --- a/examples/hello/frontend/src/hooks/useHandleCall.ts +++ b/examples/hello/frontend/src/hooks/useHandleCall.ts @@ -1,3 +1,8 @@ +import { + broadcastCommitAndBuildRevealPsbt, + buildBitcoinInscriptionCallCommitPsbt, + finalizeBitcoinInscriptionCallReveal, +} from '@zetachain/toolkit/chains/bitcoin'; import { evmCall } from '@zetachain/toolkit/chains/evm'; import { solanaCall } from '@zetachain/toolkit/chains/solana'; import { type PrimaryWallet } from '@zetachain/wallet'; @@ -5,7 +10,12 @@ import { getSolanaWalletAdapter } from '@zetachain/wallet/solana'; import { ZeroAddress } from 'ethers'; import { useCallback } from 'react'; -import type { SupportedChain } from '../constants/chains'; +import { + BITCOIN_GATEWAY_ADDRESS_SIGNET, + type SupportedChain, +} from '../constants/chains'; +import { USE_DYNAMIC_WALLET } from '../constants/wallets'; +import { useUnisatWallet } from '../context/UnisatWalletProvider'; import type { EIP6963ProviderDetail } from '../types/wallet'; import { getSignerAndProvider } from '../utils/ethersHelpers'; @@ -112,6 +122,88 @@ async function handleSolanaCall( callbacks.onTransactionConfirmed?.(result); } +/** + * Handles Bitcoin-specific call logic using Unisat + Signet + */ +async function handleBitcoinCall( + receiver: string, + message: string, + unisatWallet: ReturnType, + gatewayAddress: string, + callbacks: { + onSigningStart?: UseHandleCallParams['onSigningStart']; + onTransactionSubmitted?: UseHandleCallParams['onTransactionSubmitted']; + onTransactionConfirmed?: UseHandleCallParams['onTransactionConfirmed']; + } +): Promise { + const { paymentAccount, signPSBT, getChain } = unisatWallet; + + if (!paymentAccount) { + throw new Error('No payment account found. Please connect Unisat wallet.'); + } + + const chain = await getChain(); + + if (chain.enum !== 'BITCOIN_SIGNET') { + throw new Error('Unisat wallet is not connected to Signet'); + } + + // Use Signet testnet + const bitcoinApi = 'https://mempool.space/signet/api'; + const network = 'signet'; + + const commitResult = await buildBitcoinInscriptionCallCommitPsbt({ + bitcoinApi, + fromAddress: paymentAccount.address, + gatewayAddress, + network, + publicKey: paymentAccount.publicKey, + receiver, + types: ['string'], + values: [message], + }); + + callbacks.onSigningStart?.(); + + // Sign commit PSBT with Unisat + const signedCommitPsbt = await signPSBT(commitResult.commitPsbtBase64, [ + { + address: paymentAccount.address, + signingIndexes: commitResult.signingIndexes, + }, + ]); + + // Broadcast commit and build reveal + const revealResult = await broadcastCommitAndBuildRevealPsbt({ + bitcoinApi, + commitData: commitResult.commitData, + depositFee: commitResult.depositFee, + fromAddress: paymentAccount.address, + gatewayAddress, + network, + revealFee: commitResult.revealFee, + signedCommitPsbtBase64: signedCommitPsbt, + }); + + // Sign reveal PSBT with Unisat + const signedRevealPsbt = await signPSBT(revealResult.revealPsbtBase64, [ + { + address: paymentAccount.address, + signingIndexes: revealResult.signingIndexes, + }, + ]); + + callbacks.onTransactionSubmitted?.(); + + // Finalize and broadcast reveal + const finalResult = await finalizeBitcoinInscriptionCallReveal( + signedRevealPsbt, + bitcoinApi + ); + + callbacks.onTransactionConfirmed?.(finalResult.revealTxid); +} + /** * Custom hook for handling cross-chain calls with proper type safety * and separation of concerns between chain-specific logic and UI state management. @@ -129,9 +221,16 @@ export function useHandleCall({ onError, onComplete, }: UseHandleCallParams): UseHandleCallReturn { + // Always call the hook - will only be used when USE_DYNAMIC_WALLET is false + const unisatWallet = useUnisatWallet(); + const handleCall = useCallback(async () => { - const walletType = primaryWallet?.chain || 'EVM'; // Default to 'EVM' for EIP6963 route - const walletAddress = primaryWallet?.address || account; + const walletType = + primaryWallet?.chain || supportedChain?.chainType || 'EVM'; + const walletAddress = + primaryWallet?.address || + account || + unisatWallet?.paymentAccount?.address; if (!walletAddress) { const error = new Error('No wallet address available'); @@ -188,11 +287,24 @@ export function useHandleCall({ String(supportedChain.chainId), callbacks ); + } else if (walletType === 'BTC') { + if (USE_DYNAMIC_WALLET) { + throw new Error('Bitcoin not supported with Dynamic wallet yet'); + } + if (!unisatWallet?.paymentAccount) { + throw new Error('Bitcoin transactions require Unisat wallet'); + } + await handleBitcoinCall( + receiver, // Universal Contract address (20 bytes) + message, + unisatWallet, + BITCOIN_GATEWAY_ADDRESS_SIGNET, + callbacks + ); } else { throw new Error(`Unsupported chain: ${walletType}`); } } catch (error) { - console.error('Transaction error:', error); onError?.(error instanceof Error ? error : new Error('Unknown error')); } finally { onComplete?.(); @@ -204,6 +316,7 @@ export function useHandleCall({ receiver, message, account, + unisatWallet, onSigningStart, onTransactionSubmitted, onTransactionConfirmed, diff --git a/examples/hello/frontend/yarn.lock b/examples/hello/frontend/yarn.lock index 967707b2..c57d4e92 100644 --- a/examples/hello/frontend/yarn.lock +++ b/examples/hello/frontend/yarn.lock @@ -663,6 +663,13 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@bitcoinerlab/secp256k1@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.2.0.tgz#429d043ef4218b9c71915b50172e9aa4a2a8fea4" + integrity sha512-jeujZSzb3JOZfmJYI0ph1PVpCRV5oaexCgy+RvCXV8XlY+XFB/2n3WOcvBsKLsOw78KYgnQrQWb2HrKE4be88Q== + dependencies: + "@noble/curves" "^1.7.0" + "@coinbase/wallet-sdk@4.3.7": version "4.3.7" resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-4.3.7.tgz#e67d47238714a6f288d59d1b83c7c2c9656fecba" @@ -2447,7 +2454,7 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== -"@noble/curves@1.2.0", "@noble/curves@1.4.0", "@noble/curves@1.4.2", "@noble/curves@1.8.0", "@noble/curves@1.8.1", "@noble/curves@1.8.2", "@noble/curves@1.9.1", "@noble/curves@1.9.2", "@noble/curves@1.9.7", "@noble/curves@^1.3.0", "@noble/curves@^1.4.2", "@noble/curves@^1.6.0", "@noble/curves@^1.8.0", "@noble/curves@^1.9.1", "@noble/curves@^1.9.4", "@noble/curves@~1.4.0", "@noble/curves@~1.8.1", "@noble/curves@~1.9.0": +"@noble/curves@1.2.0", "@noble/curves@1.4.0", "@noble/curves@1.4.2", "@noble/curves@1.8.0", "@noble/curves@1.8.1", "@noble/curves@1.8.2", "@noble/curves@1.9.1", "@noble/curves@1.9.2", "@noble/curves@1.9.7", "@noble/curves@^1.3.0", "@noble/curves@^1.4.2", "@noble/curves@^1.6.0", "@noble/curves@^1.7.0", "@noble/curves@^1.8.0", "@noble/curves@^1.9.1", "@noble/curves@^1.9.4", "@noble/curves@~1.4.0", "@noble/curves@~1.8.1", "@noble/curves@~1.9.0": version "1.9.7" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== @@ -4739,11 +4746,12 @@ resolved "https://registry.yarnpkg.com/@zetachain/standard-contracts/-/standard-contracts-2.0.1.tgz#aefd28bb81f1f05b183bd73dc62bd68e456a6ebf" integrity sha512-SHV9a1bSgy8litI/LRZ4VIus7Gsjy0wj3n9bZeIsEydn0C5NNZxYO4XW+P06dlEyDQjtcVJQHoQOyHkodBoVsQ== -"@zetachain/toolkit@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@zetachain/toolkit/-/toolkit-16.1.4.tgz#2ab375a589ab0ff8e1d791e61de723e0355702f1" - integrity sha512-wR6ffBHaR9+BWA1rP/5XgswX/+NXZHZi9CM/ytct4iXj4zUGDn6SaZlSV2+rnkhX6C0ZLs9gnMfcNco2/IsKCg== +"@zetachain/toolkit@16.2.0": + version "16.2.0" + resolved "https://registry.yarnpkg.com/@zetachain/toolkit/-/toolkit-16.2.0.tgz#66b1ccb5c065ec869af7cb26e6e63b3fbaf2a061" + integrity sha512-kfLnwyNSdTUwFQqEIF0qlqcjU1hOZ6+SmazAFUqboZqIAUn8VSW83gz8RPpMHPYTxXknY6p3D8lCUpj3bpVV1A== dependencies: + "@bitcoinerlab/secp256k1" "^1.2.0" "@coral-xyz/anchor" "^0.30.1" "@ethersproject/units" "^5.8.0" "@inquirer/prompts" "^2.1.1"