remix-run / remix

Build Better Websites. Create modern, resilient user experiences with web fundamentals.
https://remix.run
MIT License
29.35k stars 2.47k forks source link

[Feature]: Web 3 #659

Closed 7flash closed 2 years ago

7flash commented 2 years ago

What is the new or updated feature that you are suggesting?

Allow custom handlers of form submission. Specifically, I would like to invoke metamask window when form is submitted, instead of sending POST action.

Why should this feature be included?

I am looking to build dapp, i.e. decentralized application which performs mutations by sending transactions directly from browser to blockchain node, avoiding backend POST actions.

sergiodxa commented 2 years ago

You can add an onSubmit to the form (both <form> and <Form> but you probably don't need to use the component for this), run event.preventDefault() and send the transaction directly to the blockchain node as you would have done in a SPA.

Basically, if you don't want to POST against a Remix action send the request manually, nothing prevents you to do this (with proper status handling)

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault()
  let body = new FormData(event.currentTarget)
  await fetch(url, { body, method: "POST" })
}

return (
  <form onSubmit={handleSubmit}>
    <input type="text" name="something" />
  </form>
);
7flash commented 2 years ago

Alternatively, I am looking into this approach, does that make sense?

import React from "react";
import { useActionData } from "remix";
import { useWeb3ExecuteFunction } from "moralis";

export function action({ request }) {
  let body = await request.formData();
  let name = body.get("visitorsName");
  return { name };
}

export default function Invoices() {
  let data = useActionData();

  useEffect(() => {
    useWeb3ExecuteFunction({
      contractAddress: "",
      functionName: "",
      params: data
    }).fetch()
  }, [data]);

  return (
    <Form method="post">
      <p>
        <label>
          What is your name?
          <input type="text" name="visitorsName" />
        </label>
      </p>
      <p>{data ? data.message : "Waiting..."}</p>;
    </Form>
  );
}
sergiodxa commented 2 years ago

That's going to send the Form to the server, to get the same data you have in the form returned untouched, so you can run an effect.

It sounds like a waste of request, attach a onSubmit to the form and send the data to the blockchain node directly.


Also, that useEffect is not going to work, you can't call a hook inside another hook, you need to do

let { fetch } = useWeb3ExecuteFunction({
  contractAddress: "",
  functionName: "",
  params: data
})

And then call fetch inside inside the onSubmit handler (or your effect).

7flash commented 2 years ago

How can I make MetamaskForm component which would keep all advantages of Remix Form but instead of POST action, it would invoke transaction with exact fields provided in form? That would be so cool then, we wouldn't need to implement effects for each transaction, but simply implement form elements each time user needs to submit any transaction

sergiodxa commented 2 years ago

You could extract the form with the onSubmit to a MetamaskForm if you want, but you are not going to get to use useTransition like with Remix Form without using an action. Is there no way you can submit the data to the blockchain node server-side? That way you keep using Form and actions and from the action you do the fetch to the node.

7flash commented 2 years ago

Because transaction should be signed client-side, but then yes it can be serialized and passed to backend to be broadcasted to network. But then it should also be observed for confirmation or failure within few minutes.

It means when form submitted, then first effect should be called to sign transaction, and after form with modified fields should be submitted, and then transaction id returned from backend, and listened to on client. I am thinking how can common component be implemented to incapsulate this logic and let us enjoy simple forms once again.

bjufre commented 2 years ago

@7flash any news on this? I'm wondering if we could easily use Remix for Web3 while using its capabilities and not relying in client-side code.

sergiodxa commented 2 years ago

@bjufre what would you expect Remix to add in order to be web3 compatible?

I know nothing about that but from the little bit I saw you either use server-side code which should work inside loaders/actions to read/write things from the blockchain or your use client-side code to connect to a user wallet and do the same which should work as normal React code.

What else is missing from Remix side?

bjufre commented 2 years ago

@sergiodxa I guess nothing that I can think of right now, I was just trying to gather some information from @7flash to see how he approached this in the end, if client side or server side.

jerrygzy commented 2 years ago

The problem is metamask does not have a public ip address /api for remix server to connect, so it not possible to remote sign transactions

7flash commented 2 years ago

Is that possible to run backend code in frontend worker and create a server listening for requests? Then we can create an abstraction over web3 interactions and common components for it.

arthuryeti commented 2 years ago

You can add an onSubmit to the form (both <form> and <Form> but you probably don't need to use the component for this), run event.preventDefault() and send the transaction directly to the blockchain node as you would have done in a SPA.

Basically, if you don't want to POST against a Remix action send the request manually, nothing prevents you to do this (with proper status handling)

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault()
  let body = new FormData(event.currentTarget)
  await fetch(url, { body, method: "POST" })
}

return (
  <form onSubmit={handleSubmit}>
    <input type="text" name="something" />
  </form>
);

For this approach, how do you trigger a rerender of the current route?

o-az commented 2 years ago

What you're requesting imo should be implementable without the need for a Remix feature addition. You can already use forms and invoke window object. You just need to connect the dots.

The form actions part is already sufficiently documented.

Here's an example of invoking MetaMask window:

// method to invoke sign message with browser wallet
declare global {
  interface Window {
    ethereum: any;
  }
}
export const hasMetaMask =
  typeof window !== "undefined" &&
  typeof window.ethereum !== "undefined" &&
  typeof window.ethereum.isMetaMask !== "undefined";

export const signMetamaskMessage = async (message: string) => {
  if (!hasMetaMask) return;
  try {
    const address = await (
      await window.ethereum.request({
        method: "eth_requestAccounts",
      })
    )[0];

    const signature = await window.ethereum.request({
      method: "personal_sign",
      params: [address, message],
    });
    return signature;
  } catch (error) {
    if ((error as any).code === 4001) console.log("User denied request");
  }
};

Then import it into one of your client files and

export default function Index() {
  return (
    <button onClick={() => signMetamaskMessage("WAGMI")}>
      Sign Message
    </button>
  );
}
EricForgy commented 2 years ago

Btw, I found this issue trying to use Moralis SDK with Remix.run so this is helpful 🙌

If anyone else comes across this, I found this video to be super helpful too:

https://youtu.be/UQYf2wJAb-w

EricForgy commented 2 years ago

Hi @7flash 👋

Did you manage to get Moralis to work with Remix? I've been struggling with this for the last few days, but I'm progressing too slowly since I'm learning both Remix and Moralis 😅

o-az's solutions looks good for signing with MetaMask, but I'm hoping to get Moralis to work and struggling to adapt. Any help from anyone would be greatly appreciated 🙏