diff --git a/src/components/TransactionBuilder/SignStep.tsx b/src/components/TransactionBuilder/SignStep.tsx index 4dc8252..b75e8fe 100644 --- a/src/components/TransactionBuilder/SignStep.tsx +++ b/src/components/TransactionBuilder/SignStep.tsx @@ -1,10 +1,17 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { clsx } from "clsx"; import { isValidSecretKey } from "../../lib/stellar"; +import { + isFreighterInstalled, + signWithFreighter, + isAlbedoAvailable, + signWithAlbedo, +} from "../../wallets"; interface SignStepProps { xdr: string | null; signedXdr: string | null; + networkPassphrase?: string; onSign: (secretKey: string) => void; onSignedXdrChange: (xdr: string) => void; onNext: () => void; @@ -15,17 +22,54 @@ interface SignStepProps { export function SignStep({ xdr, signedXdr, + networkPassphrase = "Test SDF Network ; September 2015", onSign, onSignedXdrChange, onNext, onBack, showSubmit, }: SignStepProps) { - const [secretKey, setSecretKey] = useState(""); +const [secretKey, setSecretKey] = useState(""); const [signingMode, setSigningMode] = useState<"manual" | "paste">("manual"); const [showKey, setShowKey] = useState(false); const [signError, setSignError] = useState(null); const [copied, setCopied] = useState(false); + const [walletSigning, setWalletSigning] = useState(false); + const [freighterAvailable, setFreighterAvailable] = useState(false); + const [albedoAvailable, setAlbedoAvailable] = useState(false); + + useEffect(() => { + setFreighterAvailable(isFreighterInstalled()); + setAlbedoAvailable(isAlbedoAvailable()); + }, []); + + const handleFreighterSign = async () => { + if (!xdr) return; + setWalletSigning(true); + setSignError(null); + try { + const signed = await signWithFreighter(xdr, networkPassphrase); + onSignedXdrChange(signed); + } catch (e) { + setSignError((e as Error).message); + } finally { + setWalletSigning(false); + } + }; + + const handleAlbedoSign = async () => { + if (!xdr) return; + setWalletSigning(true); + setSignError(null); + try { + const signed = await signWithAlbedo(xdr, networkPassphrase); + onSignedXdrChange(signed); + } catch (e) { + setSignError((e as Error).message); + } finally { + setWalletSigning(false); + } + }; const isKeyValid = isValidSecretKey(secretKey); @@ -54,6 +98,51 @@ export function SignStep({

+ {/* Wallet signing */} + {(freighterAvailable || albedoAvailable) && ( +
+

Sign with wallet

+
+ {freighterAvailable && ( + + )} + {albedoAvailable && ( + + )} +
+
+

Or sign manually below

+
+
+ )} + + {!freighterAvailable && !albedoAvailable && ( +
+

+ 💡 No wallet extension detected. Install{" "} + Freighter + {" "}or use{" "} + Albedo + {" "}to sign without exposing your secret key. +

+
+ )} + + {/* Signing mode */}
{(["manual", "paste"] as const).map((mode) => ( diff --git a/src/wallets/albedo.ts b/src/wallets/albedo.ts new file mode 100644 index 0000000..81e39c6 --- /dev/null +++ b/src/wallets/albedo.ts @@ -0,0 +1,23 @@ +// Albedo wallet adapter +export function isAlbedoAvailable(): boolean { + return typeof window !== 'undefined' && !!(window as any).albedo; +} + +export async function signWithAlbedo( + xdr: string, + networkPassphrase: string +): Promise { + if (!isAlbedoAvailable()) throw new Error('Albedo is not available'); + const result = await (window as any).albedo.tx({ + xdr, + network: networkPassphrase.includes('Public') ? 'public' : 'testnet', + submit: false, + }); + return result.signed_envelope_xdr; +} + +export async function getAlbedoPublicKey(): Promise { + if (!isAlbedoAvailable()) throw new Error('Albedo is not available'); + const result = await (window as any).albedo.publicKey({}); + return result.pubkey; +} diff --git a/src/wallets/freighter.ts b/src/wallets/freighter.ts new file mode 100644 index 0000000..59b915d --- /dev/null +++ b/src/wallets/freighter.ts @@ -0,0 +1,27 @@ +// Freighter wallet adapter +export interface FreighterAdapter { + isInstalled: () => boolean; + getPublicKey: () => Promise; + signTransaction: (xdr: string, network: string) => Promise; +} + +export function isFreighterInstalled(): boolean { + return typeof window !== 'undefined' && !!(window as any).freighter; +} + +export async function getFreighterPublicKey(): Promise { + if (!isFreighterInstalled()) throw new Error('Freighter is not installed'); + return await (window as any).freighterApi.getPublicKey(); +} + +export async function signWithFreighter( + xdr: string, + networkPassphrase: string +): Promise { + if (!isFreighterInstalled()) throw new Error('Freighter is not installed'); + const result = await (window as any).freighterApi.signTransaction(xdr, { + networkPassphrase, + }); + if (result.error) throw new Error(result.error); + return result.signedTxXdr; +} diff --git a/src/wallets/index.ts b/src/wallets/index.ts new file mode 100644 index 0000000..ae91759 --- /dev/null +++ b/src/wallets/index.ts @@ -0,0 +1,13 @@ +export * from './freighter'; +export * from './albedo'; + +export type WalletType = 'freighter' | 'albedo' | 'secretKey' | 'none'; + +export function detectAvailableWallets(): WalletType[] { + const wallets: WalletType[] = ['secretKey']; + if (typeof window !== 'undefined') { + if ((window as any).freighter) wallets.unshift('freighter'); + if ((window as any).albedo) wallets.unshift('albedo'); + } + return wallets; +}