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

Instructions bug on Wormhole Connect #4154

Open
virjilakrum opened this issue Nov 7, 2024 · 2 comments
Open

Instructions bug on Wormhole Connect #4154

virjilakrum opened this issue Nov 7, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@virjilakrum
Copy link

Ekran Resmi 2024-11-07 15 48 09

"No instructions found in transaction"

How can I solve this error. What I am trying to do is to send hash between two solana wallets with wormhole messaging.

The opensource code:

import React, { useState } from 'react';
import { Send } from 'lucide-react';
import { WalletInput } from './components/WalletInput';
import { HashInput } from './components/HashInput';
import { TransactionStatus } from './components/TransactionStatus';
import { isValidSolanaAddress } from './utils/mockTransaction';
import { wormhole } from '@wormhole-foundation/sdk';
import { encoding, signSendWait } from '@wormhole-foundation/sdk';
import type { UnsignedTransaction, SignAndSendSigner } from '@wormhole-foundation/sdk';
import solana from '@wormhole-foundation/sdk/solana';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
import { Transaction, Connection, clusterApiUrl } from '@solana/web3.js';
import { UniversalAddress } from "@wormhole-foundation/sdk";
import { useWallet } from '@solana/wallet-adapter-react';
import { WalletConnectButton } from './components/WalletConnectButton';

