anza-xyz / wallet-adapter

Modular TypeScript wallet adapters and components for Solana applications.
https://anza-xyz.github.io/wallet-adapter/
Apache License 2.0
1.61k stars 962 forks source link

I can't get the wallet to open to initiate a transaction. #981

Closed keolamation closed 5 months ago

keolamation commented 5 months ago

Describe the bug after connecting, if I hit the "buy" button it just reopens to model to "connect" a wallet and the developer tools complain the the public key field isn't satisfied.

To Reproduce my app.jsx

import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { transactionPush, fetchLaunchDate } from '@middleware/middleware';
import './App.css';
import './index.css';
import Wallet from './components/Wallet';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { ConnectionProvider, WalletProvider, useConnection, useWallet } from '@solana/wallet-adapter-react';
import '@solana/wallet-adapter-react-ui/styles.css';
import AppWalletProvider from './AppWalletProvider';
import FetchConnection from './components/FetchConnection';
import useEthereumProvider from './hooks/useEthereumProvider';
import { clusterApiUrl, PublicKey, Transaction, SystemProgram } from '@solana/web3.js';
import TransactionComponent from './TransactionComponent';

import WalletConnectModal from './WalletConnectModal';

const PaymentTypes = [
  {
    ID: 1,
    PaymentName: 'Solana',
    NameBrev: 'SOL',
    USDRate: 172.11,
    IconImg: './images/Solana_Icon200px.png',
    IsSelected: false,
  },
  {
    ID: 2,
    PaymentName: 'United States Dollar Coin',
    NameBrev: 'USDC',
    USDRate: 1,
    IconImg: './images/USDC_Icon200px.png',
    IsSelected: false,
  },
  {
    ID: 3,
    PaymentName: 'United States Dollar',
    NameBrev: 'FIAT',
    USDRate: 1,
    IconImg: './images/FIAT_Icon200px.png',
    IsSelected: false,
  },
];

const InputFields = [
  {
    id: 1,
    type: 'currency',
    value: '',
    placeholder: 'Enter amount',
  },
  {
    id: 2,
    type: 'token',
    value: '',
    placeholder: 'Enter amount',
  },
];

const presaleRate = 0.34;

