Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Xverse to Connect Wallet component #8

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
"use client";

import { ConnectButton } from "@rainbow-me/rainbowkit";
import { ConnectBitcoin } from "@/index";
import {
Swap,
useEthersSigner,
useZetaChainClient,
ConnectBitcoin,
useBitcoinWallet,
} from "@/index";
import { useAccount, useChainId, useWalletClient } from "wagmi";

const contract = "0xb459F14260D1dc6484CE56EB0826be317171e91F"; // universal swap contract

const Page = () => {
const account = useAccount();
const chainId = useChainId();
const { data: walletClient } = useWalletClient({ chainId });
const signer = useEthersSigner({ walletClient });
const client = useZetaChainClient({ network: "testnet", signer });
const { address: bitcoinAddress } = useBitcoinWallet();

return (
<div>
<ConnectBitcoin />
<ConnectButton />
<div className="m-4">
<div className="flex justify-end gap-2 mb-10">
<ConnectBitcoin />
<ConnectButton label="Connect EVM" showBalance={false} />
</div>
<div className="flex justify-center">
<div className="w-[400px]">
{client && (
<Swap
contract={contract}
client={client}
account={account}
bitcoin={bitcoinAddress}
/>
)}
</div>
</div>
</div>
);
};
Expand Down
31 changes: 31 additions & 0 deletions src/components/ConnectBitcoin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ import Image, { StaticImageData } from "next/image";
import okxIcon from "./okx.jpeg";
import xdefiIcon from "./xdefi.jpeg";
import unisatIcon from "./unisat.jpeg";
import xverseIcon from "./xverse.jpeg";
import {
Params,
Requests,
RpcResult,
SupportedWallet,
defaultAdapters,
getSupportedWallets,
SatsConnectAdapter,
setDefaultProvider,
getDefaultProvider,
removeDefaultProvider,
RpcErrorCode,
AddressPurpose,
BaseAdapter,
} from "@sats-connect/core";

const formatAddress = (str: string): string => {
if (str.length <= 10) {
Expand Down Expand Up @@ -130,6 +146,14 @@ const Details = React.memo(({ address, disconnect }: any) => {
});

const Connect = React.memo(({ connectWallet, loading }: any) => {
const connectXverse = async () => {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Stacks, AddressPurpose.Payment],
});
console.log(accounts);
};
Comment on lines +149 to +155
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling and logging.

Consider adding error handling and logging to the connectXverse function to handle potential issues gracefully.

const connectXverse = async () => {
  try {
    const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
    const accounts = await adapter.request("getAccounts", {
      purposes: [AddressPurpose.Stacks, AddressPurpose.Payment],
    });
    console.log(accounts);
  } catch (error) {
    console.error("Error connecting to Xverse wallet:", error);
  }
};


return (
<Dialog>
<DialogTrigger asChild>
Expand Down Expand Up @@ -166,6 +190,13 @@ const Connect = React.memo(({ connectWallet, loading }: any) => {
connectWallet={connectWallet}
loading={loading}
/>
<WalletButton
walletType="xverse"
icon={xverseIcon}
label="Xverse"
connectWallet={connectWallet}
loading={loading}
/>
</div>
</DialogContent>
</Dialog>
Expand Down
Binary file added src/components/ConnectBitcoin/xverse.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 21 additions & 25 deletions src/providers/BitcoinWalletProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import React, { createContext, useContext, useState, ReactNode } from "react";
import unisatWallet from "./unisat";
import okxWallet from "./okx";
import xdefiWallet from "./xdefi";
import xverseWallet from "./xverse";

export const walletTypes = {
unisat: unisatWallet,
okx: okxWallet,
xdefi: xdefiWallet,
xverse: xverseWallet,
};

export type WalletType = keyof typeof walletTypes;

interface WalletContextProps {
address: string | null;
publicKey: string | null;
loading: { isLoading: boolean; walletType: WalletType | null };
connectedWalletType: WalletType | null;
connectWallet: (walletType: WalletType) => void;
Expand All @@ -34,6 +37,7 @@ export const BitcoinWalletProvider = ({
children: ReactNode;
}) => {
const [address, setAddress] = useState<string | null>(null);
const [publicKey, setPublicKey] = useState<string | null>(null);
const [loading, setLoading] = useState<{
isLoading: boolean;
walletType: WalletType | null;
Expand All @@ -44,23 +48,19 @@ export const BitcoinWalletProvider = ({
const connectWallet = async (walletType: WalletType) => {
setAddress(null);
const walletConfig = walletTypes[walletType];
const wallet = (window as any)[walletConfig.name];
if (wallet) {
setLoading({ isLoading: true, walletType });
try {
const address = await walletConfig.getAddress(wallet);
setAddress(address);
setConnectedWalletType(walletType);
} catch (error) {
console.error(`Connection to ${walletConfig.label} failed:`, error);
setLoading({ isLoading: false, walletType: null });
return;
}
setLoading({ isLoading: false, walletType: null });
} else {
console.error("Unsupported wallet type");

setLoading({ isLoading: true, walletType });
try {
const { address, publicKey } = await walletConfig.getAddress();
setAddress(address);
setPublicKey(publicKey);
setConnectedWalletType(walletType);
} catch (error) {
console.error(`Connection to ${walletConfig.label} failed:`, error);
setLoading({ isLoading: false, walletType: null });
return;
}
setLoading({ isLoading: false, walletType: null });
};

const disconnect = () => {
Expand All @@ -79,23 +79,19 @@ export const BitcoinWalletProvider = ({
}

const walletConfig = walletTypes[connectedWalletType];
const wallet = (window as any)[walletConfig.name];
if (wallet) {
try {
const txHash = await walletConfig.sendTransaction(wallet, params);
console.log(`Broadcasted a transaction: ${txHash}`);
} catch (error) {
console.error(`Transaction with ${walletConfig.label} failed:`, error);
}
} else {
console.error("Unsupported wallet type");
try {
const txHash = await walletConfig.sendTransaction(params);
console.log(`Broadcasted a transaction: ${txHash}`);
} catch (error) {
console.error(`Transaction with ${walletConfig.label} failed:`, error);
}
};

return (
<BitcoinWalletContext.Provider
value={{
address,
publicKey,
loading,
connectedWalletType,
connectWallet,
Expand Down
28 changes: 14 additions & 14 deletions src/providers/BitcoinWalletProvider/okx.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
export default {
label: "OKX Wallet",
name: "okxwallet",
getAddress: async (wallet: any) => {
return (await wallet.bitcoinTestnet.connect()).address;
getAddress: async () => {
const wallet = (window as any).okxwallet;
const address = (await wallet.bitcoinTestnet.connect()).address;
return { address, publicKey: null };
},
sendTransaction: async (
wallet: any,
{
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}
) => {
sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
const wallet = (window as any).okxwallet;
const account = await wallet?.bitcoinTestnet?.connect();
if (!account) throw new Error("No account found");
const txHash = await wallet.bitcoinTestnet.send({
Expand Down
28 changes: 14 additions & 14 deletions src/providers/BitcoinWalletProvider/unisat.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
export default {
label: "Unisat Wallet",
name: "unisat",
getAddress: async (wallet: any) => {
return (await wallet.requestAccounts())[0];
getAddress: async () => {
const wallet = (window as any).unisat;
const address = (await wallet.requestAccounts())[0];
return { address, publicKey: null };
},
sendTransaction: async (
wallet: any,
{
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}
) => {
sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
const wallet = (window as any).unisat;
await wallet.requestAccounts();
const memos = memo && [memo.toLowerCase()];
const tx = await wallet.sendBitcoin(to, value * 1e8, { memos });
Expand Down
28 changes: 14 additions & 14 deletions src/providers/BitcoinWalletProvider/xdefi.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
export default {
label: "XDEFI Wallet",
name: "xfi",
getAddress: async (wallet: any) => {
getAddress: async () => {
const wallet = (window as any).xfi;
wallet.bitcoin.changeNetwork("testnet");
return (await wallet?.bitcoin?.getAccounts())[0];
const address = (await wallet?.bitcoin?.getAccounts())[0];
return { address, publicKey: null };
},
sendTransaction: async (
wallet: any,
{
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}
) => {
sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
const wallet = (window as any).unisat;
wallet.bitcoin.changeNetwork("testnet");
const account = (await wallet?.bitcoin?.getAccounts())?.[0];
if (!account) throw new Error("No account found");
Expand Down
43 changes: 43 additions & 0 deletions src/providers/BitcoinWalletProvider/xverse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AddressPurpose, BaseAdapter } from "@sats-connect/core";
import { createTransaction, signPsbt } from "./utils";

export default {
label: "Xverse Wallet",
name: "xverse",
getAddress: async () => {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
console.log(accounts);
return {
address: accounts.result[0].address,
publicKey: accounts.result[0].publicKey,
};
},
Comment on lines +7 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle potential errors in getAddress.

The getAddress method retrieves account information correctly. However, it lacks error handling, which could lead to unhandled exceptions.

  getAddress: async () => {
    try {
      const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
      const accounts: any = await adapter.request("getAccounts", {
        purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
      });
      console.log(accounts);
      return {
        address: accounts.result[0].address,
        publicKey: accounts.result[0].publicKey,
      };
    } catch (error) {
      console.error("Failed to get address:", error);
      throw error;
    }
  },
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getAddress: async () => {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
console.log(accounts);
return {
address: accounts.result[0].address,
publicKey: accounts.result[0].publicKey,
};
},
getAddress: async () => {
try {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
console.log(accounts);
return {
address: accounts.result[0].address,
publicKey: accounts.result[0].publicKey,
};
} catch (error) {
console.error("Failed to get address:", error);
throw error;
}
},

sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
const address = accounts.result[0].address;
const publicKey = accounts.result[0].publicKey;

const result = await createTransaction(publicKey, address, {
memo,
amount: value * 1e8,
to,
});

const res = await signPsbt(result.psbtB64, result.utxoCnt, address);
console.log(res);
},
Comment on lines +18 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle potential errors in sendTransaction.

The sendTransaction method creates and signs a transaction correctly. However, it lacks error handling, which could lead to unhandled exceptions.

  sendTransaction: async ({
    to,
    value,
    memo,
  }: {
    to: string;
    value: number;
    memo?: string;
  }) => {
    try {
      const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
      const accounts: any = await adapter.request("getAccounts", {
        purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
      });
      const address = accounts.result[0].address;
      const publicKey = accounts.result[0].publicKey;

      const result = await createTransaction(publicKey, address, {
        memo,
        amount: value * 1e8,
        to,
      });

      const res = await signPsbt(result.psbtB64, result.utxoCnt, address);
      console.log(res);
    } catch (error) {
      console.error("Failed to send transaction:", error);
      throw error;
    }
  },
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
const address = accounts.result[0].address;
const publicKey = accounts.result[0].publicKey;
const result = await createTransaction(publicKey, address, {
memo,
amount: value * 1e8,
to,
});
const res = await signPsbt(result.psbtB64, result.utxoCnt, address);
console.log(res);
},
sendTransaction: async ({
to,
value,
memo,
}: {
to: string;
value: number;
memo?: string;
}) => {
try {
const adapter = new BaseAdapter("XverseProviders.BitcoinProvider");
const accounts: any = await adapter.request("getAccounts", {
purposes: [AddressPurpose.Payment, AddressPurpose.Payment],
});
const address = accounts.result[0].address;
const publicKey = accounts.result[0].publicKey;
const result = await createTransaction(publicKey, address, {
memo,
amount: value * 1e8,
to,
});
const res = await signPsbt(result.psbtB64, result.utxoCnt, address);
console.log(res);
} catch (error) {
console.error("Failed to send transaction:", error);
throw error;
}
},

};
Loading
Loading