diff --git a/.changeset/breezy-mirrors-dance.md b/.changeset/breezy-mirrors-dance.md new file mode 100644 index 000000000..19cd3ef0a --- /dev/null +++ b/.changeset/breezy-mirrors-dance.md @@ -0,0 +1,7 @@ +--- +"@crossmint/client-sdk-react-native-ui": minor +"@crossmint/client-sdk-react-ui": minor +"@crossmint/wallets-sdk": minor +--- + +Unifies callback calls for otp signers diff --git a/apps/wallets/quickstart-devkit/app/providers.tsx b/apps/wallets/quickstart-devkit/app/providers.tsx index aa1f4df54..efabe5fd8 100644 --- a/apps/wallets/quickstart-devkit/app/providers.tsx +++ b/apps/wallets/quickstart-devkit/app/providers.tsx @@ -192,10 +192,7 @@ function SolanaFirebaseProvider({ children }: { children: React.ReactNode }) { function StellarCrossmintAuthProvider({ children }: { children: React.ReactNode }) { return ( - + wallet?.address, [wallet]); const url = Linking.useURL(); @@ -80,7 +81,8 @@ export default function Index() { } setIsLoading(true); try { - await getOrCreateWallet({ chain: "stellar", signer: { type: "email" } }); + // await getOrCreateWallet({ chain: "stellar", signer: { type: "email" } }); + await getOrCreateWallet({ chain: "stellar", signer: { type: "phone", phone: "+13472008361" } }); } catch (error) { console.error("Error initializing wallet:", error); } finally { @@ -113,7 +115,7 @@ export default function Index() { return; } setIsInOtpFlow(true); - await handleAction(sendEmailWithOtp); + await handleAction(sendOtp); }; const handleVerifyOtpInput = async () => { @@ -135,7 +137,19 @@ export default function Index() { } setIsLoading(true); try { - const tx = await wallet.send(recipientAddress, "usdc", amount); + const transaction = { + type: "contract-call", + contractId: "CDMUQY2ZHWIGXSTHPHX53ACR76OLXCCAAKQWQWUS2JNAH6SZEXMRU6R2", + method: "transfer", + args: { + from: wallet.address, + to: recipientAddress, + amount, + }, + }; + const stellarWallet = StellarWallet.from(wallet); + const tx = await stellarWallet.sendTransaction(transaction); + // const tx = await wallet.send(recipientAddress, "usdc", amount); console.log(`Sent ${amount} USDC to ${recipientAddress}. Tx Link: ${tx.explorerLink}`); setTxLink(tx.explorerLink); setRecipientAddress(""); diff --git a/packages/client/ui/react-native/src/hooks/index.ts b/packages/client/ui/react-native/src/hooks/index.ts index 84afe3653..beaa90887 100644 --- a/packages/client/ui/react-native/src/hooks/index.ts +++ b/packages/client/ui/react-native/src/hooks/index.ts @@ -1,3 +1,3 @@ export { useCrossmint, useWallet } from "@crossmint/client-sdk-react-base"; export * from "./useCrossmintAuth"; -export * from "./useWalletEmailSigner"; +export * from "./useWalletOtpSigner"; diff --git a/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts b/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts deleted file mode 100644 index 9a4ec572f..000000000 --- a/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CrossmintWalletEmailSignerContext } from "@/providers/CrossmintWalletProvider"; -import { useContext } from "react"; - -export type EmailSignerFunctions = { - needsAuth: boolean; - sendEmailWithOtp: () => Promise; - verifyOtp: (otp: string) => Promise; - reject: (error: Error) => void; -}; - -export function useWalletEmailSigner(): EmailSignerFunctions { - const context = useContext(CrossmintWalletEmailSignerContext); - - if (context == null) { - throw new Error("useWalletEmailSigner must be used within CrossmintWalletProvider"); - } - - if (!context.sendEmailWithOtp || !context.verifyOtp || !context.reject) { - throw new Error("Email signer functions are not available. Make sure you're using an email signer wallet."); - } - - return { - needsAuth: context.needsAuth, - sendEmailWithOtp: context.sendEmailWithOtp, - verifyOtp: context.verifyOtp, - reject: context.reject, - }; -} diff --git a/packages/client/ui/react-native/src/hooks/useWalletOtpSigner.ts b/packages/client/ui/react-native/src/hooks/useWalletOtpSigner.ts new file mode 100644 index 000000000..e00c0d6ff --- /dev/null +++ b/packages/client/ui/react-native/src/hooks/useWalletOtpSigner.ts @@ -0,0 +1,28 @@ +import { CrossmintWalletEmailSignerContext } from "@/providers/CrossmintWalletProvider"; +import { useContext } from "react"; + +export type OtpSignerFunctions = { + needsAuth: boolean; + sendOtp: () => Promise; + verifyOtp: (otp: string) => Promise; + reject: (error: Error) => void; +}; + +export function useWalletOtpSigner(): OtpSignerFunctions { + const context = useContext(CrossmintWalletEmailSignerContext); + + if (context == null) { + throw new Error("useWalletOtpSigner must be used within CrossmintWalletProvider"); + } + + if (!context.sendOtp || !context.verifyOtp || !context.reject) { + throw new Error("Otp signer functions are not available. Make sure you're using an otp signer wallet."); + } + + return { + needsAuth: context.needsAuth, + sendOtp: context.sendOtp, + verifyOtp: context.verifyOtp, + reject: context.reject, + }; +} diff --git a/packages/client/ui/react-native/src/index.ts b/packages/client/ui/react-native/src/index.ts index e0ea63aaf..522a0f661 100644 --- a/packages/client/ui/react-native/src/index.ts +++ b/packages/client/ui/react-native/src/index.ts @@ -14,5 +14,6 @@ export { type Transaction, EVMWallet, SolanaWallet, + StellarWallet, Wallet, } from "@crossmint/wallets-sdk"; diff --git a/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx b/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx index 1de714e57..e5f58126d 100644 --- a/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx +++ b/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx @@ -13,7 +13,7 @@ const throwNotAvailable = (functionName: string) => () => { type CrossmintWalletEmailSignerContext = { needsAuth: boolean; - sendEmailWithOtp: () => Promise; + sendOtp: () => Promise; verifyOtp: (otp: string) => Promise; reject: (error?: Error) => void; }; @@ -21,7 +21,7 @@ type CrossmintWalletEmailSignerContext = { // Create the auth context export const CrossmintWalletEmailSignerContext = createContext({ needsAuth: false, - sendEmailWithOtp: throwNotAvailable("sendEmailWithOtp"), + sendOtp: throwNotAvailable("sendOtp"), verifyOtp: throwNotAvailable("verifyOtp"), reject: throwNotAvailable("reject"), }); @@ -49,7 +49,7 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }: null ); const [needsAuthState, setNeedsAuthState] = useState(false); - const sendEmailWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendEmailWithOtp")); + const sendOtpRef = useRef<() => Promise>(throwNotAvailable("sendOtp")); const verifyOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyOtp")); const rejectRef = useRef<(error?: Error) => void>(throwNotAvailable("reject")); @@ -139,12 +139,12 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }: const onAuthRequired = async ( needsAuth: boolean, - sendEmailWithOtp: () => Promise, + sendOtp: () => Promise, verifyOtp: (otp: string) => Promise, reject: () => void ) => { setNeedsAuthState(needsAuth); - sendEmailWithOtpRef.current = sendEmailWithOtp; + sendOtpRef.current = sendOtp; verifyOtpRef.current = verifyOtp; rejectRef.current = reject; }; @@ -152,7 +152,7 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }: const authContextValue = useMemo( () => ({ needsAuth: needsAuthState, - sendEmailWithOtp: sendEmailWithOtpRef.current, + sendOtp: sendOtpRef.current, verifyOtp: verifyOtpRef.current, reject: rejectRef.current, }), diff --git a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx index 9677084e3..398b8446e 100644 --- a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx +++ b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx @@ -61,7 +61,7 @@ export function CrossmintWalletProvider({ const [needsAuthState, setNeedsAuthState] = useState(false); // Email signer refs (for main wallet authentication) - const sendEmailWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendEmailWithOtp")); + const sendOtpRef = useRef<() => Promise>(throwNotAvailable("sendOtp")); const verifyOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyOtp")); // Phone signer refs (for TEE handshake) @@ -99,7 +99,7 @@ export function CrossmintWalletProvider({ const emailsigners_handleSendEmailOTP = async () => { try { - await sendEmailWithOtpRef.current(); + await sendOtpRef.current(); setEmailSignerDialogStep("otp"); } catch (error) { console.error("Failed to send email OTP", error); @@ -166,7 +166,7 @@ export function CrossmintWalletProvider({ } else { // Default email signer behavior setEmailSignerDialogOpen(needsAuth); - sendEmailWithOtpRef.current = sendMessageWithOtp; + sendOtpRef.current = sendMessageWithOtp; verifyOtpRef.current = verifyOtp; } setNeedsAuthState(needsAuth); diff --git a/packages/wallets/README.md b/packages/wallets/README.md index d66f7dbb0..28f9f9ecf 100644 --- a/packages/wallets/README.md +++ b/packages/wallets/README.md @@ -2,7 +2,7 @@ A Typescript SDK to interact with Crossmint Wallets. This SDK enables developers to easily create and manage wallets on Solana and EVM chains. -Get a Crossmint client API key from [here](https://docs.crossmint.com/introduction/platform/api-keys/client-side) and add it to your `.env` file. Make sure your API key has all scopes for `Wallet API`, and `Users`. +Get a Crossmint client API key from [here](https://docs.crossmint.com/introduction/platform/api-keys/client-side) and add it to your `.env` file. Make sure your API key has all scopes for `Wallet API`, and `Users`. ## Installation @@ -23,18 +23,18 @@ const crossmint = createCrossmint({ }); const crossmintWallets = CrossmintWallets.from(crossmint); const wallet = await crossmintWallets.getOrCreateWallet({ - chain: "", + chain: "", signer: { type: "email", email: "", - onAuthRequired: async (needsAuth, sendEmailWithOtp, verifyOtp, reject) => { + onAuthRequired: async (needsAuth, sendOtp, verifyOtp, reject) => { if (needsAuth) { - await sendEmailWithOtp(); + await sendOtp(); // Prompt the user to check their email for the OTP code. // Once the user provides the OTP, pass it to verifyOtp(otp). // NOTE: If using our React/React Native SDK, this is handled automatically by the provider. } - }, + }, }, }); diff --git a/packages/wallets/src/signers/non-custodial/ncs-signer.ts b/packages/wallets/src/signers/non-custodial/ncs-signer.ts index 526380eba..d3fe2a7b2 100644 --- a/packages/wallets/src/signers/non-custodial/ncs-signer.ts +++ b/packages/wallets/src/signers/non-custodial/ncs-signer.ts @@ -44,7 +44,7 @@ export abstract class NonCustodialSigner implements Signer { `${this.type} signer requires the onAuthRequired callback to handle OTP verification. ` + `This callback manages the authentication flow (sending OTP and verifying user input). ` + `If using our React/React Native SDK, this is handled automatically by the provider. ` + - `For other environments, implement: onAuthRequired: (needsAuth, sendEmailWithOtp, verifyOtp, reject) => { /* your UI logic */ }` + `For other environments, implement: onAuthRequired: (needsAuth, sendOtp, verifyOtp, reject) => { /* your UI logic */ }` ); } throw new Error("Handshake parent not initialized"); diff --git a/packages/wallets/src/signers/types.ts b/packages/wallets/src/signers/types.ts index 4ccf61bcb..1280f915e 100644 --- a/packages/wallets/src/signers/types.ts +++ b/packages/wallets/src/signers/types.ts @@ -31,7 +31,7 @@ export type EmailSignerConfig = { email?: string; onAuthRequired?: ( needsAuth: boolean, - sendEmailWithOtp: () => Promise, + sendOtp: () => Promise, verifyOtp: (otp: string) => Promise, reject: () => void ) => Promise; @@ -42,7 +42,7 @@ export type PhoneSignerConfig = { phone?: string; onAuthRequired?: ( needsAuth: boolean, - sendEmailWithOtp: () => Promise, + sendOtp: () => Promise, verifyOtp: (otp: string) => Promise, reject: () => void ) => Promise;