export default function App() {
  const [currencyVal, setCurrencyVal] = useState('');
  const [tokenAmount, setTokenAmount] = useState('');
  const [PaymentOptionsLi, renderList] = useState(PaymentTypes);
  const [isConnected, setConnected] = useState(false);
  const [walletPublicKey, setWalletPublicKey] = useState(null);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const { connection } = useConnection();
  const { publicKey, sendTransaction } = useWallet();

  useEthereumProvider();

  const [launchDate, setLaunchDate] = useState(null);
  const [timeLeft, setTimeLeft] = useState('');

  useEffect(() => {
    const fetchDate = async () => {
      const date = await fetchLaunchDate();
      setLaunchDate(new Date(date));
    };
    fetchDate();
  }, []);

  useEffect(() => {
    if (launchDate) {
      const interval = setInterval(() => {
        const now = new Date();
        const difference = launchDate - now;
        const timeLeft = calculateTimeLeft(difference);
        setTimeLeft(timeLeft);
      }, 1000);
      return () => clearInterval(interval);
    }
  }, [launchDate]);

  const calculateTimeLeft = (difference) => {
    const days = Math.floor(difference / (1000 * 60 * 60 * 24));
    const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((difference % (1000 * 60)) / 1000);
    return `${days}:${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  };

  useEffect(() => {
    if (walletPublicKey) {
      setConnected(true);
      setIsModalOpen(false);
    } else {
      setConnected(false);
    }
  }, [walletPublicKey]);

  function handleSelected(id) {
    renderList(PaymentOptionsLi => PaymentOptionsLi.map(PaymentOption => PaymentOption.ID === id ? { ...PaymentOption, IsSelected: true } : { ...PaymentOption, IsSelected: false }));
    resetInputValues();
  }

  const selectedPaymentType = PaymentOptionsLi.find(option => option.IsSelected);

  function handleCurrencyChange(event) {
    const value = parseFloat(event.target.value);
    setCurrencyVal(value);
    if (selectedPaymentType) {
      const newTokenAmount = (value * selectedPaymentType.USDRate) / presaleRate;
      setTokenAmount(newTokenAmount.toFixed(2));
    }
  }

  function handleTokenChange(event) {
    const value = parseFloat(event.target.value);
    setTokenAmount(value);
    if (selectedPaymentType) {
      const newCurrencyVal = (value * presaleRate) / selectedPaymentType.USDRate;
      setCurrencyVal(newCurrencyVal.toFixed(2));
    }
  }

  function resetInputValues() {
    setCurrencyVal('');
    setTokenAmount('');
  }

  let tokensOwned = 0;

const handleMainButtonClick = async () => {
        if (isConnected && publicKey) {
            console.log("Initiating transaction...");
            const transactionData = {
                public_key: publicKey.toString(),
                amount: parseFloat(currencyVal),
                currency: selectedPaymentType?.NameBrev || 'SOL', // Use the selected payment type's abbreviation
            };

            try {
                const response = await fetch('http://127.0.0.1:8080/process-transaction', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(transactionData),
                });

                const result = await response.json();
                console.log('Transaction Result:', result);

                if (result.status === 'success') {
                    const transaction = Transaction.from(Buffer.from(result.transaction, 'base64'));
                    const signedTransaction = await sendTransaction(transaction, connection);

                    await fetch('http://127.0.0.1:8080/submit-transaction', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ signedTransaction })
                    });

                    setTransactionStatus({ status: 'success', message: 'Transaction submitted successfully' });
                } else {
                    setTransactionStatus({ status: 'error', message: 'Transaction generation failed' });
                }
            } catch (error) {
                console.error("Transaction Error:", error);
                setTransactionStatus({ status: 'error', message: error.toString() });
            }
        } else {
            setIsModalOpen(true);
        }
    };

  return (
    <AppWalletProvider>

    <div className='BackPanel'>
    <div className='MainPanel'>
      <p style={{marginTop:'5px',marginBottom:'5px',display:'inline'}}>🚀</p>
      <p className='LaunchHeader'style={{marginTop:'5px',marginBottom:'5px',display:'inline'}}>
      Time till launch
      </p>
      <p style={{marginTop:'5px',marginBottom:'5px',display:'inline'}}>🚀</p>
      <div className='CountDown'>
      {timeLeft}
      </div>

      <ICOprogress/>

        <h3 style={{color: 'whitesmoke', fontSize: 'medium', textAlign: 'center', 
        marginTop:'6px',marginBottom:'6px',fontWeight:350}}>
        You Own: {tokensOwned}
      </h3>

      <PaymentList paymentOP = {PaymentOptionsLi} onSelected={handleSelected}/>

     <div>
      <h5>

      </h5>
      <Inputlist
        inputFields={InputFields}
        currencyVal={currencyVal}
        tokenAmount={tokenAmount}
        handleCurrencyChange={handleCurrencyChange}
        handleTokenChange={handleTokenChange}

      />
     </div>
     <div>
     <TransactionComponent amount={currencyVal} currency={selectedPaymentType?.NameBrev || 'SOL'} />
     </div>
     <div>
     <button className="MainButton" onClick={handleMainButtonClick}>
                                {isConnected ? "Buy" : 'Connect Wallet'}
    </button>
    <WalletConnectModal isOpen={isModalOpen} onRequestClose={() => setIsModalOpen(false)} />            

     </div>

     <div>
     <FetchConnection setConnected={setConnected} setWalletPublicKey={setWalletPublicKey}/>
     </div>

    </div>
    <div className="footer-panel">
          <a href="https://linktr.ee/keolamation" target="_blank" rel="noopener noreferrer">Keolamation LLC</a>
          </div>
    </div>

   </AppWalletProvider>

  );
}

function ICOprogress() {
  const TokensSold = 0;
  const TokenPool = 30000000;
  const ProgressRate = (TokensSold / TokenPool) * 100;

  return (
    <div>
      <p style={{ marginBottom: '3px', color: '#cbcbcb', fontWeight: 50, fontSize: 12, textAlign: 'center' }}>
        Tokens sold: {TokensSold} / {TokenPool}
      </p>
      <section className='TokenMeter'>
        <section className='TokenMeter-fill' style={{ width: `${ProgressRate}%` }}></section>
      </section>
    </div>
  );
}

function CountDownTimer() {
  return (
    <div className='CountDown'>
      90:00:00:00
    </div>
  );
}

function PaymentList({paymentOP,onSelected}){

  //let PaymentsSel = PaymentOptionsLi;

    return(
    <section className={'PaymentTyeBtn'} style={{marginBottom:'-10px'}} >
      {paymentOP.map((payOP) =>(
      <PaymentType payOP={payOP}
        onSelected={onSelected}
        key = {payOP.ID}/>))}
    </section>
  )
  }

  function PaymentType({payOP,onSelected}){

    //let PaymentOp = {PaymentOptionsLi};

    console.log('contents of PaymentOP are,',payOP);
    return(
      <li style ={{display:'inline'}}>
        <button className={`PaymentTyeBtn ${payOP.IsSelected ? "Enabled" : "Disabled"}`} onClick={()=> onSelected(payOP.ID)}>
        <img className = 'icon' src={`${payOP.IconImg}`}style={{position:'relative',top:'5%',marginLeft:'-4px'}}>

        </img>
        <p style={{display:'inline',position:'relative',top:'-5%',paddingRight:'5px'}}>
        {`${payOP.NameBrev}`}
        </p>
        </button>
      </li>
    )
  }

function Inputlist({inputFields, currencyVal, tokenAmount,handleCurrencyChange, handleTokenChange}){
  return (
    <div style={{ marginTop: '5px',display:'flex', marginLeft:'11px'}}>
      {inputFields.map(field => (
        <InputField
          key={field.id}
          type={field.type}
          value={field.type === 'currency' ? currencyVal : tokenAmount}
          placeholder={field.placeholder}
          handleChange={field.type === 'currency' ? handleCurrencyChange : handleTokenChange}
        />
      ))}
    </div>
  );
}

function InputField({type, value, placeholder,handleChange}){

  return (
    <div style={{display:'inline'}}>
      <p style={{marginBottom:'2px',color:'white',marginTop:'2px',marginLeft:'12px',fontSize:'11px'}}>
        {type === 'currency'? `YOU PAY: ${value}` : `YOU GET: ${value}`}
      </p>
      <input
        className='ICOinputField'
        style={{display:'inline'}}
        type='number'
        value={value}
        onChange={handleChange}
        placeholder={placeholder}
      />
    </div>
  );
}

/*  OLD MULTIBUTTON SCR IN DIV AREA

                             {!isConnected && (
                                <WalletMultiButton style={{ display: 'none' }} />
                            )}    

*/

walletconnectmodal

// src/WalletConnectModal.jsx
import React from 'react';
import Modal from 'react-modal';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';

Modal.setAppElement('#root');

const WalletConnectModal = ({ isOpen, onRequestClose }) => {
  return (
    <Modal
      isOpen={isOpen}
      onRequestClose={onRequestClose}
      contentLabel="Connect Wallet"
      className="modal"
      overlayClassName="overlay"
    >
        <button onClick={onRequestClose}style={{textAlign:'right'}}>❌</button>
      <h2 style={{textAlign:'center'}}>Connect Your Wallet</h2>
      <WalletMultiButton style = {{marginLeft:'55px'}} />

    </Modal>
  );
};

export default WalletConnectModal;

my appwalletprovider:

import React from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { clusterApiUrl } from '@solana/web3.js';
import {
    PhantomWalletAdapter,
    SolflareWalletAdapter,
    TorusWalletAdapter,
    LedgerWalletAdapter,
} from '@solana/wallet-adapter-wallets';

const wallets = [
    new PhantomWalletAdapter(),
    new SolflareWalletAdapter(),
    new TorusWalletAdapter(),
    new LedgerWalletAdapter(),
];

const AppWalletProvider = ({ children }) => {
    const endpoint = clusterApiUrl('mainnet-beta');

    return (
        <ConnectionProvider endpoint={endpoint}>
            <WalletProvider wallets={wallets} autoConnect>
                <WalletModalProvider>
                    {children}
                </WalletModalProvider>
            </WalletProvider>
        </ConnectionProvider>
    );
};

export default AppWalletProvider;

/*
import React from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { clusterApiUrl } from '@solana/web3.js';
import {
    PhantomWalletAdapter,
    SolflareWalletAdapter,
    TorusWalletAdapter,
    LedgerWalletAdapter
} from '@solana/wallet-adapter-wallets';

const wallets = [
    new PhantomWalletAdapter(),
    new SolflareWalletAdapter(),
    new TorusWalletAdapter(),
    new LedgerWalletAdapter()
];

const AppWalletProvider = ({ children }) => {
    return (
        <ConnectionProvider endpoint={clusterApiUrl('mainnet-beta')}>
            <WalletProvider wallets={wallets} autoConnect>
                <WalletModalProvider>
                    {children}
                </WalletModalProvider>
            </WalletProvider>
        </ConnectionProvider>
    );
};

export default AppWalletProvider;
*/

Expected behavior

The wallet launches in browser to request that the user sends X amount of Solana/USDC to receiver address.

Screenshots

https://github.com/anza-xyz/wallet-adapter/assets/65205225/840c8dcb-d46e-4837-9bcb-3904edd341f5

Desktop (please complete the following information):

Additional context

I coded most of this but, I started asking chatgpt 4o and it seems my code got even more wrong because this documentation is too messy for chat gpt to train off of. So, I'd really like a direct answer/assistance.

keolamation commented 5 months ago

btw, even if i connect to a diff solana wallet I still get the same error.

image

Eiresown commented 5 months ago

Looks like this is not being satisfied

const handleMainButtonClick = async () => { if (isConnected && publicKey) {

    leading to this

     } else {
        setIsModalOpen(true);
    }

   which then in turns opens your Modal here

const WalletConnectModal = ({ isOpen, onRequestClose }) => { return ( <Modal isOpen={isOpen} onRequestClose={onRequestClose} contentLabel="Connect Wallet" className="modal" overlayClassName="overlay"

<button onClick={onRequestClose}style={{textAlign:'right'}}>❌ <h2 style={{textAlign:'center'}}>Connect Your Wallet <WalletMultiButton style = {{marginLeft:'55px'}} />

</Modal>

); };

I didn't have a look through the logic of the transactions but at the moment when you press the transaction button you're just opening the modal because isConnected and publicKey are not satisfied so we never reach the transaction code. Log both just inside the handleMainButtonClick before the if statement and see what they are logging should be a simple fix to move forward.

keolamation commented 5 months ago

I mentioned that in the question, when i was logging it, isconnected isnt being satisfied but i don’t know why considering the footer uses isconnected and uses that to gen the pubkey the client sees at the widget base.

On Sun, Jun 9, 2024 at 7:07 AM Eiresown @.***> wrote:

Looks like this is not being satisfied

const handleMainButtonClick = async () => { if (isConnected && publicKey) {

leading to this

 } else {
    setIsModalOpen(true);
}

which then in turns opens your Modal here

const WalletConnectModal = ({ isOpen, onRequestClose }) => { return (

<button onClick={onRequestClose}style={{textAlign:'right'}}>❌ <h2 style={{textAlign:'center'}}>Connect Your Wallet <WalletMultiButton style = {{marginLeft:'55px'}} />

); };

I didn't have a look through the logic of the transactions but at the moment when you press the transaction button you're just opening the modal because isConnected and publicKey are not satisfied so we never reach the transaction code. Log both just inside the handleMainButtonClick before the if statement and see what they are logging should be a simple fix to move forward.

— Reply to this email directly, view it on GitHub https://github.com/anza-xyz/wallet-adapter/issues/981#issuecomment-2156623126, or unsubscribe https://github.com/notifications/unsubscribe-auth/APRPH2IPX2EJZODIARMIU7TZGROSTAVCNFSM6AAAAABIXLWXUGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNJWGYZDGMJSGY . You are receiving this because you authored the thread.Message ID: @.***>

github-actions[bot] commented 5 months ago

Hi @keolamation,

Thanks for your question!

We want to make sure to keep signal strong in the GitHub issue tracker – to make sure that it remains the best place to track issues that affect the development of Wallet Adapter itself.

Questions like yours deserve a purpose-built Q & A forum. Unless there exists evidence that this is a bug with Wallet Adapter itself, please post your question to the Solana Stack Exchange using this link:

https://solana.stackexchange.com/questions/ask


This automated message is a result of having added the ‘question’ tag.

keolamation commented 5 months ago

I'll be honest, this is why and I sware to god on this: If I ever become anyone influential I will do everything I can to make the general public aware of how AWEFUL Solana and Solana development is. This doesn't deserve the market cap it has. I haven't had any of my questions answered on Solana stack exchange and the actual devs just ban people and "move the issue" to be someone else's problem. The WORST blockchain, I've ever had the misfortune trying to develop on.