superhero-com / superhero-wallet

Superhero Wallet – More than just a way to receive, store & send tokens on the æternity blockchain
https://wallet.superhero.com
ISC License
39 stars 38 forks source link

I can't connect wallet on chrome/safari mobile phones(IOS/Android) #3090

Closed Temitope3665 closed 3 weeks ago

Temitope3665 commented 3 months ago

Describe the bug I can connect the wallet to my project on my web but after mobile responsiveness was updated on the project, I could not connect the wallet on mobile phones.

To Reproduce Steps to reproduce the behavior:

  1. Connect Wallet on the web version, it works well
  2. Connect Wallet on the smart phone is not connecting
  3. Perform an action that requires the user to connect to that app.
  4. See error

Expected behavior I should be able to connect to my wallet on my smartphones exactly how it worked well on the web.

Screenshots Attached screenshot

Desktop (please complete the following information):

Smartphone (please complete the following information):

Additional context Add any other context about the problem here.

Liubov-crypto commented 3 months ago

Hello! I checked your issue and did not find any connection problems.

  1. I was able to connect to DEX using native app on iphone on in-app browser
  2. I was able to connect to DEX opened in Safari using native app on iphone
  3. I was able to connect to DEX using web wallet on iphone, both were opened in Safari

Device: iphone 13 OS: ios 17.5.1 browser: Safari

CedrikNikita commented 3 months ago

Hello, @Temitope3665. Can you give us some more details of your problem?

  1. Which version of aepp-sdk your project is using?
  2. Are you trying to connect to a wallet via deeplink?
  3. Are you having trouble connecting to the native app or the web version on mobile devices?

    If it is possible maybe share the link to your project.

Temitope3665 commented 3 months ago
  1. "@aeternity/aepp-sdk": "^13.2.2"

  2. I tried using deeplink but whenever I want to make a transaction, I keep getting I'm not connected to the wallet.

  3. Could you share the code that works generally instead here? or Below is the function that I'm using to connect wallet, if you can help update it with the correct, I would be happy.

export const connectWallet = async ({ setConnectingToWallet, setEnableIFrameWallet, setUser, address, setConnectionError, setOpenModal, isHome, walletObj = { info: { name: '', type: '' } }, aeSdk, }: ConnectWalletParams) => { setConnectingToWallet(true); let addressDeepLink: any;

if ((IS_MOBILE || isSafariBrowser()) && !IN_FRAME) { if (address) { setConnectingToWallet(false); return; } if (isHome) { const domainName = typeof window !== 'undefined' && window.location.origin; const dashboardURL = ${domainName}/${DASHBOARD_URL}/; addressDeepLink = createDeepLinkUrl({ type: 'address', 'x-success': ${ dashboardURL.split('?')[0] }?address={address}&networkId={networkId}, 'x-cancel': dashboardURL.split('?')[0], }); } else { addressDeepLink = createDeepLinkUrl({ type: 'address', 'x-success': ${ window.location.href.split('?')[0] }?address={address}&networkId={networkId}, 'x-cancel': window.location.href.split('?')[0], }); } if (typeof window !== 'undefined') { window.location.replace(addressDeepLink); } } else { try { await resolveWithTimeout(30000, async () => { const webWalletTimeout = IS_MOBILE ? 0 : setTimeout(() => setEnableIFrameWallet(true), 15000);

    let resolve: any = null;
    let rejected = (e: any) => {
      throw e;
    };
    let stopScan: any = null;

    const connectWallet = async (wallet: any) => {
      try {
        const { networkId } = await aeSdk.connectToWallet(
          wallet.getConnection()
        );
        const ret = await aeSdk.subscribeAddress('subscribe', 'connected');
        const {
          address: { current },
        } = ret;
        const currentAccountAddress = Object.keys(current)[0];
        if (!currentAccountAddress) return;
        stopScan?.();
        const user = { address: currentAccountAddress, isConnected: true };
        setUser(user);
        aeSdks = aeSdk;
        // localStorage.setItem('user', JSON.stringify(user));
        resolve?.(currentAccountAddress);
        setOpenModal(false);
      } catch (e: any) {
        if (!(e instanceof RpcRejectedByUserError)) {
          alert('error occured');
        }
        if (e.message === 'Operation rejected by user') {
          toast.error(e.message);
          resolve = null;
          rejected = (e: any) => {
            throw e;
          };
          stopScan = null;
        }
        console.log(e.message);
        rejected(e);
      }
    };
    if (walletObj.getConnection) {
      await connectWallet(walletObj);
    } else {
      const handleWallet = async ({ wallets }: any) => {
        const detectedWalletObject = Object.values(wallets).find(
          (wallet: any) => wallet.info.name === walletObj.info.name
        );
        if (!detectedWalletObject) return;
        clearInterval(webWalletTimeout);
        await connectWallet(detectedWalletObject);
      };
      const scannerConnection = new BrowserWindowMessageConnection();
      stopScan = walletDetector(scannerConnection, handleWallet);

      await new Promise((_resolve: any, _rejected: any) => {
        resolve = _resolve;
        rejected = _rejected;
      });
    }
  });
} catch (error) {
  if (walletObj.info.name === 'Superhero') {
    setConnectionError({
      type: 'denied',
      message:
        'Login with your wallet has failed. Please make sure that you are logged into your wallet.',
    });
  } else {
    setConnectionError({
      type: 'timeout',
      message: `Connection to ${walletObj.info.name} has been timeout, please try again later.`,
    });
  }
}

} };