function App() {
  const { connected, publicKey } = useWallet();
  const [walletAddress, setWalletAddress] = useState('');
  const [messageHash, setMessageHash] = useState('');
  const [txHash, setTxHash] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const sendMessageViaWormhole = async (
    message: string
  ): Promise<string> => {
    try {
      if (!connected || !publicKey) {
        throw new Error('Wallet not connected');
      }

      // Initialize connection
      const connection = new Connection(
        import.meta.env.VITE_SOLANA_RPC_URL || clusterApiUrl('devnet'),
        'confirmed'
      );

      const wh = await wormhole('Testnet', [solana], {
        chains: {
          Solana: {
            rpc: import.meta.env.VITE_SOLANA_RPC_URL || 'https://api.devnet.solana.com',
          },
        },
      });

      const chain = wh.getChain('Solana');
      const walletAdapter = new PhantomWalletAdapter();
      await walletAdapter.connect();

      const coreBridge = await chain.getWormholeCore();
      const payload = encoding.bytes.encode(message);
      
      const customSigner: SignAndSendSigner<'Testnet', 'Solana'> = {
        chain: () => 'Solana',
        address: () => publicKey.toBase58(),
        signAndSend: async (
          unsignedTxs: UnsignedTransaction<'Testnet', 'Solana'>[]
        ): Promise<string[]> => {
          const transactions = await Promise.all(
            unsignedTxs.map(async (unsignedTx: any) => {
              const transaction = new Transaction();
              
              const latestBlockhash = await connection.getLatestBlockhash();
              transaction.recentBlockhash = latestBlockhash.blockhash;
              transaction.feePayer = publicKey;

              if (unsignedTx.instructions && unsignedTx.instructions.length > 0) {
                transaction.add(...unsignedTx.instructions);
              } else {
                throw new Error('No instructions found in transaction');
              }

              return transaction;
            })
          );

          const signedTxs = await walletAdapter.signAllTransactions(transactions);

          const signatures = await Promise.all(
            signedTxs.map(async (tx) => {
              const rawTransaction = tx.serialize();
              return await connection.sendRawTransaction(rawTransaction);
            })
          );

          return signatures;
        }
      };

      const destinationAddress = new UniversalAddress(
        walletAddress,
        "base58"
      );

      const publishTxs = coreBridge.publishMessage(
        destinationAddress,
        payload,
        0,
        0
      );

      const txids = await signSendWait(chain, publishTxs, customSigner);
      const finalTxId = txids[txids.length - 1]?.txid;
      
      if (!finalTxId) {
        throw new Error('Failed to get transaction ID');
      }

      const [whm] = await chain.parseTransaction(finalTxId);
      
      if (!whm) {
        throw new Error('Failed to parse Wormhole message');
      }

      await wh.getVaa(whm, 'TokenBridge:Transfer', 60_000);
      return finalTxId;

    } catch (err) {
      console.error('Wormhole transfer error:', err);
      throw new Error(err instanceof Error ? err.message : 'Failed to send message');
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (!connected) {
      setError("Please connect your wallet first");
      return;
    }

    setIsLoading(true);
    setError(null);
    
    try {
      const hash = await sendMessageViaWormhole(messageHash);
      setTxHash(hash);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Transaction failed';
      setError(errorMessage);
      console.error('Transaction failed:', err);
    } finally {
      setIsLoading(false);
    }
  };

  const isValidWallet = !walletAddress || isValidSolanaAddress(walletAddress);
  const canSubmit = connected && isValidSolanaAddress(walletAddress) && messageHash.length > 0 && !isLoading;

  return (
    <div className="min-h-screen bg-[#FEFFAF] flex items-center justify-center p-4">
      <div className="w-full max-w-md space-y-8">
        <div className="text-center">
          <h1 className="text-3xl font-bold text-gray-900">Wormhole Bridge</h1>
          <p className="mt-2 text-gray-600">Send messages via Solana testnet</p>
        </div>

        <form onSubmit={handleSubmit} className="mt-8 space-y-6 bg-white/80 backdrop-blur-sm p-6 rounded-xl shadow-xl">
          <WalletConnectButton />
          
          <WalletInput
            value={walletAddress}
            onChange={setWalletAddress}
            isValid={isValidWallet}
          />
          
          <HashInput
            value={messageHash}
            onChange={setMessageHash}
          />

          {error && (
            <div className="text-red-500 text-sm mt-2">
              {error}
            </div>
          )}

          <button
            type="submit"
            disabled={!canSubmit}
            className={`w-full flex items-center justify-center space-x-2 px-4 py-2 rounded-lg text-white
              ${canSubmit
                ? 'bg-black hover:bg-gray-800 cursor-pointer'
                : 'bg-gray-400 cursor-not-allowed'
              } transition-colors duration-200`}
          >
            <Send className="w-5 h-5" />
            <span>Send Message</span>
          </button>

          <TransactionStatus txHash={txHash} isLoading={isLoading} />
        </form>
      </div>
    </div>
  );
}

export default App;
@virjilakrum virjilakrum added the bug Something isn't working label Nov 7, 2024
@Emmaward090
Copy link

How can I solve this error. What I am trying to do is to send hash between two solana wallets with wormhole messaging.

The opensource code:

import React, { useState } from 'react';
import { Send } from 'lucide-react';
import { WalletInput } from './components/WalletInput';
import { HashInput } from './components/HashInput';
import { TransactionStatus } from './components/TransactionStatus';
import { isValidSolanaAddress } from './utils/mockTransaction';
import { wormhole } from '@wormhole-foundation/sdk';
import { encoding, signSendWait } from '@wormhole-foundation/sdk';
import type { UnsignedTransaction, SignAndSendSigner } from '@wormhole-foundation/sdk';
import solana from '@wormhole-foundation/sdk/solana';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
import { Transaction, Connection, clusterApiUrl } from '@solana/web3.js';
import { UniversalAddress } from "@wormhole-foundation/sdk";
import { useWallet } from '@solana/wallet-adapter-react';
import { WalletConnectButton } from './components/WalletConnectButton';

function App() {
  const { connected, publicKey } = useWallet();
  const [walletAddress, setWalletAddress] = useState('');
  const [messageHash, setMessageHash] = useState('');
  const [txHash, setTxHash] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const sendMessageViaWormhole = async (
    message: string
  ): Promise<string> => {
    try {
      if (!connected || !publicKey) {
        throw new Error('Wallet not connected');
      }

      // Initialize connection
      const connection = new Connection(
        import.meta.env.VITE_SOLANA_RPC_URL || clusterApiUrl('devnet'),
        'confirmed'
      );

      const wh = await wormhole('Testnet', [solana], {
        chains: {
          Solana: {
            rpc: import.meta.env.VITE_SOLANA_RPC_URL || 'https://api.devnet.solana.com',
          },
        },
      });

      const chain = wh.getChain('Solana');
      const walletAdapter = new PhantomWalletAdapter();
      await walletAdapter.connect();

      const coreBridge = await chain.getWormholeCore();
      const payload = encoding.bytes.encode(message);
      
      const customSigner: SignAndSendSigner<'Testnet', 'Solana'> = {
        chain: () => 'Solana',
        address: () => publicKey.toBase58(),
        signAndSend: async (
          unsignedTxs: UnsignedTransaction<'Testnet', 'Solana'>[]
        ): Promise<string[]> => {
          const transactions = await Promise.all(
            unsignedTxs.map(async (unsignedTx: any) => {
              const transaction = new Transaction();
              
              const latestBlockhash = await connection.getLatestBlockhash();
              transaction.recentBlockhash = latestBlockhash.blockhash;
              transaction.feePayer = publicKey;

              if (unsignedTx.instructions && unsignedTx.instructions.length > 0) {
                transaction.add(...unsignedTx.instructions);
              } else {
                throw new Error('No instructions found in transaction');
              }

              return transaction;
            })
          );

          const signedTxs = await walletAdapter.signAllTransactions(transactions);

          const signatures = await Promise.all(
            signedTxs.map(async (tx) => {
              const rawTransaction = tx.serialize();
              return await connection.sendRawTransaction(rawTransaction);
            })
          );

          return signatures;
        }
      };

      const destinationAddress = new UniversalAddress(
        walletAddress,
        "base58"
      );

      const publishTxs = coreBridge.publishMessage(
        destinationAddress,
        payload,
        0,
        0
      );

      const txids = await signSendWait(chain, publishTxs, customSigner);
      const finalTxId = txids[txids.length - 1]?.txid;
      
      if (!finalTxId) {
        throw new Error('Failed to get transaction ID');
      }

      const [whm] = await chain.parseTransaction(finalTxId);
      
      if (!whm) {
        throw new Error('Failed to parse Wormhole message');
      }

      await wh.getVaa(whm, 'TokenBridge:Transfer', 60_000);
      return finalTxId;

    } catch (err) {
      console.error('Wormhole transfer error:', err);
      throw new Error(err instanceof Error ? err.message : 'Failed to send message');
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (!connected) {
      setError("Please connect your wallet first");
      return;
    }

    setIsLoading(true);
    setError(null);
    
    try {
      const hash = await sendMessageViaWormhole(messageHash);
      setTxHash(hash);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Transaction failed';
      setError(errorMessage);
      console.error('Transaction failed:', err);
    } finally {
      setIsLoading(false);
    }
  };

  const isValidWallet = !walletAddress || isValidSolanaAddress(walletAddress);
  const canSubmit = connected && isValidSolanaAddress(walletAddress) && messageHash.length > 0 && !isLoading;

  return (
    <div className="min-h-screen bg-[#FEFFAF] flex items-center justify-center p-4">
      <div className="w-full max-w-md space-y-8">
        <div className="text-center">
          <h1 className="text-3xl font-bold text-gray-900">Wormhole Bridge</h1>
          <p className="mt-2 text-gray-600">Send messages via Solana testnet</p>
        </div>

        <form onSubmit={handleSubmit} className="mt-8 space-y-6 bg-white/80 backdrop-blur-sm p-6 rounded-xl shadow-xl">
          <WalletConnectButton />
          
          <WalletInput
            value={walletAddress}
            onChange={setWalletAddress}
            isValid={isValidWallet}
          />
          
          <HashInput
            value={messageHash}
            onChange={setMessageHash}
          />

          {error && (
            <div className="text-red-500 text-sm mt-2">
              {error}
            </div>
          )}

          <button
            type="submit"
            disabled={!canSubmit}
            className={`w-full flex items-center justify-center space-x-2 px-4 py-2 rounded-lg text-white
              ${canSubmit
                ? 'bg-black hover:bg-gray-800 cursor-pointer'
                : 'bg-gray-400 cursor-not-allowed'
              } transition-colors duration-200`}
          >
            <Send className="w-5 h-5" />
            <span>Send Message</span>
          </button>

          <TransactionStatus txHash={txHash} isLoading={isLoading} />
        </form>
      </div>
    </div>
  );
}

export default App;

To solve the "AccountNotFound" error when sending a hash between two Solana wallets using Wormhole messaging, there are a few things you can check or modify in your code:

  1. Ensure Wallet Connection: Make sure that the wallet is properly connected before attempting to send the message. You already have a check for this, but ensure that the useWallet hook is correctly set up and that the wallet is indeed connected.

  2. Valid Solana Address: Before sending the message, validate the walletAddress input using the isValidSolanaAddress function to ensure that it is a correctly formatted Solana address.

  3. Transaction Initialization: Ensure that you are correctly initializing the transaction and that the message being sent is properly formatted. You may want to log the message to confirm its structure before sending.

  4. Wormhole SDK Usage: Make sure you are using the Wormhole SDK correctly. The wormhole object should be properly configured, and you should verify that the sendMessageViaWormhole function is correctly implemented to handle the sending of messages.

  5. Error Handling: Add more detailed error handling in your sendMessageViaWormhole function. This can help you identify if the error is stemming from the transaction itself or from the wallet connection.

Here’s a modified version of your sendMessageViaWormhole function with additional checks:

const sendMessageViaWormhole = async (
  message: string
): Promise<string> => {
  try {
    if (!connected || !publicKey) {
      throw new Error('Wallet not connected');
    }

    if (!isValidSolanaAddress(walletAddress)) {
      throw new Error('Invalid destination wallet address');
    }

    // Initialize connection
    const connection = new Connection(
      import.meta.env.VITE_SOLANA_RPC_URL || clusterApiUrl('mainnet-beta')
    );

    // Prepare and send the transaction
    const transaction = new Transaction().add(
      // Add your Wormhole message sending logic here
    );

    const signature = await signSendWait(connection, transaction, publicKey);
    setTxHash(signature);
    return signature;
  } catch (err) {
    setError(err.message);
    throw err; // Rethrow the error for further handling if needed
  }
};

Make sure to replace the comment with the actual Wormhole message sending logic. If you continue to encounter issues, consider checking the Wormhole documentation for any updates or changes in the SDK usage. If you have more specific error messages or logs, feel free to share them for further assistance!
@virjilakrum

@virjilakrum
Copy link
Author

virjilakrum commented Nov 9, 2024

This is the final version of the messaging part of my codes with wormhole ts sdk in devnet Solana. Do you think there is something I am missing? What else can I do to transfer hash between two wallets in Solana with Wormhole, is there an example of this ? Although I don't get an error in the code, I can't transfer the information about the accounts for some reason, do you have any idea about this?

help wanted: @Emmaward090 @SEJeff @ryoqun @Reisen

zkl-sol-sol-mes-test
import React, { useState } from 'react';
import { Send } from 'lucide-react';
import { WalletInput } from './components/WalletInput';
import { HashInput } from './components/HashInput';
import { TransactionStatus } from './components/TransactionStatus';
import { isValidSolanaAddress } from './utils/mockTransaction';
import { wormhole } from '@wormhole-foundation/sdk';
import { encoding, signSendWait } from '@wormhole-foundation/sdk';
import type { UnsignedTransaction, SignAndSendSigner } from '@wormhole-foundation/sdk';
import solana from '@wormhole-foundation/sdk/solana';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
import { 
  Transaction, 
  Connection, 
  Commitment,
  TransactionSignature,
  // PublicKey
} from '@solana/web3.js';
import { UniversalAddress } from "@wormhole-foundation/sdk";
import { useWallet } from '@solana/wallet-adapter-react';
import { WalletConnectButton } from './components/WalletConnectButton';

// RPC URL & WH Contract Details
const SOLANA_RPC_URL = 'https://api.devnet.solana.com';
const COMMITMENT: Commitment = 'confirmed';
// const WORMHOLE_BRIDGE_ADDRESS = 'worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth';

// Devnet Program IDs
const DEVNET_CONTRACTS = {
  core: 'Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o', // Devnet Core Bridge
  token_bridge: 'B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE', // Devnet Token Bridge
  nft_bridge: 'NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA' // Devnet NFT Bridge
} as const; 


function App() {
  const { connected, publicKey } = useWallet();
  const [walletAddress, setWalletAddress] = useState('');
  const [messageHash, setMessageHash] = useState('');
  const [txHash, setTxHash] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Transaction retry ayarları
  const TX_RETRY_COUNT = 5;
  const TX_RETRY_DELAY = 2000;

  // Confirmation helper
  const confirmTransaction = async (
    connection: Connection,
    signature: TransactionSignature,
  ): Promise<void> => {
    const latestBlockhash = await connection.getLatestBlockhash();
    await connection.confirmTransaction(
      {
        signature,
        blockhash: latestBlockhash.blockhash,
        lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
      },
      COMMITMENT
    );
  };

  const sendMessageViaWormhole = async (message: string): Promise<string> => {
    try {
      if (!connected || !publicKey) {
        throw new Error('Wallet not connected');
      }

      if (!isValidSolanaAddress(walletAddress)) {
        throw new Error('Invalid destination address');
      }

      console.log('Initializing connection and wormhole...');


      const connection = new Connection(SOLANA_RPC_URL, {
        commitment: COMMITMENT,
        confirmTransactionInitialTimeout: 60000,
        wsEndpoint: 'wss://api.devnet.solana.com/',
      });
      


// Wormhole Initialization
const wh = await wormhole('Devnet', [solana], {
  chains: {
    Solana: {
      rpc: SOLANA_RPC_URL,
      contracts: DEVNET_CONTRACTS,
    },
  },
});


const chain = wh.getChain('Solana');


// Core bridge initialization with retry mechanism
let coreBridge;
let retryCount = 3;
while (retryCount > 0) {
  try {
    coreBridge = await chain.getWormholeCore();
    if (!coreBridge) {
      throw new Error('Failed to initialize Wormhole core bridge');
    }
    break;
  } catch (error) {
    console.error(`Core bridge initialization attempt ${4 - retryCount} failed:`, error);
    retryCount--;
    if (retryCount === 0) {
      throw new Error('Failed to connect to Wormhole bridge after multiple attempts');
    }
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}



      console.log('Preparing message payload...');
      const payload = encoding.bytes.encode(message);

      const customSigner: SignAndSendSigner<'Devnet', 'Solana'> = {
        chain: () => 'Solana',
        address: () => publicKey.toBase58(),
        signAndSend: async (
          unsignedTxs: UnsignedTransaction<'Devnet', 'Solana'>[]
        ): Promise<string[]> => {
          console.log('Preparing transactions...');
          
          const transactions = await Promise.all(
            unsignedTxs.map(async (unsignedTx: any) => {
              console.log('Processing transaction:', unsignedTx);
              
              const transaction = new Transaction();
              
              let retries = TX_RETRY_COUNT;
              let blockhash;
              let lastValidBlockHeight;

              while (retries > 0) {
                try {
                  const latestBlockhash = await connection.getLatestBlockhash(COMMITMENT);
                  blockhash = latestBlockhash.blockhash;
                  lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
                  break;
                } catch (error) {
                  console.error('Failed to get blockhash, retrying...', error);
                  retries--;
                  if (retries === 0) throw error;
                  await new Promise(resolve => setTimeout(resolve, TX_RETRY_DELAY));
                }
              }

              transaction.recentBlockhash = blockhash!;
              transaction.lastValidBlockHeight = lastValidBlockHeight!;
              transaction.feePayer = publicKey;

              if (unsignedTx.instructions?.length) {
                console.log(`Adding ${unsignedTx.instructions.length} instructions`);
                transaction.add(...unsignedTx.instructions);
              } else {
                throw new Error('No instructions found in transaction');
              }

              return transaction;
            })
          );

          console.log('Signing transactions...');
          const walletAdapter = new PhantomWalletAdapter();
          await walletAdapter.connect();
          
          const signedTxs = await walletAdapter.signAllTransactions(transactions);

          console.log('Sending transactions...');
          const signatures = await Promise.all(
            signedTxs.map(async (tx) => {
              const rawTransaction = tx.serialize();
              let signature: string | undefined;
              
              let retries = TX_RETRY_COUNT;
              while (retries > 0) {
                try {
                  signature = await connection.sendRawTransaction(rawTransaction, {
                    skipPreflight: true,
                    maxRetries: TX_RETRY_COUNT,
                    preflightCommitment: COMMITMENT,
                  });

                  console.log('Waiting for confirmation...');
                  await confirmTransaction(connection, signature);
                  
                  console.log('Transaction confirmed:', signature);
                  return signature;
                } catch (error) {
                  console.error(`Transaction attempt failed, ${retries} retries left:`, error);
                  retries--;
                  if (retries === 0) throw error;
                  await new Promise(resolve => setTimeout(resolve, TX_RETRY_DELAY));
                }
              }
              
              throw new Error('Transaction failed after all retries');
            })
          );

          return signatures.filter((sig): sig is string => sig !== undefined);
        }
      };

      console.log('Creating destination address...');
      const destinationAddress = new UniversalAddress(
        walletAddress,
        "base58"
      );

      console.log('Create publish message...');
      if (!coreBridge) {
        throw new Error('coreBridge not defined');
      }
      let publishTxs;
      try {
        publishTxs = await coreBridge.publishMessage(
          destinationAddress,
          payload,
          0,  // nonce
          1   
        );
      } catch (error) {
        console.error('Publish message Error:', error);
        throw error;
      }

      console.log('Sending and waiting for transaction...');
      const txids = await signSendWait(chain, publishTxs, customSigner);
      console.log('Transaction IDs:', txids);

      const finalTxId = txids[txids.length - 1]?.txid;
      if (!finalTxId) {
        throw new Error('Failed to get transaction ID');
      }

      console.log('Final transaction ID:', finalTxId);

      console.log('Parsing transaction...');
      const [whm] = await chain.parseTransaction(finalTxId);
      
      if (!whm) {
        throw new Error('Failed to parse Wormhole message');
      }

      console.log('Waiting for VAA...');
      await wh.getVaa(whm, 'Uint8Array', 60_000);
      
      console.log('Transaction completed successfully');
      return finalTxId;

    } catch (err) {
      console.error('Detailed error:', err);
      throw err;
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      if (!connected) {
        throw new Error("Please connect your wallet first");
      }

      if (!walletAddress || !messageHash) {
        throw new Error("Please fill in all fields");
      }

      if (!isValidSolanaAddress(walletAddress)) {
        throw new Error("Invalid destination address");
      }

      setIsLoading(true);
      setError(null);
      
      const hash = await sendMessageViaWormhole(messageHash);
      setTxHash(hash);
      console.log('Message sent successfully! Transaction hash:', hash);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Transaction failed';
      setError(errorMessage);
      console.error('Transaction failed:', err);
    } finally {
      setIsLoading(false);
    }
  };

  const isValidWallet = !walletAddress || isValidSolanaAddress(walletAddress);
  const canSubmit = connected && 
                   isValidSolanaAddress(walletAddress) && 
                   messageHash.length > 0 && 
                   !isLoading;

  return (
    <div className="min-h-screen bg-[#FEFFAF] flex items-center justify-center p-4">
      <div className="w-full max-w-md space-y-8">
        <div className="text-center">
          <h1 className="text-3xl font-bold text-gray-900">Wormhole Bridge</h1>
          <p className="mt-2 text-gray-600">Send messages via Solana devnet</p>
        </div>

        <form onSubmit={handleSubmit} className="mt-8 space-y-6 bg-white/80 backdrop-blur-sm p-6 rounded-xl shadow-xl">
          <WalletConnectButton />
          
          <WalletInput
            value={walletAddress}
            onChange={setWalletAddress}
            isValid={isValidWallet}
          />
          
          <HashInput
            value={messageHash}
            onChange={setMessageHash}
          />

          {error && (
            <div className="text-red-500 text-sm mt-2">
              {error}
            </div>
          )}

          <button
            type="submit"
            disabled={!canSubmit}
            className={`w-full flex items-center justify-center space-x-2 px-4 py-2 rounded-lg text-white
              ${canSubmit
                ? 'bg-black hover:bg-gray-800 cursor-pointer'
                : 'bg-gray-400 cursor-not-allowed'
              } transition-colors duration-200`}
          >
            <Send className="w-5 h-5" />
            <span>Send Message</span>
          </button>

          <TransactionStatus txHash={txHash} isLoading={isLoading} />
        </form>
      </div>
    </div>
  );
}

export default App;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants