diff --git a/examples/fullstack/expo/.babelrc.js b/examples/fullstack/expo/.babelrc.js new file mode 100644 index 00000000..9d89e131 --- /dev/null +++ b/examples/fullstack/expo/.babelrc.js @@ -0,0 +1,6 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + }; +}; diff --git a/examples/fullstack/expo/.gitignore b/examples/fullstack/expo/.gitignore new file mode 100644 index 00000000..bd5d1f64 --- /dev/null +++ b/examples/fullstack/expo/.gitignore @@ -0,0 +1,36 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ + +# Native +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env +.env*.local + +# typescript +*.tsbuildinfo diff --git a/examples/fullstack/expo/app.json b/examples/fullstack/expo/app.json new file mode 100644 index 00000000..f6697b2e --- /dev/null +++ b/examples/fullstack/expo/app.json @@ -0,0 +1,48 @@ +{ + "expo": { + "name": "jan-expo-demo", + "slug": "jan-expo-demo", + "scheme": "jan-expo-demo", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "light", + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": true, + "infoPlist": { + "LSApplicationQueriesSchemes": [ + "metamask", + "trust", + "safe", + "rainbow", + "uniswap" + ] + } + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#ffffff" + } + }, + "web": { + "favicon": "./assets/favicon.png", + "output": "server", + "bundler": "metro" + }, + "plugins": [ + [ + "expo-router", + { + "origin": "https://jan-expo-demo.dev/" + } + ] + ] + } +} diff --git a/examples/fullstack/expo/app/_layout.tsx b/examples/fullstack/expo/app/_layout.tsx new file mode 100644 index 00000000..23aef8d8 --- /dev/null +++ b/examples/fullstack/expo/app/_layout.tsx @@ -0,0 +1,54 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import '@walletconnect/react-native-compat'; +import { Web3Modal, createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi-react-native'; +import * as Linking from 'expo-linking'; +import 'react-native-url-polyfill/auto'; +import { arbitrum, mainnet, polygon } from 'viem/chains'; +import { WagmiConfig } from 'wagmi'; + +import { JustaNameProvider } from '@justaname.id/react/src'; +import React from 'react'; +import HomeScreen from './home'; + +const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? "" + +const metadata = { + name: 'JAN Expo Demo', + description: 'JAN Expo Demo', + url: 'https://web3modal.com', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + redirect: { + native: Linking.createURL('/'), + universal: 'YOUR_APP_UNIVERSAL_LINK.com' + } +} + +const chains = [mainnet, polygon, arbitrum] + +const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata }); + +createWeb3Modal({ + projectId, + chains, + wagmiConfig +}) + +export default function App() { + const queryClient = new QueryClient(); + + const chainId = process.env.EXPO_PUBLIC_CHAIN_ID ? parseInt(process.env.EXPO_PUBLIC_CHAIN_ID) as 1 | 11155111 : undefined; + + return ( + + + + + + + + + ); +} + diff --git a/examples/fullstack/expo/app/api/request-challenge+api.ts b/examples/fullstack/expo/app/api/request-challenge+api.ts new file mode 100644 index 00000000..12e7dc5a --- /dev/null +++ b/examples/fullstack/expo/app/api/request-challenge+api.ts @@ -0,0 +1,32 @@ +import { ChainId } from '@justaname.id/sdk'; +import { ExpoRequest, ExpoResponse } from 'expo-router/server'; +import { getJustaNameInstance } from '../../justaname'; + +export async function GET(req: ExpoRequest): Promise { + const { searchParams } = req.expoUrl; + + const address = searchParams.get('address'); + + if (!address) { + return new ExpoResponse('Address is required', { status: 400 }); + } + + const justaname = await getJustaNameInstance(); + const chainId = parseInt( + process.env.EXPO_PUBLIC_CHAIN_ID as string + ) as ChainId; + const origin = 'exp://192.168.1.5:8081'; + const domain = process.env.EXPO_PUBLIC_ENS_DOMAIN as string; + + try { + const challenge = await justaname.siwe.requestChallenge({ + chainId, + origin, + address, + domain, + }); + return ExpoResponse.json(challenge); + } catch (e: any) { + return new ExpoResponse(e.message, { status: 500 }); + } +} diff --git a/examples/fullstack/expo/app/api/subnames/claim+api.ts b/examples/fullstack/expo/app/api/subnames/claim+api.ts new file mode 100644 index 00000000..df337145 --- /dev/null +++ b/examples/fullstack/expo/app/api/subnames/claim+api.ts @@ -0,0 +1,47 @@ +import { ChainId } from '@justaname.id/sdk'; +import { ExpoRequest, ExpoResponse } from 'expo-router/server'; +import { getJustaNameInstance } from '../../../justaname'; + +export async function POST(req: ExpoRequest): Promise { + const { username, message, signature, address } = await req.json(); + + if (!username) { + return new ExpoResponse('Username is required', { status: 400 }); + } + + if (!address) { + return new ExpoResponse('Address is required', { status: 400 }); + } + + if (!signature) { + return new ExpoResponse('Signature is required', { status: 400 }); + } + + if (!message) { + return new ExpoResponse('Message is required', { status: 400 }); + } + const justaname = await getJustaNameInstance(); + + const chainId = parseInt( + process.env.EXPO_PUBLIC_CHAIN_ID as string + ) as ChainId; + const ensDomain = process.env.EXPO_PUBLIC_ENS_DOMAIN as string; + try { + const subname = await justaname.subnames.addSubname( + { + username: username.split('.')[0], + ensDomain, + chainId, + }, + { + xSignature: signature, + xAddress: address, + xMessage: message, + } + ); + return ExpoResponse.json(subname); + } catch (e: any) { + console.log('claim error', e); + return new ExpoResponse(e.message, { status: 500 }); + } +} diff --git a/examples/fullstack/expo/app/home.tsx b/examples/fullstack/expo/app/home.tsx new file mode 100644 index 00000000..8948cefd --- /dev/null +++ b/examples/fullstack/expo/app/home.tsx @@ -0,0 +1,123 @@ +import "@ethersproject/shims"; +import { useAccountSubnames, useClaimSubname, useIsSubnameAvailable } from '@justaname.id/react/src'; +import '@walletconnect/react-native-compat'; +import { W3mButton } from '@web3modal/wagmi-react-native'; +import React, { useState } from 'react'; +import { ActivityIndicator, Button, Keyboard, KeyboardAvoidingView, Platform, StyleSheet, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native'; +import 'react-native-url-polyfill/auto'; +import { useDebounced } from "../hooks/useDebounced"; + + +export default function HomeScreen() { + const [inputValue, setInputValue] = useState(''); + // Wagmi + + const { + value: debouncedSubdomain, + } = useDebounced( + inputValue, + 200, + ); + + const { subnames } = useAccountSubnames(); + const { isAvailable, isLoading } = useIsSubnameAvailable({ + username: debouncedSubdomain, + ensDomain: process.env.EXPO_PUBLIC_ENS_DOMAIN as string, + }) + const { claimSubname } = useClaimSubname(); + + const handleAddSubdomain = async () => { + return await claimSubname({ + username: inputValue, + }); + } + + return ( + + + + JAN WALLET TEST + Connect ur wallet + + + + .{process.env.EXPO_PUBLIC_ENS_DOMAIN} + +