import type { AppInfo, GatewayConfig, PermissionType } from 'arconnect'; import { ArweaveWebWallet } from 'arweave-wallet-connector'; import { ArweaveInterface } from 'arweave-wallet-connector/lib/Arweave'; import React, { useState, useEffect, useContext, createContext, ReactNode, } from 'react'; import { calculateColorFromAddress } from './helpers' import './index.css'; import ArconnectLogo from './icons/arconnect/logo'; import ArweaveAppLogo from './icons/arweave.app/logo'; import WalletIcon from './icons/wallet'; // note: arconnect works natively with the APIs of window.arweaveWallet export type supportedWallets = | 'arconnect' | 'arweave.app' | 'window.arweaveWallet'; export type AlgorithmInterface = | AlgorithmIdentifier | RsaPssParams | EcdsaParams; export interface options { algorithm: string; hash: string; salt?: string; } type BufferEncoding = | 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex'; export interface createSignatureInterface { data: string; signatureParams?: AlgorithmInterface; returnType?: 'Uint8Array' | BufferEncoding; sigType?: 'new' | 'old'; } // export type encryptInterface = ( // options: { // name: string; // hash?: string; // salt?: string; // }, // data: BufferSource // ) => Promise; // export type decryptInterface = ( // data: Uint8Array, // algorithm: options // ) => Promise; // export interface SimpleConnectArgs { // permissions: PermissionType[], // AppInfo?: AppInfo, // gatewayConfig?: GatewayConfig, // walletChoice?: supportedWallets // } // export type SimpleConnectInterface = (args: SimpleConnectArgs) => Promise; export interface arweaveConnectInterface { permissions: PermissionType[]; AppInfo?: AppInfo; gatewayConfig?: GatewayConfig; walletChoice?: supportedWallets; } export interface arweaveContextInterface { api: any | ArweaveInterface; walletPermissions: PermissionType[]; walletConnected: boolean; walletType: supportedWallets | undefined; address: string; searchingWallet: boolean; getActiveAddress: () => Promise; arweaveConnect: ({ permissions, AppInfo, gatewayConfig, walletChoice, }: arweaveConnectInterface) => Promise<{ api: any }>; arweaveDisconnect: () => Promise; getPublicKey: () => Promise; createSignature: ({ data, signatureParams, returnType, sigType, }: createSignatureInterface) => Promise; } export const defaultSignatureParams = { name: 'RSA-PSS', saltLength: 32 }; export const defaultAlgorithmParams = { name: 'RSA-PSS', hash: 'sha256', saltLength: 32, }; export const ArweavePermissions: PermissionType[] = [ 'ACCESS_ADDRESS', 'ACCESS_PUBLIC_KEY', 'ACCESS_ALL_ADDRESSES', 'SIGN_TRANSACTION', 'ENCRYPT', 'DECRYPT', 'SIGNATURE', 'ACCESS_ARWEAVE_CONFIG', 'DISPATCH', ]; export { PermissionType }; export const shortenAddress = (address: string, maxLength = 20) => { // to avoid trimming small names by default if (address.length < maxLength) return address; return ( address.substring(0, maxLength / 2) + '...' + address.substring(address.length - maxLength / 2, address.length) ); }; export const ArweaveContext = createContext( {} as arweaveContextInterface ); export function useArweave(): arweaveContextInterface { const useArweaveContext: arweaveContextInterface = useContext(ArweaveContext); if (useArweaveContext === null) { throw new Error( 'useArweave() can only be used inside of , ' + 'please declare it at a higher level.' ); } return useArweaveContext; } export async function getWalletPermissions(arweaveWallet) { try { return await arweaveWallet.getPermissions(); } catch { return []; } } export async function getActiveAddress() { try { if (window.arweaveWallet) { const addy = await window.arweaveWallet.getActiveAddress(); return addy; } else { return null; } } catch (e) { return null; } } export type ArweaveProviderProps = { children: any; permissions?: PermissionType[]; AppInfo?: AppInfo; gatewayConfig?: GatewayConfig; defaultWallet?: supportedWallets; }; export const ArweaveProvider = ({ children, permissions, AppInfo, gatewayConfig={ host: '443', port: 88, protocol: 'https' }, }: ArweaveProviderProps) => { const [api, setApi] = useState(undefined); const [arweaveAppAPI, setArweaveAppAPI] = useState(undefined); const [walletType, setWalletType] = useState(); const [walletConnected, setWalletConnected] = useState(false); const [searchingWallet, setSearchingWallet] = useState(true); const [address, setAddress] = useState(''); const [walletPermissions, setWalletPermissions] = useState( permissions || [] ); const config = gatewayConfig; const appInfo = AppInfo; const perms = permissions; const arweaveConnect = async ({ permissions={...perms}, AppInfo={...appInfo}, gatewayConfig={...config}, walletChoice="arconnect", }: arweaveConnectInterface) => { try { // check for arconnect / any other arweave-based wallet let arweaveWallet: any = window.arweaveWallet; if ( !arweaveWallet || (walletChoice !== 'arconnect' && walletChoice !== 'window.arweaveWallet') ) { console.log('attempting to connect to arweave.app...'); try { const wallet = new ArweaveWebWallet(AppInfo); wallet.setUrl('arweave.app'); setArweaveAppAPI(wallet); wallet .connect({ permissions, AppInfo, gatewayConfig }) .then(async () => { arweaveWallet = wallet.namespaces.arweaveWallet; setApi(arweaveWallet); setArweaveAppAPI(wallet); setWalletPermissions(permissions); const address = await arweaveWallet.getActiveAddress(); setWalletType('arweave.app'); setWalletConnected(true); setAddress(address); return { api: arweaveWallet }; }); } catch (error) { console.log(error); } return { api: undefined }; } else { if ( permissions.length === 0 || !permissions?.includes('ACCESS_ADDRESS') ) throw new Error('ACCESS_ADDRESS permission is required to connect'); await arweaveWallet.connect(permissions, AppInfo, gatewayConfig); setApi(arweaveWallet); setWalletPermissions(permissions); const address = await arweaveWallet.getActiveAddress(); setAddress(address); setWalletConnected(true); setWalletType('arconnect'); return { api: arweaveWallet }; } } catch (Error) { console.error(Error); } return { api: undefined }; }; const arweaveDisconnect = async () => { try { try { await api.disconnect(); } catch (e) { await window.arweaveWallet.disconnect(); } setWalletConnected(false); setAddress(undefined); setWalletPermissions([]); setArweaveAppAPI(undefined); setWalletType(undefined); } catch (Error) { console.error(Error); } }; const getPublicKey = async () => { try { return await api.getActivePublicKey(); } catch (Error) { console.error(Error); return ''; } }; // example of params for signature generation: // data: new TextEncoder().encode("Hello world!"); // signatureParams: defaultSignatureParams // return type: signature type // sigType: whether arconnect signature will use signMessage or signature method const createSignature = async ({ data, signatureParams = defaultSignatureParams, returnType = 'base64', sigType = 'new', }: createSignatureInterface) => { try { let signature = undefined; const encoder = new TextEncoder(); const encodedString = encoder.encode(data); if (walletType === 'arweave.app') { signature = await arweaveAppAPI.signMessage(encodedString, { hashAlgorithm: 'SHA-256', }); } else { if (sigType === 'new') { signature = await api.signMessage(encodedString); } else { signature = await api.signature(encodedString, signatureParams); } } if (!signature) throw new Error('Signature generation failed'); if (returnType === 'Uint8Array') return signature; else { return Buffer.from(signature).toString(returnType); } } catch (Error) { console.error(Error); return undefined; } }; useEffect(() => { setSearchingWallet(true); getActiveAddress().then((addy) => { if (addy) { setWalletConnected(true); setSearchingWallet(false); setAddress(addy); const wallet = new ArweaveWebWallet(AppInfo); wallet.setUrl('arweave.app'); setArweaveAppAPI(wallet); let arweaveWallet = window.arweaveWallet; setApi(arweaveWallet); } else { setWalletConnected(false); setSearchingWallet(false); } }); }, []); useEffect(() => { let apiInjected = false; // double check if arconnect was added setTimeout(() => { if (apiInjected || api) return; //loadedEvent(); //Disables window pop up }, 2500); }, [api]); const value = { api, walletPermissions, walletConnected, walletType, address, searchingWallet, arweaveConnect, arweaveDisconnect, getActiveAddress, getPublicKey, createSignature, }; return ( {children} ); }; interface ConnectButtonInterface { walletChoice: supportedWallets; permissions: PermissionType[]; className?: string; onConnectCallback?: () => void; gatewayConfig?: GatewayConfig; AppInfo?: { name: string; logo: string; }; children?: ReactNode; } export const ConnectButton = ({ walletChoice, permissions, className, onConnectCallback, AppInfo, gatewayConfig, children, }: ConnectButtonInterface) => { const { arweaveConnect } = useArweave(); const connect = async () => { const val = await arweaveConnect({ walletChoice, permissions, AppInfo, gatewayConfig, }); console.log(val); onConnectCallback(); }; const classAlt = 'text-black dark:text-white bg-white dark:bg-black border-black dark:border-white rounded-lg'; return ( ); }; interface DisconnectButtonProps { className?: string; } export const DisconnectButton = ({ className }: DisconnectButtonProps) => { const { address, arweaveDisconnect } = useArweave(); const classAlt = 'text-black dark:text-white bg-white dark:bg-black border-black dark:border-white border-2 rounded-lg px-2 py-1'; return ( ); }; interface ArweaveModalProps { isOpen: boolean; onClose: () => void; } export const ArweaveModal = ({ isOpen, onClose }: ArweaveModalProps) => { if (!isOpen) return null; const permissions: PermissionType[] = [ 'ACCESS_ADDRESS', 'ACCESS_ALL_ADDRESSES', 'ACCESS_ARWEAVE_CONFIG', 'ACCESS_PUBLIC_KEY', 'DECRYPT', 'DISPATCH', 'ENCRYPT', 'SIGNATURE', 'SIGN_TRANSACTION', ]; const className = 'rounded-lg border-white border-2 bg-[#111111] px-4 py-3 text-white'; return (
e.stopPropagation()}>
Connect with Arconnect
Connect with window.arweaveWallet
Connect with an Arweave wallet
); }; interface ArweaveConnectButtonProps { connectButtonClass?: string; disconnectButtonClass?: string; } export const ArweaveConnectButton = ({ disconnectButtonClass, connectButtonClass, }: ArweaveConnectButtonProps) => { const [isOpen, setIsOpen] = useState(false); const { walletConnected } = useArweave(); return (
{walletConnected ? ( ) : ( )} setIsOpen(false)} />
); };