Temitope3665 commented 3 months ago

`export const connectWallet = async ({ setConnectingToWallet, setEnableIFrameWallet, setUser, address, setConnectionError, setOpenModal, isHome, walletObj = { info: { name: '', type: '' } }, aeSdk, }: ConnectWalletParams) => { setConnectingToWallet(true); let addressDeepLink: any;

if ((IS_MOBILE || isSafariBrowser()) && !IN_FRAME) { if (address) { setConnectingToWallet(false); return; } if (isHome) { const domainName = typeof window !== 'undefined' && window.location.origin; const dashboardURL = ${domainName}/${DASHBOARD_URL}/; addressDeepLink = createDeepLinkUrl({ type: 'address', 'x-success': ${ dashboardURL.split('?')[0] }?address={address}&networkId={networkId}, 'x-cancel': dashboardURL.split('?')[0], }); } else { addressDeepLink = createDeepLinkUrl({ type: 'address', 'x-success': ${ window.location.href.split('?')[0] }?address={address}&networkId={networkId}, 'x-cancel': window.location.href.split('?')[0], }); } if (typeof window !== 'undefined') { window.location.replace(addressDeepLink); } } else { try { await resolveWithTimeout(30000, async () => { const webWalletTimeout = IS_MOBILE ? 0 : setTimeout(() => setEnableIFrameWallet(true), 15000);

    let resolve: any = null;
    let rejected = (e: any) => {
      throw e;
    };
    let stopScan: any = null;

    const connectWallet = async (wallet: any) => {
      try {
        const { networkId } = await aeSdk.connectToWallet(
          wallet.getConnection()
        );
        const ret = await aeSdk.subscribeAddress('subscribe', 'connected');
        const {
          address: { current },
        } = ret;
        const currentAccountAddress = Object.keys(current)[0];
        if (!currentAccountAddress) return;
        stopScan?.();
        const user = { address: currentAccountAddress, isConnected: true };
        setUser(user);
        aeSdks = aeSdk;
        // localStorage.setItem('user', JSON.stringify(user));
        resolve?.(currentAccountAddress);
        setOpenModal(false);
      } catch (e: any) {
        if (!(e instanceof RpcRejectedByUserError)) {
          alert('error occured');
        }
        if (e.message === 'Operation rejected by user') {
          toast.error(e.message);
          resolve = null;
          rejected = (e: any) => {
            throw e;
          };
          stopScan = null;
        }
        console.log(e.message);
        rejected(e);
      }
    };
    if (walletObj.getConnection) {
      await connectWallet(walletObj);
    } else {
      const handleWallet = async ({ wallets }: any) => {
        const detectedWalletObject = Object.values(wallets).find(
          (wallet: any) => wallet.info.name === walletObj.info.name
        );
        if (!detectedWalletObject) return;
        clearInterval(webWalletTimeout);
        await connectWallet(detectedWalletObject);
      };
      const scannerConnection = new BrowserWindowMessageConnection();
      stopScan = walletDetector(scannerConnection, handleWallet);

      await new Promise((_resolve: any, _rejected: any) => {
        resolve = _resolve;
        rejected = _rejected;
      });
    }
  });
} catch (error) {
  if (walletObj.info.name === 'Superhero') {
    setConnectionError({
      type: 'denied',
      message:
        'Login with your wallet has failed. Please make sure that you are logged into your wallet.',
    });
  } else {
    setConnectionError({
      type: 'timeout',
      message: `Connection to ${walletObj.info.name} has been timeout, please try again later.`,
    });
  }
}

} };`

CedrikNikita commented 3 months ago

I think the problem might be in the sdk version. Version 13.3.1 containing an invisible breaking change. Since the version of the protocol for mainnet node was raised to 7.0.0 and the previous sdk is not supporting it. https://github.com/aeternity/aepp-sdk-js/releases/tag/v13.3.1

Temitope3665 commented 3 months ago

What fix would you suggest?

CedrikNikita commented 3 months ago

The potential fixes are listed in the aepp-sdk release description. Our wallet currently using version version 13.3.1.

Temitope3665 commented 3 months ago

Alright, I will check out the release description.

CedrikNikita commented 3 months ago

Hi, @Temitope3665. Were you able to resolve your issue?

AroyewonTemitope commented 3 months ago

No. It has not been resolved yet

CedrikNikita commented 2 months ago

Hi, @Temitope3665. I have few more clarification questions:

  1. Are you redirected back to your site after deeplink is approved in superhero wallet?
    • If no, can you share the deeplink your code is generating?
    • if yes, does your code process the received address?
AroyewonTemitope commented 2 months ago

Yes, it's connecting successfully, and here is the deep link it's generating below Deeplink URL result

Attached below is the response during the connection. PHOTO-2024-07-04-07-54-57

But the issue here is that, whenever we want to make a transaction or initiate an activity with the wallet, it keeps saying, wallet not connected. Here is a sample of how the error IMG_3669

CedrikNikita commented 2 months ago

In order to sign/send transaction on mobile, you are to create a new deeplink with the transaction details as such. https://github.com/superhero-com/superhero-wallet/issues/2679#issuecomment-1921008234 Currently we don't have any docs regarding deeplink creation. It seems that we need to create such (judging from this issue).

AroyewonTemitope commented 2 months ago

Alright. I will check the thread. That should help with the issue.

AroyewonTemitope commented 2 months ago

Could you help with how best to go about it especially when interacting with contract? We're building a DAO system, by interacting with the contract, I mean creating a dao, creating a proposal, etc...

CedrikNikita commented 2 months ago

This is a small example.

const createDeepLinkUrl = ({ type, callbackUrl, ...params }) => {
  const url = new URL(`https;//wallet.superhero.com/${type}`);
  if (callbackUrl) {
    url.searchParams.set('x-success', callbackUrl);
    url.searchParams.set('x-cancel', callbackUrl);
  }
  Object.entries(params)
    .filter(([, value]) => ![undefined, null].includes(value))
    .forEach(([name, value]) => url.searchParams.set(name, value));
  return url;
};

const contract = await aeSdk.initializeContract(
  {
    aci,
    address,
  },
);
const result = await contract[method](
  ...methodArgs,
  {
    callStatic: true,
    // Instead of `onAccount` property you can use `replace-caller` flag in the deep link
    onAccount: createOnAccountObject(address),
  },
);

const encodedTx = result.rawTx;

const currentUrl = new URL(window.location.href);
// reset url
currentUrl.searchParams.delete('transaction');
currentUrl.searchParams.delete('transaction-status');

// append transaction parameter for success case
const successUrl = new URL(currentUrl.href);
successUrl.searchParams.set('transaction', '{transaction}');

// append transaction parameter for failed case
const cancelUrl = new URL(currentUrl.href);
cancelUrl.searchParams.set('transaction-status', 'cancelled');

return createDeepLinkUrl({
  type: 'sign-transaction',
  transaction: encodedTx,
  networkId,
  // decode these urls because they will be encoded again
  'x-success': decodeURI(successUrl.href),
  'x-cancel': decodeURI(cancelUrl.href),
});
CedrikNikita commented 2 months ago

Here's the deep link scheme we use. https://github.com/superhero-com/superhero-wallet/blob/develop/docs/deep-link-schema.md

AroyewonTemitope commented 2 months ago

Thank you Cedrink. I was able to do it just like you suggested. Here is the code below. But I keep getting an error relating to pubkey in my console. Could you help me if I'm doing anything wrong or requires an update?

Screenshot 2024-07-16 at 07 24 05

export const createDeepLinkUrl = ({ type, callbackUrl, ...params }: DeepLinkParams): URL => { const url = new URL(${process.env.NEXT_PUBLIC_WALLET_URL}/${type}`);

if (callbackUrl) { url.searchParams.set('x-success', callbackUrl); url.searchParams.set('x-cancel', callbackUrl); }

Object.entries(params) .filter(([, value]) => value !== undefined && value !== null) // Filter out undefined and null values .forEach(([name, value]) => url.searchParams.set(name, value as string)); // Assert value as string

return url; };

const generateKey: any = () => { const secretKey = CryptoJS.lib.WordArray.random(64).toString(); return secretKey; };

const node = new Node('https://testnet.aeternity.io'); // ideally host your own node const account = new MemoryAccount(generateKey()); const newUserAccount = MemoryAccount.generate();

const aeSdk: any = new AeSdk({ nodes: [{ name: 'testnet', instance: node }], accounts: [account, newUserAccount], // onCompiler: compiler, // remove if step #2 skipped });

const createDeepLinkUrl2 = async ({ type, callbackUrl, ...params }: any) => { const url = new URL(${process.env.NEXT_PUBLIC_WALLET_URL}/${type}); if (callbackUrl) { url.searchParams.set('x-success', callbackUrl); url.searchParams.set('x-cancel', callbackUrl); }

Object.entries(params) .filter(([, value]) => value !== undefined && value !== null) // Filter out undefined and null values .forEach(([name, value]) => url.searchParams.set(name, value as string)); // Assert value as string // Object.entries(params) // .filter(([, value]) => ![undefined, null].includes(value)) // .forEach(([name, value]) => url.searchParams.set(name, value)); return url; };

const createOnAccountObject = () => { const accountObject = aeSdk.addresses()[0]; return accountObject; };

export const contractInterract = async (payload: any) => { const contract = await aeSdk.initializeContract({ aci: basicDAOAci, address: payload.daoContractAddress, });

const result = await contract['createProposal']( payload.proposalType, payload.description, payload.value, payload.target, payload.info, { callStatic: true, onAccount: createOnAccountObject(), } );

const encodedTx = result.rawTx;

const currentUrl = new URL(window.location.href); // reset url currentUrl.searchParams.delete('transaction'); currentUrl.searchParams.delete('transaction-status');

// append transaction parameter for success case const successUrl = new URL(currentUrl.href); successUrl.searchParams.set('transaction', '{transaction}');

// append transaction parameter for failed case const cancelUrl = new URL(currentUrl.href); cancelUrl.searchParams.set('transaction-status', 'cancelled');

return createDeepLinkUrl2({ type: 'sign-transaction', transaction: encodedTx, networkId: 'ae_uat', // decode these urls because they will be encoded again 'x-success': decodeURI(successUrl.href), 'x-cancel': decodeURI(cancelUrl.href), }); }; `

CedrikNikita commented 2 months ago

This is the example of createOnAccountObject function. I think this place might be the reason of the issue. And in general you can take a look at the the dex-ui implementation of the whole connection feature. https://github.com/aeternity/dex-ui/blob/main/src/lib/utils.js#L80

AroyewonTemitope commented 2 months ago

Awesome!. Thank you for this. But I noticed a few things after I confirmed, which are;

  1. I couldn't find the transaction in my history
  2. The newly created proposal was not added to the expected list of proposals the contract returns when I get all contracts.
CedrikNikita commented 2 months ago

You should also set flag broadcast to true in deep link creation if you want wallet to push it to the node.

AroyewonTemitope commented 2 months ago

Yeah. That solves the problem. Thank you. I have a question again, I noticed when redirecting to the success page, that it disconnected the wallet. Is there a way that can be prevented?

CedrikNikita commented 2 months ago

You can store user's address in localStorage.

AroyewonTemitope commented 2 months ago

Yes. That's perfect. Thank you

CedrikNikita commented 3 weeks ago

I think that's problem solved, now that we have a deeplink scheme and an actual example of creating a such in JS. Feel free to open the issue back up if there are any unresolved issues or additional questions.

Temitope3665 commented 3 weeks ago

@CedrikNikita I want to switch the network from our app, although it's switched successfully on our app but, it does not switch on super hero wallet. How can we go about it?

CedrikNikita commented 2 weeks ago

Hi. This is not possible currently. If the user is connected to other dapps in case wallet will change the network for one of them it will change the network for each. It can be frustrated to the users. The only supported wallet api methods are: https://github.com/aeternity/aepp-sdk-js/blob/c8fbffebab588389f088f9a836de457d4d8afa00/src/aepp-wallet-communication/rpc/types.ts#L34-L95 You can explain to the user how to change the network in the wallet manually. If this is not what you need, and it is an important feature for you, please post the use case. You can also propose to have a such api method in the sdk issues https://github.com/aeternity/aepp-sdk-js/issues.

AroyewonTemitope commented 2 weeks ago

Alright. Thank you. I will do that.